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