xref: /DADK/dadk-config/src/boot/hypervisor/qemu.rs (revision c6f35e8aa5fda2a3004763828fb91c7762df6087)
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