use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::os::unix::io::FromRawFd;
use std::path::Path;
use std::str::FromStr;
use nix::sys::memfd;
use vm_memory::{Address, FileOffset, GuestAddress, GuestUsize};
use crate::memory::MemorySourceType;
use crate::memory::MemorySourceType::MemFdShared;
use crate::AddressSpaceError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AddressSpaceRegionType {
DefaultMemory,
DeviceMemory,
DAXMemory,
}
#[derive(Debug, Clone)]
pub struct AddressSpaceRegion {
pub ty: AddressSpaceRegionType,
pub base: GuestAddress,
pub size: GuestUsize,
pub host_numa_node_id: Option<u32>,
file_offset: Option<FileOffset>,
perm_flags: i32,
prot_flags: i32,
is_hugepage: bool,
is_hotplug: bool,
is_anon: bool,
}
#[allow(clippy::too_many_arguments)]
impl AddressSpaceRegion {
pub fn new(ty: AddressSpaceRegionType, base: GuestAddress, size: GuestUsize) -> Self {
AddressSpaceRegion {
ty,
base,
size,
host_numa_node_id: None,
file_offset: None,
perm_flags: libc::MAP_SHARED,
prot_flags: libc::PROT_READ | libc::PROT_WRITE,
is_hugepage: false,
is_hotplug: false,
is_anon: false,
}
}
pub fn build(
ty: AddressSpaceRegionType,
base: GuestAddress,
size: GuestUsize,
host_numa_node_id: Option<u32>,
file_offset: Option<FileOffset>,
perm_flags: i32,
prot_flags: i32,
is_hotplug: bool,
) -> Self {
let mut region = Self::new(ty, base, size);
region.set_host_numa_node_id(host_numa_node_id);
region.set_file_offset(file_offset);
region.set_perm_flags(perm_flags);
region.set_prot_flags(prot_flags);
if is_hotplug {
region.set_hotplug();
}
region
}
pub fn create_default_memory_region(
base: GuestAddress,
size: GuestUsize,
numa_node_id: Option<u32>,
mem_type: &str,
mem_file_path: &str,
mem_prealloc: bool,
is_hotplug: bool,
) -> Result<AddressSpaceRegion, AddressSpaceError> {
Self::create_memory_region(
base,
size,
numa_node_id,
mem_type,
mem_file_path,
mem_prealloc,
libc::PROT_READ | libc::PROT_WRITE,
is_hotplug,
)
}
pub fn create_memory_region(
base: GuestAddress,
size: GuestUsize,
numa_node_id: Option<u32>,
mem_type: &str,
mem_file_path: &str,
mem_prealloc: bool,
prot_flags: i32,
is_hotplug: bool,
) -> Result<AddressSpaceRegion, AddressSpaceError> {
let perm_flags = if mem_prealloc {
libc::MAP_SHARED | libc::MAP_POPULATE
} else {
libc::MAP_SHARED
};
let source_type = MemorySourceType::from_str(mem_type)
.map_err(|_e| AddressSpaceError::InvalidMemorySourceType(mem_type.to_string()))?;
let mut reg = match source_type {
MemorySourceType::MemFdShared | MemorySourceType::MemFdOnHugeTlbFs => {
let fn_str = if source_type == MemFdShared {
CString::new("shmem").expect("CString::new('shmem') failed")
} else {
CString::new("hugeshmem").expect("CString::new('hugeshmem') failed")
};
let filename = fn_str.as_c_str();
let fd = memfd::memfd_create(filename, memfd::MemFdCreateFlag::empty())
.map_err(AddressSpaceError::CreateMemFd)?;
let file: File = unsafe { File::from_raw_fd(fd) };
file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
Self::build(
AddressSpaceRegionType::DefaultMemory,
base,
size,
numa_node_id,
Some(FileOffset::new(file, 0)),
perm_flags,
prot_flags,
is_hotplug,
)
}
MemorySourceType::MmapAnonymous | MemorySourceType::MmapAnonymousHugeTlbFs => {
let mut perm_flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
if mem_prealloc {
perm_flags |= libc::MAP_POPULATE
}
Self::build(
AddressSpaceRegionType::DefaultMemory,
base,
size,
numa_node_id,
None,
perm_flags,
prot_flags,
is_hotplug,
)
}
MemorySourceType::FileOnHugeTlbFs => {
let path = Path::new(mem_file_path);
if let Some(parent_dir) = path.parent() {
std::fs::create_dir_all(parent_dir).map_err(AddressSpaceError::CreateDir)?;
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(mem_file_path)
.map_err(AddressSpaceError::OpenFile)?;
nix::unistd::unlink(mem_file_path).map_err(AddressSpaceError::UnlinkFile)?;
file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
let file_offset = FileOffset::new(file, 0);
Self::build(
AddressSpaceRegionType::DefaultMemory,
base,
size,
numa_node_id,
Some(file_offset),
perm_flags,
prot_flags,
is_hotplug,
)
}
};
if source_type.is_hugepage() {
reg.set_hugepage();
}
if source_type.is_mmap_anonymous() {
reg.set_anonpage();
}
Ok(reg)
}
pub fn create_device_region(
base: GuestAddress,
size: GuestUsize,
) -> Result<AddressSpaceRegion, AddressSpaceError> {
Ok(Self::build(
AddressSpaceRegionType::DeviceMemory,
base,
size,
None,
None,
0,
0,
false,
))
}
pub fn region_type(&self) -> AddressSpaceRegionType {
self.ty
}
pub fn len(&self) -> GuestUsize {
self.size
}
pub fn start_addr(&self) -> GuestAddress {
self.base
}
pub fn last_addr(&self) -> GuestAddress {
debug_assert!(self.size > 0 && self.base.checked_add(self.size).is_some());
GuestAddress(self.base.raw_value() + self.size - 1)
}
pub fn perm_flags(&self) -> i32 {
self.perm_flags
}
pub fn set_perm_flags(&mut self, perm_flags: i32) {
self.perm_flags = perm_flags;
}
pub fn prot_flags(&self) -> i32 {
self.prot_flags
}
pub fn set_prot_flags(&mut self, prot_flags: i32) {
self.prot_flags = prot_flags;
}
pub fn host_numa_node_id(&self) -> Option<u32> {
self.host_numa_node_id
}
pub fn set_host_numa_node_id(&mut self, host_numa_node_id: Option<u32>) {
self.host_numa_node_id = host_numa_node_id;
}
pub fn has_file(&self) -> bool {
self.file_offset.is_some()
}
pub fn file_offset(&self) -> Option<&FileOffset> {
self.file_offset.as_ref()
}
pub fn set_file_offset(&mut self, file_offset: Option<FileOffset>) {
self.file_offset = file_offset;
}
pub fn set_hotplug(&mut self) {
self.is_hotplug = true
}
pub fn is_hotplug(&self) -> bool {
self.is_hotplug
}
pub fn set_hugepage(&mut self) {
self.is_hugepage = true
}
pub fn is_hugepage(&self) -> bool {
self.is_hugepage
}
pub fn set_anonpage(&mut self) {
self.is_anon = true
}
pub fn is_anonpage(&self) -> bool {
self.is_anon
}
pub fn is_valid(&self) -> bool {
self.size > 0 && self.base.checked_add(self.size).is_some()
}
pub fn intersect_with(&self, other: &AddressSpaceRegion) -> bool {
let end1 = match self.base.checked_add(self.size) {
Some(addr) => addr,
None => return true,
};
let end2 = match other.base.checked_add(other.size) {
Some(addr) => addr,
None => return true,
};
!(end1 <= other.base || self.base >= end2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use vmm_sys_util::tempfile::TempFile;
#[test]
fn test_address_space_region_valid() {
let reg1 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0xFFFFFFFFFFFFF000),
0x2000,
);
assert!(!reg1.is_valid());
let reg1 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0xFFFFFFFFFFFFF000),
0x1000,
);
assert!(!reg1.is_valid());
let reg1 = AddressSpaceRegion::new(
AddressSpaceRegionType::DeviceMemory,
GuestAddress(0xFFFFFFFFFFFFE000),
0x1000,
);
assert!(reg1.is_valid());
assert_eq!(reg1.start_addr(), GuestAddress(0xFFFFFFFFFFFFE000));
assert_eq!(reg1.len(), 0x1000);
assert!(!reg1.has_file());
assert!(reg1.file_offset().is_none());
assert_eq!(reg1.perm_flags(), libc::MAP_SHARED);
assert_eq!(reg1.prot_flags(), libc::PROT_READ | libc::PROT_WRITE);
assert_eq!(reg1.region_type(), AddressSpaceRegionType::DeviceMemory);
let tmp_file = TempFile::new().unwrap();
let mut f = tmp_file.into_file();
let sample_buf = &[1, 2, 3, 4, 5];
assert!(f.write_all(sample_buf).is_ok());
let reg2 = AddressSpaceRegion::build(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0x1000),
0x1000,
None,
Some(FileOffset::new(f, 0x0)),
0x5a,
0x5a,
false,
);
assert_eq!(reg2.region_type(), AddressSpaceRegionType::DefaultMemory);
assert!(reg2.is_valid());
assert_eq!(reg2.start_addr(), GuestAddress(0x1000));
assert_eq!(reg2.len(), 0x1000);
assert!(reg2.has_file());
assert!(reg2.file_offset().is_some());
assert_eq!(reg2.perm_flags(), 0x5a);
assert_eq!(reg2.prot_flags(), 0x5a);
}
#[test]
fn test_address_space_region_intersect() {
let reg1 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0x1000),
0x1000,
);
let reg2 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0x2000),
0x1000,
);
let reg3 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0x1000),
0x1001,
);
let reg4 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0x1100),
0x100,
);
let reg5 = AddressSpaceRegion::new(
AddressSpaceRegionType::DefaultMemory,
GuestAddress(0xFFFFFFFFFFFFF000),
0x2000,
);
assert!(!reg1.intersect_with(®2));
assert!(!reg2.intersect_with(®1));
assert!(reg1.intersect_with(®1));
assert!(reg3.intersect_with(®2));
assert!(reg2.intersect_with(®3));
assert!(reg1.intersect_with(®4));
assert!(reg4.intersect_with(®1));
assert!(reg1.intersect_with(®5));
assert!(reg5.intersect_with(®1));
}
#[test]
fn test_create_device_region() {
let reg = AddressSpaceRegion::create_device_region(GuestAddress(0x10000), 0x1000).unwrap();
assert_eq!(reg.region_type(), AddressSpaceRegionType::DeviceMemory);
assert_eq!(reg.start_addr(), GuestAddress(0x10000));
assert_eq!(reg.len(), 0x1000);
}
#[test]
fn test_create_default_memory_region() {
AddressSpaceRegion::create_default_memory_region(
GuestAddress(0x100000),
0x100000,
None,
"invalid",
"invalid",
false,
false,
)
.unwrap_err();
let reg = AddressSpaceRegion::create_default_memory_region(
GuestAddress(0x100000),
0x100000,
None,
"shmem",
"",
false,
false,
)
.unwrap();
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
assert_eq!(reg.len(), 0x100000);
assert!(reg.file_offset().is_some());
let reg = AddressSpaceRegion::create_default_memory_region(
GuestAddress(0x100000),
0x100000,
None,
"hugeshmem",
"",
true,
false,
)
.unwrap();
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
assert_eq!(reg.len(), 0x100000);
assert!(reg.file_offset().is_some());
let reg = AddressSpaceRegion::create_default_memory_region(
GuestAddress(0x100000),
0x100000,
None,
"mmap",
"",
true,
false,
)
.unwrap();
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
assert_eq!(reg.len(), 0x100000);
assert!(reg.file_offset().is_none());
}
}