Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
241
vendor/ruvector/crates/rvf/rvf-launch/src/qemu.rs
vendored
Normal file
241
vendor/ruvector/crates/rvf/rvf-launch/src/qemu.rs
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
//! 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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user