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
path(&self, arch: TargetArch) -> String39 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
apply_qemu_args(&mut self, args: &Vec<String>) -> Result<()>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
args(&self) -> String66 pub fn args(&self) -> String {
67 self.args.clone()
68 }
69
70 /// Get the hardware acceleration configuration
accelerate(&self) -> QemuAccel71 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
check_qemu_arg(arg: &str) -> Result<()>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]
test_qemu_config_path()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]
test_qemu_config_path_default()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]
test_apply_qemu_args() -> Result<()>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]
test_apply_qemu_args_invalid()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]
test_check_qemu_arg_valid() -> Result<()>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]
test_check_qemu_arg_invalid()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]
test_apply_qemu_args_multi_value_keys() -> Result<()>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]
test_apply_qemu_args_multi_value_keys_invalid()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]
test_check_qemu_arg_multi_value_keys_valid() -> Result<()>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]
test_check_qemu_arg_multi_value_keys_invalid()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]
test_qemu_config_args()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]
test_qemu_accelerate_args()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