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