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