xref: /cloud-hypervisor/src/main.rs (revision 274f1aa2e738d579ffff9d4cfd7ed7c45293af31)
1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 #[macro_use]
7 extern crate event_monitor;
8 
9 use argh::FromArgs;
10 use libc::EFD_NONBLOCK;
11 use log::{warn, LevelFilter};
12 use option_parser::OptionParser;
13 use seccompiler::SeccompAction;
14 use signal_hook::consts::SIGSYS;
15 use std::env;
16 use std::fs::File;
17 use std::os::unix::io::{FromRawFd, RawFd};
18 use std::sync::mpsc::channel;
19 use std::sync::{Arc, Mutex};
20 use thiserror::Error;
21 #[cfg(feature = "dbus_api")]
22 use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
23 use vmm::config;
24 use vmm_sys_util::eventfd::EventFd;
25 use vmm_sys_util::signal::block_signal;
26 
27 #[cfg(feature = "dhat-heap")]
28 #[global_allocator]
29 static ALLOC: dhat::Alloc = dhat::Alloc;
30 
31 #[derive(Error, Debug)]
32 enum Error {
33     #[error("Failed to create API EventFd: {0}")]
34     CreateApiEventFd(#[source] std::io::Error),
35     #[cfg(feature = "guest_debug")]
36     #[error("Failed to create Debug EventFd: {0}")]
37     CreateDebugEventFd(#[source] std::io::Error),
38     #[error("Failed to create exit EventFd: {0}")]
39     CreateExitEventFd(#[source] std::io::Error),
40     #[error("Failed to open hypervisor interface (is hypervisor interface available?): {0}")]
41     CreateHypervisor(#[source] hypervisor::HypervisorError),
42     #[error("Failed to start the VMM thread: {0}")]
43     StartVmmThread(#[source] vmm::Error),
44     #[error("Error parsing config: {0}")]
45     ParsingConfig(vmm::config::Error),
46     #[error("Error creating VM: {0:?}")]
47     VmCreate(vmm::api::ApiError),
48     #[error("Error booting VM: {0:?}")]
49     VmBoot(vmm::api::ApiError),
50     #[error("Error restoring VM: {0:?}")]
51     VmRestore(vmm::api::ApiError),
52     #[error("Error parsing restore: {0}")]
53     ParsingRestore(vmm::config::Error),
54     #[error("Failed to join on VMM thread: {0:?}")]
55     ThreadJoin(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
56     #[error("VMM thread exited with error: {0}")]
57     VmmThread(#[source] vmm::Error),
58     #[error("Error parsing --api-socket: {0}")]
59     ParsingApiSocket(std::num::ParseIntError),
60     #[error("Error parsing --event-monitor: {0}")]
61     ParsingEventMonitor(option_parser::OptionParserError),
62     #[cfg(feature = "dbus_api")]
63     #[error("`--dbus-object-path` option isn't provided")]
64     MissingDBusObjectPath,
65     #[cfg(feature = "dbus_api")]
66     #[error("`--dbus-service-name` option isn't provided")]
67     MissingDBusServiceName,
68     #[error("Error parsing --event-monitor: path or fd required")]
69     BareEventMonitor,
70     #[error("Error doing event monitor I/O: {0}")]
71     EventMonitorIo(std::io::Error),
72     #[error("Event monitor thread failed: {0}")]
73     EventMonitorThread(#[source] vmm::Error),
74     #[cfg(feature = "guest_debug")]
75     #[error("Error parsing --gdb: {0}")]
76     ParsingGdb(option_parser::OptionParserError),
77     #[cfg(feature = "guest_debug")]
78     #[error("Error parsing --gdb: path required")]
79     BareGdb,
80     #[error("Error creating log file: {0}")]
81     LogFileCreation(std::io::Error),
82     #[error("Error setting up logger: {0}")]
83     LoggerSetup(log::SetLoggerError),
84 }
85 
86 struct Logger {
87     output: Mutex<Box<dyn std::io::Write + Send>>,
88     start: std::time::Instant,
89 }
90 
91 impl log::Log for Logger {
92     fn enabled(&self, _metadata: &log::Metadata) -> bool {
93         true
94     }
95 
96     fn log(&self, record: &log::Record) {
97         if !self.enabled(record.metadata()) {
98             return;
99         }
100 
101         let now = std::time::Instant::now();
102         let duration = now.duration_since(self.start);
103 
104         if record.file().is_some() && record.line().is_some() {
105             write!(
106                 *(*(self.output.lock().unwrap())),
107                 "cloud-hypervisor: {:.6?}: <{}> {}:{}:{} -- {}\r\n",
108                 duration,
109                 std::thread::current().name().unwrap_or("anonymous"),
110                 record.level(),
111                 record.file().unwrap(),
112                 record.line().unwrap(),
113                 record.args()
114             )
115         } else {
116             write!(
117                 *(*(self.output.lock().unwrap())),
118                 "cloud-hypervisor: {:.6?}: <{}> {}:{} -- {}\r\n",
119                 duration,
120                 std::thread::current().name().unwrap_or("anonymous"),
121                 record.level(),
122                 record.target(),
123                 record.args()
124             )
125         }
126         .ok();
127     }
128     fn flush(&self) {}
129 }
130 
131 fn default_vcpus() -> String {
132     format!(
133         "boot={},max_phys_bits={}",
134         config::DEFAULT_VCPUS,
135         config::DEFAULT_MAX_PHYS_BITS
136     )
137 }
138 
139 fn default_memory() -> String {
140     format!("size={}M", config::DEFAULT_MEMORY_MB)
141 }
142 
143 fn default_rng() -> String {
144     format!("src={}", config::DEFAULT_RNG_SOURCE)
145 }
146 
147 #[derive(FromArgs)]
148 /// Launch a cloud-hypervisor VMM.
149 pub struct TopLevel {
150     #[argh(option, long = "cpus", default = "default_vcpus()")]
151     /// boot=<boot_vcpus>, max=<max_vcpus>, topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>, kvm_hyperv=on|off, max_phys_bits=<maximum_number_of_physical_bits>, affinity=<list_of_vcpus_with_their_associated_cpuset>, features=<list_of_features_to_enable>
152     cpus: String,
153 
154     #[argh(option, long = "platform")]
155     /// num_pci_segments=<num_pci_segments>, iommu_segments=<list_of_segments>, serial_number=<dmi_device_serial_number>, uuid=<dmi_device_uuid>, oem_strings=<list_of_strings>
156     platform: Option<String>,
157 
158     #[argh(option, long = "memory", default = "default_memory()")]
159     /// size=<guest_memory_size>, mergeable=on|off, shared=on|off, hugepages=on|off, hugepage_size=<hugepage_size>, hotplug_method=acpi|virtio-mem, hotplug_size=<hotpluggable_memory_size>, hotplugged_size=<hotplugged_memory_size>, prefault=on|off, thp=on|off
160     memory: String,
161 
162     #[argh(option, long = "memory-zone")]
163     /// size=<guest_memory_region_size>, file=<backing_file>, shared=on|off, hugepages=on|off, hugepage_size=<hugepage_size>, host_numa_node=<node_id>, id=<zone_identifier>, hotplug_size=<hotpluggable_memory_size>, hotplugged_size=<hotplugged_memory_size>, prefault=on|off
164     memory_zone: Vec<String>,
165 
166     #[argh(option, long = "firmware")]
167     /// path to firmware that is loaded in an architectural specific way
168     firmware: Option<String>,
169 
170     #[argh(option, long = "kernel")]
171     /// path to kernel or firmware that supports a PVH entry point or architecture equivalent
172     kernel: Option<String>,
173 
174     #[argh(option, long = "initramfs")]
175     /// path to initramfs image
176     initramfs: Option<String>,
177 
178     #[argh(option, long = "cmdline")]
179     /// kernel command line
180     cmdline: Option<String>,
181 
182     #[argh(option, long = "disk")]
183     /// path=<disk_image_path>, readonly=on|off, direct=on|off, iommu=on|off, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, vhost_user=on|off, socket=<vhost_user_socket_path>, bw_size=<bytes>, bw_one_time_burst=<bytes>, bw_refill_time=<ms>, ops_size=<io_ops>, ops_one_time_burst=<io_ops>, ops_refill_time=<ms>, id=<device_id>, pci_segment=<segment_id>
184     disk: Vec<String>,
185 
186     #[argh(option, long = "net")]
187     /// tap=<if_name>, ip=<ip_addr>, mask=<net_mask>, mac=<mac_addr>, fd=<fd1,fd2...>, iommu=on|off, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, id=<device_id>, vhost_user=<vhost_user_enable>, socket=<vhost_user_socket_path>, vhost_mode=client|server, bw_size=<bytes>, bw_one_time_burst=<bytes>, bw_refill_time=<ms>, ops_size=<io_ops>, ops_one_time_burst=<io_ops>, ops_refill_time=<ms>, pci_segment=<segment_id>, offload_tso=on|off, offload_ufo=on|off, offload_csum=on|off
188     net: Vec<String>,
189 
190     #[argh(option, long = "rng", default = "default_rng()")]
191     /// src=<entropy_source_path>, iommu=on|off
192     rng: String,
193 
194     #[argh(option, long = "balloon")]
195     /// size=<balloon_size>, deflate_on_oom=on|off, free_page_reporting=on|off
196     balloon: Option<String>,
197 
198     #[argh(option, long = "fs")]
199     /// tag=<tag_name>, socket=<socket_path>, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, id=<device_id>, pci_segment=<segment_id>
200     fs: Vec<String>,
201 
202     #[argh(option, long = "pmem")]
203     /// file=<backing_file_path>, size=<persistent_memory_size>, iommu=on|off, discard_writes=on|off, id=<device_id>, pci_segment=<segment_id>
204     pmem: Vec<String>,
205 
206     #[argh(option, long = "serial", default = "String::from(\"null\")")]
207     /// off|null|pty|tty|file=/path/to/a/file
208     serial: String,
209 
210     #[argh(option, long = "console", default = "String::from(\"tty\")")]
211     /// off|null|pty|tty|file=/path/to/a/file, iommu=on|off
212     console: String,
213 
214     #[argh(option, long = "device")]
215     /// path=<device_path>, iommu=on|off, id=<device_id>, pci_segment=<segment_id>
216     device: Vec<String>,
217 
218     #[argh(option, long = "user-device")]
219     /// socket=<socket_path>, id=<device_id>, pci_segment=<segment_id>
220     user_device: Vec<String>,
221 
222     #[argh(option, long = "vdpa")]
223     /// path=<device_path>, num_queues=<number_of_queues>, iommu=on|off, id=<device_id>, pci_segment=<segment_id>
224     vdpa: Vec<String>,
225 
226     #[argh(option, long = "vsock")]
227     /// cid=<context_id>, socket=<socket_path>, iommu=on|off, id=<device_id>, pci_segment=<segment_id>
228     vsock: Option<String>,
229 
230     #[argh(switch, long = "pvpanic")]
231     /// enable pvpanic device
232     pvpanic: bool,
233 
234     #[argh(option, long = "numa")]
235     /// guest_numa_id=<node_id>, cpus=<cpus_id>, distances=<list_of_distances_to_destination_nodes>, memory_zones=<list_of_memory_zones>, sgx_epc_sections=<list_of_sgx_epc_sections>
236     numa: Vec<String>,
237 
238     #[argh(switch, long = "watchdog")]
239     /// enable virtio-watchdog
240     watchdog: bool,
241 
242     #[argh(switch, short = 'v')]
243     /// set the level of debugging output
244     verbosity: u8,
245 
246     #[argh(option, long = "log-file")]
247     /// path to log file
248     log_file: Option<String>,
249 
250     #[argh(option, long = "api-socket")]
251     /// path=<path/to/a/file>|fd=<fd>
252     api_socket: Option<String>,
253 
254     #[cfg(feature = "dbus_api")]
255     #[argh(option, long = "dbus-service-name")]
256     /// well known name of the service
257     dbus_name: Option<String>,
258 
259     #[cfg(feature = "dbus_api")]
260     #[argh(option, long = "dbus-object-path")]
261     /// object path to serve the dbus interface
262     dbus_path: Option<String>,
263 
264     #[cfg(feature = "dbus_api")]
265     #[argh(switch, long = "dbus-system-bus")]
266     /// use the system bus instead of a session bus
267     dbus_system_bus: bool,
268 
269     #[argh(option, long = "event-monitor")]
270     /// path=<path/to/a/file>|fd=<fd>
271     event_monitor: Option<String>,
272 
273     #[argh(option, long = "restore")]
274     /// source_url=<source_url>, prefault=on|off
275     restore: Option<String>,
276 
277     #[argh(option, long = "seccomp", default = "String::from(\"true\")")]
278     /// seccomp configuration (true, false or log)
279     seccomp: String,
280 
281     #[argh(option, long = "tpm")]
282     /// socket=<path/to/a/socket>
283     tpm: Option<String>,
284 
285     #[cfg(target_arch = "x86_64")]
286     #[argh(option, long = "sgx-epc")]
287     /// id=<epc_section_identifier>, size=<epc_section_size>, prefault=on|off
288     sgx_epc: Vec<String>,
289 
290     #[cfg(feature = "guest_debug")]
291     #[argh(option, long = "gdb")]
292     /// path=<path/to/a/file>
293     gdb: Option<String>,
294 
295     #[argh(switch, short = 'V', long = "version")]
296     /// print version information
297     version: bool,
298 }
299 
300 impl TopLevel {
301     fn to_vm_params(&self) -> config::VmParams<'_> {
302         let cpus = &self.cpus;
303         let memory = &self.memory;
304         let memory_zones = if !self.memory_zone.is_empty() {
305             Some(self.memory_zone.iter().map(|x| x.as_str()).collect())
306         } else {
307             None
308         };
309         let rng = &self.rng;
310         let serial = &self.serial;
311         let firmware = self.firmware.as_deref();
312         let kernel = self.kernel.as_deref();
313         let initramfs = self.initramfs.as_deref();
314         let cmdline = self.cmdline.as_deref();
315 
316         let disks = if !self.disk.is_empty() {
317             Some(self.disk.iter().map(|x| x.as_str()).collect())
318         } else {
319             None
320         };
321 
322         let net = if !self.net.is_empty() {
323             Some(self.net.iter().map(|x| x.as_str()).collect())
324         } else {
325             None
326         };
327 
328         let console = &self.console;
329         let balloon = self.balloon.as_deref();
330         let fs = if !self.fs.is_empty() {
331             Some(self.fs.iter().map(|x| x.as_str()).collect())
332         } else {
333             None
334         };
335 
336         let pmem = if !self.pmem.is_empty() {
337             Some(self.pmem.iter().map(|x| x.as_str()).collect())
338         } else {
339             None
340         };
341         let devices = if !self.device.is_empty() {
342             Some(self.device.iter().map(|x| x.as_str()).collect())
343         } else {
344             None
345         };
346         let user_devices = if !self.user_device.is_empty() {
347             Some(self.user_device.iter().map(|x| x.as_str()).collect())
348         } else {
349             None
350         };
351         let vdpa = if !self.vdpa.is_empty() {
352             Some(self.vdpa.iter().map(|x| x.as_str()).collect())
353         } else {
354             None
355         };
356 
357         let vsock = self.vsock.as_deref();
358 
359         let pvpanic = self.pvpanic;
360 
361         #[cfg(target_arch = "x86_64")]
362         let sgx_epc = if !self.sgx_epc.is_empty() {
363             Some(self.sgx_epc.iter().map(|x| x.as_str()).collect())
364         } else {
365             None
366         };
367 
368         let numa = if !self.numa.is_empty() {
369             Some(self.numa.iter().map(|x| x.as_str()).collect())
370         } else {
371             None
372         };
373         let watchdog = self.watchdog;
374         let platform = self.platform.as_deref();
375         #[cfg(feature = "guest_debug")]
376         let gdb = self.gdb.is_some();
377         let tpm = self.tpm.as_deref();
378 
379         config::VmParams {
380             cpus,
381             memory,
382             memory_zones,
383             firmware,
384             kernel,
385             initramfs,
386             cmdline,
387             disks,
388             net,
389             rng,
390             balloon,
391             fs,
392             pmem,
393             serial,
394             console,
395             devices,
396             user_devices,
397             vdpa,
398             vsock,
399             pvpanic,
400             #[cfg(target_arch = "x86_64")]
401             sgx_epc,
402             numa,
403             watchdog,
404             #[cfg(feature = "guest_debug")]
405             gdb,
406             platform,
407             tpm,
408         }
409     }
410 }
411 
412 fn start_vmm(toplevel: TopLevel) -> Result<Option<String>, Error> {
413     let log_level = match toplevel.verbosity {
414         0 => LevelFilter::Warn,
415         1 => LevelFilter::Info,
416         2 => LevelFilter::Debug,
417         _ => LevelFilter::Trace,
418     };
419 
420     let log_file: Box<dyn std::io::Write + Send> = if let Some(ref file) = toplevel.log_file {
421         Box::new(std::fs::File::create(std::path::Path::new(file)).map_err(Error::LogFileCreation)?)
422     } else {
423         Box::new(std::io::stderr())
424     };
425 
426     log::set_boxed_logger(Box::new(Logger {
427         output: Mutex::new(log_file),
428         start: std::time::Instant::now(),
429     }))
430     .map(|()| log::set_max_level(log_level))
431     .map_err(Error::LoggerSetup)?;
432 
433     let (api_socket_path, api_socket_fd) = if let Some(ref socket_config) = toplevel.api_socket {
434         let mut parser = OptionParser::new();
435         parser.add("path").add("fd");
436         parser.parse(socket_config).unwrap_or_default();
437 
438         if let Some(fd) = parser.get("fd") {
439             (
440                 None,
441                 Some(fd.parse::<RawFd>().map_err(Error::ParsingApiSocket)?),
442             )
443         } else if let Some(path) = parser.get("path") {
444             (Some(path), None)
445         } else {
446             (toplevel.api_socket.as_ref().map(|s| s.to_string()), None)
447         }
448     } else {
449         (None, None)
450     };
451 
452     let (api_request_sender, api_request_receiver) = channel();
453     let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?;
454 
455     let api_request_sender_clone = api_request_sender.clone();
456     let seccomp_action = match &toplevel.seccomp as &str {
457         "true" => SeccompAction::Trap,
458         "false" => SeccompAction::Allow,
459         "log" => SeccompAction::Log,
460         val => {
461             // The user providing an invalid value will be rejected
462             panic!("Invalid parameter {val} for \"--seccomp\" flag");
463         }
464     };
465 
466     if seccomp_action == SeccompAction::Trap {
467         // SAFETY: We only using signal_hook for managing signals and only execute signal
468         // handler safe functions (writing to stderr) and manipulating signals.
469         unsafe {
470             signal_hook::low_level::register(signal_hook::consts::SIGSYS, || {
471                 eprint!(
472                     "\n==== Possible seccomp violation ====\n\
473                 Try running with `strace -ff` to identify the cause and open an issue: \
474                 https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n"
475                 );
476                 signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap();
477             })
478         }
479         .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}"))
480         .ok();
481     }
482 
483     // Before we start any threads, mask the signals we'll be
484     // installing handlers for, to make sure they only ever run on the
485     // dedicated signal handling thread we'll start in a bit.
486     for sig in &vmm::vm::Vm::HANDLED_SIGNALS {
487         if let Err(e) = block_signal(*sig) {
488             eprintln!("Error blocking signals: {e}");
489         }
490     }
491 
492     for sig in &vmm::Vmm::HANDLED_SIGNALS {
493         if let Err(e) = block_signal(*sig) {
494             eprintln!("Error blocking signals: {e}");
495         }
496     }
497 
498     let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?;
499 
500     #[cfg(feature = "guest_debug")]
501     let gdb_socket_path = if let Some(ref gdb_config) = toplevel.gdb {
502         let mut parser = OptionParser::new();
503         parser.add("path");
504         parser.parse(gdb_config).map_err(Error::ParsingGdb)?;
505 
506         if parser.is_set("path") {
507             Some(std::path::PathBuf::from(parser.get("path").unwrap()))
508         } else {
509             return Err(Error::BareGdb);
510         }
511     } else {
512         None
513     };
514     #[cfg(feature = "guest_debug")]
515     let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
516     #[cfg(feature = "guest_debug")]
517     let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
518 
519     let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
520 
521     #[allow(unused_mut)]
522     let mut event_monitor = toplevel
523         .event_monitor
524         .as_ref()
525         .map(|monitor_config| {
526             let mut parser = OptionParser::new();
527             parser.add("path").add("fd");
528             parser
529                 .parse(monitor_config)
530                 .map_err(Error::ParsingEventMonitor)?;
531 
532             if parser.is_set("fd") {
533                 let fd = parser
534                     .convert("fd")
535                     .map_err(Error::ParsingEventMonitor)?
536                     .unwrap();
537                 // SAFETY: fd is valid
538                 Ok(Some(unsafe { File::from_raw_fd(fd) }))
539             } else if parser.is_set("path") {
540                 Ok(Some(
541                     std::fs::OpenOptions::new()
542                         .write(true)
543                         .create(true)
544                         .open(parser.get("path").unwrap())
545                         .map_err(Error::EventMonitorIo)?,
546                 ))
547             } else {
548                 Err(Error::BareEventMonitor)
549             }
550         })
551         .transpose()?
552         .map(|event_monitor_file| {
553             event_monitor::set_monitor(event_monitor_file).map_err(Error::EventMonitorIo)
554         })
555         .transpose()?;
556 
557     #[cfg(feature = "dbus_api")]
558     let dbus_options = match (&toplevel.dbus_name, &toplevel.dbus_path) {
559         (Some(ref name), Some(ref path)) => {
560             // monitor is either set (file based) or not.
561             // if it's not set, create one without file support.
562             let mut monitor = match event_monitor.take() {
563                 Some(monitor) => monitor,
564                 None => event_monitor::set_monitor(None).map_err(Error::EventMonitorIo)?,
565             };
566             let options = DBusApiOptions {
567                 service_name: name.to_owned(),
568                 object_path: path.to_owned(),
569                 system_bus: toplevel.dbus_system_bus,
570                 event_monitor_rx: monitor.subscribe(),
571             };
572 
573             event_monitor = Some(monitor);
574             Ok(Some(options))
575         }
576         (Some(_), None) => Err(Error::MissingDBusObjectPath),
577         (None, Some(_)) => Err(Error::MissingDBusServiceName),
578         (None, None) => Ok(None),
579     }?;
580 
581     if let Some(monitor) = event_monitor {
582         vmm::start_event_monitor_thread(
583             monitor,
584             &seccomp_action,
585             hypervisor.hypervisor_type(),
586             exit_evt.try_clone().unwrap(),
587         )
588         .map_err(Error::EventMonitorThread)?;
589     }
590 
591     event!("vmm", "starting");
592 
593     let vmm_thread_handle = vmm::start_vmm_thread(
594         vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")),
595         &api_socket_path,
596         api_socket_fd,
597         #[cfg(feature = "dbus_api")]
598         dbus_options,
599         api_evt.try_clone().unwrap(),
600         api_request_sender_clone,
601         api_request_receiver,
602         #[cfg(feature = "guest_debug")]
603         gdb_socket_path,
604         #[cfg(feature = "guest_debug")]
605         debug_evt.try_clone().unwrap(),
606         #[cfg(feature = "guest_debug")]
607         vm_debug_evt.try_clone().unwrap(),
608         exit_evt.try_clone().unwrap(),
609         &seccomp_action,
610         hypervisor,
611     )
612     .map_err(Error::StartVmmThread)?;
613 
614     let r: Result<(), Error> = (|| {
615         let payload_present = toplevel.kernel.is_some() || toplevel.firmware.is_some();
616 
617         if payload_present {
618             let vm_params = toplevel.to_vm_params();
619             let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
620 
621             // Create and boot the VM based off the VM config we just built.
622             let sender = api_request_sender.clone();
623             vmm::api::vm_create(
624                 api_evt.try_clone().unwrap(),
625                 api_request_sender,
626                 Arc::new(Mutex::new(vm_config)),
627             )
628             .map_err(Error::VmCreate)?;
629             vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?;
630         } else if let Some(restore_params) = toplevel.restore {
631             vmm::api::vm_restore(
632                 api_evt.try_clone().unwrap(),
633                 api_request_sender,
634                 Arc::new(
635                     config::RestoreConfig::parse(&restore_params).map_err(Error::ParsingRestore)?,
636                 ),
637             )
638             .map_err(Error::VmRestore)?;
639         }
640 
641         Ok(())
642     })();
643 
644     if r.is_err() {
645         if let Err(e) = exit_evt.write(1) {
646             warn!("writing to exit EventFd: {e}");
647         }
648     }
649 
650     vmm_thread_handle
651         .thread_handle
652         .join()
653         .map_err(Error::ThreadJoin)?
654         .map_err(Error::VmmThread)?;
655 
656     #[cfg(feature = "dbus_api")]
657     if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs {
658         dbus_api_graceful_shutdown(chs);
659     }
660 
661     r.map(|_| api_socket_path)
662 }
663 
664 fn main() {
665     #[cfg(all(feature = "tdx", feature = "sev_snp"))]
666     compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive.");
667 
668     #[cfg(feature = "dhat-heap")]
669     let _profiler = dhat::Profiler::new_heap();
670 
671     // Ensure all created files (.e.g sockets) are only accessible by this user
672     // SAFETY: trivially safe
673     let _ = unsafe { libc::umask(0o077) };
674 
675     let toplevel: TopLevel = argh::from_env();
676 
677     if toplevel.version {
678         println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION"));
679 
680         if toplevel.verbosity != 0 {
681             println!("Enabled features: {:?}", vmm::feature_list());
682         }
683 
684         return;
685     }
686 
687     let exit_code = match start_vmm(toplevel) {
688         Ok(path) => {
689             path.map(|s| std::fs::remove_file(s).ok());
690             0
691         }
692         Err(e) => {
693             eprintln!("{e}");
694             1
695         }
696     };
697 
698     #[cfg(feature = "dhat-heap")]
699     drop(_profiler);
700 
701     std::process::exit(exit_code);
702 }
703 
704 #[cfg(test)]
705 mod unit_tests {
706     use crate::config::HotplugMethod;
707     use crate::TopLevel;
708     use std::path::PathBuf;
709     use vmm::config::{
710         ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig,
711         RngConfig, VmConfig,
712     };
713 
714     // Taken from argh
715     fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
716         std::path::Path::new(path)
717             .file_name()
718             .and_then(|s| s.to_str())
719             .unwrap_or(default)
720     }
721 
722     // Some code taken from argh since it does not provide a helper to parse arbitrary strings
723     fn get_vm_config_from_vec(args: &[&str]) -> VmConfig {
724         let strings: Vec<String> = args.iter().map(|x| x.to_string()).collect();
725         let cmd = cmd(&strings[0], &strings[0]);
726         let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
727         let toplevel = <TopLevel as argh::FromArgs>::from_args(&[cmd], &strs[1..]).unwrap_or_else(
728             |early_exit| {
729                 std::process::exit(match early_exit.status {
730                     Ok(()) => {
731                         println!("{}", early_exit.output);
732                         0
733                     }
734                     Err(()) => {
735                         eprintln!(
736                             "{}\nRun {} --help for more information.",
737                             early_exit.output, cmd
738                         );
739                         1
740                     }
741                 })
742             },
743         );
744 
745         let vm_params = toplevel.to_vm_params();
746 
747         VmConfig::parse(vm_params).unwrap()
748     }
749 
750     fn compare_vm_config_cli_vs_json(
751         cli: &[&str],
752         openapi: &str,
753         equal: bool,
754     ) -> (VmConfig, VmConfig) {
755         let cli_vm_config = get_vm_config_from_vec(cli);
756         let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap();
757 
758         if equal {
759             assert_eq!(cli_vm_config, openapi_vm_config);
760         } else {
761             assert_ne!(cli_vm_config, openapi_vm_config);
762         }
763 
764         (cli_vm_config, openapi_vm_config)
765     }
766 
767     #[test]
768     fn test_valid_vm_config_default() {
769         let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"];
770         let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#;
771 
772         // First we check we get identical VmConfig structures.
773         let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true);
774 
775         // As a second step, we validate all the default values.
776         let expected_vm_config = VmConfig {
777             cpus: CpusConfig {
778                 boot_vcpus: 1,
779                 max_vcpus: 1,
780                 topology: None,
781                 kvm_hyperv: false,
782                 max_phys_bits: 46,
783                 affinity: None,
784                 features: CpuFeatures::default(),
785             },
786             memory: MemoryConfig {
787                 size: 536_870_912,
788                 mergeable: false,
789                 hotplug_method: HotplugMethod::Acpi,
790                 hotplug_size: None,
791                 hotplugged_size: None,
792                 shared: false,
793                 hugepages: false,
794                 hugepage_size: None,
795                 prefault: false,
796                 zones: None,
797                 thp: true,
798             },
799             payload: Some(PayloadConfig {
800                 kernel: Some(PathBuf::from("/path/to/kernel")),
801                 ..Default::default()
802             }),
803             disks: None,
804             net: None,
805             rng: RngConfig {
806                 src: PathBuf::from("/dev/urandom"),
807                 iommu: false,
808             },
809             balloon: None,
810             fs: None,
811             pmem: None,
812             serial: ConsoleConfig {
813                 file: None,
814                 mode: ConsoleOutputMode::Null,
815                 iommu: false,
816                 socket: None,
817             },
818             console: ConsoleConfig {
819                 file: None,
820                 mode: ConsoleOutputMode::Tty,
821                 iommu: false,
822                 socket: None,
823             },
824             devices: None,
825             user_devices: None,
826             vdpa: None,
827             vsock: None,
828             pvpanic: false,
829             iommu: false,
830             #[cfg(target_arch = "x86_64")]
831             sgx_epc: None,
832             numa: None,
833             watchdog: false,
834             #[cfg(feature = "guest_debug")]
835             gdb: false,
836             platform: None,
837             tpm: None,
838             preserved_fds: None,
839         };
840 
841         assert_eq!(expected_vm_config, result_vm_config);
842     }
843 
844     #[test]
845     fn test_valid_vm_config_cpus() {
846         [
847             (
848                 vec![
849                     "cloud-hypervisor",
850                     "--kernel",
851                     "/path/to/kernel",
852                     "--cpus",
853                     "boot=1",
854                 ],
855                 r#"{
856                     "payload": {"kernel": "/path/to/kernel"},
857                     "cpus": {"boot_vcpus": 1, "max_vcpus": 1}
858                 }"#,
859                 true,
860             ),
861             (
862                 vec![
863                     "cloud-hypervisor",
864                     "--kernel",
865                     "/path/to/kernel",
866                     "--cpus",
867                     "boot=1,max=3",
868                 ],
869                 r#"{
870                     "payload": {"kernel": "/path/to/kernel"},
871                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
872                 }"#,
873                 true,
874             ),
875             (
876                 vec![
877                     "cloud-hypervisor",
878                     "--kernel",
879                     "/path/to/kernel",
880                     "--cpus",
881                     "boot=2,max=4",
882                 ],
883                 r#"{
884                     "payload": {"kernel": "/path/to/kernel"},
885                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
886                 }"#,
887                 false,
888             ),
889         ]
890         .iter()
891         .for_each(|(cli, openapi, equal)| {
892             compare_vm_config_cli_vs_json(cli, openapi, *equal);
893         });
894     }
895 
896     #[test]
897     fn test_valid_vm_config_memory() {
898         vec![
899             (
900                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
901                 r#"{
902                     "payload": {"kernel": "/path/to/kernel"},
903                     "memory": {"size": 1073741824}
904                 }"#,
905                 true,
906             ),
907             (
908                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"],
909                 r#"{
910                     "payload": {"kernel": "/path/to/kernel"},
911                     "memory": {"size": 1073741824}
912                 }"#,
913                 true,
914             ),
915             (
916                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
917                 r#"{
918                     "payload": {"kernel": "/path/to/kernel"},
919                     "memory": {"size": 1073741824, "mergeable": true}
920                 }"#,
921                 true,
922             ),
923             (
924                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"],
925                 r#"{
926                     "payload": {"kernel": "/path/to/kernel"},
927                     "memory": {"size": 1073741824, "mergeable": false}
928                 }"#,
929                 true,
930             ),
931             (
932                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
933                 r#"{
934                     "payload": {"kernel": "/path/to/kernel"},
935                     "memory": {"size": 1073741824, "mergeable": false}
936                 }"#,
937                 false,
938             ),
939             (
940                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"],
941                 r#"{
942                     "payload": {"kernel": "/path/to/kernel"},
943                     "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824}
944                 }"#,
945                 true,
946             ),
947             (
948                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"],
949                 r#"{
950                     "payload": {"kernel": "/path/to/kernel"},
951                     "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
952                 }"#,
953                 true,
954             ),
955         ]
956         .iter()
957         .for_each(|(cli, openapi, equal)| {
958             compare_vm_config_cli_vs_json(cli, openapi, *equal);
959         });
960     }
961 
962     #[test]
963     fn test_valid_vm_config_kernel() {
964         [(
965             vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
966             r#"{
967                 "payload": {"kernel": "/path/to/kernel"}
968             }"#,
969             true,
970         )]
971         .iter()
972         .for_each(|(cli, openapi, equal)| {
973             compare_vm_config_cli_vs_json(cli, openapi, *equal);
974         });
975     }
976 
977     #[test]
978     fn test_valid_vm_config_cmdline() {
979         [(
980             vec![
981                 "cloud-hypervisor",
982                 "--kernel",
983                 "/path/to/kernel",
984                 "--cmdline",
985                 "arg1=foo arg2=bar",
986             ],
987             r#"{
988                 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"}
989             }"#,
990             true,
991         )]
992         .iter()
993         .for_each(|(cli, openapi, equal)| {
994             compare_vm_config_cli_vs_json(cli, openapi, *equal);
995         });
996     }
997 
998     #[test]
999     fn test_valid_vm_config_disks() {
1000         [
1001             (
1002                 vec![
1003                     "cloud-hypervisor",
1004                     "--kernel",
1005                     "/path/to/kernel",
1006                     "--disk",
1007                     "path=/path/to/disk/1",
1008                     "--disk",
1009                     "path=/path/to/disk/2",
1010                 ],
1011                 r#"{
1012                     "payload": {"kernel": "/path/to/kernel"},
1013                     "disks": [
1014                         {"path": "/path/to/disk/1"},
1015                         {"path": "/path/to/disk/2"}
1016                     ]
1017                 }"#,
1018                 true,
1019             ),
1020             (
1021                 vec![
1022                     "cloud-hypervisor",
1023                     "--kernel",
1024                     "/path/to/kernel",
1025                     "--disk",
1026                     "path=/path/to/disk/1",
1027                     "--disk",
1028                     "path=/path/to/disk/2",
1029                 ],
1030                 r#"{
1031                     "payload": {"kernel": "/path/to/kernel"},
1032                     "disks": [
1033                         {"path": "/path/to/disk/1"}
1034                     ]
1035                 }"#,
1036                 false,
1037             ),
1038             (
1039                 vec![
1040                     "cloud-hypervisor",
1041                     "--kernel",
1042                     "/path/to/kernel",
1043                     "--memory",
1044                     "shared=true",
1045                     "--disk",
1046                     "vhost_user=true,socket=/tmp/sock1",
1047                 ],
1048                 r#"{
1049                     "payload": {"kernel": "/path/to/kernel"},
1050                     "memory" : { "shared": true, "size": 536870912 },
1051                     "disks": [
1052                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1053                     ]
1054                 }"#,
1055                 true,
1056             ),
1057             (
1058                 vec![
1059                     "cloud-hypervisor",
1060                     "--kernel",
1061                     "/path/to/kernel",
1062                     "--memory",
1063                     "shared=true",
1064                     "--disk",
1065                     "vhost_user=true,socket=/tmp/sock1",
1066                 ],
1067                 r#"{
1068                     "payload": {"kernel": "/path/to/kernel"},
1069                     "memory" : { "shared": true, "size": 536870912 },
1070                     "disks": [
1071                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1072                     ]
1073                 }"#,
1074                 true,
1075             ),
1076         ]
1077         .iter()
1078         .for_each(|(cli, openapi, equal)| {
1079             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1080         });
1081     }
1082 
1083     #[test]
1084     fn test_valid_vm_config_net() {
1085         vec![
1086             // This test is expected to fail because the default MAC address is
1087             // randomly generated. There's no way we can have twice the same
1088             // default value.
1089             (
1090                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="],
1091                 r#"{
1092                     "payload": {"kernel": "/path/to/kernel"},
1093                     "net": []
1094                 }"#,
1095                 false,
1096             ),
1097             (
1098                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"],
1099                 r#"{
1100                     "payload": {"kernel": "/path/to/kernel"},
1101                     "net": [
1102                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"}
1103                     ]
1104                 }"#,
1105                 true,
1106             ),
1107             (
1108                 vec![
1109                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1110                     "--net",
1111                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0",
1112                 ],
1113                 r#"{
1114                     "payload": {"kernel": "/path/to/kernel"},
1115                     "net": [
1116                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"}
1117                     ]
1118                 }"#,
1119                 true,
1120             ),
1121             (
1122                 vec![
1123                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1124                     "--net",
1125                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
1126                 ],
1127                 r#"{
1128                     "payload": {"kernel": "/path/to/kernel"},
1129                     "net": [
1130                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
1131                     ]
1132                 }"#,
1133                 true,
1134             ),
1135             (
1136                 vec![
1137                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1138                     "--net",
1139                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8",
1140                 ],
1141                 r#"{
1142                     "payload": {"kernel": "/path/to/kernel"},
1143                     "net": [
1144                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8"}
1145                     ]
1146                 }"#,
1147                 true,
1148             ),
1149             (
1150                 vec![
1151                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1152                     "--cpus", "boot=2",
1153                     "--net",
1154                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=4",
1155                 ],
1156                 r#"{
1157                     "payload": {"kernel": "/path/to/kernel"},
1158                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1159                     "net": [
1160                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 4}
1161                     ]
1162                 }"#,
1163                 true,
1164             ),
1165             (
1166                 vec![
1167                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1168                     "--cpus", "boot=2",
1169                     "--net",
1170                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=4,queue_size=128",
1171                 ],
1172                 r#"{
1173                     "payload": {"kernel": "/path/to/kernel"},
1174                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1175                     "net": [
1176                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 4, "queue_size": 128}
1177                     ]
1178                 }"#,
1179                 true,
1180             ),
1181             (
1182                 vec![
1183                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1184                     "--net",
1185                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256",
1186                 ],
1187                 r#"{
1188                     "payload": {"kernel": "/path/to/kernel"},
1189                     "net": [
1190                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8"}
1191                     ]
1192                 }"#,
1193                 true,
1194             ),
1195             (
1196                 vec![
1197                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1198                     "--net",
1199                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8",
1200                 ],
1201                 r#"{
1202                     "payload": {"kernel": "/path/to/kernel"},
1203                     "net": [
1204                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256}
1205                     ]
1206                 }"#,
1207                 true,
1208             ),
1209             #[cfg(target_arch = "x86_64")]
1210             (
1211                 vec![
1212                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1213                     "--net",
1214                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=on",
1215                 ],
1216                 r#"{
1217                     "payload": {"kernel": "/path/to/kernel"},
1218                     "net": [
1219                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": true}
1220                     ]
1221                 }"#,
1222                 false,
1223             ),
1224             #[cfg(target_arch = "x86_64")]
1225             (
1226                 vec![
1227                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1228                     "--net",
1229                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=on",
1230                 ],
1231                 r#"{
1232                     "payload": {"kernel": "/path/to/kernel"},
1233                     "net": [
1234                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": true}
1235                     ],
1236                     "iommu": true
1237                 }"#,
1238                 true,
1239             ),
1240             (
1241                 vec![
1242                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1243                     "--net",
1244                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=off",
1245                 ],
1246                 r#"{
1247                     "payload": {"kernel": "/path/to/kernel"},
1248                     "net": [
1249                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": false}
1250                     ]
1251                 }"#,
1252                 true,
1253             ),
1254             (
1255                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "shared=true", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,vhost_user=true,socket=/tmp/sock"],
1256                 r#"{
1257                     "payload": {"kernel": "/path/to/kernel"},
1258                     "memory" : { "shared": true, "size": 536870912 },
1259                     "net": [
1260                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"}
1261                     ]
1262                 }"#,
1263                 true,
1264             ),
1265         ]
1266         .iter()
1267         .for_each(|(cli, openapi, equal)| {
1268             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1269         });
1270     }
1271 
1272     #[test]
1273     fn test_valid_vm_config_rng() {
1274         [(
1275             vec![
1276                 "cloud-hypervisor",
1277                 "--kernel",
1278                 "/path/to/kernel",
1279                 "--rng",
1280                 "src=/path/to/entropy/source",
1281             ],
1282             r#"{
1283                 "payload": {"kernel": "/path/to/kernel"},
1284                 "rng": {"src": "/path/to/entropy/source"}
1285             }"#,
1286             true,
1287         )]
1288         .iter()
1289         .for_each(|(cli, openapi, equal)| {
1290             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1291         });
1292     }
1293 
1294     #[test]
1295     fn test_valid_vm_config_fs() {
1296         [(
1297                 vec![
1298                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1299                     "--memory", "shared=true",
1300                     "--fs",
1301                     "tag=virtiofs1,socket=/path/to/sock1",
1302                     "--fs",
1303                     "tag=virtiofs2,socket=/path/to/sock2",
1304                 ],
1305                 r#"{
1306                     "payload": {"kernel": "/path/to/kernel"},
1307                     "memory" : { "shared": true, "size": 536870912 },
1308                     "fs": [
1309                         {"tag": "virtiofs1", "socket": "/path/to/sock1"},
1310                         {"tag": "virtiofs2", "socket": "/path/to/sock2"}
1311                     ]
1312                 }"#,
1313                 true,
1314             ),
1315             (
1316                 vec![
1317                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1318                     "--memory", "shared=true",
1319                     "--fs",
1320                     "tag=virtiofs1,socket=/path/to/sock1",
1321                     "--fs",
1322                     "tag=virtiofs2,socket=/path/to/sock2",
1323                 ],
1324                 r#"{
1325                     "payload": {"kernel": "/path/to/kernel"},
1326                     "memory" : { "shared": true, "size": 536870912 },
1327                     "fs": [
1328                         {"tag": "virtiofs1", "socket": "/path/to/sock1"}
1329                     ]
1330                 }"#,
1331                 false,
1332             ),
1333             (
1334                 vec![
1335                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1336                     "--memory", "shared=true", "--cpus", "boot=4",
1337                     "--fs",
1338                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4",
1339                 ],
1340                 r#"{
1341                     "payload": {"kernel": "/path/to/kernel"},
1342                     "memory" : { "shared": true, "size": 536870912 },
1343                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1344                     "fs": [
1345                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4}
1346                     ]
1347                 }"#,
1348                 true,
1349             ),
1350             (
1351                 vec![
1352                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1353                     "--memory", "shared=true", "--cpus", "boot=4",
1354                     "--fs",
1355                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128"
1356                 ],
1357                 r#"{
1358                     "payload": {"kernel": "/path/to/kernel"},
1359                     "memory" : { "shared": true, "size": 536870912 },
1360                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1361                     "fs": [
1362                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128}
1363                     ]
1364                 }"#,
1365                 true,
1366             )]
1367         .iter()
1368         .for_each(|(cli, openapi, equal)| {
1369             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1370         });
1371     }
1372 
1373     #[test]
1374     fn test_valid_vm_config_pmem() {
1375         [
1376             (
1377                 vec![
1378                     "cloud-hypervisor",
1379                     "--kernel",
1380                     "/path/to/kernel",
1381                     "--pmem",
1382                     "file=/path/to/img/1,size=1G",
1383                     "--pmem",
1384                     "file=/path/to/img/2,size=2G",
1385                 ],
1386                 r#"{
1387                     "payload": {"kernel": "/path/to/kernel"},
1388                     "pmem": [
1389                         {"file": "/path/to/img/1", "size": 1073741824},
1390                         {"file": "/path/to/img/2", "size": 2147483648}
1391                     ]
1392                 }"#,
1393                 true,
1394             ),
1395             #[cfg(target_arch = "x86_64")]
1396             (
1397                 vec![
1398                     "cloud-hypervisor",
1399                     "--kernel",
1400                     "/path/to/kernel",
1401                     "--pmem",
1402                     "file=/path/to/img/1,size=1G,iommu=on",
1403                 ],
1404                 r#"{
1405                     "payload": {"kernel": "/path/to/kernel"},
1406                     "pmem": [
1407                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1408                     ],
1409                     "iommu": true
1410                 }"#,
1411                 true,
1412             ),
1413             #[cfg(target_arch = "x86_64")]
1414             (
1415                 vec![
1416                     "cloud-hypervisor",
1417                     "--kernel",
1418                     "/path/to/kernel",
1419                     "--pmem",
1420                     "file=/path/to/img/1,size=1G,iommu=on",
1421                 ],
1422                 r#"{
1423                     "payload": {"kernel": "/path/to/kernel"},
1424                     "pmem": [
1425                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1426                     ]
1427                 }"#,
1428                 false,
1429             ),
1430         ]
1431         .iter()
1432         .for_each(|(cli, openapi, equal)| {
1433             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1434         });
1435     }
1436 
1437     #[test]
1438     fn test_valid_vm_config_serial_console() {
1439         [
1440             (
1441                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1442                 r#"{
1443                     "payload": {"kernel": "/path/to/kernel"},
1444                     "serial": {"mode": "Null"},
1445                     "console": {"mode": "Tty"}
1446                 }"#,
1447                 true,
1448             ),
1449             (
1450                 vec![
1451                     "cloud-hypervisor",
1452                     "--kernel",
1453                     "/path/to/kernel",
1454                     "--serial",
1455                     "null",
1456                     "--console",
1457                     "tty",
1458                 ],
1459                 r#"{
1460                     "payload": {"kernel": "/path/to/kernel"}
1461                 }"#,
1462                 true,
1463             ),
1464             (
1465                 vec![
1466                     "cloud-hypervisor",
1467                     "--kernel",
1468                     "/path/to/kernel",
1469                     "--serial",
1470                     "tty",
1471                     "--console",
1472                     "off",
1473                 ],
1474                 r#"{
1475                     "payload": {"kernel": "/path/to/kernel"},
1476                     "serial": {"mode": "Tty"},
1477                     "console": {"mode": "Off"}
1478                 }"#,
1479                 true,
1480             ),
1481         ]
1482         .iter()
1483         .for_each(|(cli, openapi, equal)| {
1484             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1485         });
1486     }
1487 
1488     #[test]
1489     fn test_valid_vm_config_serial_pty_console_pty() {
1490         [
1491             (
1492                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1493                 r#"{
1494                     "payload": {"kernel": "/path/to/kernel"},
1495                     "serial": {"mode": "Null"},
1496                     "console": {"mode": "Tty"}
1497                 }"#,
1498                 true,
1499             ),
1500             (
1501                 vec![
1502                     "cloud-hypervisor",
1503                     "--kernel",
1504                     "/path/to/kernel",
1505                     "--serial",
1506                     "null",
1507                     "--console",
1508                     "tty",
1509                 ],
1510                 r#"{
1511                     "payload": {"kernel": "/path/to/kernel"}
1512                 }"#,
1513                 true,
1514             ),
1515             (
1516                 vec![
1517                     "cloud-hypervisor",
1518                     "--kernel",
1519                     "/path/to/kernel",
1520                     "--serial",
1521                     "pty",
1522                     "--console",
1523                     "pty",
1524                 ],
1525                 r#"{
1526                     "payload": {"kernel": "/path/to/kernel"},
1527                     "serial": {"mode": "Pty"},
1528                     "console": {"mode": "Pty"}
1529                 }"#,
1530                 true,
1531             ),
1532         ]
1533         .iter()
1534         .for_each(|(cli, openapi, equal)| {
1535             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1536         });
1537     }
1538 
1539     #[test]
1540     #[cfg(target_arch = "x86_64")]
1541     fn test_valid_vm_config_devices() {
1542         vec![
1543             (
1544                 vec![
1545                     "cloud-hypervisor",
1546                     "--kernel",
1547                     "/path/to/kernel",
1548                     "--device",
1549                     "path=/path/to/device/1",
1550                     "--device",
1551                     "path=/path/to/device/2",
1552                 ],
1553                 r#"{
1554                     "payload": {"kernel": "/path/to/kernel"},
1555                     "devices": [
1556                         {"path": "/path/to/device/1"},
1557                         {"path": "/path/to/device/2"}
1558                     ]
1559                 }"#,
1560                 true,
1561             ),
1562             (
1563                 vec![
1564                     "cloud-hypervisor",
1565                     "--kernel",
1566                     "/path/to/kernel",
1567                     "--device",
1568                     "path=/path/to/device/1",
1569                     "--device",
1570                     "path=/path/to/device/2",
1571                 ],
1572                 r#"{
1573                     "payload": {"kernel": "/path/to/kernel"},
1574                     "devices": [
1575                         {"path": "/path/to/device/1"}
1576                     ]
1577                 }"#,
1578                 false,
1579             ),
1580             (
1581                 vec![
1582                     "cloud-hypervisor",
1583                     "--kernel",
1584                     "/path/to/kernel",
1585                     "--device",
1586                     "path=/path/to/device,iommu=on",
1587                 ],
1588                 r#"{
1589                     "payload": {"kernel": "/path/to/kernel"},
1590                     "devices": [
1591                         {"path": "/path/to/device", "iommu": true}
1592                     ],
1593                     "iommu": true
1594                 }"#,
1595                 true,
1596             ),
1597             (
1598                 vec![
1599                     "cloud-hypervisor",
1600                     "--kernel",
1601                     "/path/to/kernel",
1602                     "--device",
1603                     "path=/path/to/device,iommu=on",
1604                 ],
1605                 r#"{
1606                     "payload": {"kernel": "/path/to/kernel"},
1607                     "devices": [
1608                         {"path": "/path/to/device", "iommu": true}
1609                     ]
1610                 }"#,
1611                 false,
1612             ),
1613             (
1614                 vec![
1615                     "cloud-hypervisor",
1616                     "--kernel",
1617                     "/path/to/kernel",
1618                     "--device",
1619                     "path=/path/to/device,iommu=off",
1620                 ],
1621                 r#"{
1622                     "payload": {"kernel": "/path/to/kernel"},
1623                     "devices": [
1624                         {"path": "/path/to/device", "iommu": false}
1625                     ]
1626                 }"#,
1627                 true,
1628             ),
1629         ]
1630         .iter()
1631         .for_each(|(cli, openapi, equal)| {
1632             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1633         });
1634     }
1635 
1636     #[test]
1637     fn test_valid_vm_config_vdpa() {
1638         [
1639             (
1640                 vec![
1641                     "cloud-hypervisor",
1642                     "--kernel",
1643                     "/path/to/kernel",
1644                     "--vdpa",
1645                     "path=/path/to/device/1",
1646                     "--vdpa",
1647                     "path=/path/to/device/2,num_queues=2",
1648                 ],
1649                 r#"{
1650                     "payload": {"kernel": "/path/to/kernel"},
1651                     "vdpa": [
1652                         {"path": "/path/to/device/1", "num_queues": 1},
1653                         {"path": "/path/to/device/2", "num_queues": 2}
1654                     ]
1655                 }"#,
1656                 true,
1657             ),
1658             (
1659                 vec![
1660                     "cloud-hypervisor",
1661                     "--kernel",
1662                     "/path/to/kernel",
1663                     "--vdpa",
1664                     "path=/path/to/device/1",
1665                     "--vdpa",
1666                     "path=/path/to/device/2",
1667                 ],
1668                 r#"{
1669                     "payload": {"kernel": "/path/to/kernel"},
1670                     "vdpa": [
1671                         {"path": "/path/to/device/1"}
1672                     ]
1673                 }"#,
1674                 false,
1675             ),
1676         ]
1677         .iter()
1678         .for_each(|(cli, openapi, equal)| {
1679             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1680         });
1681     }
1682 
1683     #[test]
1684     fn test_valid_vm_config_vsock() {
1685         [
1686             (
1687                 vec![
1688                     "cloud-hypervisor",
1689                     "--kernel",
1690                     "/path/to/kernel",
1691                     "--vsock",
1692                     "cid=123,socket=/path/to/sock/1",
1693                 ],
1694                 r#"{
1695                     "payload": {"kernel": "/path/to/kernel"},
1696                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1697                 }"#,
1698                 true,
1699             ),
1700             (
1701                 vec![
1702                     "cloud-hypervisor",
1703                     "--kernel",
1704                     "/path/to/kernel",
1705                     "--vsock",
1706                     "cid=124,socket=/path/to/sock/1",
1707                 ],
1708                 r#"{
1709                     "payload": {"kernel": "/path/to/kernel"},
1710                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1711                 }"#,
1712                 false,
1713             ),
1714             #[cfg(target_arch = "x86_64")]
1715             (
1716                 vec![
1717                     "cloud-hypervisor",
1718                     "--kernel",
1719                     "/path/to/kernel",
1720                     "--vsock",
1721                     "cid=123,socket=/path/to/sock/1,iommu=on",
1722                 ],
1723                 r#"{
1724                     "payload": {"kernel": "/path/to/kernel"},
1725                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true},
1726                     "iommu": true
1727                 }"#,
1728                 true,
1729             ),
1730             #[cfg(target_arch = "x86_64")]
1731             (
1732                 vec![
1733                     "cloud-hypervisor",
1734                     "--kernel",
1735                     "/path/to/kernel",
1736                     "--vsock",
1737                     "cid=123,socket=/path/to/sock/1,iommu=on",
1738                 ],
1739                 r#"{
1740                     "payload": {"kernel": "/path/to/kernel"},
1741                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}
1742                 }"#,
1743                 false,
1744             ),
1745             (
1746                 vec![
1747                     "cloud-hypervisor",
1748                     "--kernel",
1749                     "/path/to/kernel",
1750                     "--vsock",
1751                     "cid=123,socket=/path/to/sock/1,iommu=off",
1752                 ],
1753                 r#"{
1754                     "payload": {"kernel": "/path/to/kernel"},
1755                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false}
1756                 }"#,
1757                 true,
1758             ),
1759         ]
1760         .iter()
1761         .for_each(|(cli, openapi, equal)| {
1762             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1763         });
1764     }
1765 
1766     #[test]
1767     fn test_valid_vm_config_tpm_socket() {
1768         [(
1769             vec![
1770                 "cloud-hypervisor",
1771                 "--kernel",
1772                 "/path/to/kernel",
1773                 "--tpm",
1774                 "socket=/path/to/tpm/sock",
1775             ],
1776             r#"{
1777                     "payload": {"kernel": "/path/to/kernel"},
1778                     "tpm": {"socket": "/path/to/tpm/sock"}
1779                 }"#,
1780             true,
1781         )]
1782         .iter()
1783         .for_each(|(cli, openapi, equal)| {
1784             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1785         });
1786     }
1787 }
1788