Compare commits

..

9 Commits
v51.1 ... v50.1

Author SHA1 Message Date
Bo Chen
bf94d3dad9 build: Release v50.1
This release contains security fixes. Details can be found in the
release notes.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-02-19 17:14:43 -08:00
Demi Marie Obenour
02e3570bdd hypervisor: Suppress unused_unsafe warning
x86::__cpuid is safe on Rust ≥1.94 but unsafe on older versions.  This
causes unused_unsafe warnings when compiling with Rust ≥1.94.  However,
on earlier Rust versions, the code won’t compile if the unsafe blocks
are absent.

Work around this by adding #[allow(unused_unsafe)] where needed to
suppress the warnings.

See #7588 for more discussion.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-19 17:14:43 -08:00
Rob Bradford
dae66ce493 vhost_user_block: Disable use of backing files in test implementation
Remove the use of QCOW2 backing files in the test implementation used
for CI.

Signed-off-by: Rob Bradford <rbradford@meta.com>
(cherry picked from commit 76e233504b)
2026-02-19 17:14:43 -08:00
Rob Bradford
90cee24f98 vmm, virtio-devices: Deny zero sector writes for autodetected raw images
If the disk image was autodetected to raw (not specified with image_type
= 0) then in the virtio-block subsystem generate errors for writes to
block 0 (treat as if read-only). This gives an immediate error vs using
the image implementations in the block subsystem.

Signed-off-by: Rob Bradford <rbradford@meta.com>
(cherry picked from commit b3e8e2abc5)
2026-02-19 17:14:43 -08:00
Rob Bradford
5a0b6f2d06 vmm: Improve resiliency of image type handling
Add an image_type to DiskConfig to specify the image type. If none is
specified autodetect the image type but disable potentially unsafe
behaviour in the QCOW2 backend by disabling the backing file support.

If the image type is autodetected then fix it in the config so that it
will be persistant across reboots and migrations/snapshot & restores.
This also handles the case where the image type was not specified as
part of the disk configuration.

Signed-off-by: Rob Bradford <rbradford@meta.com>
(cherry picked from commit 6f2357c14e)
2026-02-19 17:14:43 -08:00
Anatol Belski
30166a4ea5 vmm: openapi: Sync DiskConfig OpenAPI spec
Add backing_files field to the REST API.

Note: This backport does not include the 'sparse' field as the
corresponding feature support is not backported.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
(cherry picked from commit e36096db3e)
2026-02-19 17:14:43 -08:00
Rob Bradford
f93340d337 vmm: Add option to control backing files
Backing files (e.g. for QCOW2) interact badly with landlock since they
are not obvious from the initial VM configuration. Only enable their use
with an explicit option.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Signed-off-by: Bo Chen <bchen@crusoe.ai>
(cherry picked from commit 509832298b)
2026-02-19 17:14:43 -08:00
Anatol Belski
4c1f854ee9 block: qcow: Use Arc<Mutex<>> for thread safe multiqueue access
Wrap QcowFile in Arc<Mutex<>> to ensure thread safety when multiple
virtio queues access the same QCOW2 image concurrently.

Previously, each queue received its own QcowSync instance via
new_async_io() that shared the underlying QcowFile through Clone.
However, cloned QcowFile instances share internal mutable state
(L2 cache, reference counts, file seek position) without
synchronization, leading to data corruption under concurrent I/O.

This change serializes all QCOW2 operations through a mutex, which
ensures correctness at the cost of parallelism. A more performant
solution would require separating metadata locking from actual I/O
operations, tracked in #7560.

Related: #7560

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
(cherry picked from commit 9bc367a27b)
2026-02-19 17:14:43 -08:00
Wei Liu
ecab9f1b96 vmm: api: Expose the nested option in API description
Signed-off-by: Wei Liu <liuwe@microsoft.com>
(cherry picked from commit 0a5e79afce)
2026-02-19 17:14:43 -08:00
112 changed files with 1716 additions and 9660 deletions

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Build
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -6,7 +6,7 @@ on:
pull_request:
paths: resources/Dockerfile
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Code Formatting
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Tests (ARM64)
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Tests (VFIO)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,5 +1,5 @@
name: MSHV Infra Setup
on:
on:
workflow_call:
inputs:
ARCH:
@@ -44,12 +44,13 @@ 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 }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
infra-setup:
name: ${{ inputs.ARCH }} VM Provision
runs-on: mshv
continue-on-error: true
outputs:
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}

View File

@@ -23,6 +23,7 @@ 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

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Consistency
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
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 }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Quality Checks
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
@@ -167,4 +167,4 @@ jobs:
steps:
- uses: actions/checkout@v6
# Executes "typos ."
- uses: crate-ci/typos@v1.43.5
- uses: crate-ci/typos@v1.40.0

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Release
on: [create, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -1,7 +1,5 @@
verbose = "info"
exclude_path = [".lychee.toml"]
exclude = [
# Availability of links below should be manually verified.
# Page for intel TDX support, returns 403 while querying.
@@ -18,13 +16,6 @@ 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

View File

@@ -21,8 +21,5 @@ liness = "liness"
outout = "outout"
[default.extend-identifiers]
consts = "consts"
fo = "fo"
fpr = "fpr"
# Public Linux API
msg_controllen = "msg_controllen"

656
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -45,12 +45,12 @@ resolver = "3"
[workspace.dependencies]
# rust-vmm crates
acpi_tables = "0.2.0"
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.7"
mshv-ioctls = "0.6.7"
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 }
@@ -64,33 +64,34 @@ vm-memory = "0.16.1"
vmm-sys-util = "0.14.0"
# igvm crates
igvm = "0.4.0"
igvm_defs = "0.4.0"
# 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" }
# serde crates
serde = "1.0.228"
serde_json = "1.0.149"
serde_json = "1.0.145"
serde_with = { version = "3.16.1", default-features = false }
# other crates
anyhow = "1.0.101"
bitflags = "2.11.0"
anyhow = "1.0.99"
bitflags = "2.10.0"
byteorder = "1.5.0"
cfg-if = "1.0.4"
clap = "4.5.59"
clap = "4.5.53"
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.182"
libc = "0.2.178"
log = "0.4.29"
signal-hook = "0.4.3"
thiserror = "2.0.18"
uuid = { version = "1.21.0" }
signal-hook = "0.3.18"
thiserror = "2.0.17"
uuid = { version = "1.19.0" }
wait-timeout = "0.2.1"
zerocopy = { version = "0.8.39", default-features = false }
zerocopy = { version = "0.8.31", default-features = false }
[workspace.lints.clippy]
# Any clippy lint (group) in alphabetical order:

View File

@@ -1,7 +1,6 @@
[package]
authors = ["The Cloud Hypervisor Authors"]
edition.workspace = true
license = "Apache-2.0"
name = "api_client"
version = "0.1.0"

View File

@@ -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, info, warn};
use log::{debug, warn};
use thiserror::Error;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
@@ -345,17 +345,6 @@ 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)?;
@@ -370,10 +359,8 @@ fn create_cpu_nodes(
fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
// 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 {
// Add `numa-node-id` property if there is any numa config.
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 u32)) {
@@ -514,14 +501,7 @@ fn create_memory_node(
) -> FdtWriterResult<()> {
// See https://github.com/torvalds/linux/blob/58ae0b51506802713aa0e9956d1853ba4c722c98/Documentation/devicetree/bindings/numa.txt
// for NUMA setting in memory node.
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 {
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));
let mut mem_reg_prop: Vec<u64> = Vec::new();
@@ -538,15 +518,12 @@ fn create_memory_node(
node_memory_addr = memory_region_start_addr;
}
}
// 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)?;
}
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.
@@ -1067,22 +1044,6 @@ fn create_pci_nodes(
}
fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtWriterResult<()> {
// When Generic Initiator nodes are present, skip ALL FDT NUMA information.
// Let ACPI (which supports Generic Initiator via SRAT Type 5) handle the entire NUMA topology.
// FDT cannot represent Generic Initiator nodes, and mixing FDT + ACPI NUMA info causes conflicts.
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
if has_generic_initiator {
info!("Skipping NUMA distance map encoding in FDT with Generic Initiator devices");
return Ok(());
}
// At this point, we know there are no Generic Initiator nodes
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
// If we only have one node, no distance map is needed
if numa_ids.len() <= 1 {
return Ok(());
}
let distance_map_node = fdt.begin_node("distance-map")?;
fdt.property_string("compatible", "numa-distance-map-v1")?;
// Construct the distance matrix.
@@ -1095,33 +1056,26 @@ 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();
// 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);
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);
distance_matrix.push(10_u32);
continue;
}
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);
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,
);
}
}
fdt.property_array_u32("distance-matrix", distance_matrix.as_ref())?;
@@ -1206,118 +1160,3 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
print_node(child, n_spaces + 2);
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::NumaNode;
// Helper function to create a simple NumaNode for testing
fn create_test_numa_node(cpus: Vec<u32>, device_id: Option<String>) -> NumaNode {
NumaNode {
memory_regions: Vec::new(),
hotplug_regions: Vec::new(),
cpus,
pci_segments: Vec::new(),
distances: BTreeMap::new(),
memory_zones: Vec::new(),
device_id,
}
}
#[test]
fn test_fdt_generic_initiator_detection_and_skip() {
// No Generic Initiator - should not skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![2, 3], None));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(
!has_gi,
"Should not detect Generic Initiator when none present"
);
// One Generic Initiator - should skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(has_gi, "Should detect Generic Initiator when present");
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should skip distance map when GI present");
// Multiple Generic Initiators - should skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
numa_nodes.insert(2, create_test_numa_node(vec![], Some("vfio1".to_string())));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(has_gi, "Should detect multiple Generic Initiators");
}
#[test]
fn test_fdt_distance_map() {
// Single NUMA node - should skip distance map
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should skip distance map for single node");
// Empty NUMA nodes - should handle gracefully
let numa_nodes = BTreeMap::new();
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should handle empty NUMA nodes");
// Non-contiguous NUMA IDs (0, 2, 5) with distance symmetry
let mut numa_nodes = BTreeMap::new();
let mut node0 = create_test_numa_node(vec![0], None);
node0.distances.insert(2, 20);
// node0 has no explicit distance to node5
let mut node2 = create_test_numa_node(vec![1], None);
node2.distances.insert(0, 20);
node2.distances.insert(5, 25);
let mut node5 = create_test_numa_node(vec![2], None);
node5.distances.insert(0, 30);
node5.distances.insert(2, 25);
// node5->node0 (should be used for node0->node5)
numa_nodes.insert(0, node0);
numa_nodes.insert(2, node2);
numa_nodes.insert(5, node5);
// Verify IDs are sorted lexicographically
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
numa_ids.sort_unstable();
assert_eq!(numa_ids, vec![0, 2, 5]);
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(
result.is_ok(),
"Should handle non-contiguous IDs and symmetry"
);
// Default distance (20) when no distance specified in either direction
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0], None));
numa_nodes.insert(1, create_test_numa_node(vec![1], None));
// Neither node has distance to the other
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should default to 20 for missing distances");
}
}

View File

@@ -120,7 +120,6 @@ pub struct NumaNode {
pub pci_segments: Vec<u16>,
pub distances: BTreeMap<u32, u8>,
pub memory_zones: Vec<String>,
pub device_id: Option<String>,
}
pub type NumaNodes = BTreeMap<u32, NumaNode>;

View File

@@ -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: {1}")]
ParseUuid(#[source] uuid::Error, String),
#[error("Failure to parse uuid")]
ParseUuid(#[source] uuid::Error),
}
pub type Result<T> = result::Result<T, Error>;
@@ -198,7 +198,7 @@ pub fn setup_smbios(
let uuid_number = uuid
.map(Uuid::parse_str)
.transpose()
.map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))?
.map_err(Error::ParseUuid)?
.unwrap_or(Uuid::nil());
let smbios_sysinfo = SmbiosSysInfo {
r#type: SYSTEM_INFORMATION,

View File

@@ -9,10 +9,9 @@ default = []
io_uring = ["dep:io-uring"]
[dependencies]
bitflags = { workspace = true }
byteorder = { workspace = true }
crc-any = "2.5.0"
flate2 = "1.1"
flate2 = "1.0"
io-uring = { version = "0.7.11", optional = true }
libc = { workspace = true }
log = { workspace = true }

View File

@@ -78,18 +78,6 @@ 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
}
/// 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
@@ -108,12 +96,6 @@ 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),
@@ -136,8 +118,6 @@ pub trait AsyncIo: Send {
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> 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

View File

@@ -115,18 +115,6 @@ 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
}

View File

@@ -113,16 +113,4 @@ 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",
)))
}
}

View File

@@ -43,7 +43,7 @@ use std::{cmp, result};
#[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode};
use libc::{S_IFBLK, S_IFMT, ioctl};
use log::{debug, error, info, warn};
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use thiserror::Error;
@@ -51,7 +51,7 @@ use virtio_bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::bitmap::Bitmap;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
@@ -157,10 +157,6 @@ 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),
}
@@ -182,8 +178,6 @@ 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
@@ -196,8 +190,6 @@ pub enum RequestType {
Out,
Flush,
GetDeviceId,
Discard,
WriteZeroes,
Unsupported(u32),
}
@@ -211,8 +203,6 @@ pub fn request_type<B: Bitmap + 'static>(
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)),
}
}
@@ -310,12 +300,6 @@ 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);
}
@@ -412,12 +396,6 @@ 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)),
}
}
@@ -430,7 +408,6 @@ impl Request {
disk_nsectors: u64,
disk_image: &mut dyn AsyncIo,
serial: &[u8],
disable_sector0_writes: bool,
user_data: u64,
) -> result::Result<ExecuteAsync, ExecuteError> {
let sector = self.sector;
@@ -568,71 +545,6 @@ 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);
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;
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;
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
.write_zeroes(wz_offset, wz_length, user_data)
.map_err(ExecuteError::AsyncWriteZeroes)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}
@@ -773,145 +685,6 @@ 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::<libc::stat>::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
}
/// 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<P: AsRef<Path>>(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,

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +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::io::{self, BufWriter, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::os::fd::{AsRawFd, RawFd};
@@ -14,184 +13,25 @@ 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<Vec<u64>>;
type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>;
/// Big-endian file access trait.
pub(super) trait BeUint: Sized + Copy {
fn from_be_slice(bytes: &[u8]) -> u64;
fn read_be<R: Read>(r: &mut R) -> io::Result<Self>;
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
}
impl BeUint for u8 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
bytes[0] as u64
}
#[inline(always)]
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
r.read_u8()
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u8(val)
}
}
impl BeUint for u16 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
}
#[inline(always)]
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
r.read_u16::<BigEndian>()
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u16::<BigEndian>(val)
}
}
impl BeUint for u32 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
}
#[inline(always)]
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
r.read_u32::<BigEndian>()
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u32::<BigEndian>(val)
}
}
impl BeUint for u64 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
#[inline(always)]
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
r.read_u64::<BigEndian>()
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u64::<BigEndian>(val)
}
}
/// Read byte-aligned refcounts.
fn read_refcount<T: BeUint>(file: &mut RawFile, count: usize) -> io::Result<Vec<u64>> {
let bytes_per_entry = size_of::<T>();
let mut data = vec![0u8; count * bytes_per_entry];
file.read_exact(&mut data)?;
Ok(data
.chunks_exact(bytes_per_entry)
.map(T::from_be_slice)
.collect())
}
/// Write byte-aligned refcounts.
fn write_refcount<T: BeUint + TryFrom<u64>>(file: &mut RawFile, table: &[u64]) -> io::Result<()>
where
<T as TryFrom<u64>>::Error: Debug,
{
let bytes_per_entry = size_of::<T>();
let mut buffer = BufWriter::with_capacity(table.len() * bytes_per_entry, file);
for &val in table {
let converted = T::try_from(val).expect("refcount values are validated on increment");
T::write_be(&mut buffer, converted)?;
}
buffer.flush()
}
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
fn read_refcount_subbyte<const BITS: usize>(
file: &mut RawFile,
count: usize,
) -> io::Result<Vec<u64>> {
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
let entries_per_byte = 8 / BITS;
let mask = (1u64 << BITS) - 1;
let bytes_needed = count.div_ceil(entries_per_byte);
let mut bytes = vec![0u8; bytes_needed];
file.read_exact(&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<const BITS: usize>(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 or refcount_bits is invalid.
pub fn from(file: RawFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
/// a power of two.
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
if !cluster_size.is_power_of_two() {
return None;
}
let (read_refcount_fn, write_refcount_fn): (RefcountReader, RefcountWriter) =
match refcount_bits {
1 => (read_refcount_subbyte::<1>, write_refcount_subbyte::<1>),
2 => (read_refcount_subbyte::<2>, write_refcount_subbyte::<2>),
4 => (read_refcount_subbyte::<4>, write_refcount_subbyte::<4>),
8 => (read_refcount::<u8>, write_refcount::<u8>),
16 => (read_refcount::<u16>, write_refcount::<u16>),
32 => (read_refcount::<u32>, write_refcount::<u32>),
64 => (read_refcount::<u64>, write_refcount::<u64>),
_ => return None,
};
// For sub-byte refcounts (1,2,4 bits), entries pack multiple per byte
let refcount_block_entries = cluster_size * 8 / refcount_bits;
Some(QcowRawFile {
file,
cluster_size,
cluster_mask: cluster_size - 1,
refcount_block_entries,
read_refcount_fn,
write_refcount_fn,
})
}
@@ -246,7 +86,7 @@ impl QcowRawFile {
for addr in entries {
let entry = f(self, *addr)?;
u64::write_be(&mut buffer, entry)?;
buffer.write_u64::<BigEndian>(entry)?;
}
buffer.flush()?;
Ok(())
@@ -261,7 +101,7 @@ impl QcowRawFile {
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
for &entry in entries {
u64::write_be(&mut buffer, entry)?;
buffer.write_u64::<BigEndian>(entry)?;
}
buffer.flush()?;
Ok(())
@@ -269,17 +109,24 @@ impl QcowRawFile {
/// Read a refcount block from the file and returns a Vec containing the block.
/// Always returns a cluster's worth of data.
#[inline]
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u16>> {
let count = self.cluster_size / size_of::<u16>() as u64;
let mut table = vec![0; count as usize];
self.file.seek(SeekFrom::Start(offset))?;
(self.read_refcount_fn)(&mut self.file, self.refcount_block_entries as usize)
self.file.read_u16_into::<BigEndian>(&mut table)?;
Ok(table)
}
/// Writes a refcount block to the file.
#[inline]
pub fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
self.file.seek(SeekFrom::Start(offset))?;
(self.write_refcount_fn)(&mut self.file, table)
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
for count in table {
buffer.write_u16::<BigEndian>(*count)?;
}
buffer.flush()?;
Ok(())
}
/// Allocates a new cluster at the end of the current file, return the address.
@@ -298,11 +145,6 @@ 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
@@ -349,9 +191,6 @@ 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,
}
}
}

View File

@@ -15,7 +15,6 @@ 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};
@@ -123,17 +122,6 @@ 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 {
@@ -339,12 +327,6 @@ 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<Option<u64>> {
match self.file.seek_hole(offset) {

View File

@@ -20,9 +20,6 @@ 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),
@@ -32,13 +29,6 @@ pub enum Error {
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
#[error("Failed to read the file into the refcount cache")]
ReadingRefCounts(#[source] io::Error),
/// `RefcountOverflow` - Refcount value exceeds maximum for the refcount width.
#[error("Refcount value {value} exceeds {refcount_bits}-bit max ({max})")]
RefcountOverflow {
value: u64,
max: u64,
refcount_bits: u64,
},
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -48,19 +38,16 @@ pub type Result<T> = std::result::Result<T, Error>;
pub struct RefCount {
ref_table: VecCache<u64>,
refcount_table_offset: u64,
refblock_cache: CacheMap<VecCache<u64>>,
refblock_cache: CacheMap<VecCache<u16>>,
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,
@@ -68,7 +55,6 @@ impl RefCount {
refcount_table_entries: u64,
refcount_block_entries: u64,
cluster_size: u64,
refcount_bits: u64,
) -> io::Result<RefCount> {
let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
refcount_table_offset,
@@ -77,11 +63,6 @@ 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,
@@ -89,8 +70,6 @@ impl RefCount {
refcount_block_entries,
cluster_size,
max_valid_cluster_offset,
max_refcount,
refcount_bits,
})
}
@@ -113,17 +92,9 @@ impl RefCount {
&mut self,
raw_file: &mut QcowRawFile,
cluster_address: u64,
refcount: u64,
mut new_cluster: Option<(u64, VecCache<u64>)>,
refcount: u16,
mut new_cluster: Option<(u64, VecCache<u16>)>,
) -> Result<Option<u64>> {
if refcount > self.max_refcount {
return Err(Error::RefcountOverflow {
value: refcount,
max: self.max_refcount,
refcount_bits: self.refcount_bits,
});
}
let (table_index, block_index) = self.get_refcount_index(cluster_address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
@@ -199,15 +170,12 @@ impl RefCount {
&mut self,
raw_file: &mut QcowRawFile,
address: u64,
) -> Result<u64> {
) -> Result<u16> {
let (table_index, block_index) = self.get_refcount_index(address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(0);
}
if block_addr_disk & (self.cluster_size - 1) != 0 {
return Err(Error::RefblockUnaligned(block_addr_disk));
}
if !self.refblock_cache.contains_key(table_index) {
let table = VecCache::from_vec(
raw_file
@@ -234,7 +202,7 @@ impl RefCount {
&mut self,
raw_file: &mut QcowRawFile,
table_index: usize,
) -> Result<Option<&[u64]>> {
) -> Result<Option<&[u16]>> {
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(None);

View File

@@ -62,21 +62,6 @@ impl<T: 'static + Copy + Default> VecCache<T> {
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<T: 'static + Copy + Default> Cacheable for VecCache<T> {

View File

@@ -4,17 +4,16 @@
use std::collections::VecDeque;
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::io::{Seek, SeekFrom};
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,
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::qcow::{Error as QcowError, MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
use crate::{AsyncAdaptor, BlockBackend};
pub struct QcowDiskSync {
@@ -30,20 +29,19 @@ pub struct QcowDiskSync {
}
impl QcowDiskSync {
pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult<Self> {
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(qcow_file)),
})
pub fn new(file: File, direct_io: bool, backing_files: bool) -> QcowResult<Self> {
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,
)?)),
})
}
}
}
@@ -70,22 +68,6 @@ impl DiskFile for QcowDiskSync {
Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box<dyn AsyncIo>)
}
fn resize(&mut self, size: u64) -> DiskFileResult<()> {
self.qcow_file
.lock()
.unwrap()
.resize(size)
.map_err(|e| DiskFileError::ResizeError(io::Error::other(e)))
}
fn supports_sparse_operations(&self) -> bool {
true
}
fn supports_zero_flag(&self) -> bool {
true
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd())
}
@@ -156,349 +138,4 @@ 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<()> {
// 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<()> {
// 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(())
}
}
}
}
#[cfg(test)]
mod unit_tests {
use std::io::{Read, Seek, SeekFrom, Write};
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::qcow::{QcowFile, QcowHeader, 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<Mutex<>>, 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"
);
}
#[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"),
}
}
}

View File

@@ -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};
pub struct RawFileDisk {
file: File,
@@ -59,10 +59,6 @@ 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())
}
@@ -257,58 +253,4 @@ impl AsyncIo for RawFileAsync {
Ok(())
}
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<()> {
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(())
}
}

View File

@@ -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,10 +58,6 @@ 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())
}
@@ -165,16 +161,4 @@ 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",
)))
}
}

View File

@@ -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,10 +52,6 @@ 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())
}
@@ -150,226 +146,4 @@ 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<()> {
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<()> {
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 = 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));
}
}

View File

@@ -9,7 +9,7 @@ use std::os::fd::AsRawFd;
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::vhdx::{Result as VhdxResult, Vhdx};
use crate::{AsyncAdaptor, BlockBackend, Error};
@@ -114,16 +114,4 @@ 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",
)))
}
}

View File

@@ -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 = "51.1.0"
version = "50.1.0"
# Minimum buildable version:
# Keep in sync with version in .github/workflows/build.yaml
# Policy on MSRV (see #4318):
@@ -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.7.1", optional = true }
[dev-dependencies]
block = { path = "../block" }

View File

@@ -218,12 +218,12 @@ fn get_cli_options_sorted(
)
.default_value(default_vcpus)
.group("vm-config"),
#[cfg(feature = "dbus_api")]
Arg::new("dbus-object-path")
.long("dbus-object-path")
.help("Object path to serve the dbus interface")
.num_args(1)
.group("vmm-config"),
#[cfg(target_arch = "x86_64")]
Arg::new("debug-console")
.long("debug-console")
.help("Debug console: off|pty|tty|file=</path/to/a/file>,iobase=<port in hex>")
.default_value("off,iobase=0xe9")
.group("vm-config"),
#[cfg(feature = "dbus_api")]
Arg::new("dbus-service-name")
.long("dbus-service-name")
@@ -231,18 +231,18 @@ fn get_cli_options_sorted(
.num_args(1)
.group("vmm-config"),
#[cfg(feature = "dbus_api")]
Arg::new("dbus-object-path")
.long("dbus-object-path")
.help("Object path to serve the dbus interface")
.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=</path/to/a/file>,iobase=<port in hex>")
.default_value("off,iobase=0xe9")
.group("vm-config"),
Arg::new("device")
.long("device")
.help(DeviceConfig::SYNTAX)

File diff suppressed because it is too large Load Diff

View File

@@ -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.31", features = [
"alloc",
"derive",
], optional = true }

View File

@@ -2,7 +2,7 @@
### WARNING
This feature is currently only supported on MSHV.
This feature is only currently 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,12 +10,13 @@ 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://docs.amd.com/v/u/en-US/amd-secure-encrypted-virtualization-solution-brief):
- [SNP Homepage](https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf):
more information about SEV-SNP technical aspects, design and specification.
## Cloud Hypervisor support
A machine with AMD SEV-SNP support which is enabled in the BIOS is required.
It is required to use a machine which has enabled support for AMD SEV-SNP in
the BIOS.
On the Cloud Hypervisor side, all you need is to build the project with the
`sev_snp` feature enabled:
@@ -25,7 +26,7 @@ cargo build --no-default-features --features "sev_snp"
```
**Note**
Please note that `sev_snp` cannot be enabled in conjunction with the `tdx` feature flag.
Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag.
You can run a SEV-SNP VM using the following command:
@@ -37,4 +38,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)

View File

@@ -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 Virtual Machine Information](#dump-virtual-machine-information)
- [Dump a Virtual Machine Information](#dump-a-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-d-bus-api-and-cli-architectural-relationship)
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-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 with each other.
communicate between each others.
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 |
| Take a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
| Task 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 Virtual Machine Information
##### Dump a 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.5.53/clap/) and then translated into
[clap crate](https://docs.rs/clap/4.3.11/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 to perform the call in zbus' async context.
the [blocking](https://github.com/smol-rs/blocking) crate is used 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 other.
etc) to send commands and responses to each others.
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,8 +365,9 @@ 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 create a virtual machine:
```shell
[REST API](#rest-api) in order to creates a virtual machine:
```
shell
#!/usr/bin/env bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
@@ -413,7 +414,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) => {

View File

@@ -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, it 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 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 when running Out Of Memory (OOM). Assuming
Allow the guest to deflate the balloon if 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.

View File

@@ -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 dependencies. For a list of packages required for additional
# features (e.g., testing), please refer to resources/Dockerfile.
# Install basic packages needed. For a package list targeting for more
# functionalities for example the test, please see 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

View File

@@ -11,8 +11,8 @@ to set vCPUs options for Cloud Hypervisor.
```rust
struct CpusConfig {
boot_vcpus: u32,
max_vcpus: u32,
boot_vcpus: u8,
max_vcpus: u8,
topology: Option<CpuTopology>,
kvm_hyperv: bool,
max_phys_bits: u8,
@@ -30,12 +30,12 @@ struct CpusConfig {
Number of vCPUs present at boot time.
This option allows defining a specific number of vCPUs to be present at the
This option allows to define 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 32 bits.
Value is an unsigned integer of 8 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 provides
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 lets the
provide 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 32 bits.
The value is an unsigned integer of 8 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 performance.
is a way to achieve better performances.
The topology is described through the following structure:
```rust
struct CpuTopology {
threads_per_core: u16,
cores_per_die: u16,
dies_per_package: u16,
packages: u16,
threads_per_core: u8,
cores_per_die: u8,
dies_per_package: u8,
packages: u8,
}
```
@@ -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 debugging purposes.
useful for debug purpose.
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 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
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
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: u32,
host_cpus: Vec<usize>,
vcpu: u8,
host_cpus: Vec<u8>,
}
```
@@ -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 a
platform-native unsigned integer (`usize`).
Multiple values can be provided to define each list. Each value is an unsigned
integer of 8 bits.
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
```
```

View File

@@ -13,7 +13,7 @@ be used simultaneously.
### `0x80` I/O port
Whenever the guest writes one byte between `0x0` and `0xF` on this particular
Whenever the guest write 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/release/hypervisor-fw \
--kernel ~/rust-hypervisor-firmware/target/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 aspects of your logging messages.
You can use different ports for different aspect of your logging messages.

View File

@@ -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 bypassing the guest page cache and improve the guest memory footprint.
allows to bypass 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`.

View File

@@ -1,6 +1,6 @@
# GDB Support
This feature allows remote guest debugging using GDB. Note that this feature is supported on x86_64 and aarch64 with KVM.
This feature allows remote guest debugging using GDB. Note that this feature is only supported on x86_64/KVM.
To enable debugging with GDB, build with the `guest_debug` feature enabled:
@@ -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 \

View File

@@ -110,7 +110,7 @@ Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi
Swap: 32Mi 0B 32Mi
```
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.
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.
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, use the `add-device` API.
To ask the VMM to add additional VFIO device then 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, use the `add-device` API.
### Add Disk Device
To ask the VMM to add additional disk device, use the `add-disk` API.
To ask the VMM to add additional disk device then 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, use the `add-disk` API.
### Add Fs Device
To ask the VMM to add additional fs device, use the `add-fs` API.
To ask the VMM to add additional fs device then 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, use the `add-fs` API.
### Add Net Device
To ask the VMM to add additional network device, use the `add-net` API.
To ask the VMM to add additional network device then 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, use the `add-net` API.
### Add Pmem Device
To ask the VMM to add additional PMEM device, use the `add-pmem` API.
To ask the VMM to add additional PMEM device then 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, use the `add-pmem` API.
### Add Vsock Device
To ask the VMM to add additional vsock device, use the `add-vsock` API.
To ask the VMM to add additional vsock device then 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 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.
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.
```shell
./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0

View File

@@ -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 built as follows:
and `LLVM` first. The TDshim can be build 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

View File

@@ -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 for the actual rate limit (which can be quite
different from the expected "refill-rate" derived from user inputs). For
can have big implications to the actual rate limit (which can be a lot
different 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=100
--rate-limit-group bw_size=1048576,bw_refill_time,bw_refill_time=100
```

View File

@@ -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 trying accessing
counterpart, validating and translating every address before to try 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 devices can be supported
to be attached to the virtual IOMMU.
Refer to the command line `--help` to find out which device support 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 an IOMMU group.
devices will be added into a 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 needs to be stored into
memory, this means the entire mapping of the L2 guest need 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 needs to be mapped based on
reach optimized performances, the L2 guest also need 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`.

View File

@@ -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 available
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.
@@ -16,10 +16,9 @@ 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 <ivshmem> device backend file "path=</path/to/a/file>,size=<file_size>"
--ivshmem <ivshmem> device backend file "path=</path/to/a/file>,size=<file_size/must=2^n>";
```
## Example
@@ -42,11 +41,11 @@ Start application to mmap the file data to a memory region:
--ivshmem path=/tmp/ivshmem.data,size=1M
```
Insmod an ivshmem device driver to enable the device. The file data will be
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 be obtained from:
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 remapping the `/tmp/ivshmem.data`.
The host process can r/w this data by remmaping the `/tmp/ivshmem.data`.

View File

@@ -16,11 +16,11 @@ permissions.
## Host Setup
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 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 support can be checked with the following command:
Landlock support can be checked with 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 at the
`vm_create` stage of guest boot.
list of files it accesses over its lifetime. So, Landlock is enabled `vm_create`
stage of guest boot.
### Command Line
Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock

View File

@@ -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 in /tmp. This is done for
> :warning: These examples place sockets /tmp. This is done for
> simplicity and should not be done in production.
## Local Migration (Suitable for Live Upgrade of VMM)

View File

@@ -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 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.
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.
```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)

View File

@@ -20,7 +20,7 @@ struct MemoryConfig {
hugepages: bool,
hugepage_size: Option<u64>,
prefault: bool,
thp: bool,
thp: bool
zones: Option<Vec<MemoryZoneConfig>>,
}
```
@@ -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
require `MAP_SHARED`.
requires `MAP_SHARED`.
_Example_
@@ -135,7 +135,8 @@ 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 is reduced.
as it speeds up the VM's boot time since the amount of IOMMU mappings are
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
@@ -184,7 +185,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 be fewer virtualisation related page faults. Unlike using
there will fewer virtualisation related page faults. Unlike using
`hugepages=on` a specific number of huge pages do not need to be allocated by
the kernel.
@@ -294,9 +295,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
require `MAP_SHARED`.
requires `MAP_SHARED`.
By default this option is turned off, which results in performing `mmap(2)`
By default this option is turned off, which result in performing `mmap(2)`
with `MAP_PRIVATE` flag.
_Example_
@@ -314,7 +315,8 @@ 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 is reduced.
as it speeds up the VM's boot time since the amount of IOMMU mappings are
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
@@ -323,7 +325,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
require `MAP_SHARED`.
requires `MAP_SHARED`.
By default this option is turned off.
@@ -429,20 +431,17 @@ 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<Vec<u32>>,
cpus: Option<Vec<u8>>,
distances: Option<Vec<NumaDistance>>,
memory_zones: Option<Vec<String>>,
device_id: Option<String>,
}
```
```
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>,device_id=<device_identifier>"
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>
```
### `guest_numa_id`
@@ -457,7 +456,7 @@ Value is an unsigned integer of 32 bits.
_Example_
```
--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0
--numa guest_numa_id=0
```
### `cpus`
@@ -471,7 +470,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 32 bits.
integer of 8 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
@@ -484,9 +483,6 @@ 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_
```
@@ -497,7 +493,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 lets
and the destination NUMA nodes listed along with distances. This option let
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.
@@ -513,34 +509,13 @@ 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`
@@ -566,9 +541,6 @@ 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_
```
@@ -577,48 +549,10 @@ _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=<device_identifier>`.
_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
has affinity to NUMA node 0. By default, all other PCI segments have affinity to NUMA node 0.
has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0.
The user may configure the NUMA affinity for any additional PCI segments.
_Example_

View File

@@ -35,31 +35,13 @@ 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
# 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 ..
$ 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
# Build tools
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
@@ -71,19 +53,10 @@ $ 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` or `workloads/CLOUDHV_EFI.fd`
when using developer container to produce firmware.
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
## Using OVMF Binaries

View File

@@ -29,7 +29,6 @@ parameters available for the vDPA device.
struct VdpaConfig {
path: PathBuf,
num_queues: usize,
iommu: bool,
id: Option<String>,
pci_segment: u16,
}
@@ -84,7 +83,7 @@ _Example_
### `pci_segment`
PCI segment number to which the vDPA device should be attached.
PCI segment number to which the vDPA device should be attached to.
This parameter is optional.

View File

@@ -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; do
for i in acpid crond
sudo ln -sf /etc/init.d/$i etc/runlevels/default/$i
done
for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom; do
end
for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom
sudo ln -sf /etc/init.d/$i etc/runlevels/boot/$i
done
end
for i in killprocs mount-ro savecache; do
for i in killprocs mount-ro savecache
sudo ln -sf /etc/init.d/$i etc/runlevels/shutdown/$i
done
end
for i in devfs dmesg hwdrivers mdev; do
for i in devfs dmesg hwdrivers mdev
sudo ln -sf /etc/init.d/$i etc/runlevels/sysinit/$i
done
end
# 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.
filesystems.

437
fuzz/Cargo.lock generated
View File

@@ -4,9 +4,8 @@ version = 4
[[package]]
name = "acpi_tables"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad581b2b0fa02638f3df6ff3f852ebc30dc7cfe531e9745d1ca4c0f283a6dbe"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a"
dependencies = [
"zerocopy",
]
@@ -69,9 +68,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.101"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
@@ -81,12 +80,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arc-swap"
version = "1.8.2"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
dependencies = [
"rustversion",
]
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arch"
@@ -100,7 +96,7 @@ dependencies = [
"linux-loader",
"log",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.17",
"uuid",
"vm-fdt",
"vm-memory",
@@ -132,15 +128,14 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block"
version = "0.1.0"
dependencies = [
"bitflags 2.11.0",
"byteorder",
"crc-any",
"flate2",
@@ -149,7 +144,7 @@ dependencies = [
"remain",
"serde",
"smallvec",
"thiserror 2.0.18",
"thiserror 2.0.17",
"uuid",
"virtio-bindings",
"virtio-queue",
@@ -161,9 +156,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
@@ -173,9 +168,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.56"
version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -191,18 +186,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.5.59"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.59"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@@ -212,9 +207,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "1.0.0"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "cloud-hypervisor-fuzz"
@@ -324,7 +319,7 @@ dependencies = [
"acpi_tables",
"anyhow",
"arch",
"bitflags 2.11.0",
"bitflags 2.10.0",
"byteorder",
"event_monitor",
"hypervisor",
@@ -333,7 +328,7 @@ dependencies = [
"num_enum",
"pci",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.17",
"tpm",
"vm-allocator",
"vm-device",
@@ -374,7 +369,7 @@ version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.10.0",
"libc",
]
@@ -384,16 +379,6 @@ 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"
@@ -411,7 +396,7 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
dependencies = [
"getrandom 0.2.17",
"getrandom 0.2.16",
]
[[package]]
@@ -422,15 +407,15 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
version = "1.1.9"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -454,31 +439,25 @@ 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.32"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-sink"
version = "0.3.32"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "gdbstub"
version = "0.7.9"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6"
checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.10.0",
"cfg-if",
"log",
"managed",
@@ -498,9 +477,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.17"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"js-sys",
@@ -521,40 +500,12 @@ 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"
@@ -575,7 +526,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vfio-ioctls",
"vm-memory",
"vmm-sys-util",
@@ -591,12 +542,6 @@ 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"
@@ -605,14 +550,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
"hashbrown",
]
[[package]]
@@ -632,9 +575,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
@@ -673,7 +616,7 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.10.0",
"kvm-bindings",
"libc",
"vmm-sys-util",
@@ -687,7 +630,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088"
dependencies = [
"enumflags2",
"libc",
"thiserror 2.0.18",
"thiserror 2.0.17",
]
[[package]]
@@ -696,23 +639,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.182"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libfuzzer-sys"
version = "0.4.12"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
dependencies = [
"arbitrary",
"cc",
@@ -750,9 +687,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "micro_http"
@@ -775,9 +712,9 @@ dependencies = [
[[package]]
name = "mshv-bindings"
version = "0.6.7"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426"
checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353"
dependencies = [
"libc",
"num_enum",
@@ -799,13 +736,13 @@ name = "net_util"
version = "0.1.0"
dependencies = [
"epoll",
"getrandom 0.4.1",
"getrandom 0.3.4",
"libc",
"log",
"net_gen",
"rate_limiter",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.17",
"virtio-bindings",
"virtio-queue",
"vm-memory",
@@ -880,7 +817,7 @@ dependencies = [
name = "option_parser"
version = "0.1.0"
dependencies = [
"thiserror 2.0.18",
"thiserror 2.0.17",
]
[[package]]
@@ -899,7 +836,7 @@ dependencies = [
"libc",
"log",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vfio-bindings",
"vfio-ioctls",
"vfio_user",
@@ -925,16 +862,6 @@ 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"
@@ -946,18 +873,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.106"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
@@ -990,9 +917,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.9.5"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.4",
]
@@ -1004,7 +931,7 @@ dependencies = [
"epoll",
"libc",
"log",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vmm-sys-util",
]
@@ -1025,6 +952,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1040,12 +973,6 @@ 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"
@@ -1078,15 +1005,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -1123,9 +1050,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.4.3"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
@@ -1133,11 +1060,10 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
dependencies = [
"errno",
"libc",
]
@@ -1170,9 +1096,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.116"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -1190,11 +1116,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.18"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl 2.0.18",
"thiserror-impl 2.0.17",
]
[[package]]
@@ -1210,9 +1136,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.18"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@@ -1221,18 +1147,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.10+spec-1.0.0"
version = "0.23.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
dependencies = [
"indexmap",
"toml_datetime",
@@ -1242,9 +1168,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.9+spec-1.1.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
"winnow",
]
@@ -1257,7 +1183,7 @@ dependencies = [
"libc",
"log",
"net_gen",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vmm-sys-util",
]
@@ -1273,15 +1199,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.24"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "utf8parse"
@@ -1291,11 +1211,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.21.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.4.1",
"getrandom 0.3.4",
"js-sys",
"rand",
"wasm-bindgen",
@@ -1321,7 +1241,7 @@ dependencies = [
"kvm-ioctls",
"libc",
"log",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vfio-bindings",
"vm-memory",
"vmm-sys-util",
@@ -1333,13 +1253,13 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.10.0",
"libc",
"log",
"serde",
"serde_derive",
"serde_json",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vfio-bindings",
"vm-memory",
"vmm-sys-util",
@@ -1351,7 +1271,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.10.0",
"libc",
"uuid",
"vm-memory",
@@ -1382,7 +1302,7 @@ dependencies = [
"serde",
"serde_with",
"serial_buffer",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vhost",
"virtio-bindings",
"virtio-queue",
@@ -1421,7 +1341,7 @@ version = "0.1.0"
dependencies = [
"hypervisor",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vfio-ioctls",
"vm-memory",
"vmm-sys-util",
@@ -1453,7 +1373,7 @@ dependencies = [
"itertools",
"serde",
"serde_json",
"thiserror 2.0.18",
"thiserror 2.0.17",
"vm-memory",
]
@@ -1472,7 +1392,7 @@ dependencies = [
"acpi_tables",
"anyhow",
"arch",
"bitflags 2.11.0",
"bitflags 2.10.0",
"block",
"cfg-if",
"clap",
@@ -1497,7 +1417,7 @@ dependencies = [
"serde_json",
"serial_buffer",
"signal-hook",
"thiserror 2.0.18",
"thiserror 2.0.17",
"tracer",
"uuid",
"vfio-ioctls",
@@ -1534,18 +1454,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
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"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
@@ -1608,40 +1519,6 @@ 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.11.0",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -1690,118 +1567,30 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.51.0"
version = "0.46.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.11.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",
]
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.39"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zstd"
version = "0.13.3"

View File

@@ -21,11 +21,11 @@ block = { path = "../block" }
devices = { path = "../devices" }
epoll = "4.3.3"
hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] }
libc = "0.2.182"
libfuzzer-sys = "0.4.12"
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" }
mshv-bindings = "0.6.6"
mshv-bindings = "0.6.5"
net_util = { path = "../net_util" }
seccompiler = "0.5.0"
virtio-devices = { path = "../virtio-devices" }

View File

@@ -67,7 +67,6 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
EventFd::new(EFD_NONBLOCK).unwrap(),
None,
queue_affinity,
true,
false,
)
.unwrap();

View File

@@ -14,7 +14,7 @@ tdx = []
[dependencies]
anyhow = { workspace = true }
arc-swap = "1.8.2"
arc-swap = "1.7.1"
bitfield-struct = "0.12.0"
byteorder = { workspace = true }
cfg-if = { workspace = true }

View File

@@ -538,13 +538,16 @@ impl<T: CpuStateManager> Emulator<'_, T> {
handler
}
pub fn emulate_insn_stream(
fn emulate_insn_stream(
&mut self,
old_state: &T,
cpu_id: usize,
insn_stream: &[u8],
num_insn: Option<usize>,
) -> EmulationResult<T, Exception> {
let mut state = old_state.clone();
let mut state = self
.platform
.cpu_state(cpu_id)
.map_err(EmulationError::PlatformEmulationError)?;
let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE);
let mut insn = Instruction::default();
let mut num_insn_emulated: usize = 0;
@@ -624,11 +627,7 @@ impl<T: CpuStateManager> Emulator<'_, T> {
/// Emulate all instructions from the instructions stream.
pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult<T, Exception> {
let state = self
.platform
.cpu_state(cpu_id)
.map_err(EmulationError::PlatformEmulationError)?;
self.emulate_insn_stream(&state, insn_stream, None)
self.emulate_insn_stream(cpu_id, insn_stream, None)
}
/// Only emulate the first instruction from the stream.
@@ -641,11 +640,7 @@ impl<T: CpuStateManager> Emulator<'_, T> {
cpu_id: usize,
insn_stream: &[u8],
) -> EmulationResult<T, Exception> {
let state = self
.platform
.cpu_state(cpu_id)
.map_err(EmulationError::PlatformEmulationError)?;
self.emulate_insn_stream(&state, insn_stream, Some(1))
self.emulate_insn_stream(cpu_id, insn_stream, Some(1))
}
}
@@ -711,13 +706,10 @@ mod mock_vmm {
insn: &[u8],
num_insn: Option<usize>,
) -> MockResult {
let state = self
.cpu_state(cpu_id)
.map_err(EmulationError::PlatformEmulationError)?;
let ip = state.ip();
let ip = self.cpu_state(cpu_id).unwrap().ip();
let mut emulator = Emulator::new(self);
let new_state = emulator.emulate_insn_stream(&state, insn, num_insn)?;
let new_state = emulator.emulate_insn_stream(cpu_id, insn, num_insn)?;
if num_insn.is_none() {
assert_eq!(ip + insn.len() as u64, new_state.ip());
}

View File

@@ -171,7 +171,6 @@ pub struct HypervisorVmConfig {
#[cfg(feature = "sev_snp")]
pub mem_size: u64,
pub nested: bool,
pub smt_enabled: bool,
}
#[derive(Copy, Clone)]
@@ -225,8 +224,6 @@ 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") },
}
}
}
@@ -245,8 +242,6 @@ 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"),
}
}
}

View File

@@ -121,6 +121,6 @@ impl Vgic for MshvGicV2M {
}
fn save_data_tables(&self) -> Result<()> {
Ok(())
unimplemented!()
}
}

View File

@@ -19,7 +19,8 @@ use mshv_bindings::*;
#[cfg(target_arch = "x86_64")]
use mshv_ioctls::InterruptRequest;
use mshv_ioctls::{
Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_synthetic_features_mask, set_registers_64,
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;
@@ -68,7 +69,6 @@ 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,
};
@@ -285,8 +285,7 @@ impl hypervisor::Hypervisor for MshvHypervisor {
VmType::Normal
};
}
let mut create_args = self.mshv.make_default_partition_create_arg(mshv_vm_type);
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 {
@@ -307,10 +306,6 @@ 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 {
@@ -719,25 +714,20 @@ 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_insn_stream(
&old_state,
.emulate_first_insn(
self.vp_index as usize,
&info.instruction_bytes[..insn_len],
Some(1),
)
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
// Set CPU state back.
context
.update_cpu_state(self.vp_index as usize, old_state, new_state)
.set_cpu_state(self.vp_index as usize, new_state)
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
Ok(cpu::VmExit::Ignore)
@@ -873,19 +863,6 @@ 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);
@@ -1735,16 +1712,6 @@ 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:?}"))
}
}
///

View File

@@ -106,38 +106,6 @@ impl MshvEmulatorContext<'_> {
Ok(())
}
pub fn update_cpu_state(
&self,
cpu_id: usize,
old_state: <Self as PlatformEmulator>::CpuState,
new_state: <Self as PlatformEmulator>::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

View File

@@ -6,7 +6,7 @@ version = "0.1.0"
[dependencies]
epoll = { workspace = true }
getrandom = "0.4.1"
getrandom = "0.3.4"
libc = { workspace = true }
log = { workspace = true }
net_gen = { path = "../net_gen" }

View File

@@ -39,10 +39,13 @@ impl MacAddr {
if v[i].len() != 2 {
return common_err;
}
if !v[i].bytes().all(|a| a.is_ascii_hexdigit()) {
return common_err;
}
bytes[i] = u8::from_str_radix(v[i], 16).unwrap();
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
))
})?;
}
Ok(MacAddr { bytes })
@@ -184,8 +187,6 @@ 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\"");
}

View File

@@ -276,17 +276,6 @@ pub struct MmioRegion {
pub(crate) user_memory_regions: Vec<UserMemoryRegion>,
}
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`

View File

@@ -173,7 +173,6 @@ pub struct BlockControl {
pub struct PerformanceTestControl {
test_timeout: u32,
test_iterations: u32,
warmup_iterations: u32,
num_queues: Option<u32>,
queue_size: Option<u32>,
net_control: Option<(bool, bool)>, // First bool is for RX(true)/TX(false), second bool is for bandwidth or PPS
@@ -184,8 +183,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 = {}, warmup_iterations = {}",
self.test_timeout, self.test_iterations, self.warmup_iterations
"test_timeout = {}s, test_iterations = {}",
self.test_timeout, self.test_iterations
);
if let Some(o) = self.num_queues {
output = format!("{output}, num_queues = {o}");
@@ -213,7 +212,6 @@ impl PerformanceTestControl {
Self {
test_timeout: 10,
test_iterations: 5,
warmup_iterations: 0,
num_queues: None,
queue_size: None,
net_control: None,
@@ -235,17 +233,6 @@ 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
@@ -278,9 +265,8 @@ 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<u32>, test_timeout: &Option<u32>) -> 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
((test_timeout.unwrap_or(self.control.test_timeout) + 20)
* test_iterations.unwrap_or(self.control.test_iterations)) as u64
}
}
@@ -333,7 +319,7 @@ mod adjuster {
}
}
const TEST_LIST: [PerformanceTest; 60] = [
const TEST_LIST: [PerformanceTest; 32] = [
PerformanceTest {
name: "boot_time_ms",
func_ptr: performance_boot_time,
@@ -724,282 +710,6 @@ const TEST_LIST: [PerformanceTest; 60] = [
},
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,
@@ -1030,160 +740,6 @@ const TEST_LIST: [PerformanceTest; 60] = [
},
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,
},
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,
},
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(

View File

@@ -35,11 +35,6 @@ 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 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");
@@ -71,32 +66,6 @@ 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());
// 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() {
@@ -106,16 +75,6 @@ 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}'."));
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
@@ -194,7 +153,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
.unwrap();
let r = std::panic::catch_unwind(|| {
guest.wait_vm_boot().unwrap();
guest.wait_vm_boot(None).unwrap();
measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, bandwidth).unwrap()
});
@@ -235,7 +194,7 @@ pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 {
.unwrap();
let r = std::panic::catch_unwind(|| {
guest.wait_vm_boot().unwrap();
guest.wait_vm_boot(None).unwrap();
// 'ethr' tool will measure the latency multiple times with provided test time
let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap();
@@ -438,12 +397,6 @@ 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"])
@@ -461,7 +414,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
guest.disk_config.disk(DiskType::CloudInit).unwrap()
)
.as_str(),
test_disk_arg.as_str(),
format!("path={test_file},queue_size={queue_size},num_queues={num_queues}").as_str(),
])
.default_net()
.args(["--api-socket", &api_socket])
@@ -472,7 +425,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
.unwrap();
let r = std::panic::catch_unwind(|| {
guest.wait_vm_boot().unwrap();
guest.wait_vm_boot(None).unwrap();
let fio_command = format!(
"sudo fio --filename=/dev/vdc --name=test --output-format=json \

View File

@@ -1,29 +1,20 @@
- [v51.1](#v511)
- [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.1](#v501)
- [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-1)
- [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images)
- [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)
- [Notable Bug Fixes](#notable-bug-fixes-1)
- [Contributors](#contributors-1)
- [Notable Bug Fixes](#notable-bug-fixes)
- [Contributors](#contributors)
- [v49.0](#v490)
- [MSHV Support Improvements](#mshv-support-improvements-1)
- [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-2)
- [Contributors](#contributors-2)
- [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)
@@ -34,62 +25,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-3)
- [Contributors](#contributors-3)
- [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-4)
- [Notable Bug Fixes](#notable-bug-fixes-3)
- [Deprecations](#deprecations)
- [Contributors](#contributors-4)
- [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-5)
- [Contributors](#contributors-5)
- [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-6)
- [Contributors](#contributors-6)
- [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-2)
- [Notable Performance Improvements](#notable-performance-improvements-1)
- [New Fuzzers](#new-fuzzers)
- [Notable Bug Fixes](#notable-bug-fixes-7)
- [Contributors](#contributors-7)
- [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-3)
- [Notable Bug Fixes](#notable-bug-fixes-8)
- [Contributors](#contributors-8)
- [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-9)
- [Notable Bug Fixes](#notable-bug-fixes-8)
- [Sponsorships](#sponsorships)
- [Contributors](#contributors-9)
- [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-4)
- [Notable Bug Fixes](#notable-bug-fixes-10)
- [Contributors](#contributors-10)
- [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-11)
- [Contributors](#contributors-11)
- [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-12)
- [Contributors](#contributors-12)
- [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)
@@ -97,16 +88,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-13)
- [Contributors](#contributors-13)
- [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-14)
- [Contributors](#contributors-14)
- [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)
@@ -115,31 +106,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-15)
- [Contributors](#contributors-15)
- [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-16)
- [Contributors](#contributors-16)
- [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-17)
- [Contributors](#contributors-17)
- [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-18)
- [Contributors](#contributors-18)
- [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-19)
- [Contributors](#contributors-19)
- [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)
@@ -147,15 +138,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-20)
- [Contributors](#contributors-20)
- [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-21)
- [Contributors](#contributors-21)
- [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)
@@ -165,10 +156,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-22)
- [Notable Bug Fixes](#notable-bug-fixes-21)
- [Removals](#removals)
- [Deprecations](#deprecations-1)
- [Contributors](#contributors-22)
- [Contributors](#contributors-21)
- [v28.1](#v281)
- [v28.0](#v280)
- [Community Engagement (Reminder)](#community-engagement-reminder)
@@ -176,9 +167,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-23)
- [Notable Bug Fixes](#notable-bug-fixes-22)
- [Removals](#removals-1)
- [Contributors](#contributors-23)
- [Contributors](#contributors-22)
- [v27.0](#v270)
- [Community Engagement](#community-engagement)
- [Prebuilt Packages](#prebuilt-packages)
@@ -187,41 +178,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-24)
- [Notable Bug Fixes](#notable-bug-fixes-23)
- [Deprecations](#deprecations-2)
- [Contributors](#contributors-24)
- [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-25)
- [Notable Bug Fixes](#notable-bug-fixes-24)
- [Deprecations](#deprecations-3)
- [Removals](#removals-2)
- [Contributors](#contributors-25)
- [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-26)
- [Notable Bug Fixes](#notable-bug-fixes-25)
- [Removals](#removals-3)
- [Contributors](#contributors-26)
- [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-27)
- [Notable Bug Fixes](#notable-bug-fixes-26)
- [Notable Improvements](#notable-improvements)
- [Deprecations](#deprecations-4)
- [New on the Website](#new-on-the-website)
- [Contributors](#contributors-27)
- [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-28)
- [Notable Bug Fixes](#notable-bug-fixes-27)
- [Deprecations](#deprecations-5)
- [Contributors](#contributors-28)
- [Contributors](#contributors-27)
- [v22.1](#v221)
- [v22.0](#v220)
- [GDB Debug Stub Support](#gdb-debug-stub-support)
@@ -232,13 +223,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-29)
- [Contributors](#contributors-29)
- [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-30)
- [Contributors](#contributors-30)
- [Notable Bug fixes](#notable-bug-fixes-29)
- [Contributors](#contributors-29)
- [v20.2](#v202)
- [v20.1](#v201)
- [v20.0](#v200)
@@ -247,8 +238,8 @@
- [Improved VFIO support](#improved-vfio-support)
- [Safer code](#safer-code)
- [Extended documentation](#extended-documentation)
- [Notable bug fixes](#notable-bug-fixes-31)
- [Contributors](#contributors-31)
- [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)
@@ -256,8 +247,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-32)
- [Contributors](#contributors-32)
- [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)
@@ -267,23 +258,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-33)
- [Contributors](#contributors-33)
- [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-34)
- [Contributors](#contributors-34)
- [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-35)
- [Notable bug fixes](#notable-bug-fixes-34)
- [Removed functionality](#removed-functionality)
- [Contributors](#contributors-35)
- [Contributors](#contributors-34)
- [v15.0](#v150)
- [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees)
- [Network device rate limiting](#network-device-rate-limiting)
@@ -291,7 +282,7 @@
- [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter)
- [Bug fixes](#bug-fixes)
- [Deprecations](#deprecations-6)
- [Contributors](#contributors-36)
- [Contributors](#contributors-35)
- [v0.14.1](#v0141)
- [v0.14.0](#v0140)
- [Structured event monitoring](#structured-event-monitoring)
@@ -301,7 +292,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-37)
- [Contributors](#contributors-36)
- [v0.13.0](#v0130)
- [Wider VFIO device support](#wider-vfio-device-support)
- [Improved huge page support](#improved-huge-page-support)
@@ -309,13 +300,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-38)
- [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-39)
- [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)
@@ -327,15 +318,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-36)
- [Contributors](#contributors-40)
- [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-37)
- [Contributors](#contributors-41)
- [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)
@@ -348,17 +339,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-38)
- [Contributors](#contributors-42)
- [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-39)
- [Notable Bug Fixes](#notable-bug-fixes-38)
- [Command Line and API Changes](#command-line-and-api-changes)
- [Contributors](#contributors-43)
- [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)
@@ -368,14 +359,14 @@
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
- [Updated Distribution Support](#updated-distribution-support)
- [Command Line and API Changes](#command-line-and-api-changes-1)
- [Contributors](#contributors-44)
- [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-45)
- [Contributors](#contributors-44)
- [v0.5.1](#v051)
- [v0.5.0](#v050)
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
@@ -383,7 +374,7 @@
- [New Interrupt Management Framework](#new-interrupt-management-framework)
- [Development Tools](#development-tools)
- [Kata Containers Integration](#kata-containers-integration)
- [Contributors](#contributors-46)
- [Contributors](#contributors-45)
- [v0.4.0](#v040)
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
@@ -392,7 +383,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-47)
- [Contributors](#contributors-46)
- [v0.3.0](#v030)
- [Block device offloading](#block-device-offloading)
- [Network device backend](#network-device-backend)
@@ -419,17 +410,9 @@
- [Unit testing](#unit-testing)
- [Integration tests parallelization](#integration-tests-parallelization)
# v51.1
# v50.1
This is a bug fix release. The following issues have been addressed:
* Fix image_type in OpenAPI definition (#7734)
# 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/).
This is a point release containing security fixes and bug fixes.
### Security Fixes
@@ -445,110 +428,12 @@ Details can be found in
reliance on format autodetection (#7728).
* Prevent sector-zero writes for autodetected raw images (#7728).
### Significant QCOW2 v3 Improvements
### Bug Fixes
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 <aastharawat@microsoft.com>
* Alyssa Ross <hi@alyssa.is>
* Anatol Belski <anbelski@linux.microsoft.com>
* Anirudh Rayabharam <anrayabh@microsoft.com>
* Bo Chen <bchen@crusoe.ai>
* Champ-Goblem <cameron@northflank.com>
* Changyuan Lyu <changyuanl@google.com>
* Damian Barabonkov <dbctl@pm.me>
* Demi Marie Obenour <demiobenour@gmail.com>
* Leander Kohler <leander.kohler@cyberus-technology.de>
* Muminul Islam <muislam@microsoft.com>
* Philipp Schuster <philipp.schuster@cyberus-technology.de>
* Rob Bradford <rbradford@meta.com>
* Rowen-Ye <rowenye1@gmail.com>
* Saravanan D <saravanand@crusoe.ai>
* Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
* Thomas Leroy <thomas.leroy.mp@gmail.com>
* Wei Liu <liuwe@microsoft.com>
* Yi Wang <foxywang@tencent.com>
* Zhibin Li <banlu.lzb@antgroup.com>
* stevenhorsman <steven@uk.ibm.com>
* Fix QCOW2 thread safety for multiple virtio queues
(`num_queues > 1`) (#7661)
# v50.0

View File

@@ -21,12 +21,12 @@ build_edk2() {
fi
# Prepare source code
checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "22130dcd98b4d4b76ac8d922adb4a2dbc86fa52c"
checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "46b4606ba23498d3d0e66b53e498eb3d5d592586"
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 "e80cbd7b52de20aa8c75bfba9845e9cb61f2e681"
checkout_repo "$ACPICA_DIR" "$ACPICA_REPO" master "b9c69f81a05c45611c91ea9cbce8756078d76233"
if [[ ! -f "$EDK2_DIR/.built" ||
! -f "$EDK2_PLAT_DIR/.built" ||
@@ -38,14 +38,10 @@ 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
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
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
popd || exit
fi
}

View File

@@ -1,37 +1,8 @@
#!/usr/bin/env bash
set -ex
usage() {
echo "Usage: $0 [-o|--output <output_file>]"
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 ::
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 ::

View File

@@ -28,9 +28,6 @@ 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"
@@ -176,23 +173,6 @@ 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")"
@@ -220,7 +200,6 @@ 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"
@@ -354,7 +333,6 @@ cmd_tests() {
integration_windows=false
integration_live_migration=false
integration_rate_limiter=false
integration_cvm=false
metrics=false
coverage=false
libc="gnu"
@@ -373,7 +351,6 @@ 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")
@@ -472,33 +449,6 @@ 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 \

View File

@@ -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" v1.8.0 "97ea7908fe7f9bc59916671a771bdcfaf4044b45"
if [ ! -f "$VIRTIOFSD_DIR/.built" ]; then
pushd "$VIRTIOFSD_DIR" || exit
@@ -100,16 +100,6 @@ 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

View File

@@ -1,33 +0,0 @@
#!/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

View File

@@ -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-common | sha1sum --check; then
if ! grep focal sha1sums-x86_64 | sha1sum --check; then
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
exit 1
fi

View File

@@ -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-common | 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

View File

@@ -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
@@ -28,7 +28,39 @@ if [ ! -f "$WORKLOADS_DIR/CLOUDHV.fd" ]; then
download_ovmf
fi
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
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"
@@ -68,16 +100,6 @@ 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
@@ -105,7 +127,7 @@ if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then
fi
pushd "$WORKLOADS_DIR" || exit
if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then
if ! sha1sum sha1sums-x86_64 --check; then
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
exit 1
fi
@@ -124,7 +146,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 v1.8.0
time cargo build --release
cp target/release/virtiofsd "$VIRTIOFSD" || exit 1
popd || exit

View File

@@ -28,7 +28,7 @@ build_fio() {
process_common_args "$@"
cp scripts/sha1sums-"${TEST_ARCH}"-common "$WORKLOADS_DIR"
cp scripts/sha1sums-"${TEST_ARCH}" "$WORKLOADS_DIR"
if [ "${TEST_ARCH}" == "aarch64" ]; then
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0.qcow2"
@@ -58,11 +58,10 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
fi
pushd "$WORKLOADS_DIR" || exit
if ! grep focal sha1sums-"${TEST_ARCH}"-common | sha1sum --check; then
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

View File

@@ -1,3 +1,7 @@
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

View File

@@ -1,4 +0,0 @@
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

View File

@@ -1,6 +1,4 @@
#!/usr/bin/env bash
# shellcheck disable=SC1009,SC2048,SC2086,SC1073,SC1040,SC1072
# shellcheck source=/dev/null
set -x
hypervisor="kvm"
@@ -214,7 +212,7 @@ mount_and_exec() {
local COMMAND_STATUS=0
# Cleanup function to unmount and detach loop device
# shellcheck disable=SC2317,SC2329
# shellcheck disable=SC2317
cleanup() {
if [ -n "$MOUNT_DIR" ]; then
echo "Cleanup: Unmounting $MOUNT_DIR..." >&2
@@ -303,40 +301,3 @@ 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
}

View File

@@ -8,7 +8,6 @@ version = "0.1.0"
dirs = { workspace = true }
epoll = { workspace = true }
libc = { workspace = true }
rand = "0.10.0"
serde_json = { workspace = true }
ssh2 = { version = "0.9.5", features = ["vendored-openssl"] }
thiserror = { workspace = true }

View File

@@ -18,7 +18,6 @@ use std::str::FromStr;
use std::time::Duration;
use std::{env, fmt, fs, io, thread};
use rand::Rng;
use serde_json::Value;
use ssh2::Session;
use thiserror::Error;
@@ -75,8 +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: u32 = 120;
pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 120;
pub const DEFAULT_TCP_LISTENER_TIMEOUT: i32 = 120;
#[derive(Error, Debug)]
pub enum WaitForBootError {
@@ -93,12 +91,16 @@ pub enum WaitForBootError {
}
impl GuestNetworkConfig {
pub fn wait_vm_boot(&self, custom_timeout: u32) -> Result<(), WaitForBootError> {
pub fn wait_vm_boot(&self, custom_timeout: Option<i32>) -> 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 =
@@ -120,11 +122,7 @@ 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,
(custom_timeout * 1000).try_into().unwrap(),
&mut events[..],
) {
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,
@@ -164,7 +162,7 @@ impl GuestNetworkConfig {
let duration = start.elapsed();
eprintln!(
"\n\n==== Start 'wait_vm_boot' (FAILED) ==== \
\n\nduration =\"{duration:?}, timeout = {custom_timeout}s\" \
\n\nduration =\"{duration:?}, timeout = {timeout}s\" \
\nlisten_addr=\"{listen_addr}\" \
\nexpected_guest_addr=\"{expected_guest_addr}\" \
\nmessage=\"{s}\" \
@@ -889,11 +887,6 @@ pub struct Guest {
pub tmp_dir: TempDir,
pub disk_config: Box<dyn DiskConfig>,
pub network: GuestNetworkConfig,
pub vm_type: GuestVmType,
pub boot_timeout: u32,
pub kernel_path: Option<String>,
pub kernel_cmdline: Option<String>,
pub console_type: Option<String>,
}
// Return the next id that can be used for this guest. This is stored in a
@@ -958,11 +951,6 @@ impl Guest {
tmp_dir,
disk_config,
network,
vm_type: GuestVmType::Regular,
boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT,
kernel_path: None,
kernel_cmdline: None,
console_type: None,
}
}
@@ -1088,17 +1076,7 @@ impl Guest {
.map_err(Error::Parsing)
}
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> {
pub fn wait_vm_boot(&self, custom_timeout: Option<i32>) -> Result<(), Error> {
self.network
.wait_vm_boot(custom_timeout)
.map_err(Error::WaitForBoot)
@@ -1236,7 +1214,7 @@ impl Guest {
);
}
pub fn reboot_linux(&self, current_reboot_count: u32) {
pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option<i32>) {
let list_boots_cmd = "sudo last | grep -c reboot";
let boot_count = self
.ssh_command(list_boots_cmd)
@@ -1248,7 +1226,7 @@ impl Guest {
assert_eq!(boot_count, current_reboot_count + 1);
self.ssh_command("sudo reboot").unwrap();
self.wait_vm_boot().unwrap();
self.wait_vm_boot(custom_timeout).unwrap();
let boot_count = self
.ssh_command(list_boots_cmd)
.unwrap()
@@ -1483,29 +1461,6 @@ 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 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]);
}
}
self
}
}
/// Returns the absolute path into the workspaces target directory to locate the desired
@@ -1903,33 +1858,3 @@ pub fn extract_bar_address(output: &str, device_desc: &str, bar_index: usize) ->
}
None
}
#[derive(PartialEq, Clone, Copy)]
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<PathBuf> {
// 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()
}

View File

@@ -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, warn};
use log::{debug, error, info};
use option_parser::{OptionParser, OptionParserError, Toggle};
use thiserror::Error;
use vhost::vhost_user::Listener;
@@ -225,7 +225,7 @@ impl VhostUserBlkBackend {
let image = match image_type {
ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc<Mutex<dyn DiskFile>>,
ImageType::Qcow2 => Arc::new(Mutex::new(
QcowFile::from_with_nesting_depth(raw_img, 0, true).unwrap(),
QcowFile::from_with_nesting_depth(raw_img, 0).unwrap(),
)) as Arc<Mutex<dyn DiskFile>>,
};
@@ -395,18 +395,8 @@ impl VhostUserBackendMut for VhostUserBlkBackend {
}
}
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
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 get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
self.config.as_slice().to_vec()
}
fn set_config(&mut self, offset: u32, data: &[u8]) -> result::Result<(), io::Error> {

View File

@@ -175,9 +175,7 @@ impl BlockEpollHandler {
) -> 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)
&& !(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
@@ -263,7 +261,6 @@ 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,
);
@@ -690,7 +687,6 @@ impl Block {
exit_evt: EventFd,
state: Option<BlockState>,
queue_affinity: BTreeMap<u16, Vec<usize>>,
sparse: bool,
disable_sector0_writes: bool,
) -> io::Result<Self> {
let (disk_nsectors, avail_features, acked_features, config, paused) =
@@ -722,20 +718,6 @@ impl Block {
| (1u64 << VIRTIO_BLK_F_SEG_MAX)
| (1u64 << VIRTIO_RING_F_EVENT_IDX)
| (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
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;
}
} else if sparse {
warn!("sparse=on requested but backend does not support sparse operations");
}
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
@@ -1004,13 +986,6 @@ impl VirtioDevice for Block {
interrupt_cb: Arc<dyn VirtioInterrupt>,
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();

View File

@@ -293,7 +293,6 @@ fn virtio_thread_common() -> Vec<(i64, Vec<SeccompRule>)> {
(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![]),

View File

@@ -11,7 +11,8 @@ use log::{error, info};
use seccompiler::SeccompAction;
use serde::{Deserialize, Serialize};
use vhost::vhost_user::message::{
VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
VHOST_USER_CONFIG_OFFSET, VhostUserConfigFlags, VhostUserProtocolFeatures,
VhostUserVirtioFeatures,
};
use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
use virtio_bindings::virtio_blk::{
@@ -146,7 +147,7 @@ impl Blk {
let (_, config_space) = vu
.socket_handle()
.get_config(
0,
VHOST_USER_CONFIG_OFFSET,
config_len as u32,
VhostUserConfigFlags::WRITABLE,
config_space.as_slice(),

View File

@@ -375,13 +375,15 @@ 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(

View File

@@ -28,10 +28,11 @@ 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::{Error, Result, VhostUserCommon};
use crate::{
ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_IOMMU_PLATFORM,
VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType,
VirtioInterrupt,
};
const DEFAULT_QUEUE_NUMBER: usize = 2;
@@ -121,7 +122,9 @@ 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)
| DEFAULT_VIRTIO_FEATURES;
| (1 << VIRTIO_F_RING_EVENT_IDX)
| (1 << VIRTIO_F_VERSION_1)
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
if mtu.is_some() {
avail_features |= 1u64 << VIRTIO_NET_F_MTU;

View File

@@ -130,16 +130,7 @@ where
) {
Ok(mut pkt) => {
if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() {
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
}
}
pkt.hdr().len() as u32 + pkt.len()
} 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.

View File

@@ -3,13 +3,14 @@
//
//! `VsockPacket` provides a thin wrapper over the buffers exchanged via virtio queues.
//! There are two components to a vsock packet, each described by a virtio descriptor chain:
//! There are two components to a vsock packet, each using its own descriptor in a
//! virtio queue:
//! - 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 the remaining descriptors (if any) hold the data. The data descriptors are
//! only present for data packets (VSOCK_OP_RW).
//! the header, and an optional second descriptor holds the data. The second descriptor is 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
@@ -19,7 +20,7 @@ use std::ops::Deref;
use byteorder::{ByteOrder, LittleEndian};
use virtio_queue::DescriptorChain;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory};
use vm_memory::{Address, GuestMemory};
use vm_virtio::{AccessPlatform, Translatable};
use super::{Result, VsockError, defs};
@@ -90,26 +91,14 @@ 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
/// - (optional) buffer, only present for data packets (VSOCK_OP_RW).
/// - (an optional) data/buffer descriptor, 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<PacketBuffer>,
hdr: *mut u8,
buf: Option<*mut u8>,
buf_size: usize,
}
impl VsockPacket {
@@ -140,24 +129,16 @@ 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 {
guest_hdr_addr,
hdr,
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)?,
buf: None,
buf_size: 0,
};
// No point looking for a data/buffer descriptor, if the packet is zero-length.
@@ -171,85 +152,44 @@ impl VsockPacket {
return Err(VsockError::InvalidPktLen(pkt.len()));
}
// 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,
});
// Prior to Linux v6.3 there are two descriptors
if head.has_next() {
let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?;
return Ok(pkt);
}
// 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
};
// TX data should be read-only.
if buf_desc.is_write_only() {
return Err(VsockError::UnreadableDescriptor);
}
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;
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,
});
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)?,
);
}
Ok(pkt)
@@ -281,52 +221,41 @@ 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;
// 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(PacketBuffer::Borrowed {
ptr: get_host_address_range(
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)?,
buf: Some(
get_host_address_range(
desc_chain.memory(),
buf_desc.addr().translate_gva(access_platform, buf_size),
buf_size,
)
.ok_or(VsockError::GuestMemory)?,
len: buf_size,
}),
),
buf_size,
})
} else {
let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE;
Ok(Self {
guest_hdr_addr,
hdr,
buf: Some(PacketBuffer::Borrowed {
ptr: get_host_address_range(
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)?,
buf: Some(
get_host_address_range(
desc_chain.memory(),
head.addr()
.checked_add(VSOCK_PKT_HDR_SIZE as u64)
@@ -335,8 +264,8 @@ impl VsockPacket {
buf_size,
)
.ok_or(VsockError::GuestMemory)?,
len: buf_size,
}),
),
buf_size,
})
}
}
@@ -344,27 +273,17 @@ impl VsockPacket {
/// Provides in-place, byte-slice, access to the vsock packet header.
///
pub fn hdr(&self) -> &[u8] {
self.hdr.as_slice()
// 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) }
}
/// Provides in-place, byte-slice, mutable access to the vsock packet header.
///
pub fn hdr_mut(&mut self) -> &mut [u8] {
self.hdr.as_mut_slice()
}
/// Writes the local copy of the packet header to the guest memory.
///
pub fn commit_hdr<M: GuestMemory>(&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(())
// 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) }
}
/// Provides in-place, byte-slice access to the vsock packet data buffer.
@@ -375,14 +294,11 @@ 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]> {
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) })
}
}
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) }
})
}
/// Provides in-place, byte-slice, mutable access to the vsock packet data buffer.
@@ -393,14 +309,11 @@ 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]> {
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) })
}
}
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) }
})
}
pub fn src_cid(&self) -> u64 {
@@ -508,7 +421,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::{VirtQueue as GuestQ, VirtqDesc as GuestQDesc};
use vm_virtio::queue::testing::VirtqDesc as GuestQDesc;
use super::super::unit_tests::TestContext;
use super::*;
@@ -644,45 +557,6 @@ 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.

Some files were not shown because too many files have changed in this diff Show More