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