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