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