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