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