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