1 //! This file contains the configuration for qemu. 2 //! 3 //! The file is partitially taken from asterinas osdk, and is Licensed under both GPL-2.0 and MPL-2.0. 4 //! (GPL-2.0 is compatible with MPL-2.0) 5 //! https://www.gnu.org/licenses/license-list.zh-cn.html#MPL-2.0 6 7 use anyhow::{anyhow, Result}; 8 use serde::Deserialize; 9 10 use crate::{ 11 common::target_arch::TargetArch, 12 utils::{apply_kv_array, get_key, split_to_kv_array}, 13 }; 14 15 #[derive(Debug, Clone, Deserialize, Default)] 16 pub struct QemuConfig { 17 /// Path prefix for qemu binary. 18 /// 19 /// If not set, the default path will be used. 20 /// 21 /// Example: 22 /// Fill in `/usr/bin/qemu-system-`, 23 /// then for the `x86_64` architecture, `/usr/bin/qemu-system-x86_64` will be used. 24 #[serde(rename = "path-prefix")] 25 path_prefix: Option<String>, 26 /// Arguments to pass to qemu. 27 args: String, 28 29 /// Parameters to apply when no-graphic is enabled 30 #[serde(rename = "no-graphic-args")] 31 pub no_graphic_args: String, 32 33 /// Hardware acceleration 34 accelerate: Option<QemuAccel>, 35 } 36 37 impl QemuConfig { 38 /// Get the path to the qemu binary 39 pub fn path(&self, arch: TargetArch) -> String { 40 let arch_name: &str = arch.into(); 41 if let Some(prefix) = &self.path_prefix { 42 format!("{}{}", prefix, arch_name) 43 } else { 44 format!("qemu-system-{}", arch_name) 45 } 46 } 47 48 /// Apply the arguments to the qemu configuration 49 pub fn apply_qemu_args(&mut self, args: &Vec<String>) -> Result<()> { 50 let mut joined = 51 split_to_kv_array(&self.args).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?; 52 53 // Check the soundness of qemu arguments 54 for arg in joined.iter() { 55 check_qemu_arg(arg).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?; 56 } 57 log::warn!("apply_qemu_args: joined: {:?}", joined); 58 59 apply_kv_array(&mut joined, args, " ", MULTI_VALUE_KEYS, SINGLE_VALUE_KEYS)?; 60 61 self.args = joined.join(" "); 62 Ok(()) 63 } 64 65 /// Get the arguments to pass to qemu 66 pub fn args(&self) -> String { 67 self.args.clone() 68 } 69 70 /// Get the hardware acceleration configuration 71 pub fn accelerate(&self) -> QemuAccel { 72 self.accelerate.clone().unwrap_or(QemuAccel::None) 73 } 74 } 75 76 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 77 pub enum QemuAccel { 78 #[serde(rename = "none")] 79 None, 80 #[serde(rename = "kvm")] 81 Kvm, 82 #[serde(rename = "hvf")] 83 Hvf, 84 #[serde(rename = "tcg")] 85 Tcg, 86 } 87 88 // Below are checked keys in qemu arguments. The key list is non-exhaustive. 89 90 /// Keys with multiple values 91 const MULTI_VALUE_KEYS: &[&str] = &[ 92 "-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom", 93 ]; 94 /// Keys with only single value 95 const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"]; 96 /// Keys with no value 97 const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"]; 98 /// Keys are not allowed to set in configuration files and command line 99 const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-append", "-initrd"]; 100 101 fn check_qemu_arg(arg: &str) -> Result<()> { 102 let key = if let Some(key) = get_key(arg, " ") { 103 key 104 } else { 105 arg.to_string() 106 }; 107 108 if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) { 109 return Err(anyhow!("`{}` is not allowed to set", arg)); 110 } 111 112 if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg { 113 return Err(anyhow!("`{}` cannot have value", arg)); 114 } 115 116 if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str())) 117 && key.as_str() == arg 118 { 119 return Err(anyhow!("`{}` must have value", arg)); 120 } 121 Ok(()) 122 } 123 124 #[cfg(test)] 125 mod tests { 126 use super::*; 127 128 #[test] 129 fn test_qemu_config_path() { 130 let config = QemuConfig { 131 path_prefix: Some("/usr/bin/qemu-system-".to_string()), 132 args: "".to_string(), 133 ..Default::default() 134 }; 135 136 assert_eq!( 137 config.path(TargetArch::X86_64), 138 "/usr/bin/qemu-system-x86_64" 139 ); 140 assert_eq!( 141 config.path(TargetArch::RiscV64), 142 "/usr/bin/qemu-system-riscv64" 143 ); 144 } 145 146 #[test] 147 fn test_qemu_config_path_default() { 148 let config = QemuConfig { 149 path_prefix: None, 150 args: "".to_string(), 151 ..Default::default() 152 }; 153 154 assert_eq!(config.path(TargetArch::X86_64), "qemu-system-x86_64"); 155 assert_eq!(config.path(TargetArch::RiscV64), "qemu-system-riscv64"); 156 } 157 158 #[test] 159 fn test_apply_qemu_args() -> Result<()> { 160 let mut config = QemuConfig { 161 path_prefix: None, 162 args: "-m 1G -nographic".to_string(), 163 ..Default::default() 164 }; 165 166 let args = vec!["-m 2G".to_string(), "-enable-kvm".to_string()]; 167 config.apply_qemu_args(&args)?; 168 169 assert_eq!(config.args, "-m 2G -nographic -enable-kvm"); 170 Ok(()) 171 } 172 173 #[test] 174 fn test_apply_qemu_args_invalid() { 175 // 不允许直接设置 -kernel 176 let mut config = QemuConfig { 177 path_prefix: None, 178 args: "-kernel path/to/kernel".to_string(), 179 ..Default::default() 180 }; 181 182 let args = vec!["".to_string()]; 183 let result = config.apply_qemu_args(&args); 184 185 assert!(result.is_err()); 186 } 187 188 #[test] 189 fn test_check_qemu_arg_valid() -> Result<()> { 190 assert!(check_qemu_arg("-m 1G").is_ok()); 191 assert!(check_qemu_arg("-nographic").is_ok()); 192 Ok(()) 193 } 194 195 #[test] 196 fn test_check_qemu_arg_invalid() { 197 assert!(check_qemu_arg("-kernel path/to/kernel").is_err()); 198 assert!(check_qemu_arg("-m").is_err()); 199 assert!(check_qemu_arg("-nographic value").is_err()); 200 } 201 202 #[test] 203 fn test_apply_qemu_args_multi_value_keys() -> Result<()> { 204 let mut config = QemuConfig { 205 path_prefix: None, 206 args: "-device virtio-net-pci,netdev=net0 -netdev user,id=net0".to_string(), 207 ..Default::default() 208 }; 209 210 let args = vec![ 211 "-device virtio-net-pci,netdev=net1".to_string(), 212 "-netdev user,id=net1".to_string(), 213 ]; 214 config.apply_qemu_args(&args)?; 215 216 assert_eq!( 217 config.args, 218 "-device virtio-net-pci,netdev=net0 -device virtio-net-pci,netdev=net1 -netdev user,id=net0 -netdev user,id=net1" 219 ); 220 Ok(()) 221 } 222 223 #[test] 224 fn test_apply_qemu_args_multi_value_keys_invalid() { 225 let mut config = QemuConfig { 226 path_prefix: None, 227 args: "-device virtio-net-pci,netdev=net0".to_string(), 228 ..Default::default() 229 }; 230 231 let args = vec!["-device".to_string()]; 232 let result = config.apply_qemu_args(&args); 233 234 assert!(result.is_err()); 235 } 236 237 #[test] 238 fn test_check_qemu_arg_multi_value_keys_valid() -> Result<()> { 239 assert!(check_qemu_arg("-device virtio-net-pci,netdev=net0").is_ok()); 240 assert!(check_qemu_arg("-chardev socket,id=chr0,path=/tmp/qemu.sock").is_ok()); 241 Ok(()) 242 } 243 244 #[test] 245 fn test_check_qemu_arg_multi_value_keys_invalid() { 246 assert!(check_qemu_arg("-device").is_err()); 247 assert!(check_qemu_arg("-chardev").is_err()); 248 } 249 250 #[test] 251 fn test_qemu_config_args() { 252 let mut config = QemuConfig { 253 path_prefix: None, 254 args: "-m 1G -nographic".to_string(), 255 ..Default::default() 256 }; 257 258 config.apply_qemu_args(&vec![]).unwrap(); 259 assert_eq!(config.args(), "-m 1G -nographic"); 260 } 261 262 #[test] 263 fn test_qemu_accelerate_args() { 264 let s = r#""kvm""#; 265 let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s); 266 match result { 267 Ok(QemuAccel::Kvm) => assert!(true), 268 _ => assert!(false, "Expected Ok(QemuAccel::Kvm) but got {:?}", result), 269 } 270 271 let s = r#""tcg""#; 272 let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s); 273 match result { 274 Ok(QemuAccel::Tcg) => assert!(true), 275 _ => assert!(false, "Expected Ok(QemuAccel::Tcg) but got {:?}", result), 276 } 277 278 let s = r#""none""#; 279 let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s); 280 match result { 281 Ok(QemuAccel::None) => assert!(true), 282 _ => assert!(false, "Expected Ok(QemuAccel::None) but got {:?}", result), 283 } 284 285 let s = r#""hvf""#; 286 let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s); 287 match result { 288 Ok(QemuAccel::Hvf) => assert!(true), 289 _ => assert!(false, "Expected Ok(QemuAccel::Hvf) but got {:?}", result), 290 } 291 292 let s = r#""invalid""#; 293 let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s); 294 assert!(result.is_err(), "Expected Err but got {:?}", result); 295 } 296 } 297