Files
cloud-hypervisor/pci/src/mmap.rs
Julian Schindel 6de472f1bb pci: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00

90 lines
2.9 KiB
Rust

// Copyright © 2025 Demi Marie Obenour
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//! Helpers for `mmap()`
use core::ffi::c_int;
use core::ptr::null_mut;
use std::io::{Error, ErrorKind};
use std::os::fd::{AsRawFd as _, BorrowedFd};
use libc::size_t;
/// A region of `mmap()`-allocated memory that calls `munmap()` when dropped.
/// This guarantees that the buffer is valid and that its address space
/// will be reserved. The address space is not guaranteed to be accessible.
/// Atomic access to the data will not cause undefined behavior but might
/// cause SIGSEGV or SIGBUS. Non-atomic access will generally cause data
/// races and thus Undefined Behavior.
#[derive(Debug)]
pub struct MmapRegion {
addr: *mut u8,
len: size_t,
}
impl Drop for MmapRegion {
fn drop(&mut self) {
// SAFETY: guaranteed by type validity invariant
unsafe { assert_eq!(libc::munmap(self.addr.cast(), self.len), 0) }
}
}
// SAFETY: the caller is responsible for avoiding data races
unsafe impl Send for MmapRegion {}
// SAFETY: the caller is responsible for avoiding data races
unsafe impl Sync for MmapRegion {}
impl MmapRegion {
#[inline]
pub fn addr(&self) -> *mut u8 {
self.addr
}
/// Return the length of the region.
/// This function promises that the return value fits in [`libc::size_t`]
/// and in [`isize`] and `unsafe` code can rely on this.
#[inline]
pub fn len(&self) -> usize {
self.len
}
/// Create an [`MmapRegion`] using `mmap` of a file descriptor.
pub fn mmap(
len: u64,
prot: c_int,
fd: BorrowedFd,
offset1: u64,
offset2: u64,
) -> std::io::Result<Self> {
const BAD_LENGTH: &str = "Offsets must fit in libc::off_t";
const BAD_OFFSET: &str = "Mapping length must fit \
in both isize and libc::size_t";
let Some(offset) = offset1.checked_add(offset2) else {
return Err(Error::new(ErrorKind::InvalidInput, BAD_OFFSET));
};
let Ok(offset) = libc::off_t::try_from(offset) else {
return Err(Error::new(ErrorKind::InvalidInput, BAD_OFFSET));
};
if isize::try_from(len).is_err() {
return Err(Error::new(ErrorKind::InvalidInput, BAD_LENGTH));
}
let Ok(len) = libc::size_t::try_from(len) else {
return Err(Error::new(ErrorKind::InvalidInput, BAD_LENGTH));
};
assert!(
(prot & !(libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC)) == 0,
"bad protection"
);
let flags = libc::MAP_SHARED;
// SAFETY: FFI call with correct parameters.
let addr = unsafe { libc::mmap(null_mut(), len, prot, flags, fd.as_raw_fd(), offset) };
if addr == libc::MAP_FAILED {
Err(Error::last_os_error())
} else {
let addr = addr.cast();
Ok(Self { addr, len })
}
}
}