xref: /cloud-hypervisor/src/main.rs (revision 07d1208dd53a207a65b649b8952780dfd0ca59d9)
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         return;
680     }
681 
682     let exit_code = match start_vmm(toplevel) {
683         Ok(path) => {
684             path.map(|s| std::fs::remove_file(s).ok());
685             0
686         }
687         Err(e) => {
688             eprintln!("{e}");
689             1
690         }
691     };
692 
693     #[cfg(feature = "dhat-heap")]
694     drop(_profiler);
695 
696     std::process::exit(exit_code);
697 }
698 
699 #[cfg(test)]
700 mod unit_tests {
701     use crate::config::HotplugMethod;
702     use crate::TopLevel;
703     use std::path::PathBuf;
704     use vmm::config::{
705         ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig,
706         RngConfig, VmConfig,
707     };
708 
709     // Taken from argh
710     fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
711         std::path::Path::new(path)
712             .file_name()
713             .and_then(|s| s.to_str())
714             .unwrap_or(default)
715     }
716 
717     // Some code taken from argh since it does not provide a helper to parse arbitrary strings
718     fn get_vm_config_from_vec(args: &[&str]) -> VmConfig {
719         let strings: Vec<String> = args.iter().map(|x| x.to_string()).collect();
720         let cmd = cmd(&strings[0], &strings[0]);
721         let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
722         let toplevel = <TopLevel as argh::FromArgs>::from_args(&[cmd], &strs[1..]).unwrap_or_else(
723             |early_exit| {
724                 std::process::exit(match early_exit.status {
725                     Ok(()) => {
726                         println!("{}", early_exit.output);
727                         0
728                     }
729                     Err(()) => {
730                         eprintln!(
731                             "{}\nRun {} --help for more information.",
732                             early_exit.output, cmd
733                         );
734                         1
735                     }
736                 })
737             },
738         );
739 
740         let vm_params = toplevel.to_vm_params();
741 
742         VmConfig::parse(vm_params).unwrap()
743     }
744 
745     fn compare_vm_config_cli_vs_json(
746         cli: &[&str],
747         openapi: &str,
748         equal: bool,
749     ) -> (VmConfig, VmConfig) {
750         let cli_vm_config = get_vm_config_from_vec(cli);
751         let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap();
752 
753         if equal {
754             assert_eq!(cli_vm_config, openapi_vm_config);
755         } else {
756             assert_ne!(cli_vm_config, openapi_vm_config);
757         }
758 
759         (cli_vm_config, openapi_vm_config)
760     }
761 
762     #[test]
763     fn test_valid_vm_config_default() {
764         let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"];
765         let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#;
766 
767         // First we check we get identical VmConfig structures.
768         let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true);
769 
770         // As a second step, we validate all the default values.
771         let expected_vm_config = VmConfig {
772             cpus: CpusConfig {
773                 boot_vcpus: 1,
774                 max_vcpus: 1,
775                 topology: None,
776                 kvm_hyperv: false,
777                 max_phys_bits: 46,
778                 affinity: None,
779                 features: CpuFeatures::default(),
780             },
781             memory: MemoryConfig {
782                 size: 536_870_912,
783                 mergeable: false,
784                 hotplug_method: HotplugMethod::Acpi,
785                 hotplug_size: None,
786                 hotplugged_size: None,
787                 shared: false,
788                 hugepages: false,
789                 hugepage_size: None,
790                 prefault: false,
791                 zones: None,
792                 thp: true,
793             },
794             payload: Some(PayloadConfig {
795                 kernel: Some(PathBuf::from("/path/to/kernel")),
796                 ..Default::default()
797             }),
798             disks: None,
799             net: None,
800             rng: RngConfig {
801                 src: PathBuf::from("/dev/urandom"),
802                 iommu: false,
803             },
804             balloon: None,
805             fs: None,
806             pmem: None,
807             serial: ConsoleConfig {
808                 file: None,
809                 mode: ConsoleOutputMode::Null,
810                 iommu: false,
811             },
812             console: ConsoleConfig {
813                 file: None,
814                 mode: ConsoleOutputMode::Tty,
815                 iommu: false,
816             },
817             devices: None,
818             user_devices: None,
819             vdpa: None,
820             vsock: None,
821             pvpanic: false,
822             iommu: false,
823             #[cfg(target_arch = "x86_64")]
824             sgx_epc: None,
825             numa: None,
826             watchdog: false,
827             #[cfg(feature = "guest_debug")]
828             gdb: false,
829             platform: None,
830             tpm: None,
831             preserved_fds: None,
832         };
833 
834         assert_eq!(expected_vm_config, result_vm_config);
835     }
836 
837     #[test]
838     fn test_valid_vm_config_cpus() {
839         [
840             (
841                 vec![
842                     "cloud-hypervisor",
843                     "--kernel",
844                     "/path/to/kernel",
845                     "--cpus",
846                     "boot=1",
847                 ],
848                 r#"{
849                     "payload": {"kernel": "/path/to/kernel"},
850                     "cpus": {"boot_vcpus": 1, "max_vcpus": 1}
851                 }"#,
852                 true,
853             ),
854             (
855                 vec![
856                     "cloud-hypervisor",
857                     "--kernel",
858                     "/path/to/kernel",
859                     "--cpus",
860                     "boot=1,max=3",
861                 ],
862                 r#"{
863                     "payload": {"kernel": "/path/to/kernel"},
864                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
865                 }"#,
866                 true,
867             ),
868             (
869                 vec![
870                     "cloud-hypervisor",
871                     "--kernel",
872                     "/path/to/kernel",
873                     "--cpus",
874                     "boot=2,max=4",
875                 ],
876                 r#"{
877                     "payload": {"kernel": "/path/to/kernel"},
878                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
879                 }"#,
880                 false,
881             ),
882         ]
883         .iter()
884         .for_each(|(cli, openapi, equal)| {
885             compare_vm_config_cli_vs_json(cli, openapi, *equal);
886         });
887     }
888 
889     #[test]
890     fn test_valid_vm_config_memory() {
891         vec![
892             (
893                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
894                 r#"{
895                     "payload": {"kernel": "/path/to/kernel"},
896                     "memory": {"size": 1073741824}
897                 }"#,
898                 true,
899             ),
900             (
901                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"],
902                 r#"{
903                     "payload": {"kernel": "/path/to/kernel"},
904                     "memory": {"size": 1073741824}
905                 }"#,
906                 true,
907             ),
908             (
909                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
910                 r#"{
911                     "payload": {"kernel": "/path/to/kernel"},
912                     "memory": {"size": 1073741824, "mergeable": true}
913                 }"#,
914                 true,
915             ),
916             (
917                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"],
918                 r#"{
919                     "payload": {"kernel": "/path/to/kernel"},
920                     "memory": {"size": 1073741824, "mergeable": false}
921                 }"#,
922                 true,
923             ),
924             (
925                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
926                 r#"{
927                     "payload": {"kernel": "/path/to/kernel"},
928                     "memory": {"size": 1073741824, "mergeable": false}
929                 }"#,
930                 false,
931             ),
932             (
933                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"],
934                 r#"{
935                     "payload": {"kernel": "/path/to/kernel"},
936                     "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824}
937                 }"#,
938                 true,
939             ),
940             (
941                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"],
942                 r#"{
943                     "payload": {"kernel": "/path/to/kernel"},
944                     "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
945                 }"#,
946                 true,
947             ),
948         ]
949         .iter()
950         .for_each(|(cli, openapi, equal)| {
951             compare_vm_config_cli_vs_json(cli, openapi, *equal);
952         });
953     }
954 
955     #[test]
956     fn test_valid_vm_config_kernel() {
957         [(
958             vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
959             r#"{
960                 "payload": {"kernel": "/path/to/kernel"}
961             }"#,
962             true,
963         )]
964         .iter()
965         .for_each(|(cli, openapi, equal)| {
966             compare_vm_config_cli_vs_json(cli, openapi, *equal);
967         });
968     }
969 
970     #[test]
971     fn test_valid_vm_config_cmdline() {
972         [(
973             vec![
974                 "cloud-hypervisor",
975                 "--kernel",
976                 "/path/to/kernel",
977                 "--cmdline",
978                 "arg1=foo arg2=bar",
979             ],
980             r#"{
981                 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"}
982             }"#,
983             true,
984         )]
985         .iter()
986         .for_each(|(cli, openapi, equal)| {
987             compare_vm_config_cli_vs_json(cli, openapi, *equal);
988         });
989     }
990 
991     #[test]
992     fn test_valid_vm_config_disks() {
993         [
994             (
995                 vec![
996                     "cloud-hypervisor",
997                     "--kernel",
998                     "/path/to/kernel",
999                     "--disk",
1000                     "path=/path/to/disk/1",
1001                     "--disk",
1002                     "path=/path/to/disk/2",
1003                 ],
1004                 r#"{
1005                     "payload": {"kernel": "/path/to/kernel"},
1006                     "disks": [
1007                         {"path": "/path/to/disk/1"},
1008                         {"path": "/path/to/disk/2"}
1009                     ]
1010                 }"#,
1011                 true,
1012             ),
1013             (
1014                 vec![
1015                     "cloud-hypervisor",
1016                     "--kernel",
1017                     "/path/to/kernel",
1018                     "--disk",
1019                     "path=/path/to/disk/1",
1020                     "--disk",
1021                     "path=/path/to/disk/2",
1022                 ],
1023                 r#"{
1024                     "payload": {"kernel": "/path/to/kernel"},
1025                     "disks": [
1026                         {"path": "/path/to/disk/1"}
1027                     ]
1028                 }"#,
1029                 false,
1030             ),
1031             (
1032                 vec![
1033                     "cloud-hypervisor",
1034                     "--kernel",
1035                     "/path/to/kernel",
1036                     "--memory",
1037                     "shared=true",
1038                     "--disk",
1039                     "vhost_user=true,socket=/tmp/sock1",
1040                 ],
1041                 r#"{
1042                     "payload": {"kernel": "/path/to/kernel"},
1043                     "memory" : { "shared": true, "size": 536870912 },
1044                     "disks": [
1045                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1046                     ]
1047                 }"#,
1048                 true,
1049             ),
1050             (
1051                 vec![
1052                     "cloud-hypervisor",
1053                     "--kernel",
1054                     "/path/to/kernel",
1055                     "--memory",
1056                     "shared=true",
1057                     "--disk",
1058                     "vhost_user=true,socket=/tmp/sock1",
1059                 ],
1060                 r#"{
1061                     "payload": {"kernel": "/path/to/kernel"},
1062                     "memory" : { "shared": true, "size": 536870912 },
1063                     "disks": [
1064                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1065                     ]
1066                 }"#,
1067                 true,
1068             ),
1069         ]
1070         .iter()
1071         .for_each(|(cli, openapi, equal)| {
1072             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1073         });
1074     }
1075 
1076     #[test]
1077     fn test_valid_vm_config_net() {
1078         vec![
1079             // This test is expected to fail because the default MAC address is
1080             // randomly generated. There's no way we can have twice the same
1081             // default value.
1082             (
1083                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="],
1084                 r#"{
1085                     "payload": {"kernel": "/path/to/kernel"},
1086                     "net": []
1087                 }"#,
1088                 false,
1089             ),
1090             (
1091                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"],
1092                 r#"{
1093                     "payload": {"kernel": "/path/to/kernel"},
1094                     "net": [
1095                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"}
1096                     ]
1097                 }"#,
1098                 true,
1099             ),
1100             (
1101                 vec![
1102                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1103                     "--net",
1104                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0",
1105                 ],
1106                 r#"{
1107                     "payload": {"kernel": "/path/to/kernel"},
1108                     "net": [
1109                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"}
1110                     ]
1111                 }"#,
1112                 true,
1113             ),
1114             (
1115                 vec![
1116                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1117                     "--net",
1118                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
1119                 ],
1120                 r#"{
1121                     "payload": {"kernel": "/path/to/kernel"},
1122                     "net": [
1123                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
1124                     ]
1125                 }"#,
1126                 true,
1127             ),
1128             (
1129                 vec![
1130                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1131                     "--net",
1132                     "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",
1133                 ],
1134                 r#"{
1135                     "payload": {"kernel": "/path/to/kernel"},
1136                     "net": [
1137                         {"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"}
1138                     ]
1139                 }"#,
1140                 true,
1141             ),
1142             (
1143                 vec![
1144                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1145                     "--cpus", "boot=2",
1146                     "--net",
1147                     "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",
1148                 ],
1149                 r#"{
1150                     "payload": {"kernel": "/path/to/kernel"},
1151                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1152                     "net": [
1153                         {"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}
1154                     ]
1155                 }"#,
1156                 true,
1157             ),
1158             (
1159                 vec![
1160                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1161                     "--cpus", "boot=2",
1162                     "--net",
1163                     "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",
1164                 ],
1165                 r#"{
1166                     "payload": {"kernel": "/path/to/kernel"},
1167                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1168                     "net": [
1169                         {"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}
1170                     ]
1171                 }"#,
1172                 true,
1173             ),
1174             (
1175                 vec![
1176                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1177                     "--net",
1178                     "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",
1179                 ],
1180                 r#"{
1181                     "payload": {"kernel": "/path/to/kernel"},
1182                     "net": [
1183                         {"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"}
1184                     ]
1185                 }"#,
1186                 true,
1187             ),
1188             (
1189                 vec![
1190                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1191                     "--net",
1192                     "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",
1193                 ],
1194                 r#"{
1195                     "payload": {"kernel": "/path/to/kernel"},
1196                     "net": [
1197                         {"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}
1198                     ]
1199                 }"#,
1200                 true,
1201             ),
1202             #[cfg(target_arch = "x86_64")]
1203             (
1204                 vec![
1205                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1206                     "--net",
1207                     "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",
1208                 ],
1209                 r#"{
1210                     "payload": {"kernel": "/path/to/kernel"},
1211                     "net": [
1212                         {"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}
1213                     ]
1214                 }"#,
1215                 false,
1216             ),
1217             #[cfg(target_arch = "x86_64")]
1218             (
1219                 vec![
1220                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1221                     "--net",
1222                     "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",
1223                 ],
1224                 r#"{
1225                     "payload": {"kernel": "/path/to/kernel"},
1226                     "net": [
1227                         {"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}
1228                     ],
1229                     "iommu": true
1230                 }"#,
1231                 true,
1232             ),
1233             (
1234                 vec![
1235                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1236                     "--net",
1237                     "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",
1238                 ],
1239                 r#"{
1240                     "payload": {"kernel": "/path/to/kernel"},
1241                     "net": [
1242                         {"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}
1243                     ]
1244                 }"#,
1245                 true,
1246             ),
1247             (
1248                 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"],
1249                 r#"{
1250                     "payload": {"kernel": "/path/to/kernel"},
1251                     "memory" : { "shared": true, "size": 536870912 },
1252                     "net": [
1253                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"}
1254                     ]
1255                 }"#,
1256                 true,
1257             ),
1258         ]
1259         .iter()
1260         .for_each(|(cli, openapi, equal)| {
1261             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1262         });
1263     }
1264 
1265     #[test]
1266     fn test_valid_vm_config_rng() {
1267         [(
1268             vec![
1269                 "cloud-hypervisor",
1270                 "--kernel",
1271                 "/path/to/kernel",
1272                 "--rng",
1273                 "src=/path/to/entropy/source",
1274             ],
1275             r#"{
1276                 "payload": {"kernel": "/path/to/kernel"},
1277                 "rng": {"src": "/path/to/entropy/source"}
1278             }"#,
1279             true,
1280         )]
1281         .iter()
1282         .for_each(|(cli, openapi, equal)| {
1283             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1284         });
1285     }
1286 
1287     #[test]
1288     fn test_valid_vm_config_fs() {
1289         [(
1290                 vec![
1291                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1292                     "--memory", "shared=true",
1293                     "--fs",
1294                     "tag=virtiofs1,socket=/path/to/sock1",
1295                     "--fs",
1296                     "tag=virtiofs2,socket=/path/to/sock2",
1297                 ],
1298                 r#"{
1299                     "payload": {"kernel": "/path/to/kernel"},
1300                     "memory" : { "shared": true, "size": 536870912 },
1301                     "fs": [
1302                         {"tag": "virtiofs1", "socket": "/path/to/sock1"},
1303                         {"tag": "virtiofs2", "socket": "/path/to/sock2"}
1304                     ]
1305                 }"#,
1306                 true,
1307             ),
1308             (
1309                 vec![
1310                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1311                     "--memory", "shared=true",
1312                     "--fs",
1313                     "tag=virtiofs1,socket=/path/to/sock1",
1314                     "--fs",
1315                     "tag=virtiofs2,socket=/path/to/sock2",
1316                 ],
1317                 r#"{
1318                     "payload": {"kernel": "/path/to/kernel"},
1319                     "memory" : { "shared": true, "size": 536870912 },
1320                     "fs": [
1321                         {"tag": "virtiofs1", "socket": "/path/to/sock1"}
1322                     ]
1323                 }"#,
1324                 false,
1325             ),
1326             (
1327                 vec![
1328                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1329                     "--memory", "shared=true", "--cpus", "boot=4",
1330                     "--fs",
1331                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4",
1332                 ],
1333                 r#"{
1334                     "payload": {"kernel": "/path/to/kernel"},
1335                     "memory" : { "shared": true, "size": 536870912 },
1336                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1337                     "fs": [
1338                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4}
1339                     ]
1340                 }"#,
1341                 true,
1342             ),
1343             (
1344                 vec![
1345                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1346                     "--memory", "shared=true", "--cpus", "boot=4",
1347                     "--fs",
1348                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128"
1349                 ],
1350                 r#"{
1351                     "payload": {"kernel": "/path/to/kernel"},
1352                     "memory" : { "shared": true, "size": 536870912 },
1353                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1354                     "fs": [
1355                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128}
1356                     ]
1357                 }"#,
1358                 true,
1359             )]
1360         .iter()
1361         .for_each(|(cli, openapi, equal)| {
1362             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1363         });
1364     }
1365 
1366     #[test]
1367     fn test_valid_vm_config_pmem() {
1368         [
1369             (
1370                 vec![
1371                     "cloud-hypervisor",
1372                     "--kernel",
1373                     "/path/to/kernel",
1374                     "--pmem",
1375                     "file=/path/to/img/1,size=1G",
1376                     "--pmem",
1377                     "file=/path/to/img/2,size=2G",
1378                 ],
1379                 r#"{
1380                     "payload": {"kernel": "/path/to/kernel"},
1381                     "pmem": [
1382                         {"file": "/path/to/img/1", "size": 1073741824},
1383                         {"file": "/path/to/img/2", "size": 2147483648}
1384                     ]
1385                 }"#,
1386                 true,
1387             ),
1388             #[cfg(target_arch = "x86_64")]
1389             (
1390                 vec![
1391                     "cloud-hypervisor",
1392                     "--kernel",
1393                     "/path/to/kernel",
1394                     "--pmem",
1395                     "file=/path/to/img/1,size=1G,iommu=on",
1396                 ],
1397                 r#"{
1398                     "payload": {"kernel": "/path/to/kernel"},
1399                     "pmem": [
1400                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1401                     ],
1402                     "iommu": true
1403                 }"#,
1404                 true,
1405             ),
1406             #[cfg(target_arch = "x86_64")]
1407             (
1408                 vec![
1409                     "cloud-hypervisor",
1410                     "--kernel",
1411                     "/path/to/kernel",
1412                     "--pmem",
1413                     "file=/path/to/img/1,size=1G,iommu=on",
1414                 ],
1415                 r#"{
1416                     "payload": {"kernel": "/path/to/kernel"},
1417                     "pmem": [
1418                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1419                     ]
1420                 }"#,
1421                 false,
1422             ),
1423         ]
1424         .iter()
1425         .for_each(|(cli, openapi, equal)| {
1426             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1427         });
1428     }
1429 
1430     #[test]
1431     fn test_valid_vm_config_serial_console() {
1432         [
1433             (
1434                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1435                 r#"{
1436                     "payload": {"kernel": "/path/to/kernel"},
1437                     "serial": {"mode": "Null"},
1438                     "console": {"mode": "Tty"}
1439                 }"#,
1440                 true,
1441             ),
1442             (
1443                 vec![
1444                     "cloud-hypervisor",
1445                     "--kernel",
1446                     "/path/to/kernel",
1447                     "--serial",
1448                     "null",
1449                     "--console",
1450                     "tty",
1451                 ],
1452                 r#"{
1453                     "payload": {"kernel": "/path/to/kernel"}
1454                 }"#,
1455                 true,
1456             ),
1457             (
1458                 vec![
1459                     "cloud-hypervisor",
1460                     "--kernel",
1461                     "/path/to/kernel",
1462                     "--serial",
1463                     "tty",
1464                     "--console",
1465                     "off",
1466                 ],
1467                 r#"{
1468                     "payload": {"kernel": "/path/to/kernel"},
1469                     "serial": {"mode": "Tty"},
1470                     "console": {"mode": "Off"}
1471                 }"#,
1472                 true,
1473             ),
1474         ]
1475         .iter()
1476         .for_each(|(cli, openapi, equal)| {
1477             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1478         });
1479     }
1480 
1481     #[test]
1482     fn test_valid_vm_config_serial_pty_console_pty() {
1483         [
1484             (
1485                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1486                 r#"{
1487                     "payload": {"kernel": "/path/to/kernel"},
1488                     "serial": {"mode": "Null"},
1489                     "console": {"mode": "Tty"}
1490                 }"#,
1491                 true,
1492             ),
1493             (
1494                 vec![
1495                     "cloud-hypervisor",
1496                     "--kernel",
1497                     "/path/to/kernel",
1498                     "--serial",
1499                     "null",
1500                     "--console",
1501                     "tty",
1502                 ],
1503                 r#"{
1504                     "payload": {"kernel": "/path/to/kernel"}
1505                 }"#,
1506                 true,
1507             ),
1508             (
1509                 vec![
1510                     "cloud-hypervisor",
1511                     "--kernel",
1512                     "/path/to/kernel",
1513                     "--serial",
1514                     "pty",
1515                     "--console",
1516                     "pty",
1517                 ],
1518                 r#"{
1519                     "payload": {"kernel": "/path/to/kernel"},
1520                     "serial": {"mode": "Pty"},
1521                     "console": {"mode": "Pty"}
1522                 }"#,
1523                 true,
1524             ),
1525         ]
1526         .iter()
1527         .for_each(|(cli, openapi, equal)| {
1528             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1529         });
1530     }
1531 
1532     #[test]
1533     #[cfg(target_arch = "x86_64")]
1534     fn test_valid_vm_config_devices() {
1535         vec![
1536             (
1537                 vec![
1538                     "cloud-hypervisor",
1539                     "--kernel",
1540                     "/path/to/kernel",
1541                     "--device",
1542                     "path=/path/to/device/1",
1543                     "--device",
1544                     "path=/path/to/device/2",
1545                 ],
1546                 r#"{
1547                     "payload": {"kernel": "/path/to/kernel"},
1548                     "devices": [
1549                         {"path": "/path/to/device/1"},
1550                         {"path": "/path/to/device/2"}
1551                     ]
1552                 }"#,
1553                 true,
1554             ),
1555             (
1556                 vec![
1557                     "cloud-hypervisor",
1558                     "--kernel",
1559                     "/path/to/kernel",
1560                     "--device",
1561                     "path=/path/to/device/1",
1562                     "--device",
1563                     "path=/path/to/device/2",
1564                 ],
1565                 r#"{
1566                     "payload": {"kernel": "/path/to/kernel"},
1567                     "devices": [
1568                         {"path": "/path/to/device/1"}
1569                     ]
1570                 }"#,
1571                 false,
1572             ),
1573             (
1574                 vec![
1575                     "cloud-hypervisor",
1576                     "--kernel",
1577                     "/path/to/kernel",
1578                     "--device",
1579                     "path=/path/to/device,iommu=on",
1580                 ],
1581                 r#"{
1582                     "payload": {"kernel": "/path/to/kernel"},
1583                     "devices": [
1584                         {"path": "/path/to/device", "iommu": true}
1585                     ],
1586                     "iommu": true
1587                 }"#,
1588                 true,
1589             ),
1590             (
1591                 vec![
1592                     "cloud-hypervisor",
1593                     "--kernel",
1594                     "/path/to/kernel",
1595                     "--device",
1596                     "path=/path/to/device,iommu=on",
1597                 ],
1598                 r#"{
1599                     "payload": {"kernel": "/path/to/kernel"},
1600                     "devices": [
1601                         {"path": "/path/to/device", "iommu": true}
1602                     ]
1603                 }"#,
1604                 false,
1605             ),
1606             (
1607                 vec![
1608                     "cloud-hypervisor",
1609                     "--kernel",
1610                     "/path/to/kernel",
1611                     "--device",
1612                     "path=/path/to/device,iommu=off",
1613                 ],
1614                 r#"{
1615                     "payload": {"kernel": "/path/to/kernel"},
1616                     "devices": [
1617                         {"path": "/path/to/device", "iommu": false}
1618                     ]
1619                 }"#,
1620                 true,
1621             ),
1622         ]
1623         .iter()
1624         .for_each(|(cli, openapi, equal)| {
1625             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1626         });
1627     }
1628 
1629     #[test]
1630     fn test_valid_vm_config_vdpa() {
1631         [
1632             (
1633                 vec![
1634                     "cloud-hypervisor",
1635                     "--kernel",
1636                     "/path/to/kernel",
1637                     "--vdpa",
1638                     "path=/path/to/device/1",
1639                     "--vdpa",
1640                     "path=/path/to/device/2,num_queues=2",
1641                 ],
1642                 r#"{
1643                     "payload": {"kernel": "/path/to/kernel"},
1644                     "vdpa": [
1645                         {"path": "/path/to/device/1", "num_queues": 1},
1646                         {"path": "/path/to/device/2", "num_queues": 2}
1647                     ]
1648                 }"#,
1649                 true,
1650             ),
1651             (
1652                 vec![
1653                     "cloud-hypervisor",
1654                     "--kernel",
1655                     "/path/to/kernel",
1656                     "--vdpa",
1657                     "path=/path/to/device/1",
1658                     "--vdpa",
1659                     "path=/path/to/device/2",
1660                 ],
1661                 r#"{
1662                     "payload": {"kernel": "/path/to/kernel"},
1663                     "vdpa": [
1664                         {"path": "/path/to/device/1"}
1665                     ]
1666                 }"#,
1667                 false,
1668             ),
1669         ]
1670         .iter()
1671         .for_each(|(cli, openapi, equal)| {
1672             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1673         });
1674     }
1675 
1676     #[test]
1677     fn test_valid_vm_config_vsock() {
1678         [
1679             (
1680                 vec![
1681                     "cloud-hypervisor",
1682                     "--kernel",
1683                     "/path/to/kernel",
1684                     "--vsock",
1685                     "cid=123,socket=/path/to/sock/1",
1686                 ],
1687                 r#"{
1688                     "payload": {"kernel": "/path/to/kernel"},
1689                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1690                 }"#,
1691                 true,
1692             ),
1693             (
1694                 vec![
1695                     "cloud-hypervisor",
1696                     "--kernel",
1697                     "/path/to/kernel",
1698                     "--vsock",
1699                     "cid=124,socket=/path/to/sock/1",
1700                 ],
1701                 r#"{
1702                     "payload": {"kernel": "/path/to/kernel"},
1703                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1704                 }"#,
1705                 false,
1706             ),
1707             #[cfg(target_arch = "x86_64")]
1708             (
1709                 vec![
1710                     "cloud-hypervisor",
1711                     "--kernel",
1712                     "/path/to/kernel",
1713                     "--vsock",
1714                     "cid=123,socket=/path/to/sock/1,iommu=on",
1715                 ],
1716                 r#"{
1717                     "payload": {"kernel": "/path/to/kernel"},
1718                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true},
1719                     "iommu": true
1720                 }"#,
1721                 true,
1722             ),
1723             #[cfg(target_arch = "x86_64")]
1724             (
1725                 vec![
1726                     "cloud-hypervisor",
1727                     "--kernel",
1728                     "/path/to/kernel",
1729                     "--vsock",
1730                     "cid=123,socket=/path/to/sock/1,iommu=on",
1731                 ],
1732                 r#"{
1733                     "payload": {"kernel": "/path/to/kernel"},
1734                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}
1735                 }"#,
1736                 false,
1737             ),
1738             (
1739                 vec![
1740                     "cloud-hypervisor",
1741                     "--kernel",
1742                     "/path/to/kernel",
1743                     "--vsock",
1744                     "cid=123,socket=/path/to/sock/1,iommu=off",
1745                 ],
1746                 r#"{
1747                     "payload": {"kernel": "/path/to/kernel"},
1748                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false}
1749                 }"#,
1750                 true,
1751             ),
1752         ]
1753         .iter()
1754         .for_each(|(cli, openapi, equal)| {
1755             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1756         });
1757     }
1758 
1759     #[test]
1760     fn test_valid_vm_config_tpm_socket() {
1761         [(
1762             vec![
1763                 "cloud-hypervisor",
1764                 "--kernel",
1765                 "/path/to/kernel",
1766                 "--tpm",
1767                 "socket=/path/to/tpm/sock",
1768             ],
1769             r#"{
1770                     "payload": {"kernel": "/path/to/kernel"},
1771                     "tpm": {"socket": "/path/to/tpm/sock"}
1772                 }"#,
1773             true,
1774         )]
1775         .iter()
1776         .for_each(|(cli, openapi, equal)| {
1777             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1778         });
1779     }
1780 }
1781