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