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