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