xref: /cloud-hypervisor/src/main.rs (revision 5641e3a283db4149052b1e9278c640bcef8a000e)
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     // Before we start any threads, mask the signals we'll be
565     // installing handlers for, to make sure they only ever run on the
566     // dedicated signal handling thread we'll start in a bit.
567     for sig in &vmm::vm::Vm::HANDLED_SIGNALS {
568         if let Err(e) = block_signal(*sig) {
569             eprintln!("Error blocking signals: {e}");
570         }
571     }
572 
573     for sig in &vmm::Vmm::HANDLED_SIGNALS {
574         if let Err(e) = block_signal(*sig) {
575             eprintln!("Error blocking signals: {e}");
576         }
577     }
578 
579     let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?;
580 
581     #[cfg(feature = "guest_debug")]
582     let gdb_socket_path = if let Some(gdb_config) = cmd_arguments.get_one::<String>("gdb") {
583         let mut parser = OptionParser::new();
584         parser.add("path");
585         parser.parse(gdb_config).map_err(Error::ParsingGdb)?;
586 
587         if parser.is_set("path") {
588             Some(std::path::PathBuf::from(parser.get("path").unwrap()))
589         } else {
590             return Err(Error::BareGdb);
591         }
592     } else {
593         None
594     };
595     #[cfg(feature = "guest_debug")]
596     let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
597     #[cfg(feature = "guest_debug")]
598     let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
599 
600     let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
601 
602     #[allow(unused_mut)]
603     let mut event_monitor = cmd_arguments
604         .get_one::<String>("event-monitor")
605         .as_ref()
606         .map(|monitor_config| {
607             let mut parser = OptionParser::new();
608             parser.add("path").add("fd");
609             parser
610                 .parse(monitor_config)
611                 .map_err(Error::ParsingEventMonitor)?;
612 
613             if parser.is_set("fd") {
614                 let fd = parser
615                     .convert("fd")
616                     .map_err(Error::ParsingEventMonitor)?
617                     .unwrap();
618                 // SAFETY: fd is valid
619                 Ok(Some(unsafe { File::from_raw_fd(fd) }))
620             } else if parser.is_set("path") {
621                 Ok(Some(
622                     std::fs::OpenOptions::new()
623                         .write(true)
624                         .create(true)
625                         .truncate(true)
626                         .open(parser.get("path").unwrap())
627                         .map_err(Error::EventMonitorIo)?,
628                 ))
629             } else {
630                 Err(Error::BareEventMonitor)
631             }
632         })
633         .transpose()?
634         .map(|event_monitor_file| {
635             event_monitor::set_monitor(event_monitor_file).map_err(Error::EventMonitorIo)
636         })
637         .transpose()?;
638 
639     #[cfg(feature = "dbus_api")]
640     let dbus_options = match (
641         cmd_arguments.get_one::<String>("dbus-service-name"),
642         cmd_arguments.get_one::<String>("dbus-object-path"),
643     ) {
644         (Some(name), Some(path)) => {
645             // monitor is either set (file based) or not.
646             // if it's not set, create one without file support.
647             let mut monitor = match event_monitor.take() {
648                 Some(monitor) => monitor,
649                 None => event_monitor::set_monitor(None).map_err(Error::EventMonitorIo)?,
650             };
651             let options = DBusApiOptions {
652                 service_name: name.to_string(),
653                 object_path: path.to_string(),
654                 system_bus: cmd_arguments.get_flag("dbus-system-bus"),
655                 event_monitor_rx: monitor.subscribe(),
656             };
657 
658             event_monitor = Some(monitor);
659             Ok(Some(options))
660         }
661         (Some(_), None) => Err(Error::MissingDBusObjectPath),
662         (None, Some(_)) => Err(Error::MissingDBusServiceName),
663         (None, None) => Ok(None),
664     }?;
665 
666     if let Some(monitor) = event_monitor {
667         vmm::start_event_monitor_thread(
668             monitor,
669             &seccomp_action,
670             hypervisor.hypervisor_type(),
671             exit_evt.try_clone().unwrap(),
672         )
673         .map_err(Error::EventMonitorThread)?;
674     }
675 
676     event!("vmm", "starting");
677 
678     let vmm_thread_handle = vmm::start_vmm_thread(
679         vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")),
680         &api_socket_path,
681         api_socket_fd,
682         #[cfg(feature = "dbus_api")]
683         dbus_options,
684         api_evt.try_clone().unwrap(),
685         api_request_sender_clone,
686         api_request_receiver,
687         #[cfg(feature = "guest_debug")]
688         gdb_socket_path,
689         #[cfg(feature = "guest_debug")]
690         debug_evt.try_clone().unwrap(),
691         #[cfg(feature = "guest_debug")]
692         vm_debug_evt.try_clone().unwrap(),
693         exit_evt.try_clone().unwrap(),
694         &seccomp_action,
695         hypervisor,
696     )
697     .map_err(Error::StartVmmThread)?;
698 
699     let r: Result<(), Error> = (|| {
700         let payload_present =
701             cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware");
702 
703         if payload_present {
704             let vm_params = config::VmParams::from_arg_matches(&cmd_arguments);
705             let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
706 
707             // Create and boot the VM based off the VM config we just built.
708             let sender = api_request_sender.clone();
709             vmm::api::VmCreate
710                 .send(
711                     api_evt.try_clone().unwrap(),
712                     api_request_sender,
713                     Arc::new(Mutex::new(vm_config)),
714                 )
715                 .map_err(Error::VmCreate)?;
716             vmm::api::VmBoot
717                 .send(api_evt.try_clone().unwrap(), sender, ())
718                 .map_err(Error::VmBoot)?;
719         } else if let Some(restore_params) = cmd_arguments.get_one::<String>("restore") {
720             vmm::api::VmRestore
721                 .send(
722                     api_evt.try_clone().unwrap(),
723                     api_request_sender,
724                     config::RestoreConfig::parse(restore_params).map_err(Error::ParsingRestore)?,
725                 )
726                 .map_err(Error::VmRestore)?;
727         }
728 
729         Ok(())
730     })();
731 
732     if r.is_err() {
733         if let Err(e) = exit_evt.write(1) {
734             warn!("writing to exit EventFd: {e}");
735         }
736     }
737 
738     vmm_thread_handle
739         .thread_handle
740         .join()
741         .map_err(Error::ThreadJoin)?
742         .map_err(Error::VmmThread)?;
743 
744     #[cfg(feature = "dbus_api")]
745     if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs {
746         dbus_api_graceful_shutdown(chs);
747     }
748 
749     r.map(|_| api_socket_path)
750 }
751 
752 fn main() {
753     #[cfg(all(feature = "tdx", feature = "sev_snp"))]
754     compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive.");
755 
756     #[cfg(all(feature = "sev_snp", not(target_arch = "x86_64")))]
757     compile_error!("Feature 'sev_snp' needs target 'x86_64'");
758 
759     #[cfg(feature = "dhat-heap")]
760     let _profiler = dhat::Profiler::new_heap();
761 
762     // Ensure all created files (.e.g sockets) are only accessible by this user
763     // SAFETY: trivially safe
764     let _ = unsafe { libc::umask(0o077) };
765 
766     let (default_vcpus, default_memory, default_rng) = prepare_default_values();
767     let cmd_arguments = create_app(default_vcpus, default_memory, default_rng).get_matches();
768 
769     if cmd_arguments.get_flag("version") {
770         println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION"));
771 
772         if cmd_arguments.get_count("v") != 0 {
773             println!("Enabled features: {:?}", vmm::feature_list());
774         }
775 
776         return;
777     }
778 
779     let exit_code = match start_vmm(cmd_arguments) {
780         Ok(path) => {
781             path.map(|s| std::fs::remove_file(s).ok());
782             0
783         }
784         Err(e) => {
785             eprintln!("{e}");
786             1
787         }
788     };
789 
790     #[cfg(feature = "dhat-heap")]
791     drop(_profiler);
792 
793     std::process::exit(exit_code);
794 }
795 
796 #[cfg(test)]
797 mod unit_tests {
798     use crate::config::HotplugMethod;
799     use crate::{create_app, prepare_default_values};
800     use std::path::PathBuf;
801     use vmm::config::{
802         ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig,
803         RngConfig, VmConfig, VmParams,
804     };
805     #[cfg(target_arch = "x86_64")]
806     use vmm::vm_config::DebugConsoleConfig;
807 
808     fn get_vm_config_from_vec(args: &[&str]) -> VmConfig {
809         let (default_vcpus, default_memory, default_rng) = prepare_default_values();
810         let cmd_arguments =
811             create_app(default_vcpus, default_memory, default_rng).get_matches_from(args);
812         let vm_params = VmParams::from_arg_matches(&cmd_arguments);
813 
814         VmConfig::parse(vm_params).unwrap()
815     }
816 
817     fn compare_vm_config_cli_vs_json(
818         cli: &[&str],
819         openapi: &str,
820         equal: bool,
821     ) -> (VmConfig, VmConfig) {
822         let cli_vm_config = get_vm_config_from_vec(cli);
823         let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap();
824 
825         if equal {
826             assert_eq!(cli_vm_config, openapi_vm_config);
827         } else {
828             assert_ne!(cli_vm_config, openapi_vm_config);
829         }
830 
831         (cli_vm_config, openapi_vm_config)
832     }
833 
834     #[test]
835     fn test_valid_vm_config_default() {
836         let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"];
837         let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#;
838 
839         // First we check we get identical VmConfig structures.
840         let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true);
841 
842         // As a second step, we validate all the default values.
843         let expected_vm_config = VmConfig {
844             cpus: CpusConfig {
845                 boot_vcpus: 1,
846                 max_vcpus: 1,
847                 topology: None,
848                 kvm_hyperv: false,
849                 max_phys_bits: 46,
850                 affinity: None,
851                 features: CpuFeatures::default(),
852             },
853             memory: MemoryConfig {
854                 size: 536_870_912,
855                 mergeable: false,
856                 hotplug_method: HotplugMethod::Acpi,
857                 hotplug_size: None,
858                 hotplugged_size: None,
859                 shared: false,
860                 hugepages: false,
861                 hugepage_size: None,
862                 prefault: false,
863                 zones: None,
864                 thp: true,
865             },
866             payload: Some(PayloadConfig {
867                 kernel: Some(PathBuf::from("/path/to/kernel")),
868                 firmware: None,
869                 cmdline: None,
870                 initramfs: None,
871                 #[cfg(feature = "igvm")]
872                 igvm: None,
873             }),
874             rate_limit_groups: None,
875             disks: None,
876             net: None,
877             rng: RngConfig {
878                 src: PathBuf::from("/dev/urandom"),
879                 iommu: false,
880             },
881             balloon: None,
882             fs: None,
883             pmem: None,
884             serial: ConsoleConfig {
885                 file: None,
886                 mode: ConsoleOutputMode::Null,
887                 iommu: false,
888                 socket: None,
889             },
890             console: ConsoleConfig {
891                 file: None,
892                 mode: ConsoleOutputMode::Tty,
893                 iommu: false,
894                 socket: None,
895             },
896             #[cfg(target_arch = "x86_64")]
897             debug_console: DebugConsoleConfig::default(),
898             devices: None,
899             user_devices: None,
900             vdpa: None,
901             vsock: None,
902             pvpanic: false,
903             iommu: false,
904             #[cfg(target_arch = "x86_64")]
905             sgx_epc: None,
906             numa: None,
907             watchdog: false,
908             #[cfg(feature = "guest_debug")]
909             gdb: false,
910             platform: None,
911             tpm: None,
912             preserved_fds: None,
913         };
914 
915         assert_eq!(expected_vm_config, result_vm_config);
916     }
917 
918     #[test]
919     fn test_valid_vm_config_cpus() {
920         [
921             (
922                 vec![
923                     "cloud-hypervisor",
924                     "--kernel",
925                     "/path/to/kernel",
926                     "--cpus",
927                     "boot=1",
928                 ],
929                 r#"{
930                     "payload": {"kernel": "/path/to/kernel"},
931                     "cpus": {"boot_vcpus": 1, "max_vcpus": 1}
932                 }"#,
933                 true,
934             ),
935             (
936                 vec![
937                     "cloud-hypervisor",
938                     "--kernel",
939                     "/path/to/kernel",
940                     "--cpus",
941                     "boot=1,max=3",
942                 ],
943                 r#"{
944                     "payload": {"kernel": "/path/to/kernel"},
945                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
946                 }"#,
947                 true,
948             ),
949             (
950                 vec![
951                     "cloud-hypervisor",
952                     "--kernel",
953                     "/path/to/kernel",
954                     "--cpus",
955                     "boot=2,max=4",
956                 ],
957                 r#"{
958                     "payload": {"kernel": "/path/to/kernel"},
959                     "cpus": {"boot_vcpus": 1, "max_vcpus": 3}
960                 }"#,
961                 false,
962             ),
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_memory() {
972         vec![
973             (
974                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
975                 r#"{
976                     "payload": {"kernel": "/path/to/kernel"},
977                     "memory": {"size": 1073741824}
978                 }"#,
979                 true,
980             ),
981             (
982                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"],
983                 r#"{
984                     "payload": {"kernel": "/path/to/kernel"},
985                     "memory": {"size": 1073741824}
986                 }"#,
987                 true,
988             ),
989             (
990                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
991                 r#"{
992                     "payload": {"kernel": "/path/to/kernel"},
993                     "memory": {"size": 1073741824, "mergeable": true}
994                 }"#,
995                 true,
996             ),
997             (
998                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"],
999                 r#"{
1000                     "payload": {"kernel": "/path/to/kernel"},
1001                     "memory": {"size": 1073741824, "mergeable": false}
1002                 }"#,
1003                 true,
1004             ),
1005             (
1006                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
1007                 r#"{
1008                     "payload": {"kernel": "/path/to/kernel"},
1009                     "memory": {"size": 1073741824, "mergeable": false}
1010                 }"#,
1011                 false,
1012             ),
1013             (
1014                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"],
1015                 r#"{
1016                     "payload": {"kernel": "/path/to/kernel"},
1017                     "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824}
1018                 }"#,
1019                 true,
1020             ),
1021             (
1022                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"],
1023                 r#"{
1024                     "payload": {"kernel": "/path/to/kernel"},
1025                     "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
1026                 }"#,
1027                 true,
1028             ),
1029         ]
1030         .iter()
1031         .for_each(|(cli, openapi, equal)| {
1032             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1033         });
1034     }
1035 
1036     #[test]
1037     fn test_valid_vm_config_kernel() {
1038         [(
1039             vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1040             r#"{
1041                 "payload": {"kernel": "/path/to/kernel"}
1042             }"#,
1043             true,
1044         )]
1045         .iter()
1046         .for_each(|(cli, openapi, equal)| {
1047             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1048         });
1049     }
1050 
1051     #[test]
1052     fn test_valid_vm_config_cmdline() {
1053         [(
1054             vec![
1055                 "cloud-hypervisor",
1056                 "--kernel",
1057                 "/path/to/kernel",
1058                 "--cmdline",
1059                 "arg1=foo arg2=bar",
1060             ],
1061             r#"{
1062                 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"}
1063             }"#,
1064             true,
1065         )]
1066         .iter()
1067         .for_each(|(cli, openapi, equal)| {
1068             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1069         });
1070     }
1071 
1072     #[test]
1073     fn test_valid_vm_config_disks() {
1074         [
1075             (
1076                 vec![
1077                     "cloud-hypervisor",
1078                     "--kernel",
1079                     "/path/to/kernel",
1080                     "--disk",
1081                     "path=/path/to/disk/1",
1082                     "path=/path/to/disk/2",
1083                 ],
1084                 r#"{
1085                     "payload": {"kernel": "/path/to/kernel"},
1086                     "disks": [
1087                         {"path": "/path/to/disk/1"},
1088                         {"path": "/path/to/disk/2"}
1089                     ]
1090                 }"#,
1091                 true,
1092             ),
1093             (
1094                 vec![
1095                     "cloud-hypervisor",
1096                     "--kernel",
1097                     "/path/to/kernel",
1098                     "--disk",
1099                     "path=/path/to/disk/1",
1100                     "path=/path/to/disk/2",
1101                 ],
1102                 r#"{
1103                     "payload": {"kernel": "/path/to/kernel"},
1104                     "disks": [
1105                         {"path": "/path/to/disk/1"}
1106                     ]
1107                 }"#,
1108                 false,
1109             ),
1110             (
1111                 vec![
1112                     "cloud-hypervisor",
1113                     "--kernel",
1114                     "/path/to/kernel",
1115                     "--memory",
1116                     "shared=true",
1117                     "--disk",
1118                     "vhost_user=true,socket=/tmp/sock1",
1119                 ],
1120                 r#"{
1121                     "payload": {"kernel": "/path/to/kernel"},
1122                     "memory" : { "shared": true, "size": 536870912 },
1123                     "disks": [
1124                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1125                     ]
1126                 }"#,
1127                 true,
1128             ),
1129             (
1130                 vec![
1131                     "cloud-hypervisor",
1132                     "--kernel",
1133                     "/path/to/kernel",
1134                     "--memory",
1135                     "shared=true",
1136                     "--disk",
1137                     "vhost_user=true,socket=/tmp/sock1",
1138                 ],
1139                 r#"{
1140                     "payload": {"kernel": "/path/to/kernel"},
1141                     "memory" : { "shared": true, "size": 536870912 },
1142                     "disks": [
1143                         {"vhost_user":true, "vhost_socket":"/tmp/sock1"}
1144                     ]
1145                 }"#,
1146                 true,
1147             ),
1148             (
1149                 vec![
1150                     "cloud-hypervisor",
1151                     "--kernel",
1152                     "/path/to/kernel",
1153                     "--disk",
1154                     "path=/path/to/disk/1,rate_limit_group=group0",
1155                     "path=/path/to/disk/2,rate_limit_group=group0",
1156                     "--rate-limit-group",
1157                     "id=group0,bw_size=1000,bw_refill_time=100",
1158                 ],
1159                 r#"{
1160                     "payload": {"kernel": "/path/to/kernel"},
1161                     "disks": [
1162                         {"path": "/path/to/disk/1", "rate_limit_group": "group0"},
1163                         {"path": "/path/to/disk/2", "rate_limit_group": "group0"}
1164                     ],
1165                     "rate_limit_groups": [
1166                         {"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}}
1167                     ]
1168                 }"#,
1169                 true,
1170             ),
1171         ]
1172         .iter()
1173         .for_each(|(cli, openapi, equal)| {
1174             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1175         });
1176     }
1177 
1178     #[test]
1179     fn test_valid_vm_config_net() {
1180         vec![
1181             // This test is expected to fail because the default MAC address is
1182             // randomly generated. There's no way we can have twice the same
1183             // default value.
1184             (
1185                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="],
1186                 r#"{
1187                     "payload": {"kernel": "/path/to/kernel"},
1188                     "net": []
1189                 }"#,
1190                 false,
1191             ),
1192             (
1193                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"],
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"}
1198                     ]
1199                 }"#,
1200                 true,
1201             ),
1202             (
1203                 vec![
1204                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1205                     "--net",
1206                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0",
1207                 ],
1208                 r#"{
1209                     "payload": {"kernel": "/path/to/kernel"},
1210                     "net": [
1211                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"}
1212                     ]
1213                 }"#,
1214                 true,
1215             ),
1216             (
1217                 vec![
1218                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1219                     "--net",
1220                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
1221                 ],
1222                 r#"{
1223                     "payload": {"kernel": "/path/to/kernel"},
1224                     "net": [
1225                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
1226                     ]
1227                 }"#,
1228                 true,
1229             ),
1230             (
1231                 vec![
1232                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1233                     "--net",
1234                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8",
1235                 ],
1236                 r#"{
1237                     "payload": {"kernel": "/path/to/kernel"},
1238                     "net": [
1239                         {"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"}
1240                     ]
1241                 }"#,
1242                 true,
1243             ),
1244             (
1245                 vec![
1246                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1247                     "--cpus", "boot=2",
1248                     "--net",
1249                     "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=4",
1250                 ],
1251                 r#"{
1252                     "payload": {"kernel": "/path/to/kernel"},
1253                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1254                     "net": [
1255                         {"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}
1256                     ]
1257                 }"#,
1258                 true,
1259             ),
1260             (
1261                 vec![
1262                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1263                     "--cpus", "boot=2",
1264                     "--net",
1265                     "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",
1266                 ],
1267                 r#"{
1268                     "payload": {"kernel": "/path/to/kernel"},
1269                     "cpus": {"boot_vcpus": 2, "max_vcpus": 2},
1270                     "net": [
1271                         {"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}
1272                     ]
1273                 }"#,
1274                 true,
1275             ),
1276             (
1277                 vec![
1278                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
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=2,queue_size=256",
1281                 ],
1282                 r#"{
1283                     "payload": {"kernel": "/path/to/kernel"},
1284                     "net": [
1285                         {"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"}
1286                     ]
1287                 }"#,
1288                 true,
1289             ),
1290             (
1291                 vec![
1292                     "cloud-hypervisor", "--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                 r#"{
1297                     "payload": {"kernel": "/path/to/kernel"},
1298                     "net": [
1299                         {"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}
1300                     ]
1301                 }"#,
1302                 true,
1303             ),
1304             #[cfg(target_arch = "x86_64")]
1305             (
1306                 vec![
1307                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1308                     "--net",
1309                     "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",
1310                 ],
1311                 r#"{
1312                     "payload": {"kernel": "/path/to/kernel"},
1313                     "net": [
1314                         {"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}
1315                     ]
1316                 }"#,
1317                 false,
1318             ),
1319             #[cfg(target_arch = "x86_64")]
1320             (
1321                 vec![
1322                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1323                     "--net",
1324                     "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",
1325                 ],
1326                 r#"{
1327                     "payload": {"kernel": "/path/to/kernel"},
1328                     "net": [
1329                         {"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}
1330                     ],
1331                     "iommu": true
1332                 }"#,
1333                 true,
1334             ),
1335             (
1336                 vec![
1337                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1338                     "--net",
1339                     "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",
1340                 ],
1341                 r#"{
1342                     "payload": {"kernel": "/path/to/kernel"},
1343                     "net": [
1344                         {"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}
1345                     ]
1346                 }"#,
1347                 true,
1348             ),
1349             (
1350                 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"],
1351                 r#"{
1352                     "payload": {"kernel": "/path/to/kernel"},
1353                     "memory" : { "shared": true, "size": 536870912 },
1354                     "net": [
1355                         {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"}
1356                     ]
1357                 }"#,
1358                 true,
1359             ),
1360         ]
1361         .iter()
1362         .for_each(|(cli, openapi, equal)| {
1363             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1364         });
1365     }
1366 
1367     #[test]
1368     fn test_valid_vm_config_rng() {
1369         [(
1370             vec![
1371                 "cloud-hypervisor",
1372                 "--kernel",
1373                 "/path/to/kernel",
1374                 "--rng",
1375                 "src=/path/to/entropy/source",
1376             ],
1377             r#"{
1378                 "payload": {"kernel": "/path/to/kernel"},
1379                 "rng": {"src": "/path/to/entropy/source"}
1380             }"#,
1381             true,
1382         )]
1383         .iter()
1384         .for_each(|(cli, openapi, equal)| {
1385             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1386         });
1387     }
1388 
1389     #[test]
1390     fn test_valid_vm_config_fs() {
1391         [(
1392                 vec![
1393                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1394                     "--memory", "shared=true",
1395                     "--fs",
1396                     "tag=virtiofs1,socket=/path/to/sock1",
1397                     "tag=virtiofs2,socket=/path/to/sock2",
1398                 ],
1399                 r#"{
1400                     "payload": {"kernel": "/path/to/kernel"},
1401                     "memory" : { "shared": true, "size": 536870912 },
1402                     "fs": [
1403                         {"tag": "virtiofs1", "socket": "/path/to/sock1"},
1404                         {"tag": "virtiofs2", "socket": "/path/to/sock2"}
1405                     ]
1406                 }"#,
1407                 true,
1408             ),
1409             (
1410                 vec![
1411                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1412                     "--memory", "shared=true",
1413                     "--fs",
1414                     "tag=virtiofs1,socket=/path/to/sock1",
1415                     "tag=virtiofs2,socket=/path/to/sock2",
1416                 ],
1417                 r#"{
1418                     "payload": {"kernel": "/path/to/kernel"},
1419                     "memory" : { "shared": true, "size": 536870912 },
1420                     "fs": [
1421                         {"tag": "virtiofs1", "socket": "/path/to/sock1"}
1422                     ]
1423                 }"#,
1424                 false,
1425             ),
1426             (
1427                 vec![
1428                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1429                     "--memory", "shared=true", "--cpus", "boot=4",
1430                     "--fs",
1431                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4",
1432                 ],
1433                 r#"{
1434                     "payload": {"kernel": "/path/to/kernel"},
1435                     "memory" : { "shared": true, "size": 536870912 },
1436                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1437                     "fs": [
1438                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4}
1439                     ]
1440                 }"#,
1441                 true,
1442             ),
1443             (
1444                 vec![
1445                     "cloud-hypervisor", "--kernel", "/path/to/kernel",
1446                     "--memory", "shared=true", "--cpus", "boot=4",
1447                     "--fs",
1448                     "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128"
1449                 ],
1450                 r#"{
1451                     "payload": {"kernel": "/path/to/kernel"},
1452                     "memory" : { "shared": true, "size": 536870912 },
1453                     "cpus": {"boot_vcpus": 4, "max_vcpus": 4},
1454                     "fs": [
1455                         {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128}
1456                     ]
1457                 }"#,
1458                 true,
1459             )]
1460         .iter()
1461         .for_each(|(cli, openapi, equal)| {
1462             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1463         });
1464     }
1465 
1466     #[test]
1467     fn test_valid_vm_config_pmem() {
1468         [
1469             (
1470                 vec![
1471                     "cloud-hypervisor",
1472                     "--kernel",
1473                     "/path/to/kernel",
1474                     "--pmem",
1475                     "file=/path/to/img/1,size=1G",
1476                     "file=/path/to/img/2,size=2G",
1477                 ],
1478                 r#"{
1479                     "payload": {"kernel": "/path/to/kernel"},
1480                     "pmem": [
1481                         {"file": "/path/to/img/1", "size": 1073741824},
1482                         {"file": "/path/to/img/2", "size": 2147483648}
1483                     ]
1484                 }"#,
1485                 true,
1486             ),
1487             #[cfg(target_arch = "x86_64")]
1488             (
1489                 vec![
1490                     "cloud-hypervisor",
1491                     "--kernel",
1492                     "/path/to/kernel",
1493                     "--pmem",
1494                     "file=/path/to/img/1,size=1G,iommu=on",
1495                 ],
1496                 r#"{
1497                     "payload": {"kernel": "/path/to/kernel"},
1498                     "pmem": [
1499                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1500                     ],
1501                     "iommu": true
1502                 }"#,
1503                 true,
1504             ),
1505             #[cfg(target_arch = "x86_64")]
1506             (
1507                 vec![
1508                     "cloud-hypervisor",
1509                     "--kernel",
1510                     "/path/to/kernel",
1511                     "--pmem",
1512                     "file=/path/to/img/1,size=1G,iommu=on",
1513                 ],
1514                 r#"{
1515                     "payload": {"kernel": "/path/to/kernel"},
1516                     "pmem": [
1517                         {"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
1518                     ]
1519                 }"#,
1520                 false,
1521             ),
1522         ]
1523         .iter()
1524         .for_each(|(cli, openapi, equal)| {
1525             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1526         });
1527     }
1528 
1529     #[cfg(target_arch = "x86_64")]
1530     #[test]
1531     fn test_valid_vm_config_debug_console() {
1532         [(
1533             vec![
1534                 "cloud-hypervisor",
1535                 "--kernel",
1536                 "/path/to/kernel",
1537                 "--debug-console",
1538                 "tty,iobase=0xe9",
1539             ],
1540             // 233 == 0xe9
1541             r#"{
1542                 "payload": {"kernel": "/path/to/kernel" },
1543                 "debug_console": {"mode": "Tty", "iobase": 233 }
1544             }"#,
1545             true,
1546         )]
1547         .iter()
1548         .for_each(|(cli, openapi, equal)| {
1549             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1550         });
1551     }
1552 
1553     #[test]
1554     fn test_valid_vm_config_serial_console() {
1555         [
1556             (
1557                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1558                 r#"{
1559                     "payload": {"kernel": "/path/to/kernel"},
1560                     "serial": {"mode": "Null"},
1561                     "console": {"mode": "Tty"}
1562                 }"#,
1563                 true,
1564             ),
1565             (
1566                 vec![
1567                     "cloud-hypervisor",
1568                     "--kernel",
1569                     "/path/to/kernel",
1570                     "--serial",
1571                     "null",
1572                     "--console",
1573                     "tty",
1574                 ],
1575                 r#"{
1576                     "payload": {"kernel": "/path/to/kernel"}
1577                 }"#,
1578                 true,
1579             ),
1580             (
1581                 vec![
1582                     "cloud-hypervisor",
1583                     "--kernel",
1584                     "/path/to/kernel",
1585                     "--serial",
1586                     "tty",
1587                     "--console",
1588                     "off",
1589                 ],
1590                 r#"{
1591                     "payload": {"kernel": "/path/to/kernel"},
1592                     "serial": {"mode": "Tty"},
1593                     "console": {"mode": "Off"}
1594                 }"#,
1595                 true,
1596             ),
1597         ]
1598         .iter()
1599         .for_each(|(cli, openapi, equal)| {
1600             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1601         });
1602     }
1603 
1604     #[test]
1605     fn test_valid_vm_config_serial_pty_console_pty() {
1606         [
1607             (
1608                 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
1609                 r#"{
1610                     "payload": {"kernel": "/path/to/kernel"},
1611                     "serial": {"mode": "Null"},
1612                     "console": {"mode": "Tty"}
1613                 }"#,
1614                 true,
1615             ),
1616             (
1617                 vec![
1618                     "cloud-hypervisor",
1619                     "--kernel",
1620                     "/path/to/kernel",
1621                     "--serial",
1622                     "null",
1623                     "--console",
1624                     "tty",
1625                 ],
1626                 r#"{
1627                     "payload": {"kernel": "/path/to/kernel"}
1628                 }"#,
1629                 true,
1630             ),
1631             (
1632                 vec![
1633                     "cloud-hypervisor",
1634                     "--kernel",
1635                     "/path/to/kernel",
1636                     "--serial",
1637                     "pty",
1638                     "--console",
1639                     "pty",
1640                 ],
1641                 r#"{
1642                     "payload": {"kernel": "/path/to/kernel"},
1643                     "serial": {"mode": "Pty"},
1644                     "console": {"mode": "Pty"}
1645                 }"#,
1646                 true,
1647             ),
1648         ]
1649         .iter()
1650         .for_each(|(cli, openapi, equal)| {
1651             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1652         });
1653     }
1654 
1655     #[test]
1656     #[cfg(target_arch = "x86_64")]
1657     fn test_valid_vm_config_devices() {
1658         vec![
1659             (
1660                 vec![
1661                     "cloud-hypervisor",
1662                     "--kernel",
1663                     "/path/to/kernel",
1664                     "--device",
1665                     "path=/path/to/device/1",
1666                     "path=/path/to/device/2",
1667                 ],
1668                 r#"{
1669                     "payload": {"kernel": "/path/to/kernel"},
1670                     "devices": [
1671                         {"path": "/path/to/device/1"},
1672                         {"path": "/path/to/device/2"}
1673                     ]
1674                 }"#,
1675                 true,
1676             ),
1677             (
1678                 vec![
1679                     "cloud-hypervisor",
1680                     "--kernel",
1681                     "/path/to/kernel",
1682                     "--device",
1683                     "path=/path/to/device/1",
1684                     "path=/path/to/device/2",
1685                 ],
1686                 r#"{
1687                     "payload": {"kernel": "/path/to/kernel"},
1688                     "devices": [
1689                         {"path": "/path/to/device/1"}
1690                     ]
1691                 }"#,
1692                 false,
1693             ),
1694             (
1695                 vec![
1696                     "cloud-hypervisor",
1697                     "--kernel",
1698                     "/path/to/kernel",
1699                     "--device",
1700                     "path=/path/to/device,iommu=on",
1701                 ],
1702                 r#"{
1703                     "payload": {"kernel": "/path/to/kernel"},
1704                     "devices": [
1705                         {"path": "/path/to/device", "iommu": true}
1706                     ],
1707                     "iommu": true
1708                 }"#,
1709                 true,
1710             ),
1711             (
1712                 vec![
1713                     "cloud-hypervisor",
1714                     "--kernel",
1715                     "/path/to/kernel",
1716                     "--device",
1717                     "path=/path/to/device,iommu=on",
1718                 ],
1719                 r#"{
1720                     "payload": {"kernel": "/path/to/kernel"},
1721                     "devices": [
1722                         {"path": "/path/to/device", "iommu": true}
1723                     ]
1724                 }"#,
1725                 false,
1726             ),
1727             (
1728                 vec![
1729                     "cloud-hypervisor",
1730                     "--kernel",
1731                     "/path/to/kernel",
1732                     "--device",
1733                     "path=/path/to/device,iommu=off",
1734                 ],
1735                 r#"{
1736                     "payload": {"kernel": "/path/to/kernel"},
1737                     "devices": [
1738                         {"path": "/path/to/device", "iommu": false}
1739                     ]
1740                 }"#,
1741                 true,
1742             ),
1743         ]
1744         .iter()
1745         .for_each(|(cli, openapi, equal)| {
1746             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1747         });
1748     }
1749 
1750     #[test]
1751     fn test_valid_vm_config_vdpa() {
1752         [
1753             (
1754                 vec![
1755                     "cloud-hypervisor",
1756                     "--kernel",
1757                     "/path/to/kernel",
1758                     "--vdpa",
1759                     "path=/path/to/device/1",
1760                     "path=/path/to/device/2,num_queues=2",
1761                 ],
1762                 r#"{
1763                     "payload": {"kernel": "/path/to/kernel"},
1764                     "vdpa": [
1765                         {"path": "/path/to/device/1", "num_queues": 1},
1766                         {"path": "/path/to/device/2", "num_queues": 2}
1767                     ]
1768                 }"#,
1769                 true,
1770             ),
1771             (
1772                 vec![
1773                     "cloud-hypervisor",
1774                     "--kernel",
1775                     "/path/to/kernel",
1776                     "--vdpa",
1777                     "path=/path/to/device/1",
1778                     "path=/path/to/device/2",
1779                 ],
1780                 r#"{
1781                     "payload": {"kernel": "/path/to/kernel"},
1782                     "vdpa": [
1783                         {"path": "/path/to/device/1"}
1784                     ]
1785                 }"#,
1786                 false,
1787             ),
1788         ]
1789         .iter()
1790         .for_each(|(cli, openapi, equal)| {
1791             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1792         });
1793     }
1794 
1795     #[test]
1796     fn test_valid_vm_config_vsock() {
1797         [
1798             (
1799                 vec![
1800                     "cloud-hypervisor",
1801                     "--kernel",
1802                     "/path/to/kernel",
1803                     "--vsock",
1804                     "cid=123,socket=/path/to/sock/1",
1805                 ],
1806                 r#"{
1807                     "payload": {"kernel": "/path/to/kernel"},
1808                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1809                 }"#,
1810                 true,
1811             ),
1812             (
1813                 vec![
1814                     "cloud-hypervisor",
1815                     "--kernel",
1816                     "/path/to/kernel",
1817                     "--vsock",
1818                     "cid=124,socket=/path/to/sock/1",
1819                 ],
1820                 r#"{
1821                     "payload": {"kernel": "/path/to/kernel"},
1822                     "vsock": {"cid": 123, "socket": "/path/to/sock/1"}
1823                 }"#,
1824                 false,
1825             ),
1826             #[cfg(target_arch = "x86_64")]
1827             (
1828                 vec![
1829                     "cloud-hypervisor",
1830                     "--kernel",
1831                     "/path/to/kernel",
1832                     "--vsock",
1833                     "cid=123,socket=/path/to/sock/1,iommu=on",
1834                 ],
1835                 r#"{
1836                     "payload": {"kernel": "/path/to/kernel"},
1837                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true},
1838                     "iommu": true
1839                 }"#,
1840                 true,
1841             ),
1842             #[cfg(target_arch = "x86_64")]
1843             (
1844                 vec![
1845                     "cloud-hypervisor",
1846                     "--kernel",
1847                     "/path/to/kernel",
1848                     "--vsock",
1849                     "cid=123,socket=/path/to/sock/1,iommu=on",
1850                 ],
1851                 r#"{
1852                     "payload": {"kernel": "/path/to/kernel"},
1853                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}
1854                 }"#,
1855                 false,
1856             ),
1857             (
1858                 vec![
1859                     "cloud-hypervisor",
1860                     "--kernel",
1861                     "/path/to/kernel",
1862                     "--vsock",
1863                     "cid=123,socket=/path/to/sock/1,iommu=off",
1864                 ],
1865                 r#"{
1866                     "payload": {"kernel": "/path/to/kernel"},
1867                     "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false}
1868                 }"#,
1869                 true,
1870             ),
1871         ]
1872         .iter()
1873         .for_each(|(cli, openapi, equal)| {
1874             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1875         });
1876     }
1877 
1878     #[test]
1879     fn test_valid_vm_config_tpm_socket() {
1880         [(
1881             vec![
1882                 "cloud-hypervisor",
1883                 "--kernel",
1884                 "/path/to/kernel",
1885                 "--tpm",
1886                 "socket=/path/to/tpm/sock",
1887             ],
1888             r#"{
1889                     "payload": {"kernel": "/path/to/kernel"},
1890                     "tpm": {"socket": "/path/to/tpm/sock"}
1891                 }"#,
1892             true,
1893         )]
1894         .iter()
1895         .for_each(|(cli, openapi, equal)| {
1896             compare_vm_config_cli_vs_json(cli, openapi, *equal);
1897         });
1898     }
1899 }
1900