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