use anyhow::{anyhow, Result};
use serde::Deserialize;
use crate::{
common::target_arch::TargetArch,
utils::{apply_kv_array, get_key, split_to_kv_array},
};
#[derive(Debug, Clone, Deserialize, Default)]
pub struct QemuConfig {
#[serde(rename = "path-prefix")]
path_prefix: Option<String>,
args: String,
#[serde(rename = "no-graphic-args")]
pub no_graphic_args: String,
accelerate: Option<QemuAccel>,
}
impl QemuConfig {
pub fn path(&self, arch: TargetArch) -> String {
let arch_name: &str = arch.into();
if let Some(prefix) = &self.path_prefix {
format!("{}{}", prefix, arch_name)
} else {
format!("qemu-system-{}", arch_name)
}
}
pub fn apply_qemu_args(&mut self, args: &Vec<String>) -> Result<()> {
let mut joined =
split_to_kv_array(&self.args).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?;
for arg in joined.iter() {
check_qemu_arg(arg).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?;
}
log::warn!("apply_qemu_args: joined: {:?}", joined);
apply_kv_array(&mut joined, args, " ", MULTI_VALUE_KEYS, SINGLE_VALUE_KEYS)?;
self.args = joined.join(" ");
Ok(())
}
pub fn args(&self) -> String {
self.args.clone()
}
pub fn accelerate(&self) -> QemuAccel {
self.accelerate.clone().unwrap_or(QemuAccel::None)
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub enum QemuAccel {
#[serde(rename = "none")]
None,
#[serde(rename = "kvm")]
Kvm,
#[serde(rename = "hvf")]
Hvf,
#[serde(rename = "tcg")]
Tcg,
}
const MULTI_VALUE_KEYS: &[&str] = &[
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
];
const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-append", "-initrd"];
fn check_qemu_arg(arg: &str) -> Result<()> {
let key = if let Some(key) = get_key(arg, " ") {
key
} else {
arg.to_string()
};
if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
return Err(anyhow!("`{}` is not allowed to set", arg));
}
if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
return Err(anyhow!("`{}` cannot have value", arg));
}
if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
&& key.as_str() == arg
{
return Err(anyhow!("`{}` must have value", arg));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qemu_config_path() {
let config = QemuConfig {
path_prefix: Some("/usr/bin/qemu-system-".to_string()),
args: "".to_string(),
..Default::default()
};
assert_eq!(
config.path(TargetArch::X86_64),
"/usr/bin/qemu-system-x86_64"
);
assert_eq!(
config.path(TargetArch::RiscV64),
"/usr/bin/qemu-system-riscv64"
);
}
#[test]
fn test_qemu_config_path_default() {
let config = QemuConfig {
path_prefix: None,
args: "".to_string(),
..Default::default()
};
assert_eq!(config.path(TargetArch::X86_64), "qemu-system-x86_64");
assert_eq!(config.path(TargetArch::RiscV64), "qemu-system-riscv64");
}
#[test]
fn test_apply_qemu_args() -> Result<()> {
let mut config = QemuConfig {
path_prefix: None,
args: "-m 1G -nographic".to_string(),
..Default::default()
};
let args = vec!["-m 2G".to_string(), "-enable-kvm".to_string()];
config.apply_qemu_args(&args)?;
assert_eq!(config.args, "-m 2G -nographic -enable-kvm");
Ok(())
}
#[test]
fn test_apply_qemu_args_invalid() {
let mut config = QemuConfig {
path_prefix: None,
args: "-kernel path/to/kernel".to_string(),
..Default::default()
};
let args = vec!["".to_string()];
let result = config.apply_qemu_args(&args);
assert!(result.is_err());
}
#[test]
fn test_check_qemu_arg_valid() -> Result<()> {
assert!(check_qemu_arg("-m 1G").is_ok());
assert!(check_qemu_arg("-nographic").is_ok());
Ok(())
}
#[test]
fn test_check_qemu_arg_invalid() {
assert!(check_qemu_arg("-kernel path/to/kernel").is_err());
assert!(check_qemu_arg("-m").is_err());
assert!(check_qemu_arg("-nographic value").is_err());
}
#[test]
fn test_apply_qemu_args_multi_value_keys() -> Result<()> {
let mut config = QemuConfig {
path_prefix: None,
args: "-device virtio-net-pci,netdev=net0 -netdev user,id=net0".to_string(),
..Default::default()
};
let args = vec![
"-device virtio-net-pci,netdev=net1".to_string(),
"-netdev user,id=net1".to_string(),
];
config.apply_qemu_args(&args)?;
assert_eq!(
config.args,
"-device virtio-net-pci,netdev=net0 -device virtio-net-pci,netdev=net1 -netdev user,id=net0 -netdev user,id=net1"
);
Ok(())
}
#[test]
fn test_apply_qemu_args_multi_value_keys_invalid() {
let mut config = QemuConfig {
path_prefix: None,
args: "-device virtio-net-pci,netdev=net0".to_string(),
..Default::default()
};
let args = vec!["-device".to_string()];
let result = config.apply_qemu_args(&args);
assert!(result.is_err());
}
#[test]
fn test_check_qemu_arg_multi_value_keys_valid() -> Result<()> {
assert!(check_qemu_arg("-device virtio-net-pci,netdev=net0").is_ok());
assert!(check_qemu_arg("-chardev socket,id=chr0,path=/tmp/qemu.sock").is_ok());
Ok(())
}
#[test]
fn test_check_qemu_arg_multi_value_keys_invalid() {
assert!(check_qemu_arg("-device").is_err());
assert!(check_qemu_arg("-chardev").is_err());
}
#[test]
fn test_qemu_config_args() {
let mut config = QemuConfig {
path_prefix: None,
args: "-m 1G -nographic".to_string(),
..Default::default()
};
config.apply_qemu_args(&vec![]).unwrap();
assert_eq!(config.args(), "-m 1G -nographic");
}
#[test]
fn test_qemu_accelerate_args() {
let s = r#""kvm""#;
let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
match result {
Ok(QemuAccel::Kvm) => assert!(true),
_ => assert!(false, "Expected Ok(QemuAccel::Kvm) but got {:?}", result),
}
let s = r#""tcg""#;
let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
match result {
Ok(QemuAccel::Tcg) => assert!(true),
_ => assert!(false, "Expected Ok(QemuAccel::Tcg) but got {:?}", result),
}
let s = r#""none""#;
let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
match result {
Ok(QemuAccel::None) => assert!(true),
_ => assert!(false, "Expected Ok(QemuAccel::None) but got {:?}", result),
}
let s = r#""hvf""#;
let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
match result {
Ok(QemuAccel::Hvf) => assert!(true),
_ => assert!(false, "Expected Ok(QemuAccel::Hvf) but got {:?}", result),
}
let s = r#""invalid""#;
let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
assert!(result.is_err(), "Expected Err but got {:?}", result);
}
}