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