Files
wifi-densepose/vendor/ruvector/crates/rvf/rvf-launch/src/qemu.rs

242 lines
7.0 KiB
Rust

//! QEMU command-line builder.
//!
//! Constructs the `qemu-system-*` command line for launching a microVM
//! from an extracted RVF kernel.
use std::path::{Path, PathBuf};
use std::process::Command;
use rvf_types::kernel::{KernelArch, KERNEL_FLAG_REQUIRES_KVM};
use crate::error::LaunchError;
use crate::extract::ExtractedKernel;
use crate::LaunchConfig;
/// Resolved QEMU invocation ready to be spawned.
pub struct QemuCommand {
pub command: Command,
pub qmp_socket: PathBuf,
}
/// Check if KVM is available on this host.
pub fn kvm_available() -> bool {
Path::new("/dev/kvm").exists()
&& std::fs::metadata("/dev/kvm")
.map(|m| {
use std::os::unix::fs::PermissionsExt;
let mode = m.permissions().mode();
// Check if the file is readable+writable by someone
mode & 0o666 != 0
})
.unwrap_or(false)
}
/// Locate the QEMU binary for the given architecture.
pub fn find_qemu(arch: KernelArch) -> Result<PathBuf, LaunchError> {
let candidates = match arch {
KernelArch::X86_64 | KernelArch::Universal | KernelArch::Unknown => {
vec![
"qemu-system-x86_64",
"/usr/bin/qemu-system-x86_64",
"/usr/local/bin/qemu-system-x86_64",
]
}
KernelArch::Aarch64 => {
vec![
"qemu-system-aarch64",
"/usr/bin/qemu-system-aarch64",
"/usr/local/bin/qemu-system-aarch64",
]
}
KernelArch::Riscv64 => {
vec![
"qemu-system-riscv64",
"/usr/bin/qemu-system-riscv64",
"/usr/local/bin/qemu-system-riscv64",
]
}
};
for candidate in &candidates {
if let Ok(output) = std::process::Command::new("which").arg(candidate).output() {
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
return Ok(PathBuf::from(path));
}
}
// Also check if the path exists directly (absolute paths)
let p = Path::new(candidate);
if p.is_absolute() && p.exists() {
return Ok(p.to_path_buf());
}
}
Err(LaunchError::QemuNotFound {
searched: candidates.iter().map(|s| s.to_string()).collect(),
})
}
/// Build a QEMU command for the given config and extracted kernel.
pub fn build_command(
config: &LaunchConfig,
extracted: &ExtractedKernel,
work_dir: &Path,
) -> Result<QemuCommand, LaunchError> {
let arch = KernelArch::try_from(extracted.header.arch).unwrap_or(KernelArch::X86_64);
// Resolve QEMU binary
let qemu_bin = match &config.qemu_binary {
Some(p) => {
if !p.exists() {
return Err(LaunchError::QemuNotFound {
searched: vec![p.display().to_string()],
});
}
p.clone()
}
None => find_qemu(arch)?,
};
// KVM
let use_kvm = if config.enable_kvm {
if kvm_available() {
true
} else if extracted.header.kernel_flags & KERNEL_FLAG_REQUIRES_KVM != 0 {
return Err(LaunchError::KvmRequired);
} else {
false
}
} else {
false
};
let qmp_socket = work_dir.join("qmp.sock");
let mut cmd = Command::new(&qemu_bin);
// Machine type
match arch {
KernelArch::X86_64 | KernelArch::Universal | KernelArch::Unknown => {
if use_kvm {
cmd.args(["-machine", "microvm,accel=kvm"]);
cmd.args(["-cpu", "host"]);
} else {
cmd.args(["-machine", "microvm,accel=tcg"]);
cmd.args(["-cpu", "qemu64"]);
}
}
KernelArch::Aarch64 => {
if use_kvm {
cmd.args(["-machine", "virt,accel=kvm"]);
cmd.args(["-cpu", "host"]);
} else {
cmd.args(["-machine", "virt,accel=tcg"]);
cmd.args(["-cpu", "cortex-a72"]);
}
}
KernelArch::Riscv64 => {
if use_kvm {
cmd.args(["-machine", "virt,accel=kvm"]);
cmd.args(["-cpu", "host"]);
} else {
cmd.args(["-machine", "virt,accel=tcg"]);
cmd.args(["-cpu", "rv64"]);
}
}
}
// Memory and CPUs
cmd.arg("-m").arg(format!("{}M", config.memory_mb));
cmd.arg("-smp").arg(config.vcpus.to_string());
// Kernel image
let kernel_path = config
.kernel_path
.as_deref()
.unwrap_or(&extracted.kernel_path);
cmd.arg("-kernel").arg(kernel_path);
// Initramfs
let initramfs = config
.initramfs_path
.as_deref()
.or(extracted.initramfs_path.as_deref());
if let Some(initrd) = initramfs {
cmd.arg("-initrd").arg(initrd);
}
// Kernel command line
let default_cmdline = format!(
"console=ttyS0 reboot=t panic=-1 rvf.port={}",
config.api_port
);
let cmdline = if extracted.cmdline.is_empty() {
default_cmdline
} else {
format!("{} {}", extracted.cmdline, default_cmdline)
};
cmd.arg("-append").arg(&cmdline);
// RVF file as a virtio-blk device (read-only)
cmd.arg("-drive").arg(format!(
"id=rvf,file={},format=raw,if=none,readonly=on",
config.rvf_path.display()
));
cmd.args(["-device", "virtio-blk-device,drive=rvf"]);
// Network: forward API port and optional SSH port
let mut hostfwd = format!("user,id=net0,hostfwd=tcp::{}:-:8080", config.api_port);
if let Some(ssh_port) = config.ssh_port {
hostfwd.push_str(&format!(",hostfwd=tcp::{}:-:2222", ssh_port));
}
cmd.arg("-netdev").arg(&hostfwd);
cmd.args(["-device", "virtio-net-device,netdev=net0"]);
// Serial console on stdio
cmd.args(["-chardev", "stdio,id=char0"]);
cmd.args(["-serial", "chardev:char0"]);
// QMP socket for management
cmd.arg("-qmp")
.arg(format!("unix:{},server,nowait", qmp_socket.display()));
// No graphics, no reboot on panic
cmd.arg("-nographic");
cmd.arg("-no-reboot");
// Extra user-specified arguments
for arg in &config.extra_args {
cmd.arg(arg);
}
Ok(QemuCommand {
command: cmd,
qmp_socket,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kvm_detection_does_not_panic() {
// Just ensure the function runs without panicking.
let _ = kvm_available();
}
#[test]
fn find_qemu_returns_result() {
// In CI, QEMU may not be installed, so we just check it returns
// either Ok or a proper error.
let result = find_qemu(KernelArch::X86_64);
match result {
Ok(path) => assert!(path.to_str().unwrap().contains("qemu")),
Err(LaunchError::QemuNotFound { searched }) => {
assert!(!searched.is_empty());
}
Err(other) => panic!("unexpected error: {other}"),
}
}
}