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