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