use nix::sys::uio::{RemoteIoVec, process_vm_readv, process_vm_writev};
use nix::unistd::Pid;
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::io::{IoSlice, IoSliceMut};
use crate::backend::MemoryOps;
use crate::error::{Error, Result};
use crate::types::PhysAddr;
struct MemoryRegion {
start: u64,
#[allow(dead_code)]
end: u64,
length: u64,
}
pub struct KvmHandle {
memory: MemoryRegion,
pid: Pid,
}
fn get_kvm_pid() -> Result<i32> {
for entry in fs::read_dir("/proc")? {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
if !entry.path().is_dir() {
continue;
}
let fd_path = entry.path().join("fd");
let fd_iter = match fs::read_dir(&fd_path) {
Ok(iter) => iter,
Err(_) => continue,
};
for fd_entry in fd_iter {
let fd_entry = match fd_entry {
Ok(e) => e,
Err(_) => continue,
};
if let Ok(target) = fs::read_link(fd_entry.path())
&& target == std::path::Path::new("/dev/kvm")
&& let Some(pid_str) = entry.file_name().to_str()
&& let Ok(pid) = pid_str.parse::<i32>()
{
return Ok(pid);
}
}
}
Err(Error::KvmNotFound)
}
fn get_kvm_primary_memory(pid: i32) -> Result<MemoryRegion> {
let maps = File::open(format!("/proc/{}/maps", pid))?;
let reader = BufReader::new(maps);
let region = reader
.lines()
.map_while(|line| line.ok())
.filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
return None;
}
let addresses: Vec<&str> = parts[0].split('-').collect();
if addresses.len() != 2 {
return None;
}
let start = u64::from_str_radix(addresses[0], 16).ok()?;
let end = u64::from_str_radix(addresses[1], 16).ok()?;
Some(MemoryRegion {
start,
end,
length: end - start,
})
})
.max_by_key(|region| region.length)
.ok_or(Error::NoKvmRegions)?;
Ok(region)
}
fn gpa2hva(x: PhysAddr) -> u64 {
if x < 0x80000000 {
return x;
}
x - 0x80000000
}
impl KvmHandle {
pub fn new() -> Result<Self> {
let pid = get_kvm_pid()?;
let memory = get_kvm_primary_memory(pid)?;
Ok(Self {
memory,
pid: Pid::from_raw(pid),
})
}
}
impl MemoryOps<PhysAddr> for KvmHandle {
fn read_bytes(&self, addr: PhysAddr, buf: &mut [u8]) -> Result<()> {
let hva = self.memory.start + gpa2hva(addr);
if hva + buf.len() as u64 > self.memory.end {
return Err(Error::BadPhysicalAddress(addr));
}
let remote_iov = RemoteIoVec {
base: hva as usize,
len: buf.len(),
};
let local_iov = IoSliceMut::new(buf);
let bytes_read = process_vm_readv(self.pid, &mut [local_iov], &[remote_iov])?;
if bytes_read != buf.len() {
return Err(Error::PartialRead(bytes_read));
}
Ok(())
}
fn write_bytes(&self, addr: PhysAddr, buf: &[u8]) -> Result<()> {
let hva = self.memory.start + gpa2hva(addr);
if hva + buf.len() as u64 > self.memory.end {
return Err(Error::BadPhysicalAddress(addr));
}
let remote_iov = RemoteIoVec {
base: hva as usize,
len: buf.len(),
};
let local_iov = IoSlice::new(buf);
let bytes_written = process_vm_writev(self.pid, &[local_iov], &[remote_iov])?;
if bytes_written != buf.len() {
return Err(Error::PartialWrite(bytes_written));
}
Ok(())
}
}