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