Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions block/src/aligned_operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause

use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::io;

use vm_memory::GuestAddress;

/// Owns an aligned bounce buffer used when a guest descriptor's host VA
/// does not meet the disk backend's alignment requirement.
#[derive(Debug)]
pub struct AlignedOperation {
data_addr: GuestAddress,
aligned_ptr: *mut u8,
size: usize,
layout: Layout,
}

impl AlignedOperation {
/// Allocate a zero-initialized buffer of `size` bytes aligned to
/// `alignment`. Returns `InvalidInput` if `size` is zero;
/// `alignment` must be a power of two and not exceed `isize::MAX`
/// after rounding up.
pub fn new(data_addr: GuestAddress, size: usize, alignment: usize) -> io::Result<Self> {
if size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"AlignedOperation requires a non-zero size",
));
}
let layout = Layout::from_size_align(size, alignment)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
// SAFETY: size is non-zero (checked above) and Layout::from_size_align
// rejects alignments that are not a power of two or that overflow.
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Self {
data_addr,
aligned_ptr,
size,
layout,
})
}

/// Gets the raw pointer to the aligned buffer.
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.aligned_ptr
}

/// Returns the aligned buffer as a slice.
pub fn as_bytes(&self) -> &[u8] {
// SAFETY: `new` allocates `size` bytes via alloc_zeroed (so they
// are initialized) and AlignedOperation owns the buffer
// exclusively.
unsafe { std::slice::from_raw_parts(self.aligned_ptr, self.size) }
}

/// Returns the aligned buffer as a mutable slice.
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
// SAFETY: same invariant as as_bytes; &mut self rules out other
// simultaneous borrows.
unsafe { std::slice::from_raw_parts_mut(self.aligned_ptr, self.size) }
}

/// Returns the guest address for this op.
pub fn data_addr(&self) -> GuestAddress {
self.data_addr
}
}

impl Drop for AlignedOperation {
fn drop(&mut self) {
// SAFETY: `new` is the only constructor, and it stores a pointer
// returned by `alloc_zeroed` paired with the exact `layout` used
// for that allocation. Ownership has not escaped (the type is
// neither `Clone` nor `Copy`).
unsafe {
dealloc(self.aligned_ptr, self.layout);
}
}
}

// SAFETY: AlignedOperation owns its heap allocation exclusively (no Clone/
// Copy, no shared aliases) and the allocation's lifetime is tied to the
// value's. Moving an AlignedOperation between threads transfers that
// ownership; the same rationale Box<T> uses for its Send impl.
unsafe impl Send for AlignedOperation {}
66 changes: 17 additions & 49 deletions block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause

mod aligned_operation;
pub mod async_io;
pub mod fcntl;
pub mod fixed_vhd;
Expand All @@ -28,7 +29,7 @@ pub mod vhd;
pub mod vhdx;
pub mod vhdx_sync;

use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::alloc::{Layout, alloc_zeroed};
use std::collections::VecDeque;
use std::fmt::{self, Debug};
use std::fs::File;
Expand All @@ -40,6 +41,7 @@ use std::str::FromStr;
use std::time::Instant;
use std::{cmp, result};

pub use aligned_operation::AlignedOperation;
#[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode};
use libc::{S_IFBLK, S_IFMT, ioctl};
Expand Down Expand Up @@ -232,14 +234,6 @@ fn sector<B: Bitmap + 'static>(

const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;

#[derive(Debug)]
pub struct AlignedOperation {
origin_ptr: u64,
aligned_ptr: u64,
size: usize,
layout: Layout,
}

pub struct BatchRequest {
pub offset: libc::off_t,
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
Expand Down Expand Up @@ -473,31 +467,19 @@ impl Request {
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
origin_ptr.as_ptr() as *mut libc::c_void
} else {
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
// SAFETY: layout has non-zero size
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {
return Err(ExecuteError::TemporaryBufferAllocation(
io::Error::last_os_error(),
));
}
let mut aligned_op =
AlignedOperation::new(data_addr, data_len, SECTOR_SIZE as usize)
.map_err(ExecuteError::TemporaryBufferAllocation)?;

// We need to perform the copy beforehand in case we're writing
// data out.
if request_type == RequestType::Out {
// SAFETY: destination buffer has been allocated with
// the proper size.
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) };
mem.read_slice(aligned_op.as_bytes_mut(), data_addr)
.map_err(ExecuteError::Read)?;
}

// Store both origin and aligned pointers for complete_async()
// to process them.
self.aligned_operations.push(AlignedOperation {
origin_ptr: origin_ptr.as_ptr() as u64,
aligned_ptr: aligned_ptr as u64,
size: data_len,
layout,
});
let aligned_ptr = aligned_op.as_mut_ptr();
self.aligned_operations.push(aligned_op);

aligned_ptr as *mut libc::c_void
};
Expand Down Expand Up @@ -639,31 +621,17 @@ impl Request {
Ok(ret)
}

pub fn complete_async(&mut self) -> result::Result<(), Error> {
for aligned_operation in self.aligned_operations.drain(..) {
pub fn complete_async<B: Bitmap + 'static>(
&mut self,
mem: &vm_memory::GuestMemoryMmap<B>,
) -> result::Result<(), Error> {
for aligned_op in self.aligned_operations.drain(..) {
// We need to perform the copy after the data has been read inside
// the aligned buffer in case we're reading data in.
if self.request_type == RequestType::In {
// SAFETY: origin buffer has been allocated with the
// proper size.
unsafe {
std::ptr::copy(
aligned_operation.aligned_ptr as *const u8,
aligned_operation.origin_ptr as *mut u8,
aligned_operation.size,
);
};
mem.write_slice(aligned_op.as_bytes(), aligned_op.data_addr())
.map_err(Error::GuestMemory)?;
}

// Free the temporary aligned buffer.
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
// layout
unsafe {
dealloc(
aligned_operation.aligned_ptr as *mut u8,
aligned_operation.layout,
);
};
}

Ok(())
Expand Down
8 changes: 8 additions & 0 deletions block/src/raw_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ impl AsyncIo for RawFileAsync {
let (submitter, mut sq, _) = self.io_uring.split();
let mut submitted = false;

// Refuse the whole batch if it can't fit in the SQ to avoid having to unroll a partially
// successful push.
if batch_request.len() > sq.capacity() - sq.len() {
return Err(AsyncIoError::SubmitBatchRequests(Error::other(
"io_uring submission queue is full",
)));
}

for req in batch_request {
match req.request_type {
RequestType::In => {
Expand Down
2 changes: 1 addition & 1 deletion cloud-hypervisor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "51.2.0"
# Minimum buildable version:
# Keep in sync with version in .github/workflows/build.yaml
# Policy on MSRV (see #4318):
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/balloon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,16 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
reporting_queue_evt.write(1).unwrap();

balloon
.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![
.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![
(0, inflate_q, inflate_evt),
(1, deflate_q, deflate_evt),
(2, reporting_q, reporting_evt),
],
)
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and balloon device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
queue_evt.write(1).unwrap();

block
.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, q, evt)],
)
.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, q, evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and block device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
pipe_tx.write_all(console_input_bytes).unwrap(); // To use fuzzed data;

console
.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
)
.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.unwrap();

// Wait for the events to finish and console device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/iommu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,15 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
request_queue_evt.write(1).unwrap();

iommu
.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![
.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![
(0, request_queue, request_evt),
(0, _event_queue, _event_evt),
],
)
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and vIOMMU device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
queue_evt.write(1).unwrap();

virtio_mem
.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, q, evt)],
)
.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, q, evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and virtio-mem device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
input_queue_evt.write(1).unwrap();
output_queue_evt.write(1).unwrap();

net.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
)
net.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.unwrap();

// Wait for the events to finish and net device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/pmem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
// Kick the 'queue' event before activate the pmem device
queue_evt.write(1).unwrap();

pmem.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, q, evt)],
)
pmem.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, q, evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and pmem device worker thread to return
Expand Down
11 changes: 6 additions & 5 deletions fuzz/fuzz_targets/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
// Kick the 'queue' event before activate the rng device
queue_evt.write(1).unwrap();

rng.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, q, evt)],
)
rng.activate(virtio_devices::ActivationContext {
mem: guest_memory,
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
queues: vec![(0, q, evt)],
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
})
.ok();

// Wait for the events to finish and rng device worker thread to return
Expand Down
Loading
Loading