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