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