xref: /cloud-hypervisor/src/main.rs (revision 2571e59438597f53aa4993cd70d6462fe1364ba7)
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     #[cfg(feature = "dbus_api")]
453     let dbus_options = match (&toplevel.dbus_name, &toplevel.dbus_path) {
454         (Some(ref name), Some(ref path)) => Ok(Some(DBusApiOptions {
455             service_name: name.to_owned(),
456             object_path: path.to_owned(),
457             system_bus: toplevel.dbus_system_bus,
458         })),
459         (Some(_), None) => Err(Error::MissingDBusObjectPath),
460         (None, Some(_)) => Err(Error::MissingDBusServiceName),
461         (None, None) => Ok(None),
462     }?;
463 
464     let (api_request_sender, api_request_receiver) = channel();
465     let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?;
466 
467     let api_request_sender_clone = api_request_sender.clone();
468     let seccomp_action = match &toplevel.seccomp as &str {
469         "true" => SeccompAction::Trap,
470         "false" => SeccompAction::Allow,
471         "log" => SeccompAction::Log,
472         val => {
473             // The user providing an invalid value will be rejected
474             panic!("Invalid parameter {val} for \"--seccomp\" flag");
475         }
476     };
477 
478     if seccomp_action == SeccompAction::Trap {
479         // SAFETY: We only using signal_hook for managing signals and only execute signal
480         // handler safe functions (writing to stderr) and manipulating signals.
481         unsafe {
482             signal_hook::low_level::register(signal_hook::consts::SIGSYS, || {
483                 eprint!(
484                     "\n==== Possible seccomp violation ====\n\
485                 Try running with `strace -ff` to identify the cause and open an issue: \
486                 https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n"
487                 );
488                 signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap();
489             })
490         }
491         .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}"))
492         .ok();
493     }
494 
495     // Before we start any threads, mask the signals we'll be
496     // installing handlers for, to make sure they only ever run on the
497     // dedicated signal handling thread we'll start in a bit.
498     for sig in &vmm::vm::Vm::HANDLED_SIGNALS {
499         if let Err(e) = block_signal(*sig) {
500             eprintln!("Error blocking signals: {e}");
501         }
502     }
503 
504     for sig in &vmm::Vmm::HANDLED_SIGNALS {
505         if let Err(e) = block_signal(*sig) {
506             eprintln!("Error blocking signals: {e}");
507         }
508     }
509 
510     let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?;
511 
512     #[cfg(feature = "guest_debug")]
513     let gdb_socket_path = if let Some(ref gdb_config) = toplevel.gdb {
514         let mut parser = OptionParser::new();
515         parser.add("path");
516         parser.parse(gdb_config).map_err(Error::ParsingGdb)?;
517 
518         if parser.is_set("path") {
519             Some(std::path::PathBuf::from(parser.get("path").unwrap()))
520         } else {
521             return Err(Error::BareGdb);
522         }
523     } else {
524         None
525     };
526     #[cfg(feature = "guest_debug")]
527     let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
528     #[cfg(feature = "guest_debug")]
529     let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
530 
531     let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
532 
533     if let Some(ref monitor_config) = toplevel.event_monitor {
534         let mut parser = OptionParser::new();
535         parser.add("path").add("fd");
536         parser
537             .parse(monitor_config)
538             .map_err(Error::ParsingEventMonitor)?;
539 
540         let file = if parser.is_set("fd") {
541             let fd = parser
542                 .convert("fd")
543                 .map_err(Error::ParsingEventMonitor)?
544                 .unwrap();
545             // SAFETY: fd is valid
546             unsafe { File::from_raw_fd(fd) }
547         } else if parser.is_set("path") {
548             std::fs::OpenOptions::new()
549                 .write(true)
550                 .create(true)
551                 .open(parser.get("path").unwrap())
552                 .map_err(Error::EventMonitorIo)?
553         } else {
554             return Err(Error::BareEventMonitor);
555         };
556 
557         let monitor = event_monitor::set_monitor(file).map_err(Error::EventMonitorIo)?;
558         vmm::start_event_monitor_thread(
559             monitor,
560             &seccomp_action,
561             hypervisor.hypervisor_type(),
562             exit_evt.try_clone().unwrap(),
563         )
564         .map_err(Error::EventMonitorThread)?;
565     }
566 
567     event!("vmm", "starting");
568 
569     let vmm_thread_handle = vmm::start_vmm_thread(
570         vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")),
571         &api_socket_path,
572         api_socket_fd,
573         #[cfg(feature = "dbus_api")]
574         dbus_options,
575         api_evt.try_clone().unwrap(),
576         api_request_sender_clone,
577         api_request_receiver,
578         #[cfg(feature = "guest_debug")]
579         gdb_socket_path,
580         #[cfg(feature = "guest_debug")]
581         debug_evt.try_clone().unwrap(),
582         #[cfg(feature = "guest_debug")]
583         vm_debug_evt.try_clone().unwrap(),
584         exit_evt.try_clone().unwrap(),
585         &seccomp_action,
586         hypervisor,
587     )
588     .map_err(Error::StartVmmThread)?;
589 
590     let r: Result<(), Error> = (|| {
591         let payload_present = toplevel.kernel.is_some() || toplevel.firmware.is_some();
592 
593         if payload_present {
594             let vm_params = toplevel.to_vm_params();
595             let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
596 
597             // Create and boot the VM based off the VM config we just built.
598             let sender = api_request_sender.clone();
599             vmm::api::vm_create(
600                 api_evt.try_clone().unwrap(),
601                 api_request_sender,
602                 Arc::new(Mutex::new(vm_config)),
603             )
604             .map_err(Error::VmCreate)?;
605             vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?;
606         } else if let Some(restore_params) = toplevel.restore {
607             vmm::api::vm_restore(
608                 api_evt.try_clone().unwrap(),
609                 api_request_sender,
610                 Arc::new(
611                     config::RestoreConfig::parse(&restore_params).map_err(Error::ParsingRestore)?,
612                 ),
613             )
614             .map_err(Error::VmRestore)?;
615         }
616 
617         Ok(())
618     })();
619 
620     if r.is_err() {
621         if let Err(e) = exit_evt.write(1) {
622             warn!("writing to exit EventFd: {e}");
623         }
624     }
625 
626     vmm_thread_handle
627         .thread_handle
628         .join()
629         .map_err(Error::ThreadJoin)?
630         .map_err(Error::VmmThread)?;
631 
632     #[cfg(feature = "dbus_api")]
633     if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs {
634         dbus_api_graceful_shutdown(chs);
635     }
636 
637     r.map(|_| api_socket_path)
638 }
639 
640 fn main() {
641     #[cfg(feature = "dhat-heap")]
642     let _profiler = dhat::Profiler::new_heap();
643 
644     // Ensure all created files (.e.g sockets) are only accessible by this user
645     // SAFETY: trivially safe
646     let _ = unsafe { libc::umask(0o077) };
647 
648     let toplevel: TopLevel = argh::from_env();
649 
650     if toplevel.version {
651         println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION"));
652         return;
653     }
654 
655     let exit_code = match start_vmm(toplevel) {
656         Ok(path) => {
657             path.map(|s| std::fs::remove_file(s).ok());
658             0
659         }
660         Err(e) => {
661             eprintln!("{e}");
662             1
663         }
664     };
665 
666     #[cfg(feature = "dhat-heap")]
667     drop(_profiler);
668 
669     std::process::exit(exit_code);
670 }
671 
672 #[cfg(test)]
673 mod unit_tests {
674     use crate::config::HotplugMethod;
675     use crate::TopLevel;
676     use std::path::PathBuf;
677     use vmm::config::{
678         ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig,
679         RngConfig, VmConfig,
680     };
681 
682     // Taken from argh
683     fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
684         std::path::Path::new(path)
685             .file_name()
686             .and_then(|s| s.to_str())
687             .unwrap_or(default)
688     }
689 
690     // Some code taken from argh since it does not provide a helper to parse arbitrary strings
691     fn get_vm_config_from_vec(args: &[&str]) -> VmConfig {
692         let strings: Vec<String> = args.iter().map(|x| x.to_string()).collect();
693         let cmd = cmd(&strings[0], &strings[0]);
694         let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
695         let toplevel = <TopLevel as argh::FromArgs>::from_args(&[cmd], &strs[1..]).unwrap_or_else(
696             |early_exit| {
697                 std::process::exit(match early_exit.status {
698                     Ok(()) => {
699                         println!("{}", early_exit.output);
700                         0
701                     }
702                     Err(()) => {
703                         eprintln!(
704                             "{}\nRun {} --help for more information.",
705                             early_exit.output, cmd
706                         );
707                         1
708                     }
709                 })
710             },
711         );
712 
713         let vm_params = toplevel.to_vm_params();
714 
715         VmConfig::parse(vm_params).unwrap()
716     }
717 
718     fn compare_vm_config_cli_vs_json(
719         cli: &[&str],
720         openapi: &str,
721         equal: bool,
722     ) -> (VmConfig, VmConfig) {
723         let cli_vm_config = get_vm_config_from_vec(cli);
724         let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap();
725 
726         if equal {
727             assert_eq!(cli_vm_config, openapi_vm_config);
728         } else {
729             assert_ne!(cli_vm_config, openapi_vm_config);
730         }
731 
732         (cli_vm_config, openapi_vm_config)
733     }
734 
735     #[test]
736     fn test_valid_vm_config_default() {
737         let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"];
738         let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#;
739 
740         // First we check we get identical VmConfig structures.
741         let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true);
742 
743         // As a second step, we validate all the default values.
744         let expected_vm_config = VmConfig {
745             cpus: CpusConfig {
746                 boot_vcpus: 1,
747                 max_vcpus: 1,
748                 topology: None,
749                 kvm_hyperv: false,
750                 max_phys_bits: 46,
751                 affinity: None,
752                 features: CpuFeatures::default(),
753             },
754             memory: MemoryConfig {
755                 size: 536_870_912,
756                 mergeable: false,
757                 hotplug_method: HotplugMethod::Acpi,
758                 hotplug_size: None,
759                 hotplugged_size: None,
760                 shared: false,
761                 hugepages: false,
762                 hugepage_size: None,
763                 prefault: false,
764                 zones: None,
765                 thp: true,
766             },
767             payload: Some(PayloadConfig {
768                 kernel: Some(PathBuf::from("/path/to/kernel")),
769                 ..Default::default()
770             }),
771             disks: None,
772             net: None,
773             rng: RngConfig {
774                 src: PathBuf::from("/dev/urandom"),
775                 iommu: false,
776             },
777             balloon: None,
778             fs: None,
779             pmem: None,
780             serial: ConsoleConfig {
781                 file: None,
782                 mode: ConsoleOutputMode::Null,
783                 iommu: false,
784             },
785             console: ConsoleConfig {
786                 file: None,
787                 mode: ConsoleOutputMode::Tty,
788                 iommu: false,
789             },
790             devices: None,
791             user_devices: None,
792             vdpa: None,
793             vsock: None,
794             pvpanic: false,
795             iommu: false,
796             #[cfg(target_arch = "x86_64")]
797             sgx_epc: None,
798             numa: None,
799             watchdog: false,
800             #[cfg(feature = "guest_debug")]
801             gdb: false,
802             platform: None,
803             tpm: None,
804             preserved_fds: None,
805         };
806 
807         assert_eq!(expected_vm_config, result_vm_config);
808     }
809 
810     #[test]
811     fn test_valid_vm_config_cpus() {
812         [
813             (
814                 vec![
815                     "cloud-hypervisor",
816                     "--kernel",
817                     "/path/to/kernel",
818                     "--cpus",
819                     "boot=1",
820                 ],
821                 r#"{
822                     "payload": {"kernel": "/path/to/kernel"},
823                     "cpus": {"boot_vcpus": 1, "max_vcpus": 1}
824                 }"#,
825                 true,
826             ),
827             (
828                 vec![
829                     "cloud-hypervisor",
830                     "--kernel",
831                     "/path/to/kernel",
832                     "--cpus",
833                     "boot=1,max=3",
834                 ],
835                 r#"{
836                     "payload": {"kernel": "/path/to/kernel"},
837                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
838                 }"#,
839                 true,
840             ),
841             (
842                 vec![
843                     "cloud-hypervisor",
844                     "--kernel",
845                     "/path/to/kernel",
846                     "--cpus",
847                     "boot=2,max=4",
848                 ],
849                 r#"{
850                     "payload": {"kernel": "/path/to/kernel"},
851                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
852                 }"#,
853                 false,
854             ),
855         ]
856         .iter()
857         .for_each(|(cli, openapi, equal)| {
858             compare_vm_config_cli_vs_json(cli, openapi, *equal);
859         });
860     }
861 
862     #[test]
863     fn test_valid_vm_config_memory() {
864         vec![
865             (
866                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
867                 r#"{
868                     "payload": {"kernel": "/path/to/kernel"},
869                     "memory": {"size": 1073741824}
870                 }"#,
871                 true,
872             ),
873             (
874                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"],
875                 r#"{
876                     "payload": {"kernel": "/path/to/kernel"},
877                     "memory": {"size": 1073741824}
878                 }"#,
879                 true,
880             ),
881             (
882                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
883                 r#"{
884                     "payload": {"kernel": "/path/to/kernel"},
885                     "memory": {"size": 1073741824, "mergeable": true}
886                 }"#,
887                 true,
888             ),
889             (
890                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"],
891                 r#"{
892                     "payload": {"kernel": "/path/to/kernel"},
893                     "memory": {"size": 1073741824, "mergeable": false}
894                 }"#,
895                 true,
896             ),
897             (
898                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
899                 r#"{
900                     "payload": {"kernel": "/path/to/kernel"},
901                     "memory": {"size": 1073741824, "mergeable": false}
902                 }"#,
903                 false,
904             ),
905             (
906                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"],
907                 r#"{
908                     "payload": {"kernel": "/path/to/kernel"},
909                     "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824}
910                 }"#,
911                 true,
912             ),
913             (
914                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"],
915                 r#"{
916                     "payload": {"kernel": "/path/to/kernel"},
917                     "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
918                 }"#,
919                 true,
920             ),
921         ]
922         .iter()
923         .for_each(|(cli, openapi, equal)| {
924             compare_vm_config_cli_vs_json(cli, openapi, *equal);
925         });
926     }
927 
928     #[test]
929     fn test_valid_vm_config_kernel() {
930         [(
931             vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
932             r#"{
933                 "payload": {"kernel": "/path/to/kernel"}
934             }"#,
935             true,
936         )]
937         .iter()
938         .for_each(|(cli, openapi, equal)| {
939             compare_vm_config_cli_vs_json(cli, openapi, *equal);
940         });
941     }
942 
943     #[test]
944     fn test_valid_vm_config_cmdline() {
945         [(
946             vec![
947                 "cloud-hypervisor",
948                 "--kernel",
949                 "/path/to/kernel",
950                 "--cmdline",
951                 "arg1=foo arg2=bar",
952             ],
953             r#"{
954                 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"}
955             }"#,
956             true,
957         )]
958         .iter()
959         .for_each(|(cli, openapi, equal)| {
960             compare_vm_config_cli_vs_json(cli, openapi, *equal);
961         });
962     }
963 
964     #[test]
965     fn test_valid_vm_config_disks() {
966         [
967             (
968                 vec![
969                     "cloud-hypervisor",
970                     "--kernel",
971                     "/path/to/kernel",
972                     "--disk",
973                     "path=/path/to/disk/1",
974                     "--disk",
975                     "path=/path/to/disk/2",
976                 ],
977                 r#"{
978                     "payload": {"kernel": "/path/to/kernel"},
979                     "disks": [
980                         {"path": "/path/to/disk/1"},
981                         {"path": "/path/to/disk/2"}
982                     ]
983                 }"#,
984                 true,
985             ),
986             (
987                 vec![
988                     "cloud-hypervisor",
989                     "--kernel",
990                     "/path/to/kernel",
991                     "--disk",
992                     "path=/path/to/disk/1",
993                     "--disk",
994                     "path=/path/to/disk/2",
995                 ],
996                 r#"{
997                     "payload": {"kernel": "/path/to/kernel"},
998                     "disks": [
999                         {"path": "/path/to/disk/1"}
1000                     ]
1001                 }"#,
1002                 false,
1003             ),
1004             (
1005                 vec![
1006                     "cloud-hypervisor",
1007                     "--kernel",
1008                     "/path/to/kernel",
1009                     "--memory",
1010                     "shared=true",
1011                     "--disk",
1012                     "vhost_user=true,socket=/tmp/sock1",
1013                 ],
1014                 r#"{
1015                     "payload": {"kernel": "/path/to/kernel"},
1016                     "memory" : { "shared": true, "size": 536870912 },
1017                     "disks": [
1018                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1019                     ]
1020                 }"#,
1021                 true,
1022             ),
1023             (
1024                 vec![
1025                     "cloud-hypervisor",
1026                     "--kernel",
1027                     "/path/to/kernel",
1028                     "--memory",
1029                     "shared=true",
1030                     "--disk",
1031                     "vhost_user=true,socket=/tmp/sock1",
1032                 ],
1033                 r#"{
1034                     "payload": {"kernel": "/path/to/kernel"},
1035                     "memory" : { "shared": true, "size": 536870912 },
1036                     "disks": [
1037                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1038                     ]
1039                 }"#,
1040                 true,
1041             ),
1042         ]
1043         .iter()
1044         .for_each(|(cli, openapi, equal)| {
1045             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1046         });
1047     }
1048 
1049     #[test]
1050     fn test_valid_vm_config_net() {
1051         vec![
1052             // This test is expected to fail because the default MAC address is
1053             // randomly generated. There's no way we can have twice the same
1054             // default value.
1055             (
1056                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="],
1057                 r#"{
1058                     "payload": {"kernel": "/path/to/kernel"},
1059                     "net": []
1060                 }"#,
1061                 false,
1062             ),
1063             (
1064                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"],
1065                 r#"{
1066                     "payload": {"kernel": "/path/to/kernel"},
1067                     "net": [
1068                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"}
1069                     ]
1070                 }"#,
1071                 true,
1072             ),
1073             (
1074                 vec![
1075                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1076                     "--net",
1077                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0",
1078                 ],
1079                 r#"{
1080                     "payload": {"kernel": "/path/to/kernel"},
1081                     "net": [
1082                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"}
1083                     ]
1084                 }"#,
1085                 true,
1086             ),
1087             (
1088                 vec![
1089                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1090                     "--net",
1091                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
1092                 ],
1093                 r#"{
1094                     "payload": {"kernel": "/path/to/kernel"},
1095                     "net": [
1096                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
1097                     ]
1098                 }"#,
1099                 true,
1100             ),
1101             (
1102                 vec![
1103                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1104                     "--net",
1105                     "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",
1106                 ],
1107                 r#"{
1108                     "payload": {"kernel": "/path/to/kernel"},
1109                     "net": [
1110                         {"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"}
1111                     ]
1112                 }"#,
1113                 true,
1114             ),
1115             (
1116                 vec![
1117                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1118                     "--cpus", "boot=2",
1119                     "--net",
1120                     "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",
1121                 ],
1122                 r#"{
1123                     "payload": {"kernel": "/path/to/kernel"},
1124                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1125                     "net": [
1126                         {"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}
1127                     ]
1128                 }"#,
1129                 true,
1130             ),
1131             (
1132                 vec![
1133                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1134                     "--cpus", "boot=2",
1135                     "--net",
1136                     "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",
1137                 ],
1138                 r#"{
1139                     "payload": {"kernel": "/path/to/kernel"},
1140                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1141                     "net": [
1142                         {"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}
1143                     ]
1144                 }"#,
1145                 true,
1146             ),
1147             (
1148                 vec![
1149                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1150                     "--net",
1151                     "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",
1152                 ],
1153                 r#"{
1154                     "payload": {"kernel": "/path/to/kernel"},
1155                     "net": [
1156                         {"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"}
1157                     ]
1158                 }"#,
1159                 true,
1160             ),
1161             (
1162                 vec![
1163                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1164                     "--net",
1165                     "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",
1166                 ],
1167                 r#"{
1168                     "payload": {"kernel": "/path/to/kernel"},
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": 2, "queue_size": 256}
1171                     ]
1172                 }"#,
1173                 true,
1174             ),
1175             #[cfg(target_arch = "x86_64")]
1176             (
1177                 vec![
1178                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1179                     "--net",
1180                     "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",
1181                 ],
1182                 r#"{
1183                     "payload": {"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, "iommu": true}
1186                     ]
1187                 }"#,
1188                 false,
1189             ),
1190             #[cfg(target_arch = "x86_64")]
1191             (
1192                 vec![
1193                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1194                     "--net",
1195                     "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",
1196                 ],
1197                 r#"{
1198                     "payload": {"kernel": "/path/to/kernel"},
1199                     "net": [
1200                         {"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}
1201                     ],
1202                     "iommu": true
1203                 }"#,
1204                 true,
1205             ),
1206             (
1207                 vec![
1208                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1209                     "--net",
1210                     "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",
1211                 ],
1212                 r#"{
1213                     "payload": {"kernel": "/path/to/kernel"},
1214                     "net": [
1215                         {"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}
1216                     ]
1217                 }"#,
1218                 true,
1219             ),
1220             (
1221                 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"],
1222                 r#"{
1223                     "payload": {"kernel": "/path/to/kernel"},
1224                     "memory" : { "shared": true, "size": 536870912 },
1225                     "net": [
1226                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"}
1227                     ]
1228                 }"#,
1229                 true,
1230             ),
1231         ]
1232         .iter()
1233         .for_each(|(cli, openapi, equal)| {
1234             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1235         });
1236     }
1237 
1238     #[test]
1239     fn test_valid_vm_config_rng() {
1240         [(
1241             vec![
1242                 "cloud-hypervisor",
1243                 "--kernel",
1244                 "/path/to/kernel",
1245                 "--rng",
1246                 "src=/path/to/entropy/source",
1247             ],
1248             r#"{
1249                 "payload": {"kernel": "/path/to/kernel"},
1250                 "rng": {"src": "/path/to/entropy/source"}
1251             }"#,
1252             true,
1253         )]
1254         .iter()
1255         .for_each(|(cli, openapi, equal)| {
1256             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1257         });
1258     }
1259 
1260     #[test]
1261     fn test_valid_vm_config_fs() {
1262         [(
1263                 vec![
1264                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1265                     "--memory", "shared=true",
1266                     "--fs",
1267                     "tag=virtiofs1,socket=/path/to/sock1",
1268                     "--fs",
1269                     "tag=virtiofs2,socket=/path/to/sock2",
1270                 ],
1271                 r#"{
1272                     "payload": {"kernel": "/path/to/kernel"},
1273                     "memory" : { "shared": true, "size": 536870912 },
1274                     "fs": [
1275                         {"tag": "virtiofs1", "socket": "/path/to/sock1"},
1276                         {"tag": "virtiofs2", "socket": "/path/to/sock2"}
1277                     ]
1278                 }"#,
1279                 true,
1280             ),
1281             (
1282                 vec![
1283                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1284                     "--memory", "shared=true",
1285                     "--fs",
1286                     "tag=virtiofs1,socket=/path/to/sock1",
1287                     "--fs",
1288                     "tag=virtiofs2,socket=/path/to/sock2",
1289                 ],
1290                 r#"{
1291                     "payload": {"kernel": "/path/to/kernel"},
1292                     "memory" : { "shared": true, "size": 536870912 },
1293                     "fs": [
1294                         {"tag": "virtiofs1", "socket": "/path/to/sock1"}
1295                     ]
1296                 }"#,
1297                 false,
1298             ),
1299             (
1300                 vec![
1301                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1302                     "--memory", "shared=true", "--cpus", "boot=4",
1303                     "--fs",
1304                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4",
1305                 ],
1306                 r#"{
1307                     "payload": {"kernel": "/path/to/kernel"},
1308                     "memory" : { "shared": true, "size": 536870912 },
1309                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1310                     "fs": [
1311                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4}
1312                     ]
1313                 }"#,
1314                 true,
1315             ),
1316             (
1317                 vec![
1318                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1319                     "--memory", "shared=true", "--cpus", "boot=4",
1320                     "--fs",
1321                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128"
1322                 ],
1323                 r#"{
1324                     "payload": {"kernel": "/path/to/kernel"},
1325                     "memory" : { "shared": true, "size": 536870912 },
1326                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1327                     "fs": [
1328                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128}
1329                     ]
1330                 }"#,
1331                 true,
1332             )]
1333         .iter()
1334         .for_each(|(cli, openapi, equal)| {
1335             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1336         });
1337     }
1338 
1339     #[test]
1340     fn test_valid_vm_config_pmem() {
1341         [
1342             (
1343                 vec![
1344                     "cloud-hypervisor",
1345                     "--kernel",
1346                     "/path/to/kernel",
1347                     "--pmem",
1348                     "file=/path/to/img/1,size=1G",
1349                     "--pmem",
1350                     "file=/path/to/img/2,size=2G",
1351                 ],
1352                 r#"{
1353                     "payload": {"kernel": "/path/to/kernel"},
1354                     "pmem": [
1355                         {"file": "/path/to/img/1", "size": 1073741824},
1356                         {"file": "/path/to/img/2", "size": 2147483648}
1357                     ]
1358                 }"#,
1359                 true,
1360             ),
1361             #[cfg(target_arch = "x86_64")]
1362             (
1363                 vec![
1364                     "cloud-hypervisor",
1365                     "--kernel",
1366                     "/path/to/kernel",
1367                     "--pmem",
1368                     "file=/path/to/img/1,size=1G,iommu=on",
1369                 ],
1370                 r#"{
1371                     "payload": {"kernel": "/path/to/kernel"},
1372                     "pmem": [
1373                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1374                     ],
1375                     "iommu": true
1376                 }"#,
1377                 true,
1378             ),
1379             #[cfg(target_arch = "x86_64")]
1380             (
1381                 vec![
1382                     "cloud-hypervisor",
1383                     "--kernel",
1384                     "/path/to/kernel",
1385                     "--pmem",
1386                     "file=/path/to/img/1,size=1G,iommu=on",
1387                 ],
1388                 r#"{
1389                     "payload": {"kernel": "/path/to/kernel"},
1390                     "pmem": [
1391                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1392                     ]
1393                 }"#,
1394                 false,
1395             ),
1396         ]
1397         .iter()
1398         .for_each(|(cli, openapi, equal)| {
1399             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1400         });
1401     }
1402 
1403     #[test]
1404     fn test_valid_vm_config_serial_console() {
1405         [
1406             (
1407                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1408                 r#"{
1409                     "payload": {"kernel": "/path/to/kernel"},
1410                     "serial": {"mode": "Null"},
1411                     "console": {"mode": "Tty"}
1412                 }"#,
1413                 true,
1414             ),
1415             (
1416                 vec![
1417                     "cloud-hypervisor",
1418                     "--kernel",
1419                     "/path/to/kernel",
1420                     "--serial",
1421                     "null",
1422                     "--console",
1423                     "tty",
1424                 ],
1425                 r#"{
1426                     "payload": {"kernel": "/path/to/kernel"}
1427                 }"#,
1428                 true,
1429             ),
1430             (
1431                 vec![
1432                     "cloud-hypervisor",
1433                     "--kernel",
1434                     "/path/to/kernel",
1435                     "--serial",
1436                     "tty",
1437                     "--console",
1438                     "off",
1439                 ],
1440                 r#"{
1441                     "payload": {"kernel": "/path/to/kernel"},
1442                     "serial": {"mode": "Tty"},
1443                     "console": {"mode": "Off"}
1444                 }"#,
1445                 true,
1446             ),
1447         ]
1448         .iter()
1449         .for_each(|(cli, openapi, equal)| {
1450             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1451         });
1452     }
1453 
1454     #[test]
1455     fn test_valid_vm_config_serial_pty_console_pty() {
1456         [
1457             (
1458                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1459                 r#"{
1460                     "payload": {"kernel": "/path/to/kernel"},
1461                     "serial": {"mode": "Null"},
1462                     "console": {"mode": "Tty"}
1463                 }"#,
1464                 true,
1465             ),
1466             (
1467                 vec![
1468                     "cloud-hypervisor",
1469                     "--kernel",
1470                     "/path/to/kernel",
1471                     "--serial",
1472                     "null",
1473                     "--console",
1474                     "tty",
1475                 ],
1476                 r#"{
1477                     "payload": {"kernel": "/path/to/kernel"}
1478                 }"#,
1479                 true,
1480             ),
1481             (
1482                 vec![
1483                     "cloud-hypervisor",
1484                     "--kernel",
1485                     "/path/to/kernel",
1486                     "--serial",
1487                     "pty",
1488                     "--console",
1489                     "pty",
1490                 ],
1491                 r#"{
1492                     "payload": {"kernel": "/path/to/kernel"},
1493                     "serial": {"mode": "Pty"},
1494                     "console": {"mode": "Pty"}
1495                 }"#,
1496                 true,
1497             ),
1498         ]
1499         .iter()
1500         .for_each(|(cli, openapi, equal)| {
1501             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1502         });
1503     }
1504 
1505     #[test]
1506     #[cfg(target_arch = "x86_64")]
1507     fn test_valid_vm_config_devices() {
1508         vec![
1509             (
1510                 vec![
1511                     "cloud-hypervisor",
1512                     "--kernel",
1513                     "/path/to/kernel",
1514                     "--device",
1515                     "path=/path/to/device/1",
1516                     "--device",
1517                     "path=/path/to/device/2",
1518                 ],
1519                 r#"{
1520                     "payload": {"kernel": "/path/to/kernel"},
1521                     "devices": [
1522                         {"path": "/path/to/device/1"},
1523                         {"path": "/path/to/device/2"}
1524                     ]
1525                 }"#,
1526                 true,
1527             ),
1528             (
1529                 vec![
1530                     "cloud-hypervisor",
1531                     "--kernel",
1532                     "/path/to/kernel",
1533                     "--device",
1534                     "path=/path/to/device/1",
1535                     "--device",
1536                     "path=/path/to/device/2",
1537                 ],
1538                 r#"{
1539                     "payload": {"kernel": "/path/to/kernel"},
1540                     "devices": [
1541                         {"path": "/path/to/device/1"}
1542                     ]
1543                 }"#,
1544                 false,
1545             ),
1546             (
1547                 vec![
1548                     "cloud-hypervisor",
1549                     "--kernel",
1550                     "/path/to/kernel",
1551                     "--device",
1552                     "path=/path/to/device,iommu=on",
1553                 ],
1554                 r#"{
1555                     "payload": {"kernel": "/path/to/kernel"},
1556                     "devices": [
1557                         {"path": "/path/to/device", "iommu": true}
1558                     ],
1559                     "iommu": true
1560                 }"#,
1561                 true,
1562             ),
1563             (
1564                 vec![
1565                     "cloud-hypervisor",
1566                     "--kernel",
1567                     "/path/to/kernel",
1568                     "--device",
1569                     "path=/path/to/device,iommu=on",
1570                 ],
1571                 r#"{
1572                     "payload": {"kernel": "/path/to/kernel"},
1573                     "devices": [
1574                         {"path": "/path/to/device", "iommu": true}
1575                     ]
1576                 }"#,
1577                 false,
1578             ),
1579             (
1580                 vec![
1581                     "cloud-hypervisor",
1582                     "--kernel",
1583                     "/path/to/kernel",
1584                     "--device",
1585                     "path=/path/to/device,iommu=off",
1586                 ],
1587                 r#"{
1588                     "payload": {"kernel": "/path/to/kernel"},
1589                     "devices": [
1590                         {"path": "/path/to/device", "iommu": false}
1591                     ]
1592                 }"#,
1593                 true,
1594             ),
1595         ]
1596         .iter()
1597         .for_each(|(cli, openapi, equal)| {
1598             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1599         });
1600     }
1601 
1602     #[test]
1603     fn test_valid_vm_config_vdpa() {
1604         [
1605             (
1606                 vec![
1607                     "cloud-hypervisor",
1608                     "--kernel",
1609                     "/path/to/kernel",
1610                     "--vdpa",
1611                     "path=/path/to/device/1",
1612                     "--vdpa",
1613                     "path=/path/to/device/2,num_queues=2",
1614                 ],
1615                 r#"{
1616                     "payload": {"kernel": "/path/to/kernel"},
1617                     "vdpa": [
1618                         {"path": "/path/to/device/1", "num_queues": 1},
1619                         {"path": "/path/to/device/2", "num_queues": 2}
1620                     ]
1621                 }"#,
1622                 true,
1623             ),
1624             (
1625                 vec![
1626                     "cloud-hypervisor",
1627                     "--kernel",
1628                     "/path/to/kernel",
1629                     "--vdpa",
1630                     "path=/path/to/device/1",
1631                     "--vdpa",
1632                     "path=/path/to/device/2",
1633                 ],
1634                 r#"{
1635                     "payload": {"kernel": "/path/to/kernel"},
1636                     "vdpa": [
1637                         {"path": "/path/to/device/1"}
1638                     ]
1639                 }"#,
1640                 false,
1641             ),
1642         ]
1643         .iter()
1644         .for_each(|(cli, openapi, equal)| {
1645             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1646         });
1647     }
1648 
1649     #[test]
1650     fn test_valid_vm_config_vsock() {
1651         [
1652             (
1653                 vec![
1654                     "cloud-hypervisor",
1655                     "--kernel",
1656                     "/path/to/kernel",
1657                     "--vsock",
1658                     "cid=123,socket=/path/to/sock/1",
1659                 ],
1660                 r#"{
1661                     "payload": {"kernel": "/path/to/kernel"},
1662                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1663                 }"#,
1664                 true,
1665             ),
1666             (
1667                 vec![
1668                     "cloud-hypervisor",
1669                     "--kernel",
1670                     "/path/to/kernel",
1671                     "--vsock",
1672                     "cid=124,socket=/path/to/sock/1",
1673                 ],
1674                 r#"{
1675                     "payload": {"kernel": "/path/to/kernel"},
1676                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1677                 }"#,
1678                 false,
1679             ),
1680             #[cfg(target_arch = "x86_64")]
1681             (
1682                 vec![
1683                     "cloud-hypervisor",
1684                     "--kernel",
1685                     "/path/to/kernel",
1686                     "--vsock",
1687                     "cid=123,socket=/path/to/sock/1,iommu=on",
1688                 ],
1689                 r#"{
1690                     "payload": {"kernel": "/path/to/kernel"},
1691                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true},
1692                     "iommu": true
1693                 }"#,
1694                 true,
1695             ),
1696             #[cfg(target_arch = "x86_64")]
1697             (
1698                 vec![
1699                     "cloud-hypervisor",
1700                     "--kernel",
1701                     "/path/to/kernel",
1702                     "--vsock",
1703                     "cid=123,socket=/path/to/sock/1,iommu=on",
1704                 ],
1705                 r#"{
1706                     "payload": {"kernel": "/path/to/kernel"},
1707                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}
1708                 }"#,
1709                 false,
1710             ),
1711             (
1712                 vec![
1713                     "cloud-hypervisor",
1714                     "--kernel",
1715                     "/path/to/kernel",
1716                     "--vsock",
1717                     "cid=123,socket=/path/to/sock/1,iommu=off",
1718                 ],
1719                 r#"{
1720                     "payload": {"kernel": "/path/to/kernel"},
1721                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false}
1722                 }"#,
1723                 true,
1724             ),
1725         ]
1726         .iter()
1727         .for_each(|(cli, openapi, equal)| {
1728             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1729         });
1730     }
1731 
1732     #[test]
1733     fn test_valid_vm_config_tpm_socket() {
1734         [(
1735             vec![
1736                 "cloud-hypervisor",
1737                 "--kernel",
1738                 "/path/to/kernel",
1739                 "--tpm",
1740                 "socket=/path/to/tpm/sock",
1741             ],
1742             r#"{
1743                     "payload": {"kernel": "/path/to/kernel"},
1744                     "tpm": {"socket": "/path/to/tpm/sock"}
1745                 }"#,
1746             true,
1747         )]
1748         .iter()
1749         .for_each(|(cli, openapi, equal)| {
1750             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1751         });
1752     }
1753 }
1754