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