1 // Copyright © 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 pub use crate::vm_config::*; 7 use clap::ArgMatches; 8 use option_parser::{ 9 ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, 10 }; 11 use serde::{Deserialize, Serialize}; 12 use std::collections::{BTreeSet, HashMap}; 13 use std::fmt; 14 use std::path::PathBuf; 15 use std::result; 16 use std::str::FromStr; 17 use thiserror::Error; 18 use virtio_devices::{RateLimiterConfig, TokenBucketConfig}; 19 20 const MAX_NUM_PCI_SEGMENTS: u16 = 96; 21 22 /// Errors associated with VM configuration parameters. 23 #[derive(Debug, Error)] 24 pub enum Error { 25 /// Filesystem tag is missing 26 ParseFsTagMissing, 27 /// Filesystem socket is missing 28 ParseFsSockMissing, 29 /// Missing persistent memory file parameter. 30 ParsePmemFileMissing, 31 /// Missing vsock socket path parameter. 32 ParseVsockSockMissing, 33 /// Missing vsock cid parameter. 34 ParseVsockCidMissing, 35 /// Missing restore source_url parameter. 36 ParseRestoreSourceUrlMissing, 37 /// Error parsing CPU options 38 ParseCpus(OptionParserError), 39 /// Invalid CPU features 40 InvalidCpuFeatures(String), 41 /// Error parsing memory options 42 ParseMemory(OptionParserError), 43 /// Error parsing memory zone options 44 ParseMemoryZone(OptionParserError), 45 /// Missing 'id' from memory zone 46 ParseMemoryZoneIdMissing, 47 /// Error parsing rate-limiter group options 48 ParseRateLimiterGroup(OptionParserError), 49 /// Error parsing disk options 50 ParseDisk(OptionParserError), 51 /// Error parsing network options 52 ParseNetwork(OptionParserError), 53 /// Error parsing RNG options 54 ParseRng(OptionParserError), 55 /// Error parsing balloon options 56 ParseBalloon(OptionParserError), 57 /// Error parsing filesystem parameters 58 ParseFileSystem(OptionParserError), 59 /// Error parsing persistent memory parameters 60 ParsePersistentMemory(OptionParserError), 61 /// Failed parsing console 62 ParseConsole(OptionParserError), 63 #[cfg(target_arch = "x86_64")] 64 /// Failed parsing debug-console 65 ParseDebugConsole(OptionParserError), 66 /// No mode given for console 67 ParseConsoleInvalidModeGiven, 68 /// Failed parsing device parameters 69 ParseDevice(OptionParserError), 70 /// Missing path from device, 71 ParseDevicePathMissing, 72 /// Failed parsing vsock parameters 73 ParseVsock(OptionParserError), 74 /// Failed parsing restore parameters 75 ParseRestore(OptionParserError), 76 /// Failed parsing SGX EPC parameters 77 #[cfg(target_arch = "x86_64")] 78 ParseSgxEpc(OptionParserError), 79 /// Missing 'id' from SGX EPC section 80 #[cfg(target_arch = "x86_64")] 81 ParseSgxEpcIdMissing, 82 /// Failed parsing NUMA parameters 83 ParseNuma(OptionParserError), 84 /// Failed validating configuration 85 Validation(ValidationError), 86 #[cfg(feature = "sev_snp")] 87 /// Failed parsing SEV-SNP config 88 ParseSevSnp(OptionParserError), 89 #[cfg(feature = "tdx")] 90 /// Failed parsing TDX config 91 ParseTdx(OptionParserError), 92 #[cfg(feature = "tdx")] 93 /// No TDX firmware 94 FirmwarePathMissing, 95 /// Failed parsing userspace device 96 ParseUserDevice(OptionParserError), 97 /// Missing socket for userspace device 98 ParseUserDeviceSocketMissing, 99 /// Failed parsing platform parameters 100 ParsePlatform(OptionParserError), 101 /// Failed parsing vDPA device 102 ParseVdpa(OptionParserError), 103 /// Missing path for vDPA device 104 ParseVdpaPathMissing, 105 /// Failed parsing TPM device 106 ParseTpm(OptionParserError), 107 /// Missing path for TPM device 108 ParseTpmPathMissing, 109 } 110 111 #[derive(Debug, PartialEq, Eq, Error)] 112 pub enum ValidationError { 113 /// No kernel specified 114 KernelMissing, 115 /// Missing file value for console 116 ConsoleFileMissing, 117 /// Missing socket path for console 118 ConsoleSocketPathMissing, 119 /// Max is less than boot 120 CpusMaxLowerThanBoot, 121 /// Missing file value for debug-console 122 #[cfg(target_arch = "x86_64")] 123 DebugconFileMissing, 124 /// Both socket and path specified 125 DiskSocketAndPath, 126 /// Using vhost user requires shared memory 127 VhostUserRequiresSharedMemory, 128 /// No socket provided for vhost_use 129 VhostUserMissingSocket, 130 /// Trying to use IOMMU without PCI 131 IommuUnsupported, 132 /// Trying to use VFIO without PCI 133 VfioUnsupported, 134 /// CPU topology count doesn't match max 135 CpuTopologyCount, 136 /// One part of the CPU topology was zero 137 CpuTopologyZeroPart, 138 #[cfg(target_arch = "aarch64")] 139 /// Dies per package must be 1 140 CpuTopologyDiesPerPackage, 141 /// Virtio needs a min of 2 queues 142 VnetQueueLowerThan2, 143 /// The input queue number for virtio_net must match the number of input fds 144 VnetQueueFdMismatch, 145 /// Using reserved fd 146 VnetReservedFd, 147 /// Hardware checksum offload is disabled. 148 NoHardwareChecksumOffload, 149 /// Hugepages not turned on 150 HugePageSizeWithoutHugePages, 151 /// Huge page size is not power of 2 152 InvalidHugePageSize(u64), 153 /// CPU Hotplug is not permitted with TDX 154 #[cfg(feature = "tdx")] 155 TdxNoCpuHotplug, 156 /// Missing firmware for TDX 157 #[cfg(feature = "tdx")] 158 TdxFirmwareMissing, 159 /// Insufficient vCPUs for queues 160 TooManyQueues, 161 /// Need shared memory for vfio-user 162 UserDevicesRequireSharedMemory, 163 /// VSOCK Context Identifier has a special meaning, unsuitable for a VM. 164 VsockSpecialCid(u32), 165 /// Memory zone is reused across NUMA nodes 166 MemoryZoneReused(String, u32, u32), 167 /// Invalid number of PCI segments 168 InvalidNumPciSegments(u16), 169 /// Invalid PCI segment id 170 InvalidPciSegment(u16), 171 /// Balloon too big 172 BalloonLargerThanRam(u64, u64), 173 /// On a IOMMU segment but not behind IOMMU 174 OnIommuSegment(u16), 175 // On a IOMMU segment but IOMMU not supported 176 IommuNotSupportedOnSegment(u16), 177 // Identifier is not unique 178 IdentifierNotUnique(String), 179 /// Invalid identifier 180 InvalidIdentifier(String), 181 /// Placing the device behind a virtual IOMMU is not supported 182 IommuNotSupported, 183 /// Duplicated device path (device added twice) 184 DuplicateDevicePath(String), 185 /// Provided MTU is lower than what the VIRTIO specification expects 186 InvalidMtu(u16), 187 /// PCI segment is reused across NUMA nodes 188 PciSegmentReused(u16, u32, u32), 189 /// Default PCI segment is assigned to NUMA node other than 0. 190 DefaultPciSegmentInvalidNode(u32), 191 /// Invalid rate-limiter group 192 InvalidRateLimiterGroup, 193 /// The specified I/O port was invalid. It should be provided in hex, such as `0xe9`. 194 #[cfg(target_arch = "x86_64")] 195 InvalidIoPortHex(String), 196 } 197 198 type ValidationResult<T> = std::result::Result<T, ValidationError>; 199 200 impl fmt::Display for ValidationError { 201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 202 use self::ValidationError::*; 203 match self { 204 KernelMissing => write!(f, "No kernel specified"), 205 ConsoleFileMissing => write!(f, "Path missing when using file console mode"), 206 ConsoleSocketPathMissing => write!(f, "Path missing when using socket console mode"), 207 CpusMaxLowerThanBoot => write!(f, "Max CPUs lower than boot CPUs"), 208 #[cfg(target_arch = "x86_64")] 209 DebugconFileMissing => write!(f, "Path missing when using file mode for debug console"), 210 DiskSocketAndPath => write!(f, "Disk path and vhost socket both provided"), 211 VhostUserRequiresSharedMemory => { 212 write!( 213 f, 214 "Using vhost-user requires using shared memory or huge pages" 215 ) 216 } 217 VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"), 218 IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"), 219 VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"), 220 CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"), 221 CpuTopologyCount => write!( 222 f, 223 "Product of CPU topology parts does not match maximum vCPUs" 224 ), 225 #[cfg(target_arch = "aarch64")] 226 CpuTopologyDiesPerPackage => write!(f, "Dies per package must be 1"), 227 VnetQueueLowerThan2 => write!(f, "Number of queues to virtio_net less than 2"), 228 VnetQueueFdMismatch => write!( 229 f, 230 "Number of queues to virtio_net does not match the number of input FDs" 231 ), 232 VnetReservedFd => write!(f, "Reserved fd number (<= 2)"), 233 NoHardwareChecksumOffload => write!( 234 f, 235 "\"offload_tso\" and \"offload_ufo\" depend on \"offload_tso\"" 236 ), 237 HugePageSizeWithoutHugePages => { 238 write!(f, "Huge page size specified but huge pages not enabled") 239 } 240 InvalidHugePageSize(s) => { 241 write!(f, "Huge page size is not power of 2: {s}") 242 } 243 #[cfg(feature = "tdx")] 244 TdxNoCpuHotplug => { 245 write!(f, "CPU hotplug is not permitted with TDX") 246 } 247 #[cfg(feature = "tdx")] 248 TdxFirmwareMissing => { 249 write!(f, "No TDX firmware specified") 250 } 251 TooManyQueues => { 252 write!(f, "Number of vCPUs is insufficient for number of queues") 253 } 254 UserDevicesRequireSharedMemory => { 255 write!( 256 f, 257 "Using user devices requires using shared memory or huge pages" 258 ) 259 } 260 VsockSpecialCid(cid) => { 261 write!(f, "{cid} is a special VSOCK CID") 262 } 263 MemoryZoneReused(s, u1, u2) => { 264 write!( 265 f, 266 "Memory zone: {s} belongs to multiple NUMA nodes {u1} and {u2}" 267 ) 268 } 269 InvalidNumPciSegments(n) => { 270 write!( 271 f, 272 "Number of PCI segments ({n}) not in range of 1 to {MAX_NUM_PCI_SEGMENTS}" 273 ) 274 } 275 InvalidPciSegment(pci_segment) => { 276 write!(f, "Invalid PCI segment id: {pci_segment}") 277 } 278 BalloonLargerThanRam(balloon_size, ram_size) => { 279 write!( 280 f, 281 "Ballon size ({balloon_size}) greater than RAM ({ram_size})" 282 ) 283 } 284 OnIommuSegment(pci_segment) => { 285 write!( 286 f, 287 "Device is on an IOMMU PCI segment ({pci_segment}) but not placed behind IOMMU" 288 ) 289 } 290 IommuNotSupportedOnSegment(pci_segment) => { 291 write!( 292 f, 293 "Device is on an IOMMU PCI segment ({pci_segment}) but does not support being placed behind IOMMU" 294 ) 295 } 296 IdentifierNotUnique(s) => { 297 write!(f, "Identifier {s} is not unique") 298 } 299 InvalidIdentifier(s) => { 300 write!(f, "Identifier {s} is invalid") 301 } 302 IommuNotSupported => { 303 write!(f, "Device does not support being placed behind IOMMU") 304 } 305 DuplicateDevicePath(p) => write!(f, "Duplicated device path: {p}"), 306 &InvalidMtu(mtu) => { 307 write!( 308 f, 309 "Provided MTU {mtu} is lower than 1280 (expected by VIRTIO specification)" 310 ) 311 } 312 PciSegmentReused(pci_segment, u1, u2) => { 313 write!( 314 f, 315 "PCI segment: {pci_segment} belongs to multiple NUMA nodes {u1} and {u2}" 316 ) 317 } 318 DefaultPciSegmentInvalidNode(u1) => { 319 write!(f, "Default PCI segment assigned to non-zero NUMA node {u1}") 320 } 321 InvalidRateLimiterGroup => { 322 write!(f, "Invalid rate-limiter group") 323 } 324 #[cfg(target_arch = "x86_64")] 325 InvalidIoPortHex(s) => { 326 write!( 327 f, 328 "The IO port was not properly provided in hex or a `0x` prefix is missing: {s}" 329 ) 330 } 331 } 332 } 333 } 334 335 impl fmt::Display for Error { 336 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 337 use self::Error::*; 338 match self { 339 ParseConsole(o) => write!(f, "Error parsing --console: {o}"), 340 #[cfg(target_arch = "x86_64")] 341 ParseDebugConsole(o) => write!(f, "Error parsing --debug-console: {o}"), 342 ParseConsoleInvalidModeGiven => { 343 write!(f, "Error parsing --console: invalid console mode given") 344 } 345 ParseCpus(o) => write!(f, "Error parsing --cpus: {o}"), 346 InvalidCpuFeatures(o) => write!(f, "Invalid feature in --cpus features list: {o}"), 347 ParseDevice(o) => write!(f, "Error parsing --device: {o}"), 348 ParseDevicePathMissing => write!(f, "Error parsing --device: path missing"), 349 ParseFileSystem(o) => write!(f, "Error parsing --fs: {o}"), 350 ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"), 351 ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"), 352 ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {o}"), 353 ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"), 354 ParseVsock(o) => write!(f, "Error parsing --vsock: {o}"), 355 ParseVsockCidMissing => write!(f, "Error parsing --vsock: cid missing"), 356 ParseVsockSockMissing => write!(f, "Error parsing --vsock: socket missing"), 357 ParseMemory(o) => write!(f, "Error parsing --memory: {o}"), 358 ParseMemoryZone(o) => write!(f, "Error parsing --memory-zone: {o}"), 359 ParseMemoryZoneIdMissing => write!(f, "Error parsing --memory-zone: id missing"), 360 ParseNetwork(o) => write!(f, "Error parsing --net: {o}"), 361 ParseRateLimiterGroup(o) => write!(f, "Error parsing --rate-limit-group: {o}"), 362 ParseDisk(o) => write!(f, "Error parsing --disk: {o}"), 363 ParseRng(o) => write!(f, "Error parsing --rng: {o}"), 364 ParseBalloon(o) => write!(f, "Error parsing --balloon: {o}"), 365 ParseRestore(o) => write!(f, "Error parsing --restore: {o}"), 366 #[cfg(target_arch = "x86_64")] 367 ParseSgxEpc(o) => write!(f, "Error parsing --sgx-epc: {o}"), 368 #[cfg(target_arch = "x86_64")] 369 ParseSgxEpcIdMissing => write!(f, "Error parsing --sgx-epc: id missing"), 370 ParseNuma(o) => write!(f, "Error parsing --numa: {o}"), 371 ParseRestoreSourceUrlMissing => { 372 write!(f, "Error parsing --restore: source_url missing") 373 } 374 ParseUserDeviceSocketMissing => { 375 write!(f, "Error parsing --user-device: socket missing") 376 } 377 ParseUserDevice(o) => write!(f, "Error parsing --user-device: {o}"), 378 Validation(v) => write!(f, "Error validating configuration: {v}"), 379 #[cfg(feature = "sev_snp")] 380 ParseSevSnp(o) => write!(f, "Error parsing --sev_snp: {o}"), 381 #[cfg(feature = "tdx")] 382 ParseTdx(o) => write!(f, "Error parsing --tdx: {o}"), 383 #[cfg(feature = "tdx")] 384 FirmwarePathMissing => write!(f, "TDX firmware missing"), 385 ParsePlatform(o) => write!(f, "Error parsing --platform: {o}"), 386 ParseVdpa(o) => write!(f, "Error parsing --vdpa: {o}"), 387 ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"), 388 ParseTpm(o) => write!(f, "Error parsing --tpm: {o}"), 389 ParseTpmPathMissing => write!(f, "Error parsing --tpm: path missing"), 390 } 391 } 392 } 393 394 pub fn add_to_config<T>(items: &mut Option<Vec<T>>, item: T) { 395 if let Some(items) = items { 396 items.push(item); 397 } else { 398 *items = Some(vec![item]); 399 } 400 } 401 402 pub type Result<T> = result::Result<T, Error>; 403 404 pub struct VmParams<'a> { 405 pub cpus: &'a str, 406 pub memory: &'a str, 407 pub memory_zones: Option<Vec<&'a str>>, 408 pub firmware: Option<&'a str>, 409 pub kernel: Option<&'a str>, 410 pub initramfs: Option<&'a str>, 411 pub cmdline: Option<&'a str>, 412 pub rate_limit_groups: Option<Vec<&'a str>>, 413 pub disks: Option<Vec<&'a str>>, 414 pub net: Option<Vec<&'a str>>, 415 pub rng: &'a str, 416 pub balloon: Option<&'a str>, 417 pub fs: Option<Vec<&'a str>>, 418 pub pmem: Option<Vec<&'a str>>, 419 pub serial: &'a str, 420 pub console: &'a str, 421 #[cfg(target_arch = "x86_64")] 422 pub debug_console: &'a str, 423 pub devices: Option<Vec<&'a str>>, 424 pub user_devices: Option<Vec<&'a str>>, 425 pub vdpa: Option<Vec<&'a str>>, 426 pub vsock: Option<&'a str>, 427 pub pvpanic: bool, 428 #[cfg(target_arch = "x86_64")] 429 pub sgx_epc: Option<Vec<&'a str>>, 430 pub numa: Option<Vec<&'a str>>, 431 pub watchdog: bool, 432 #[cfg(feature = "guest_debug")] 433 pub gdb: bool, 434 pub platform: Option<&'a str>, 435 pub tpm: Option<&'a str>, 436 #[cfg(feature = "igvm")] 437 pub igvm: Option<&'a str>, 438 } 439 440 impl<'a> VmParams<'a> { 441 pub fn from_arg_matches(args: &'a ArgMatches) -> Self { 442 // These .unwrap()s cannot fail as there is a default value defined 443 let cpus = args.get_one::<String>("cpus").unwrap(); 444 let memory = args.get_one::<String>("memory").unwrap(); 445 let memory_zones: Option<Vec<&str>> = args 446 .get_many::<String>("memory-zone") 447 .map(|x| x.map(|y| y as &str).collect()); 448 let rng = args.get_one::<String>("rng").unwrap(); 449 let serial = args.get_one::<String>("serial").unwrap(); 450 let firmware = args.get_one::<String>("firmware").map(|x| x as &str); 451 let kernel = args.get_one::<String>("kernel").map(|x| x as &str); 452 let initramfs = args.get_one::<String>("initramfs").map(|x| x as &str); 453 let cmdline = args.get_one::<String>("cmdline").map(|x| x as &str); 454 let rate_limit_groups: Option<Vec<&str>> = args 455 .get_many::<String>("rate-limit-group") 456 .map(|x| x.map(|y| y as &str).collect()); 457 let disks: Option<Vec<&str>> = args 458 .get_many::<String>("disk") 459 .map(|x| x.map(|y| y as &str).collect()); 460 let net: Option<Vec<&str>> = args 461 .get_many::<String>("net") 462 .map(|x| x.map(|y| y as &str).collect()); 463 let console = args.get_one::<String>("console").unwrap(); 464 #[cfg(target_arch = "x86_64")] 465 let debug_console = args.get_one::<String>("debug-console").unwrap().as_str(); 466 let balloon = args.get_one::<String>("balloon").map(|x| x as &str); 467 let fs: Option<Vec<&str>> = args 468 .get_many::<String>("fs") 469 .map(|x| x.map(|y| y as &str).collect()); 470 let pmem: Option<Vec<&str>> = args 471 .get_many::<String>("pmem") 472 .map(|x| x.map(|y| y as &str).collect()); 473 let devices: Option<Vec<&str>> = args 474 .get_many::<String>("device") 475 .map(|x| x.map(|y| y as &str).collect()); 476 let user_devices: Option<Vec<&str>> = args 477 .get_many::<String>("user-device") 478 .map(|x| x.map(|y| y as &str).collect()); 479 let vdpa: Option<Vec<&str>> = args 480 .get_many::<String>("vdpa") 481 .map(|x| x.map(|y| y as &str).collect()); 482 let vsock: Option<&str> = args.get_one::<String>("vsock").map(|x| x as &str); 483 let pvpanic = args.get_flag("pvpanic"); 484 #[cfg(target_arch = "x86_64")] 485 let sgx_epc: Option<Vec<&str>> = args 486 .get_many::<String>("sgx-epc") 487 .map(|x| x.map(|y| y as &str).collect()); 488 let numa: Option<Vec<&str>> = args 489 .get_many::<String>("numa") 490 .map(|x| x.map(|y| y as &str).collect()); 491 let watchdog = args.get_flag("watchdog"); 492 let platform = args.get_one::<String>("platform").map(|x| x as &str); 493 #[cfg(feature = "guest_debug")] 494 let gdb = args.contains_id("gdb"); 495 let tpm: Option<&str> = args.get_one::<String>("tpm").map(|x| x as &str); 496 #[cfg(feature = "igvm")] 497 let igvm = args.get_one::<String>("igvm").map(|x| x as &str); 498 VmParams { 499 cpus, 500 memory, 501 memory_zones, 502 firmware, 503 kernel, 504 initramfs, 505 cmdline, 506 rate_limit_groups, 507 disks, 508 net, 509 rng, 510 balloon, 511 fs, 512 pmem, 513 serial, 514 console, 515 #[cfg(target_arch = "x86_64")] 516 debug_console, 517 devices, 518 user_devices, 519 vdpa, 520 vsock, 521 pvpanic, 522 #[cfg(target_arch = "x86_64")] 523 sgx_epc, 524 numa, 525 watchdog, 526 #[cfg(feature = "guest_debug")] 527 gdb, 528 platform, 529 tpm, 530 #[cfg(feature = "igvm")] 531 igvm, 532 } 533 } 534 } 535 536 #[derive(Debug)] 537 pub enum ParseHotplugMethodError { 538 InvalidValue(String), 539 } 540 541 impl FromStr for HotplugMethod { 542 type Err = ParseHotplugMethodError; 543 544 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 545 match s.to_lowercase().as_str() { 546 "acpi" => Ok(HotplugMethod::Acpi), 547 "virtio-mem" => Ok(HotplugMethod::VirtioMem), 548 _ => Err(ParseHotplugMethodError::InvalidValue(s.to_owned())), 549 } 550 } 551 } 552 553 pub enum CpuTopologyParseError { 554 InvalidValue(String), 555 } 556 557 impl FromStr for CpuTopology { 558 type Err = CpuTopologyParseError; 559 560 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 561 let parts: Vec<&str> = s.split(':').collect(); 562 563 if parts.len() != 4 { 564 return Err(Self::Err::InvalidValue(s.to_owned())); 565 } 566 567 let t = CpuTopology { 568 threads_per_core: parts[0] 569 .parse() 570 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 571 cores_per_die: parts[1] 572 .parse() 573 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 574 dies_per_package: parts[2] 575 .parse() 576 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 577 packages: parts[3] 578 .parse() 579 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?, 580 }; 581 582 Ok(t) 583 } 584 } 585 586 impl CpusConfig { 587 pub fn parse(cpus: &str) -> Result<Self> { 588 let mut parser = OptionParser::new(); 589 parser 590 .add("boot") 591 .add("max") 592 .add("topology") 593 .add("kvm_hyperv") 594 .add("max_phys_bits") 595 .add("affinity") 596 .add("features"); 597 parser.parse(cpus).map_err(Error::ParseCpus)?; 598 599 let boot_vcpus: u8 = parser 600 .convert("boot") 601 .map_err(Error::ParseCpus)? 602 .unwrap_or(DEFAULT_VCPUS); 603 let max_vcpus: u8 = parser 604 .convert("max") 605 .map_err(Error::ParseCpus)? 606 .unwrap_or(boot_vcpus); 607 let topology = parser.convert("topology").map_err(Error::ParseCpus)?; 608 let kvm_hyperv = parser 609 .convert::<Toggle>("kvm_hyperv") 610 .map_err(Error::ParseCpus)? 611 .unwrap_or(Toggle(false)) 612 .0; 613 let max_phys_bits = parser 614 .convert::<u8>("max_phys_bits") 615 .map_err(Error::ParseCpus)? 616 .unwrap_or(DEFAULT_MAX_PHYS_BITS); 617 let affinity = parser 618 .convert::<Tuple<u8, Vec<usize>>>("affinity") 619 .map_err(Error::ParseCpus)? 620 .map(|v| { 621 v.0.iter() 622 .map(|(e1, e2)| CpuAffinity { 623 vcpu: *e1, 624 host_cpus: e2.clone(), 625 }) 626 .collect() 627 }); 628 let features_list = parser 629 .convert::<StringList>("features") 630 .map_err(Error::ParseCpus)? 631 .unwrap_or_default(); 632 // Some ugliness here as the features being checked might be disabled 633 // at compile time causing the below allow and the need to specify the 634 // ref type in the match. 635 // The issue will go away once kvm_hyperv is moved under the features 636 // list as it will always be checked for. 637 #[allow(unused_mut)] 638 let mut features = CpuFeatures::default(); 639 for s in features_list.0 { 640 match <std::string::String as AsRef<str>>::as_ref(&s) { 641 #[cfg(target_arch = "x86_64")] 642 "amx" => { 643 features.amx = true; 644 Ok(()) 645 } 646 _ => Err(Error::InvalidCpuFeatures(s)), 647 }?; 648 } 649 650 Ok(CpusConfig { 651 boot_vcpus, 652 max_vcpus, 653 topology, 654 kvm_hyperv, 655 max_phys_bits, 656 affinity, 657 features, 658 }) 659 } 660 } 661 662 impl PlatformConfig { 663 pub fn parse(platform: &str) -> Result<Self> { 664 let mut parser = OptionParser::new(); 665 parser 666 .add("num_pci_segments") 667 .add("iommu_segments") 668 .add("serial_number") 669 .add("uuid") 670 .add("oem_strings"); 671 #[cfg(feature = "tdx")] 672 parser.add("tdx"); 673 #[cfg(feature = "sev_snp")] 674 parser.add("sev_snp"); 675 parser.parse(platform).map_err(Error::ParsePlatform)?; 676 677 let num_pci_segments: u16 = parser 678 .convert("num_pci_segments") 679 .map_err(Error::ParsePlatform)? 680 .unwrap_or(DEFAULT_NUM_PCI_SEGMENTS); 681 let iommu_segments = parser 682 .convert::<IntegerList>("iommu_segments") 683 .map_err(Error::ParsePlatform)? 684 .map(|v| v.0.iter().map(|e| *e as u16).collect()); 685 let serial_number = parser 686 .convert("serial_number") 687 .map_err(Error::ParsePlatform)?; 688 let uuid = parser.convert("uuid").map_err(Error::ParsePlatform)?; 689 let oem_strings = parser 690 .convert::<StringList>("oem_strings") 691 .map_err(Error::ParsePlatform)? 692 .map(|v| v.0); 693 #[cfg(feature = "tdx")] 694 let tdx = parser 695 .convert::<Toggle>("tdx") 696 .map_err(Error::ParsePlatform)? 697 .unwrap_or(Toggle(false)) 698 .0; 699 #[cfg(feature = "sev_snp")] 700 let sev_snp = parser 701 .convert::<Toggle>("sev_snp") 702 .map_err(Error::ParsePlatform)? 703 .unwrap_or(Toggle(false)) 704 .0; 705 Ok(PlatformConfig { 706 num_pci_segments, 707 iommu_segments, 708 serial_number, 709 uuid, 710 oem_strings, 711 #[cfg(feature = "tdx")] 712 tdx, 713 #[cfg(feature = "sev_snp")] 714 sev_snp, 715 }) 716 } 717 718 pub fn validate(&self) -> ValidationResult<()> { 719 if self.num_pci_segments == 0 || self.num_pci_segments > MAX_NUM_PCI_SEGMENTS { 720 return Err(ValidationError::InvalidNumPciSegments( 721 self.num_pci_segments, 722 )); 723 } 724 725 if let Some(iommu_segments) = &self.iommu_segments { 726 for segment in iommu_segments { 727 if *segment >= self.num_pci_segments { 728 return Err(ValidationError::InvalidPciSegment(*segment)); 729 } 730 } 731 } 732 733 Ok(()) 734 } 735 } 736 737 impl MemoryConfig { 738 pub fn parse(memory: &str, memory_zones: Option<Vec<&str>>) -> Result<Self> { 739 let mut parser = OptionParser::new(); 740 parser 741 .add("size") 742 .add("file") 743 .add("mergeable") 744 .add("hotplug_method") 745 .add("hotplug_size") 746 .add("hotplugged_size") 747 .add("shared") 748 .add("hugepages") 749 .add("hugepage_size") 750 .add("prefault") 751 .add("thp"); 752 parser.parse(memory).map_err(Error::ParseMemory)?; 753 754 let size = parser 755 .convert::<ByteSized>("size") 756 .map_err(Error::ParseMemory)? 757 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 758 .0; 759 let mergeable = parser 760 .convert::<Toggle>("mergeable") 761 .map_err(Error::ParseMemory)? 762 .unwrap_or(Toggle(false)) 763 .0; 764 let hotplug_method = parser 765 .convert("hotplug_method") 766 .map_err(Error::ParseMemory)? 767 .unwrap_or_default(); 768 let hotplug_size = parser 769 .convert::<ByteSized>("hotplug_size") 770 .map_err(Error::ParseMemory)? 771 .map(|v| v.0); 772 let hotplugged_size = parser 773 .convert::<ByteSized>("hotplugged_size") 774 .map_err(Error::ParseMemory)? 775 .map(|v| v.0); 776 let shared = parser 777 .convert::<Toggle>("shared") 778 .map_err(Error::ParseMemory)? 779 .unwrap_or(Toggle(false)) 780 .0; 781 let hugepages = parser 782 .convert::<Toggle>("hugepages") 783 .map_err(Error::ParseMemory)? 784 .unwrap_or(Toggle(false)) 785 .0; 786 let hugepage_size = parser 787 .convert::<ByteSized>("hugepage_size") 788 .map_err(Error::ParseMemory)? 789 .map(|v| v.0); 790 let prefault = parser 791 .convert::<Toggle>("prefault") 792 .map_err(Error::ParseMemory)? 793 .unwrap_or(Toggle(false)) 794 .0; 795 let thp = parser 796 .convert::<Toggle>("thp") 797 .map_err(Error::ParseMemory)? 798 .unwrap_or(Toggle(true)) 799 .0; 800 801 let zones: Option<Vec<MemoryZoneConfig>> = if let Some(memory_zones) = &memory_zones { 802 let mut zones = Vec::new(); 803 for memory_zone in memory_zones.iter() { 804 let mut parser = OptionParser::new(); 805 parser 806 .add("id") 807 .add("size") 808 .add("file") 809 .add("shared") 810 .add("hugepages") 811 .add("hugepage_size") 812 .add("host_numa_node") 813 .add("hotplug_size") 814 .add("hotplugged_size") 815 .add("prefault"); 816 parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?; 817 818 let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?; 819 let size = parser 820 .convert::<ByteSized>("size") 821 .map_err(Error::ParseMemoryZone)? 822 .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20)) 823 .0; 824 let file = parser.get("file").map(PathBuf::from); 825 let shared = parser 826 .convert::<Toggle>("shared") 827 .map_err(Error::ParseMemoryZone)? 828 .unwrap_or(Toggle(false)) 829 .0; 830 let hugepages = parser 831 .convert::<Toggle>("hugepages") 832 .map_err(Error::ParseMemoryZone)? 833 .unwrap_or(Toggle(false)) 834 .0; 835 let hugepage_size = parser 836 .convert::<ByteSized>("hugepage_size") 837 .map_err(Error::ParseMemoryZone)? 838 .map(|v| v.0); 839 840 let host_numa_node = parser 841 .convert::<u32>("host_numa_node") 842 .map_err(Error::ParseMemoryZone)?; 843 let hotplug_size = parser 844 .convert::<ByteSized>("hotplug_size") 845 .map_err(Error::ParseMemoryZone)? 846 .map(|v| v.0); 847 let hotplugged_size = parser 848 .convert::<ByteSized>("hotplugged_size") 849 .map_err(Error::ParseMemoryZone)? 850 .map(|v| v.0); 851 let prefault = parser 852 .convert::<Toggle>("prefault") 853 .map_err(Error::ParseMemoryZone)? 854 .unwrap_or(Toggle(false)) 855 .0; 856 857 zones.push(MemoryZoneConfig { 858 id, 859 size, 860 file, 861 shared, 862 hugepages, 863 hugepage_size, 864 host_numa_node, 865 hotplug_size, 866 hotplugged_size, 867 prefault, 868 }); 869 } 870 Some(zones) 871 } else { 872 None 873 }; 874 875 Ok(MemoryConfig { 876 size, 877 mergeable, 878 hotplug_method, 879 hotplug_size, 880 hotplugged_size, 881 shared, 882 hugepages, 883 hugepage_size, 884 prefault, 885 zones, 886 thp, 887 }) 888 } 889 890 pub fn total_size(&self) -> u64 { 891 let mut size = self.size; 892 if let Some(hotplugged_size) = self.hotplugged_size { 893 size += hotplugged_size; 894 } 895 896 if let Some(zones) = &self.zones { 897 for zone in zones.iter() { 898 size += zone.size; 899 if let Some(hotplugged_size) = zone.hotplugged_size { 900 size += hotplugged_size; 901 } 902 } 903 } 904 905 size 906 } 907 } 908 909 impl RateLimiterGroupConfig { 910 pub const SYNTAX: &'static str = "Rate Limit Group parameters \ 911 \"bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 912 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\ 913 id=<device_id>\""; 914 915 pub fn parse(rate_limit_group: &str) -> Result<Self> { 916 let mut parser = OptionParser::new(); 917 parser 918 .add("bw_size") 919 .add("bw_one_time_burst") 920 .add("bw_refill_time") 921 .add("ops_size") 922 .add("ops_one_time_burst") 923 .add("ops_refill_time") 924 .add("id"); 925 parser 926 .parse(rate_limit_group) 927 .map_err(Error::ParseRateLimiterGroup)?; 928 929 let id = parser.get("id").unwrap_or_default(); 930 let bw_size = parser 931 .convert("bw_size") 932 .map_err(Error::ParseRateLimiterGroup)? 933 .unwrap_or_default(); 934 let bw_one_time_burst = parser 935 .convert("bw_one_time_burst") 936 .map_err(Error::ParseRateLimiterGroup)? 937 .unwrap_or_default(); 938 let bw_refill_time = parser 939 .convert("bw_refill_time") 940 .map_err(Error::ParseRateLimiterGroup)? 941 .unwrap_or_default(); 942 let ops_size = parser 943 .convert("ops_size") 944 .map_err(Error::ParseRateLimiterGroup)? 945 .unwrap_or_default(); 946 let ops_one_time_burst = parser 947 .convert("ops_one_time_burst") 948 .map_err(Error::ParseRateLimiterGroup)? 949 .unwrap_or_default(); 950 let ops_refill_time = parser 951 .convert("ops_refill_time") 952 .map_err(Error::ParseRateLimiterGroup)? 953 .unwrap_or_default(); 954 955 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 956 Some(TokenBucketConfig { 957 size: bw_size, 958 one_time_burst: Some(bw_one_time_burst), 959 refill_time: bw_refill_time, 960 }) 961 } else { 962 None 963 }; 964 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 965 Some(TokenBucketConfig { 966 size: ops_size, 967 one_time_burst: Some(ops_one_time_burst), 968 refill_time: ops_refill_time, 969 }) 970 } else { 971 None 972 }; 973 974 Ok(RateLimiterGroupConfig { 975 id, 976 rate_limiter_config: RateLimiterConfig { 977 bandwidth: bw_tb_config, 978 ops: ops_tb_config, 979 }, 980 }) 981 } 982 983 pub fn validate(&self, _vm_config: &VmConfig) -> ValidationResult<()> { 984 if self.rate_limiter_config.bandwidth.is_none() && self.rate_limiter_config.ops.is_none() { 985 return Err(ValidationError::InvalidRateLimiterGroup); 986 } 987 988 if self.id.is_empty() { 989 return Err(ValidationError::InvalidRateLimiterGroup); 990 } 991 992 Ok(()) 993 } 994 } 995 996 impl DiskConfig { 997 pub const SYNTAX: &'static str = "Disk parameters \ 998 \"path=<disk_image_path>,readonly=on|off,direct=on|off,iommu=on|off,\ 999 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,\ 1000 vhost_user=on|off,socket=<vhost_user_socket_path>,\ 1001 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 1002 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\ 1003 id=<device_id>,pci_segment=<segment_id>,rate_limit_group=<group_id>,\ 1004 queue_affinity=<list_of_queue_indices_with_their_associated_cpuset>"; 1005 1006 pub fn parse(disk: &str) -> Result<Self> { 1007 let mut parser = OptionParser::new(); 1008 parser 1009 .add("path") 1010 .add("readonly") 1011 .add("direct") 1012 .add("iommu") 1013 .add("queue_size") 1014 .add("num_queues") 1015 .add("vhost_user") 1016 .add("socket") 1017 .add("bw_size") 1018 .add("bw_one_time_burst") 1019 .add("bw_refill_time") 1020 .add("ops_size") 1021 .add("ops_one_time_burst") 1022 .add("ops_refill_time") 1023 .add("id") 1024 .add("_disable_io_uring") 1025 .add("_disable_aio") 1026 .add("pci_segment") 1027 .add("serial") 1028 .add("rate_limit_group") 1029 .add("queue_affinity"); 1030 parser.parse(disk).map_err(Error::ParseDisk)?; 1031 1032 let path = parser.get("path").map(PathBuf::from); 1033 let readonly = parser 1034 .convert::<Toggle>("readonly") 1035 .map_err(Error::ParseDisk)? 1036 .unwrap_or(Toggle(false)) 1037 .0; 1038 let direct = parser 1039 .convert::<Toggle>("direct") 1040 .map_err(Error::ParseDisk)? 1041 .unwrap_or(Toggle(false)) 1042 .0; 1043 let iommu = parser 1044 .convert::<Toggle>("iommu") 1045 .map_err(Error::ParseDisk)? 1046 .unwrap_or(Toggle(false)) 1047 .0; 1048 let queue_size = parser 1049 .convert("queue_size") 1050 .map_err(Error::ParseDisk)? 1051 .unwrap_or_else(default_diskconfig_queue_size); 1052 let num_queues = parser 1053 .convert("num_queues") 1054 .map_err(Error::ParseDisk)? 1055 .unwrap_or_else(default_diskconfig_num_queues); 1056 let vhost_user = parser 1057 .convert::<Toggle>("vhost_user") 1058 .map_err(Error::ParseDisk)? 1059 .unwrap_or(Toggle(false)) 1060 .0; 1061 let vhost_socket = parser.get("socket"); 1062 let id = parser.get("id"); 1063 let disable_io_uring = parser 1064 .convert::<Toggle>("_disable_io_uring") 1065 .map_err(Error::ParseDisk)? 1066 .unwrap_or(Toggle(false)) 1067 .0; 1068 let disable_aio = parser 1069 .convert::<Toggle>("_disable_aio") 1070 .map_err(Error::ParseDisk)? 1071 .unwrap_or(Toggle(false)) 1072 .0; 1073 let pci_segment = parser 1074 .convert("pci_segment") 1075 .map_err(Error::ParseDisk)? 1076 .unwrap_or_default(); 1077 let rate_limit_group = parser.get("rate_limit_group"); 1078 let bw_size = parser 1079 .convert("bw_size") 1080 .map_err(Error::ParseDisk)? 1081 .unwrap_or_default(); 1082 let bw_one_time_burst = parser 1083 .convert("bw_one_time_burst") 1084 .map_err(Error::ParseDisk)? 1085 .unwrap_or_default(); 1086 let bw_refill_time = parser 1087 .convert("bw_refill_time") 1088 .map_err(Error::ParseDisk)? 1089 .unwrap_or_default(); 1090 let ops_size = parser 1091 .convert("ops_size") 1092 .map_err(Error::ParseDisk)? 1093 .unwrap_or_default(); 1094 let ops_one_time_burst = parser 1095 .convert("ops_one_time_burst") 1096 .map_err(Error::ParseDisk)? 1097 .unwrap_or_default(); 1098 let ops_refill_time = parser 1099 .convert("ops_refill_time") 1100 .map_err(Error::ParseDisk)? 1101 .unwrap_or_default(); 1102 let serial = parser.get("serial"); 1103 let queue_affinity = parser 1104 .convert::<Tuple<u16, Vec<usize>>>("queue_affinity") 1105 .map_err(Error::ParseDisk)? 1106 .map(|v| { 1107 v.0.iter() 1108 .map(|(e1, e2)| VirtQueueAffinity { 1109 queue_index: *e1, 1110 host_cpus: e2.clone(), 1111 }) 1112 .collect() 1113 }); 1114 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 1115 Some(TokenBucketConfig { 1116 size: bw_size, 1117 one_time_burst: Some(bw_one_time_burst), 1118 refill_time: bw_refill_time, 1119 }) 1120 } else { 1121 None 1122 }; 1123 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 1124 Some(TokenBucketConfig { 1125 size: ops_size, 1126 one_time_burst: Some(ops_one_time_burst), 1127 refill_time: ops_refill_time, 1128 }) 1129 } else { 1130 None 1131 }; 1132 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 1133 Some(RateLimiterConfig { 1134 bandwidth: bw_tb_config, 1135 ops: ops_tb_config, 1136 }) 1137 } else { 1138 None 1139 }; 1140 1141 Ok(DiskConfig { 1142 path, 1143 readonly, 1144 direct, 1145 iommu, 1146 num_queues, 1147 queue_size, 1148 vhost_user, 1149 vhost_socket, 1150 rate_limit_group, 1151 rate_limiter_config, 1152 id, 1153 disable_io_uring, 1154 disable_aio, 1155 pci_segment, 1156 serial, 1157 queue_affinity, 1158 }) 1159 } 1160 1161 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1162 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 1163 return Err(ValidationError::TooManyQueues); 1164 } 1165 1166 if self.vhost_user && self.iommu { 1167 return Err(ValidationError::IommuNotSupported); 1168 } 1169 1170 if let Some(platform_config) = vm_config.platform.as_ref() { 1171 if self.pci_segment >= platform_config.num_pci_segments { 1172 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1173 } 1174 1175 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1176 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1177 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1178 } 1179 } 1180 } 1181 1182 if self.rate_limiter_config.is_some() && self.rate_limit_group.is_some() { 1183 return Err(ValidationError::InvalidRateLimiterGroup); 1184 } 1185 1186 Ok(()) 1187 } 1188 } 1189 1190 #[derive(Debug)] 1191 pub enum ParseVhostModeError { 1192 InvalidValue(String), 1193 } 1194 1195 impl FromStr for VhostMode { 1196 type Err = ParseVhostModeError; 1197 1198 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 1199 match s.to_lowercase().as_str() { 1200 "client" => Ok(VhostMode::Client), 1201 "server" => Ok(VhostMode::Server), 1202 _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())), 1203 } 1204 } 1205 } 1206 1207 impl NetConfig { 1208 pub const SYNTAX: &'static str = "Network parameters \ 1209 \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1,fd2...>,iommu=on|off,\ 1210 num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\ 1211 vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\ 1212 bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\ 1213 ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,pci_segment=<segment_id>\ 1214 offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\""; 1215 1216 pub fn parse(net: &str) -> Result<Self> { 1217 let mut parser = OptionParser::new(); 1218 1219 parser 1220 .add("tap") 1221 .add("ip") 1222 .add("mask") 1223 .add("mac") 1224 .add("host_mac") 1225 .add("offload_tso") 1226 .add("offload_ufo") 1227 .add("offload_csum") 1228 .add("mtu") 1229 .add("iommu") 1230 .add("queue_size") 1231 .add("num_queues") 1232 .add("vhost_user") 1233 .add("socket") 1234 .add("vhost_mode") 1235 .add("id") 1236 .add("fd") 1237 .add("bw_size") 1238 .add("bw_one_time_burst") 1239 .add("bw_refill_time") 1240 .add("ops_size") 1241 .add("ops_one_time_burst") 1242 .add("ops_refill_time") 1243 .add("pci_segment"); 1244 parser.parse(net).map_err(Error::ParseNetwork)?; 1245 1246 let tap = parser.get("tap"); 1247 let ip = parser 1248 .convert("ip") 1249 .map_err(Error::ParseNetwork)? 1250 .unwrap_or_else(default_netconfig_ip); 1251 let mask = parser 1252 .convert("mask") 1253 .map_err(Error::ParseNetwork)? 1254 .unwrap_or_else(default_netconfig_mask); 1255 let mac = parser 1256 .convert("mac") 1257 .map_err(Error::ParseNetwork)? 1258 .unwrap_or_else(default_netconfig_mac); 1259 let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?; 1260 let offload_tso = parser 1261 .convert::<Toggle>("offload_tso") 1262 .map_err(Error::ParseNetwork)? 1263 .unwrap_or(Toggle(true)) 1264 .0; 1265 let offload_ufo = parser 1266 .convert::<Toggle>("offload_ufo") 1267 .map_err(Error::ParseNetwork)? 1268 .unwrap_or(Toggle(true)) 1269 .0; 1270 let offload_csum = parser 1271 .convert::<Toggle>("offload_csum") 1272 .map_err(Error::ParseNetwork)? 1273 .unwrap_or(Toggle(true)) 1274 .0; 1275 let mtu = parser.convert("mtu").map_err(Error::ParseNetwork)?; 1276 let iommu = parser 1277 .convert::<Toggle>("iommu") 1278 .map_err(Error::ParseNetwork)? 1279 .unwrap_or(Toggle(false)) 1280 .0; 1281 let queue_size = parser 1282 .convert("queue_size") 1283 .map_err(Error::ParseNetwork)? 1284 .unwrap_or_else(default_netconfig_queue_size); 1285 let num_queues = parser 1286 .convert("num_queues") 1287 .map_err(Error::ParseNetwork)? 1288 .unwrap_or_else(default_netconfig_num_queues); 1289 let vhost_user = parser 1290 .convert::<Toggle>("vhost_user") 1291 .map_err(Error::ParseNetwork)? 1292 .unwrap_or(Toggle(false)) 1293 .0; 1294 let vhost_socket = parser.get("socket"); 1295 let vhost_mode = parser 1296 .convert("vhost_mode") 1297 .map_err(Error::ParseNetwork)? 1298 .unwrap_or_default(); 1299 let id = parser.get("id"); 1300 let fds = parser 1301 .convert::<IntegerList>("fd") 1302 .map_err(Error::ParseNetwork)? 1303 .map(|v| v.0.iter().map(|e| *e as i32).collect()); 1304 let pci_segment = parser 1305 .convert("pci_segment") 1306 .map_err(Error::ParseNetwork)? 1307 .unwrap_or_default(); 1308 let bw_size = parser 1309 .convert("bw_size") 1310 .map_err(Error::ParseNetwork)? 1311 .unwrap_or_default(); 1312 let bw_one_time_burst = parser 1313 .convert("bw_one_time_burst") 1314 .map_err(Error::ParseNetwork)? 1315 .unwrap_or_default(); 1316 let bw_refill_time = parser 1317 .convert("bw_refill_time") 1318 .map_err(Error::ParseNetwork)? 1319 .unwrap_or_default(); 1320 let ops_size = parser 1321 .convert("ops_size") 1322 .map_err(Error::ParseNetwork)? 1323 .unwrap_or_default(); 1324 let ops_one_time_burst = parser 1325 .convert("ops_one_time_burst") 1326 .map_err(Error::ParseNetwork)? 1327 .unwrap_or_default(); 1328 let ops_refill_time = parser 1329 .convert("ops_refill_time") 1330 .map_err(Error::ParseNetwork)? 1331 .unwrap_or_default(); 1332 let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 { 1333 Some(TokenBucketConfig { 1334 size: bw_size, 1335 one_time_burst: Some(bw_one_time_burst), 1336 refill_time: bw_refill_time, 1337 }) 1338 } else { 1339 None 1340 }; 1341 let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 { 1342 Some(TokenBucketConfig { 1343 size: ops_size, 1344 one_time_burst: Some(ops_one_time_burst), 1345 refill_time: ops_refill_time, 1346 }) 1347 } else { 1348 None 1349 }; 1350 let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() { 1351 Some(RateLimiterConfig { 1352 bandwidth: bw_tb_config, 1353 ops: ops_tb_config, 1354 }) 1355 } else { 1356 None 1357 }; 1358 1359 let config = NetConfig { 1360 tap, 1361 ip, 1362 mask, 1363 mac, 1364 host_mac, 1365 mtu, 1366 iommu, 1367 num_queues, 1368 queue_size, 1369 vhost_user, 1370 vhost_socket, 1371 vhost_mode, 1372 id, 1373 fds, 1374 rate_limiter_config, 1375 pci_segment, 1376 offload_tso, 1377 offload_ufo, 1378 offload_csum, 1379 }; 1380 Ok(config) 1381 } 1382 1383 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1384 if self.num_queues < 2 { 1385 return Err(ValidationError::VnetQueueLowerThan2); 1386 } 1387 1388 if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues { 1389 return Err(ValidationError::VnetQueueFdMismatch); 1390 } 1391 1392 if let Some(fds) = self.fds.as_ref() { 1393 for fd in fds { 1394 if *fd <= 2 { 1395 return Err(ValidationError::VnetReservedFd); 1396 } 1397 } 1398 } 1399 1400 if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize { 1401 return Err(ValidationError::TooManyQueues); 1402 } 1403 1404 if self.vhost_user && self.iommu { 1405 return Err(ValidationError::IommuNotSupported); 1406 } 1407 1408 if let Some(platform_config) = vm_config.platform.as_ref() { 1409 if self.pci_segment >= platform_config.num_pci_segments { 1410 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1411 } 1412 1413 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1414 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1415 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1416 } 1417 } 1418 } 1419 1420 if let Some(mtu) = self.mtu { 1421 if mtu < virtio_devices::net::MIN_MTU { 1422 return Err(ValidationError::InvalidMtu(mtu)); 1423 } 1424 } 1425 1426 if !self.offload_csum && (self.offload_tso || self.offload_ufo) { 1427 return Err(ValidationError::NoHardwareChecksumOffload); 1428 } 1429 1430 Ok(()) 1431 } 1432 } 1433 1434 impl RngConfig { 1435 pub fn parse(rng: &str) -> Result<Self> { 1436 let mut parser = OptionParser::new(); 1437 parser.add("src").add("iommu"); 1438 parser.parse(rng).map_err(Error::ParseRng)?; 1439 1440 let src = PathBuf::from( 1441 parser 1442 .get("src") 1443 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()), 1444 ); 1445 let iommu = parser 1446 .convert::<Toggle>("iommu") 1447 .map_err(Error::ParseRng)? 1448 .unwrap_or(Toggle(false)) 1449 .0; 1450 1451 Ok(RngConfig { src, iommu }) 1452 } 1453 } 1454 1455 impl BalloonConfig { 1456 pub const SYNTAX: &'static str = 1457 "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\ 1458 free_page_reporting=on|off\""; 1459 1460 pub fn parse(balloon: &str) -> Result<Self> { 1461 let mut parser = OptionParser::new(); 1462 parser.add("size"); 1463 parser.add("deflate_on_oom"); 1464 parser.add("free_page_reporting"); 1465 parser.parse(balloon).map_err(Error::ParseBalloon)?; 1466 1467 let size = parser 1468 .convert::<ByteSized>("size") 1469 .map_err(Error::ParseBalloon)? 1470 .map(|v| v.0) 1471 .unwrap_or(0); 1472 1473 let deflate_on_oom = parser 1474 .convert::<Toggle>("deflate_on_oom") 1475 .map_err(Error::ParseBalloon)? 1476 .unwrap_or(Toggle(false)) 1477 .0; 1478 1479 let free_page_reporting = parser 1480 .convert::<Toggle>("free_page_reporting") 1481 .map_err(Error::ParseBalloon)? 1482 .unwrap_or(Toggle(false)) 1483 .0; 1484 1485 Ok(BalloonConfig { 1486 size, 1487 deflate_on_oom, 1488 free_page_reporting, 1489 }) 1490 } 1491 } 1492 1493 impl FsConfig { 1494 pub const SYNTAX: &'static str = "virtio-fs parameters \ 1495 \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\ 1496 queue_size=<size_of_each_queue>,id=<device_id>,pci_segment=<segment_id>\""; 1497 1498 pub fn parse(fs: &str) -> Result<Self> { 1499 let mut parser = OptionParser::new(); 1500 parser 1501 .add("tag") 1502 .add("queue_size") 1503 .add("num_queues") 1504 .add("socket") 1505 .add("id") 1506 .add("pci_segment"); 1507 parser.parse(fs).map_err(Error::ParseFileSystem)?; 1508 1509 let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?; 1510 let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?); 1511 1512 let queue_size = parser 1513 .convert("queue_size") 1514 .map_err(Error::ParseFileSystem)? 1515 .unwrap_or_else(default_fsconfig_queue_size); 1516 let num_queues = parser 1517 .convert("num_queues") 1518 .map_err(Error::ParseFileSystem)? 1519 .unwrap_or_else(default_fsconfig_num_queues); 1520 1521 let id = parser.get("id"); 1522 1523 let pci_segment = parser 1524 .convert("pci_segment") 1525 .map_err(Error::ParseFileSystem)? 1526 .unwrap_or_default(); 1527 1528 Ok(FsConfig { 1529 tag, 1530 socket, 1531 num_queues, 1532 queue_size, 1533 id, 1534 pci_segment, 1535 }) 1536 } 1537 1538 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1539 if self.num_queues > vm_config.cpus.boot_vcpus as usize { 1540 return Err(ValidationError::TooManyQueues); 1541 } 1542 1543 if let Some(platform_config) = vm_config.platform.as_ref() { 1544 if self.pci_segment >= platform_config.num_pci_segments { 1545 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1546 } 1547 1548 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1549 if iommu_segments.contains(&self.pci_segment) { 1550 return Err(ValidationError::IommuNotSupportedOnSegment( 1551 self.pci_segment, 1552 )); 1553 } 1554 } 1555 } 1556 1557 Ok(()) 1558 } 1559 } 1560 1561 impl PmemConfig { 1562 pub const SYNTAX: &'static str = "Persistent memory parameters \ 1563 \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\ 1564 discard_writes=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1565 1566 pub fn parse(pmem: &str) -> Result<Self> { 1567 let mut parser = OptionParser::new(); 1568 parser 1569 .add("size") 1570 .add("file") 1571 .add("iommu") 1572 .add("discard_writes") 1573 .add("id") 1574 .add("pci_segment"); 1575 parser.parse(pmem).map_err(Error::ParsePersistentMemory)?; 1576 1577 let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?); 1578 let size = parser 1579 .convert::<ByteSized>("size") 1580 .map_err(Error::ParsePersistentMemory)? 1581 .map(|v| v.0); 1582 let iommu = parser 1583 .convert::<Toggle>("iommu") 1584 .map_err(Error::ParsePersistentMemory)? 1585 .unwrap_or(Toggle(false)) 1586 .0; 1587 let discard_writes = parser 1588 .convert::<Toggle>("discard_writes") 1589 .map_err(Error::ParsePersistentMemory)? 1590 .unwrap_or(Toggle(false)) 1591 .0; 1592 let id = parser.get("id"); 1593 let pci_segment = parser 1594 .convert("pci_segment") 1595 .map_err(Error::ParsePersistentMemory)? 1596 .unwrap_or_default(); 1597 1598 Ok(PmemConfig { 1599 file, 1600 size, 1601 iommu, 1602 discard_writes, 1603 id, 1604 pci_segment, 1605 }) 1606 } 1607 1608 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1609 if let Some(platform_config) = vm_config.platform.as_ref() { 1610 if self.pci_segment >= platform_config.num_pci_segments { 1611 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1612 } 1613 1614 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1615 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1616 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1617 } 1618 } 1619 } 1620 1621 Ok(()) 1622 } 1623 } 1624 1625 impl ConsoleConfig { 1626 pub fn parse(console: &str) -> Result<Self> { 1627 let mut parser = OptionParser::new(); 1628 parser 1629 .add_valueless("off") 1630 .add_valueless("pty") 1631 .add_valueless("tty") 1632 .add_valueless("null") 1633 .add("file") 1634 .add("iommu") 1635 .add("socket"); 1636 parser.parse(console).map_err(Error::ParseConsole)?; 1637 1638 let mut file: Option<PathBuf> = default_consoleconfig_file(); 1639 let mut socket: Option<PathBuf> = None; 1640 let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; 1641 1642 if parser.is_set("off") { 1643 } else if parser.is_set("pty") { 1644 mode = ConsoleOutputMode::Pty 1645 } else if parser.is_set("tty") { 1646 mode = ConsoleOutputMode::Tty 1647 } else if parser.is_set("null") { 1648 mode = ConsoleOutputMode::Null 1649 } else if parser.is_set("file") { 1650 mode = ConsoleOutputMode::File; 1651 file = 1652 Some(PathBuf::from(parser.get("file").ok_or( 1653 Error::Validation(ValidationError::ConsoleFileMissing), 1654 )?)); 1655 } else if parser.is_set("socket") { 1656 mode = ConsoleOutputMode::Socket; 1657 socket = Some(PathBuf::from(parser.get("socket").ok_or( 1658 Error::Validation(ValidationError::ConsoleSocketPathMissing), 1659 )?)); 1660 } else { 1661 return Err(Error::ParseConsoleInvalidModeGiven); 1662 } 1663 let iommu = parser 1664 .convert::<Toggle>("iommu") 1665 .map_err(Error::ParseConsole)? 1666 .unwrap_or(Toggle(false)) 1667 .0; 1668 1669 Ok(Self { 1670 file, 1671 mode, 1672 iommu, 1673 socket, 1674 }) 1675 } 1676 } 1677 1678 #[cfg(target_arch = "x86_64")] 1679 impl DebugConsoleConfig { 1680 pub fn parse(debug_console_ops: &str) -> Result<Self> { 1681 let mut parser = OptionParser::new(); 1682 parser 1683 .add_valueless("off") 1684 .add_valueless("pty") 1685 .add_valueless("tty") 1686 .add_valueless("null") 1687 .add("file") 1688 .add("iobase"); 1689 parser 1690 .parse(debug_console_ops) 1691 .map_err(Error::ParseConsole)?; 1692 1693 let mut file: Option<PathBuf> = default_consoleconfig_file(); 1694 let mut iobase: Option<u16> = None; 1695 let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; 1696 1697 if parser.is_set("off") { 1698 } else if parser.is_set("pty") { 1699 mode = ConsoleOutputMode::Pty 1700 } else if parser.is_set("tty") { 1701 mode = ConsoleOutputMode::Tty 1702 } else if parser.is_set("null") { 1703 mode = ConsoleOutputMode::Null 1704 } else if parser.is_set("file") { 1705 mode = ConsoleOutputMode::File; 1706 file = 1707 Some(PathBuf::from(parser.get("file").ok_or( 1708 Error::Validation(ValidationError::ConsoleFileMissing), 1709 )?)); 1710 } else { 1711 return Err(Error::ParseConsoleInvalidModeGiven); 1712 } 1713 1714 if parser.is_set("iobase") { 1715 if let Some(iobase_opt) = parser.get("iobase") { 1716 if !iobase_opt.starts_with("0x") { 1717 return Err(Error::Validation(ValidationError::InvalidIoPortHex( 1718 iobase_opt, 1719 ))); 1720 } 1721 iobase = Some(u16::from_str_radix(&iobase_opt[2..], 16).map_err(|_| { 1722 Error::Validation(ValidationError::InvalidIoPortHex(iobase_opt)) 1723 })?); 1724 } 1725 } 1726 1727 Ok(Self { file, mode, iobase }) 1728 } 1729 } 1730 1731 impl DeviceConfig { 1732 pub const SYNTAX: &'static str = 1733 "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1734 1735 pub fn parse(device: &str) -> Result<Self> { 1736 let mut parser = OptionParser::new(); 1737 parser.add("path").add("id").add("iommu").add("pci_segment"); 1738 parser.parse(device).map_err(Error::ParseDevice)?; 1739 1740 let path = parser 1741 .get("path") 1742 .map(PathBuf::from) 1743 .ok_or(Error::ParseDevicePathMissing)?; 1744 let iommu = parser 1745 .convert::<Toggle>("iommu") 1746 .map_err(Error::ParseDevice)? 1747 .unwrap_or(Toggle(false)) 1748 .0; 1749 let id = parser.get("id"); 1750 let pci_segment = parser 1751 .convert::<u16>("pci_segment") 1752 .map_err(Error::ParseDevice)? 1753 .unwrap_or_default(); 1754 1755 Ok(DeviceConfig { 1756 path, 1757 iommu, 1758 id, 1759 pci_segment, 1760 }) 1761 } 1762 1763 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1764 if let Some(platform_config) = vm_config.platform.as_ref() { 1765 if self.pci_segment >= platform_config.num_pci_segments { 1766 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1767 } 1768 1769 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1770 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1771 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1772 } 1773 } 1774 } 1775 1776 Ok(()) 1777 } 1778 } 1779 1780 impl UserDeviceConfig { 1781 pub const SYNTAX: &'static str = 1782 "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\""; 1783 1784 pub fn parse(user_device: &str) -> Result<Self> { 1785 let mut parser = OptionParser::new(); 1786 parser.add("socket").add("id").add("pci_segment"); 1787 parser.parse(user_device).map_err(Error::ParseUserDevice)?; 1788 1789 let socket = parser 1790 .get("socket") 1791 .map(PathBuf::from) 1792 .ok_or(Error::ParseUserDeviceSocketMissing)?; 1793 let id = parser.get("id"); 1794 let pci_segment = parser 1795 .convert::<u16>("pci_segment") 1796 .map_err(Error::ParseUserDevice)? 1797 .unwrap_or_default(); 1798 1799 Ok(UserDeviceConfig { 1800 socket, 1801 id, 1802 pci_segment, 1803 }) 1804 } 1805 1806 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1807 if let Some(platform_config) = vm_config.platform.as_ref() { 1808 if self.pci_segment >= platform_config.num_pci_segments { 1809 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1810 } 1811 1812 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1813 if iommu_segments.contains(&self.pci_segment) { 1814 return Err(ValidationError::IommuNotSupportedOnSegment( 1815 self.pci_segment, 1816 )); 1817 } 1818 } 1819 } 1820 1821 Ok(()) 1822 } 1823 } 1824 1825 impl VdpaConfig { 1826 pub const SYNTAX: &'static str = "vDPA device \ 1827 \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\ 1828 id=<device_id>,pci_segment=<segment_id>\""; 1829 1830 pub fn parse(vdpa: &str) -> Result<Self> { 1831 let mut parser = OptionParser::new(); 1832 parser 1833 .add("path") 1834 .add("num_queues") 1835 .add("iommu") 1836 .add("id") 1837 .add("pci_segment"); 1838 parser.parse(vdpa).map_err(Error::ParseVdpa)?; 1839 1840 let path = parser 1841 .get("path") 1842 .map(PathBuf::from) 1843 .ok_or(Error::ParseVdpaPathMissing)?; 1844 let num_queues = parser 1845 .convert("num_queues") 1846 .map_err(Error::ParseVdpa)? 1847 .unwrap_or_else(default_vdpaconfig_num_queues); 1848 let iommu = parser 1849 .convert::<Toggle>("iommu") 1850 .map_err(Error::ParseVdpa)? 1851 .unwrap_or(Toggle(false)) 1852 .0; 1853 let id = parser.get("id"); 1854 let pci_segment = parser 1855 .convert("pci_segment") 1856 .map_err(Error::ParseVdpa)? 1857 .unwrap_or_default(); 1858 1859 Ok(VdpaConfig { 1860 path, 1861 num_queues, 1862 iommu, 1863 id, 1864 pci_segment, 1865 }) 1866 } 1867 1868 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1869 if let Some(platform_config) = vm_config.platform.as_ref() { 1870 if self.pci_segment >= platform_config.num_pci_segments { 1871 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1872 } 1873 1874 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1875 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1876 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1877 } 1878 } 1879 } 1880 1881 Ok(()) 1882 } 1883 } 1884 1885 impl VsockConfig { 1886 pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ 1887 \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1888 1889 pub fn parse(vsock: &str) -> Result<Self> { 1890 let mut parser = OptionParser::new(); 1891 parser 1892 .add("socket") 1893 .add("cid") 1894 .add("iommu") 1895 .add("id") 1896 .add("pci_segment"); 1897 parser.parse(vsock).map_err(Error::ParseVsock)?; 1898 1899 let socket = parser 1900 .get("socket") 1901 .map(PathBuf::from) 1902 .ok_or(Error::ParseVsockSockMissing)?; 1903 let iommu = parser 1904 .convert::<Toggle>("iommu") 1905 .map_err(Error::ParseVsock)? 1906 .unwrap_or(Toggle(false)) 1907 .0; 1908 let cid = parser 1909 .convert("cid") 1910 .map_err(Error::ParseVsock)? 1911 .ok_or(Error::ParseVsockCidMissing)?; 1912 let id = parser.get("id"); 1913 let pci_segment = parser 1914 .convert("pci_segment") 1915 .map_err(Error::ParseVsock)? 1916 .unwrap_or_default(); 1917 1918 Ok(VsockConfig { 1919 cid, 1920 socket, 1921 iommu, 1922 id, 1923 pci_segment, 1924 }) 1925 } 1926 1927 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1928 if let Some(platform_config) = vm_config.platform.as_ref() { 1929 if self.pci_segment >= platform_config.num_pci_segments { 1930 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1931 } 1932 1933 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1934 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1935 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1936 } 1937 } 1938 } 1939 1940 Ok(()) 1941 } 1942 } 1943 1944 #[cfg(target_arch = "x86_64")] 1945 impl SgxEpcConfig { 1946 pub const SYNTAX: &'static str = "SGX EPC parameters \ 1947 \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\""; 1948 1949 pub fn parse(sgx_epc: &str) -> Result<Self> { 1950 let mut parser = OptionParser::new(); 1951 parser.add("id").add("size").add("prefault"); 1952 parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?; 1953 1954 let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?; 1955 let size = parser 1956 .convert::<ByteSized>("size") 1957 .map_err(Error::ParseSgxEpc)? 1958 .unwrap_or(ByteSized(0)) 1959 .0; 1960 let prefault = parser 1961 .convert::<Toggle>("prefault") 1962 .map_err(Error::ParseSgxEpc)? 1963 .unwrap_or(Toggle(false)) 1964 .0; 1965 1966 Ok(SgxEpcConfig { id, size, prefault }) 1967 } 1968 } 1969 1970 impl NumaConfig { 1971 pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ 1972 \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\ 1973 memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>,\ 1974 pci_segments=<list_of_pci_segments>\""; 1975 1976 pub fn parse(numa: &str) -> Result<Self> { 1977 let mut parser = OptionParser::new(); 1978 parser 1979 .add("guest_numa_id") 1980 .add("cpus") 1981 .add("distances") 1982 .add("memory_zones") 1983 .add("sgx_epc_sections") 1984 .add("pci_segments"); 1985 1986 parser.parse(numa).map_err(Error::ParseNuma)?; 1987 1988 let guest_numa_id = parser 1989 .convert::<u32>("guest_numa_id") 1990 .map_err(Error::ParseNuma)? 1991 .unwrap_or(0); 1992 let cpus = parser 1993 .convert::<IntegerList>("cpus") 1994 .map_err(Error::ParseNuma)? 1995 .map(|v| v.0.iter().map(|e| *e as u8).collect()); 1996 let distances = parser 1997 .convert::<Tuple<u64, u64>>("distances") 1998 .map_err(Error::ParseNuma)? 1999 .map(|v| { 2000 v.0.iter() 2001 .map(|(e1, e2)| NumaDistance { 2002 destination: *e1 as u32, 2003 distance: *e2 as u8, 2004 }) 2005 .collect() 2006 }); 2007 let memory_zones = parser 2008 .convert::<StringList>("memory_zones") 2009 .map_err(Error::ParseNuma)? 2010 .map(|v| v.0); 2011 #[cfg(target_arch = "x86_64")] 2012 let sgx_epc_sections = parser 2013 .convert::<StringList>("sgx_epc_sections") 2014 .map_err(Error::ParseNuma)? 2015 .map(|v| v.0); 2016 let pci_segments = parser 2017 .convert::<IntegerList>("pci_segments") 2018 .map_err(Error::ParseNuma)? 2019 .map(|v| v.0.iter().map(|e| *e as u16).collect()); 2020 Ok(NumaConfig { 2021 guest_numa_id, 2022 cpus, 2023 distances, 2024 memory_zones, 2025 #[cfg(target_arch = "x86_64")] 2026 sgx_epc_sections, 2027 pci_segments, 2028 }) 2029 } 2030 } 2031 2032 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] 2033 pub struct RestoreConfig { 2034 pub source_url: PathBuf, 2035 #[serde(default)] 2036 pub prefault: bool, 2037 } 2038 2039 impl RestoreConfig { 2040 pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ 2041 \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \ 2042 \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ 2043 \n`prefault` brings memory pages in when enabled (disabled by default)"; 2044 2045 pub fn parse(restore: &str) -> Result<Self> { 2046 let mut parser = OptionParser::new(); 2047 parser.add("source_url").add("prefault"); 2048 parser.parse(restore).map_err(Error::ParseRestore)?; 2049 2050 let source_url = parser 2051 .get("source_url") 2052 .map(PathBuf::from) 2053 .ok_or(Error::ParseRestoreSourceUrlMissing)?; 2054 let prefault = parser 2055 .convert::<Toggle>("prefault") 2056 .map_err(Error::ParseRestore)? 2057 .unwrap_or(Toggle(false)) 2058 .0; 2059 2060 Ok(RestoreConfig { 2061 source_url, 2062 prefault, 2063 }) 2064 } 2065 } 2066 2067 impl TpmConfig { 2068 pub const SYNTAX: &'static str = "TPM device \ 2069 \"(UNIX Domain Socket from swtpm) socket=</path/to/a/socket>\""; 2070 2071 pub fn parse(tpm: &str) -> Result<Self> { 2072 let mut parser = OptionParser::new(); 2073 parser.add("socket"); 2074 parser.parse(tpm).map_err(Error::ParseTpm)?; 2075 let socket = parser 2076 .get("socket") 2077 .map(PathBuf::from) 2078 .ok_or(Error::ParseTpmPathMissing)?; 2079 Ok(TpmConfig { socket }) 2080 } 2081 } 2082 2083 impl VmConfig { 2084 fn validate_identifier( 2085 id_list: &mut BTreeSet<String>, 2086 id: &Option<String>, 2087 ) -> ValidationResult<()> { 2088 if let Some(id) = id.as_ref() { 2089 if id.starts_with("__") { 2090 return Err(ValidationError::InvalidIdentifier(id.clone())); 2091 } 2092 2093 if !id_list.insert(id.clone()) { 2094 return Err(ValidationError::IdentifierNotUnique(id.clone())); 2095 } 2096 } 2097 2098 Ok(()) 2099 } 2100 2101 pub fn backed_by_shared_memory(&self) -> bool { 2102 if self.memory.shared || self.memory.hugepages { 2103 return true; 2104 } 2105 2106 if self.memory.size == 0 { 2107 for zone in self.memory.zones.as_ref().unwrap() { 2108 if !zone.shared && !zone.hugepages { 2109 return false; 2110 } 2111 } 2112 true 2113 } else { 2114 false 2115 } 2116 } 2117 2118 // Also enables virtio-iommu if the config needs it 2119 // Returns the list of unique identifiers provided through the 2120 // configuration. 2121 pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> { 2122 let mut id_list = BTreeSet::new(); 2123 2124 self.payload 2125 .as_ref() 2126 .ok_or(ValidationError::KernelMissing)?; 2127 2128 #[cfg(feature = "tdx")] 2129 { 2130 let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false); 2131 // At this point we know payload isn't None. 2132 if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() { 2133 return Err(ValidationError::TdxFirmwareMissing); 2134 } 2135 if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) { 2136 return Err(ValidationError::TdxNoCpuHotplug); 2137 } 2138 } 2139 2140 // The 'conflict' check is introduced in commit 24438e0390d3 2141 // (vm-virtio: Enable the vmm support for virtio-console). 2142 // 2143 // Allow simultaneously set serial and console as TTY mode, for 2144 // someone want to use virtio console for better performance, and 2145 // want to keep legacy serial to catch boot stage logs for debug. 2146 // Using such double tty mode, you need to configure the kernel 2147 // properly, such as: 2148 // "console=hvc0 earlyprintk=ttyS0" 2149 2150 let mut tty_consoles = Vec::new(); 2151 if self.console.mode == ConsoleOutputMode::Tty { 2152 tty_consoles.push("virtio-console"); 2153 }; 2154 if self.serial.mode == ConsoleOutputMode::Tty { 2155 tty_consoles.push("serial-console"); 2156 }; 2157 #[cfg(target_arch = "x86_64")] 2158 if self.debug_console.mode == ConsoleOutputMode::Tty { 2159 tty_consoles.push("debug-console"); 2160 }; 2161 if tty_consoles.len() > 1 { 2162 warn!("Using TTY output for multiple consoles: {:?}", tty_consoles); 2163 } 2164 2165 if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { 2166 return Err(ValidationError::ConsoleFileMissing); 2167 } 2168 2169 if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() { 2170 return Err(ValidationError::ConsoleFileMissing); 2171 } 2172 2173 if self.cpus.max_vcpus < self.cpus.boot_vcpus { 2174 return Err(ValidationError::CpusMaxLowerThanBoot); 2175 } 2176 2177 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2178 for rate_limit_group in rate_limit_groups { 2179 rate_limit_group.validate(self)?; 2180 2181 Self::validate_identifier(&mut id_list, &Some(rate_limit_group.id.clone()))?; 2182 } 2183 } 2184 2185 if let Some(disks) = &self.disks { 2186 for disk in disks { 2187 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() { 2188 return Err(ValidationError::DiskSocketAndPath); 2189 } 2190 if disk.vhost_user && !self.backed_by_shared_memory() { 2191 return Err(ValidationError::VhostUserRequiresSharedMemory); 2192 } 2193 if disk.vhost_user && disk.vhost_socket.is_none() { 2194 return Err(ValidationError::VhostUserMissingSocket); 2195 } 2196 if let Some(rate_limit_group) = &disk.rate_limit_group { 2197 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2198 if !rate_limit_groups 2199 .iter() 2200 .any(|cfg| &cfg.id == rate_limit_group) 2201 { 2202 return Err(ValidationError::InvalidRateLimiterGroup); 2203 } 2204 } else { 2205 return Err(ValidationError::InvalidRateLimiterGroup); 2206 } 2207 } 2208 2209 disk.validate(self)?; 2210 self.iommu |= disk.iommu; 2211 2212 Self::validate_identifier(&mut id_list, &disk.id)?; 2213 } 2214 } 2215 2216 if let Some(nets) = &self.net { 2217 for net in nets { 2218 if net.vhost_user && !self.backed_by_shared_memory() { 2219 return Err(ValidationError::VhostUserRequiresSharedMemory); 2220 } 2221 net.validate(self)?; 2222 self.iommu |= net.iommu; 2223 2224 Self::validate_identifier(&mut id_list, &net.id)?; 2225 } 2226 } 2227 2228 if let Some(fses) = &self.fs { 2229 if !fses.is_empty() && !self.backed_by_shared_memory() { 2230 return Err(ValidationError::VhostUserRequiresSharedMemory); 2231 } 2232 for fs in fses { 2233 fs.validate(self)?; 2234 2235 Self::validate_identifier(&mut id_list, &fs.id)?; 2236 } 2237 } 2238 2239 if let Some(pmems) = &self.pmem { 2240 for pmem in pmems { 2241 pmem.validate(self)?; 2242 self.iommu |= pmem.iommu; 2243 2244 Self::validate_identifier(&mut id_list, &pmem.id)?; 2245 } 2246 } 2247 2248 self.iommu |= self.rng.iommu; 2249 self.iommu |= self.console.iommu; 2250 2251 if let Some(t) = &self.cpus.topology { 2252 if t.threads_per_core == 0 2253 || t.cores_per_die == 0 2254 || t.dies_per_package == 0 2255 || t.packages == 0 2256 { 2257 return Err(ValidationError::CpuTopologyZeroPart); 2258 } 2259 2260 // The setting of dies doesen't apply on AArch64. 2261 // Only '1' value is accepted, so its impact on the vcpu topology 2262 // setting can be ignored. 2263 #[cfg(target_arch = "aarch64")] 2264 if t.dies_per_package != 1 { 2265 return Err(ValidationError::CpuTopologyDiesPerPackage); 2266 } 2267 2268 let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages; 2269 if total != self.cpus.max_vcpus { 2270 return Err(ValidationError::CpuTopologyCount); 2271 } 2272 } 2273 2274 if let Some(hugepage_size) = &self.memory.hugepage_size { 2275 if !self.memory.hugepages { 2276 return Err(ValidationError::HugePageSizeWithoutHugePages); 2277 } 2278 if !hugepage_size.is_power_of_two() { 2279 return Err(ValidationError::InvalidHugePageSize(*hugepage_size)); 2280 } 2281 } 2282 2283 if let Some(user_devices) = &self.user_devices { 2284 if !user_devices.is_empty() && !self.backed_by_shared_memory() { 2285 return Err(ValidationError::UserDevicesRequireSharedMemory); 2286 } 2287 2288 for user_device in user_devices { 2289 user_device.validate(self)?; 2290 2291 Self::validate_identifier(&mut id_list, &user_device.id)?; 2292 } 2293 } 2294 2295 if let Some(vdpa_devices) = &self.vdpa { 2296 for vdpa_device in vdpa_devices { 2297 vdpa_device.validate(self)?; 2298 self.iommu |= vdpa_device.iommu; 2299 2300 Self::validate_identifier(&mut id_list, &vdpa_device.id)?; 2301 } 2302 } 2303 2304 if let Some(vsock) = &self.vsock { 2305 if [!0, 0, 1, 2].contains(&vsock.cid) { 2306 return Err(ValidationError::VsockSpecialCid(vsock.cid)); 2307 } 2308 } 2309 2310 if let Some(balloon) = &self.balloon { 2311 let mut ram_size = self.memory.size; 2312 2313 if let Some(zones) = &self.memory.zones { 2314 for zone in zones { 2315 ram_size += zone.size; 2316 } 2317 } 2318 2319 if balloon.size >= ram_size { 2320 return Err(ValidationError::BalloonLargerThanRam( 2321 balloon.size, 2322 ram_size, 2323 )); 2324 } 2325 } 2326 2327 if let Some(devices) = &self.devices { 2328 let mut device_paths = BTreeSet::new(); 2329 for device in devices { 2330 if !device_paths.insert(device.path.to_string_lossy()) { 2331 return Err(ValidationError::DuplicateDevicePath( 2332 device.path.to_string_lossy().to_string(), 2333 )); 2334 } 2335 2336 device.validate(self)?; 2337 self.iommu |= device.iommu; 2338 2339 Self::validate_identifier(&mut id_list, &device.id)?; 2340 } 2341 } 2342 2343 if let Some(vsock) = &self.vsock { 2344 vsock.validate(self)?; 2345 self.iommu |= vsock.iommu; 2346 2347 Self::validate_identifier(&mut id_list, &vsock.id)?; 2348 } 2349 2350 let num_pci_segments = match &self.platform { 2351 Some(platform_config) => platform_config.num_pci_segments, 2352 None => 1, 2353 }; 2354 if let Some(numa) = &self.numa { 2355 let mut used_numa_node_memory_zones = HashMap::new(); 2356 let mut used_pci_segments = HashMap::new(); 2357 for numa_node in numa.iter() { 2358 if let Some(memory_zones) = numa_node.memory_zones.clone() { 2359 for memory_zone in memory_zones.iter() { 2360 if !used_numa_node_memory_zones.contains_key(memory_zone) { 2361 used_numa_node_memory_zones 2362 .insert(memory_zone.to_string(), numa_node.guest_numa_id); 2363 } else { 2364 return Err(ValidationError::MemoryZoneReused( 2365 memory_zone.to_string(), 2366 *used_numa_node_memory_zones.get(memory_zone).unwrap(), 2367 numa_node.guest_numa_id, 2368 )); 2369 } 2370 } 2371 } 2372 2373 if let Some(pci_segments) = numa_node.pci_segments.clone() { 2374 for pci_segment in pci_segments.iter() { 2375 if *pci_segment >= num_pci_segments { 2376 return Err(ValidationError::InvalidPciSegment(*pci_segment)); 2377 } 2378 if *pci_segment == 0 && numa_node.guest_numa_id != 0 { 2379 return Err(ValidationError::DefaultPciSegmentInvalidNode( 2380 numa_node.guest_numa_id, 2381 )); 2382 } 2383 if !used_pci_segments.contains_key(pci_segment) { 2384 used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id); 2385 } else { 2386 return Err(ValidationError::PciSegmentReused( 2387 *pci_segment, 2388 *used_pci_segments.get(pci_segment).unwrap(), 2389 numa_node.guest_numa_id, 2390 )); 2391 } 2392 } 2393 } 2394 } 2395 } 2396 2397 if let Some(zones) = &self.memory.zones { 2398 for zone in zones.iter() { 2399 let id = zone.id.clone(); 2400 Self::validate_identifier(&mut id_list, &Some(id))?; 2401 } 2402 } 2403 2404 #[cfg(target_arch = "x86_64")] 2405 if let Some(sgx_epcs) = &self.sgx_epc { 2406 for sgx_epc in sgx_epcs.iter() { 2407 let id = sgx_epc.id.clone(); 2408 Self::validate_identifier(&mut id_list, &Some(id))?; 2409 } 2410 } 2411 2412 self.platform.as_ref().map(|p| p.validate()).transpose()?; 2413 self.iommu |= self 2414 .platform 2415 .as_ref() 2416 .map(|p| p.iommu_segments.is_some()) 2417 .unwrap_or_default(); 2418 2419 Ok(id_list) 2420 } 2421 2422 pub fn parse(vm_params: VmParams) -> Result<Self> { 2423 let mut rate_limit_groups: Option<Vec<RateLimiterGroupConfig>> = None; 2424 if let Some(rate_limit_group_list) = &vm_params.rate_limit_groups { 2425 let mut rate_limit_group_config_list = Vec::new(); 2426 for item in rate_limit_group_list.iter() { 2427 let rate_limit_group_config = RateLimiterGroupConfig::parse(item)?; 2428 rate_limit_group_config_list.push(rate_limit_group_config); 2429 } 2430 rate_limit_groups = Some(rate_limit_group_config_list); 2431 } 2432 2433 let mut disks: Option<Vec<DiskConfig>> = None; 2434 if let Some(disk_list) = &vm_params.disks { 2435 let mut disk_config_list = Vec::new(); 2436 for item in disk_list.iter() { 2437 let disk_config = DiskConfig::parse(item)?; 2438 disk_config_list.push(disk_config); 2439 } 2440 disks = Some(disk_config_list); 2441 } 2442 2443 let mut net: Option<Vec<NetConfig>> = None; 2444 if let Some(net_list) = &vm_params.net { 2445 let mut net_config_list = Vec::new(); 2446 for item in net_list.iter() { 2447 let net_config = NetConfig::parse(item)?; 2448 net_config_list.push(net_config); 2449 } 2450 net = Some(net_config_list); 2451 } 2452 2453 let rng = RngConfig::parse(vm_params.rng)?; 2454 2455 let mut balloon: Option<BalloonConfig> = None; 2456 if let Some(balloon_params) = &vm_params.balloon { 2457 balloon = Some(BalloonConfig::parse(balloon_params)?); 2458 } 2459 2460 let mut fs: Option<Vec<FsConfig>> = None; 2461 if let Some(fs_list) = &vm_params.fs { 2462 let mut fs_config_list = Vec::new(); 2463 for item in fs_list.iter() { 2464 fs_config_list.push(FsConfig::parse(item)?); 2465 } 2466 fs = Some(fs_config_list); 2467 } 2468 2469 let mut pmem: Option<Vec<PmemConfig>> = None; 2470 if let Some(pmem_list) = &vm_params.pmem { 2471 let mut pmem_config_list = Vec::new(); 2472 for item in pmem_list.iter() { 2473 let pmem_config = PmemConfig::parse(item)?; 2474 pmem_config_list.push(pmem_config); 2475 } 2476 pmem = Some(pmem_config_list); 2477 } 2478 2479 let console = ConsoleConfig::parse(vm_params.console)?; 2480 let serial = ConsoleConfig::parse(vm_params.serial)?; 2481 #[cfg(target_arch = "x86_64")] 2482 let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?; 2483 2484 let mut devices: Option<Vec<DeviceConfig>> = None; 2485 if let Some(device_list) = &vm_params.devices { 2486 let mut device_config_list = Vec::new(); 2487 for item in device_list.iter() { 2488 let device_config = DeviceConfig::parse(item)?; 2489 device_config_list.push(device_config); 2490 } 2491 devices = Some(device_config_list); 2492 } 2493 2494 let mut user_devices: Option<Vec<UserDeviceConfig>> = None; 2495 if let Some(user_device_list) = &vm_params.user_devices { 2496 let mut user_device_config_list = Vec::new(); 2497 for item in user_device_list.iter() { 2498 let user_device_config = UserDeviceConfig::parse(item)?; 2499 user_device_config_list.push(user_device_config); 2500 } 2501 user_devices = Some(user_device_config_list); 2502 } 2503 2504 let mut vdpa: Option<Vec<VdpaConfig>> = None; 2505 if let Some(vdpa_list) = &vm_params.vdpa { 2506 let mut vdpa_config_list = Vec::new(); 2507 for item in vdpa_list.iter() { 2508 let vdpa_config = VdpaConfig::parse(item)?; 2509 vdpa_config_list.push(vdpa_config); 2510 } 2511 vdpa = Some(vdpa_config_list); 2512 } 2513 2514 let mut vsock: Option<VsockConfig> = None; 2515 if let Some(vs) = &vm_params.vsock { 2516 let vsock_config = VsockConfig::parse(vs)?; 2517 vsock = Some(vsock_config); 2518 } 2519 2520 let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?; 2521 2522 #[cfg(target_arch = "x86_64")] 2523 let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None; 2524 #[cfg(target_arch = "x86_64")] 2525 { 2526 if let Some(sgx_epc_list) = &vm_params.sgx_epc { 2527 let mut sgx_epc_config_list = Vec::new(); 2528 for item in sgx_epc_list.iter() { 2529 let sgx_epc_config = SgxEpcConfig::parse(item)?; 2530 sgx_epc_config_list.push(sgx_epc_config); 2531 } 2532 sgx_epc = Some(sgx_epc_config_list); 2533 } 2534 } 2535 2536 let mut numa: Option<Vec<NumaConfig>> = None; 2537 if let Some(numa_list) = &vm_params.numa { 2538 let mut numa_config_list = Vec::new(); 2539 for item in numa_list.iter() { 2540 let numa_config = NumaConfig::parse(item)?; 2541 numa_config_list.push(numa_config); 2542 } 2543 numa = Some(numa_config_list); 2544 } 2545 2546 #[cfg(not(feature = "igvm"))] 2547 let payload_present = vm_params.kernel.is_some() || vm_params.firmware.is_some(); 2548 2549 #[cfg(feature = "igvm")] 2550 let payload_present = 2551 vm_params.kernel.is_some() || vm_params.firmware.is_some() || vm_params.igvm.is_some(); 2552 2553 let payload = if payload_present { 2554 Some(PayloadConfig { 2555 kernel: vm_params.kernel.map(PathBuf::from), 2556 initramfs: vm_params.initramfs.map(PathBuf::from), 2557 cmdline: vm_params.cmdline.map(|s| s.to_string()), 2558 firmware: vm_params.firmware.map(PathBuf::from), 2559 #[cfg(feature = "igvm")] 2560 igvm: vm_params.igvm.map(PathBuf::from), 2561 }) 2562 } else { 2563 None 2564 }; 2565 2566 let mut tpm: Option<TpmConfig> = None; 2567 if let Some(tc) = vm_params.tpm { 2568 let tpm_conf = TpmConfig::parse(tc)?; 2569 tpm = Some(TpmConfig { 2570 socket: tpm_conf.socket, 2571 }); 2572 } 2573 2574 #[cfg(feature = "guest_debug")] 2575 let gdb = vm_params.gdb; 2576 2577 let mut config = VmConfig { 2578 cpus: CpusConfig::parse(vm_params.cpus)?, 2579 memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, 2580 payload, 2581 rate_limit_groups, 2582 disks, 2583 net, 2584 rng, 2585 balloon, 2586 fs, 2587 pmem, 2588 serial, 2589 console, 2590 #[cfg(target_arch = "x86_64")] 2591 debug_console, 2592 devices, 2593 user_devices, 2594 vdpa, 2595 vsock, 2596 pvpanic: vm_params.pvpanic, 2597 iommu: false, // updated in VmConfig::validate() 2598 #[cfg(target_arch = "x86_64")] 2599 sgx_epc, 2600 numa, 2601 watchdog: vm_params.watchdog, 2602 #[cfg(feature = "guest_debug")] 2603 gdb, 2604 platform, 2605 tpm, 2606 preserved_fds: None, 2607 }; 2608 config.validate().map_err(Error::Validation)?; 2609 Ok(config) 2610 } 2611 2612 pub fn remove_device(&mut self, id: &str) -> bool { 2613 let mut removed = false; 2614 2615 // Remove if VFIO device 2616 if let Some(devices) = self.devices.as_mut() { 2617 let len = devices.len(); 2618 devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2619 removed |= devices.len() != len; 2620 } 2621 2622 // Remove if VFIO user device 2623 if let Some(user_devices) = self.user_devices.as_mut() { 2624 let len = user_devices.len(); 2625 user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2626 removed |= user_devices.len() != len; 2627 } 2628 2629 // Remove if disk device 2630 if let Some(disks) = self.disks.as_mut() { 2631 let len = disks.len(); 2632 disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2633 removed |= disks.len() != len; 2634 } 2635 2636 // Remove if fs device 2637 if let Some(fs) = self.fs.as_mut() { 2638 let len = fs.len(); 2639 fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2640 removed |= fs.len() != len; 2641 } 2642 2643 // Remove if net device 2644 if let Some(net) = self.net.as_mut() { 2645 let len = net.len(); 2646 net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2647 removed |= net.len() != len; 2648 } 2649 2650 // Remove if pmem device 2651 if let Some(pmem) = self.pmem.as_mut() { 2652 let len = pmem.len(); 2653 pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2654 removed |= pmem.len() != len; 2655 } 2656 2657 // Remove if vDPA device 2658 if let Some(vdpa) = self.vdpa.as_mut() { 2659 let len = vdpa.len(); 2660 vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2661 removed |= vdpa.len() != len; 2662 } 2663 2664 // Remove if vsock device 2665 if let Some(vsock) = self.vsock.as_ref() { 2666 if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) { 2667 self.vsock = None; 2668 removed = true; 2669 } 2670 } 2671 2672 removed 2673 } 2674 2675 /// # Safety 2676 /// To use this safely, the caller must guarantee that the input 2677 /// fds are all valid. 2678 pub unsafe fn add_preserved_fds(&mut self, mut fds: Vec<i32>) { 2679 if fds.is_empty() { 2680 return; 2681 } 2682 2683 if let Some(preserved_fds) = &self.preserved_fds { 2684 fds.append(&mut preserved_fds.clone()); 2685 } 2686 2687 self.preserved_fds = Some(fds); 2688 } 2689 2690 #[cfg(feature = "tdx")] 2691 pub fn is_tdx_enabled(&self) -> bool { 2692 self.platform.as_ref().map(|p| p.tdx).unwrap_or(false) 2693 } 2694 2695 #[cfg(feature = "sev_snp")] 2696 pub fn is_sev_snp_enabled(&self) -> bool { 2697 self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false) 2698 } 2699 } 2700 2701 impl Clone for VmConfig { 2702 fn clone(&self) -> Self { 2703 VmConfig { 2704 cpus: self.cpus.clone(), 2705 memory: self.memory.clone(), 2706 payload: self.payload.clone(), 2707 rate_limit_groups: self.rate_limit_groups.clone(), 2708 disks: self.disks.clone(), 2709 net: self.net.clone(), 2710 rng: self.rng.clone(), 2711 balloon: self.balloon.clone(), 2712 fs: self.fs.clone(), 2713 pmem: self.pmem.clone(), 2714 serial: self.serial.clone(), 2715 console: self.console.clone(), 2716 #[cfg(target_arch = "x86_64")] 2717 debug_console: self.debug_console.clone(), 2718 devices: self.devices.clone(), 2719 user_devices: self.user_devices.clone(), 2720 vdpa: self.vdpa.clone(), 2721 vsock: self.vsock.clone(), 2722 #[cfg(target_arch = "x86_64")] 2723 sgx_epc: self.sgx_epc.clone(), 2724 numa: self.numa.clone(), 2725 platform: self.platform.clone(), 2726 tpm: self.tpm.clone(), 2727 preserved_fds: self 2728 .preserved_fds 2729 .as_ref() 2730 // SAFETY: FFI call with valid FDs 2731 .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()), 2732 ..*self 2733 } 2734 } 2735 } 2736 2737 impl Drop for VmConfig { 2738 fn drop(&mut self) { 2739 if let Some(mut fds) = self.preserved_fds.take() { 2740 for fd in fds.drain(..) { 2741 // SAFETY: FFI call with valid FDs 2742 unsafe { libc::close(fd) }; 2743 } 2744 } 2745 } 2746 } 2747 2748 #[cfg(test)] 2749 mod tests { 2750 use super::*; 2751 use net_util::MacAddr; 2752 use std::fs::File; 2753 use std::net::Ipv4Addr; 2754 use std::os::unix::io::AsRawFd; 2755 2756 #[test] 2757 fn test_cpu_parsing() -> Result<()> { 2758 assert_eq!(CpusConfig::parse("")?, CpusConfig::default()); 2759 2760 assert_eq!( 2761 CpusConfig::parse("boot=1")?, 2762 CpusConfig { 2763 boot_vcpus: 1, 2764 max_vcpus: 1, 2765 ..Default::default() 2766 } 2767 ); 2768 assert_eq!( 2769 CpusConfig::parse("boot=1,max=2")?, 2770 CpusConfig { 2771 boot_vcpus: 1, 2772 max_vcpus: 2, 2773 ..Default::default() 2774 } 2775 ); 2776 assert_eq!( 2777 CpusConfig::parse("boot=8,topology=2:2:1:2")?, 2778 CpusConfig { 2779 boot_vcpus: 8, 2780 max_vcpus: 8, 2781 topology: Some(CpuTopology { 2782 threads_per_core: 2, 2783 cores_per_die: 2, 2784 dies_per_package: 1, 2785 packages: 2 2786 }), 2787 ..Default::default() 2788 } 2789 ); 2790 2791 assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err()); 2792 assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err()); 2793 assert_eq!( 2794 CpusConfig::parse("boot=1,kvm_hyperv=on")?, 2795 CpusConfig { 2796 boot_vcpus: 1, 2797 max_vcpus: 1, 2798 kvm_hyperv: true, 2799 ..Default::default() 2800 } 2801 ); 2802 assert_eq!( 2803 CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?, 2804 CpusConfig { 2805 boot_vcpus: 2, 2806 max_vcpus: 2, 2807 affinity: Some(vec![ 2808 CpuAffinity { 2809 vcpu: 0, 2810 host_cpus: vec![0, 2], 2811 }, 2812 CpuAffinity { 2813 vcpu: 1, 2814 host_cpus: vec![1, 3], 2815 } 2816 ]), 2817 ..Default::default() 2818 }, 2819 ); 2820 2821 Ok(()) 2822 } 2823 2824 #[test] 2825 fn test_mem_parsing() -> Result<()> { 2826 assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); 2827 // Default string 2828 assert_eq!( 2829 MemoryConfig::parse("size=512M", None)?, 2830 MemoryConfig::default() 2831 ); 2832 assert_eq!( 2833 MemoryConfig::parse("size=512M,mergeable=on", None)?, 2834 MemoryConfig { 2835 size: 512 << 20, 2836 mergeable: true, 2837 ..Default::default() 2838 } 2839 ); 2840 assert_eq!( 2841 MemoryConfig::parse("mergeable=on", None)?, 2842 MemoryConfig { 2843 mergeable: true, 2844 ..Default::default() 2845 } 2846 ); 2847 assert_eq!( 2848 MemoryConfig::parse("size=1G,mergeable=off", None)?, 2849 MemoryConfig { 2850 size: 1 << 30, 2851 mergeable: false, 2852 ..Default::default() 2853 } 2854 ); 2855 assert_eq!( 2856 MemoryConfig::parse("hotplug_method=acpi", None)?, 2857 MemoryConfig { 2858 ..Default::default() 2859 } 2860 ); 2861 assert_eq!( 2862 MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?, 2863 MemoryConfig { 2864 hotplug_size: Some(512 << 20), 2865 ..Default::default() 2866 } 2867 ); 2868 assert_eq!( 2869 MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?, 2870 MemoryConfig { 2871 hotplug_size: Some(512 << 20), 2872 hotplug_method: HotplugMethod::VirtioMem, 2873 ..Default::default() 2874 } 2875 ); 2876 assert_eq!( 2877 MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?, 2878 MemoryConfig { 2879 hugepage_size: Some(2 << 20), 2880 size: 1 << 30, 2881 hugepages: true, 2882 ..Default::default() 2883 } 2884 ); 2885 Ok(()) 2886 } 2887 2888 #[test] 2889 fn test_rate_limit_group_parsing() -> Result<()> { 2890 assert_eq!( 2891 RateLimiterGroupConfig::parse("id=group0,bw_size=1000,bw_refill_time=100")?, 2892 RateLimiterGroupConfig { 2893 id: "group0".to_string(), 2894 rate_limiter_config: RateLimiterConfig { 2895 bandwidth: Some(TokenBucketConfig { 2896 size: 1000, 2897 one_time_burst: Some(0), 2898 refill_time: 100, 2899 }), 2900 ops: None, 2901 } 2902 } 2903 ); 2904 assert_eq!( 2905 RateLimiterGroupConfig::parse("id=group0,ops_size=1000,ops_refill_time=100")?, 2906 RateLimiterGroupConfig { 2907 id: "group0".to_string(), 2908 rate_limiter_config: RateLimiterConfig { 2909 bandwidth: None, 2910 ops: Some(TokenBucketConfig { 2911 size: 1000, 2912 one_time_burst: Some(0), 2913 refill_time: 100, 2914 }), 2915 } 2916 } 2917 ); 2918 Ok(()) 2919 } 2920 2921 fn disk_fixture() -> DiskConfig { 2922 DiskConfig { 2923 path: Some(PathBuf::from("/path/to_file")), 2924 readonly: false, 2925 direct: false, 2926 iommu: false, 2927 num_queues: 1, 2928 queue_size: 128, 2929 vhost_user: false, 2930 vhost_socket: None, 2931 id: None, 2932 disable_io_uring: false, 2933 disable_aio: false, 2934 rate_limit_group: None, 2935 rate_limiter_config: None, 2936 pci_segment: 0, 2937 serial: None, 2938 queue_affinity: None, 2939 } 2940 } 2941 2942 #[test] 2943 fn test_disk_parsing() -> Result<()> { 2944 assert_eq!( 2945 DiskConfig::parse("path=/path/to_file")?, 2946 DiskConfig { ..disk_fixture() } 2947 ); 2948 assert_eq!( 2949 DiskConfig::parse("path=/path/to_file,id=mydisk0")?, 2950 DiskConfig { 2951 id: Some("mydisk0".to_owned()), 2952 ..disk_fixture() 2953 } 2954 ); 2955 assert_eq!( 2956 DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?, 2957 DiskConfig { 2958 path: None, 2959 vhost_socket: Some(String::from("/tmp/sock")), 2960 vhost_user: true, 2961 ..disk_fixture() 2962 } 2963 ); 2964 assert_eq!( 2965 DiskConfig::parse("path=/path/to_file,iommu=on")?, 2966 DiskConfig { 2967 iommu: true, 2968 ..disk_fixture() 2969 } 2970 ); 2971 assert_eq!( 2972 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?, 2973 DiskConfig { 2974 iommu: true, 2975 queue_size: 256, 2976 ..disk_fixture() 2977 } 2978 ); 2979 assert_eq!( 2980 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?, 2981 DiskConfig { 2982 iommu: true, 2983 queue_size: 256, 2984 num_queues: 4, 2985 ..disk_fixture() 2986 } 2987 ); 2988 assert_eq!( 2989 DiskConfig::parse("path=/path/to_file,direct=on")?, 2990 DiskConfig { 2991 direct: true, 2992 ..disk_fixture() 2993 } 2994 ); 2995 assert_eq!( 2996 DiskConfig::parse("path=/path/to_file,serial=test")?, 2997 DiskConfig { 2998 serial: Some(String::from("test")), 2999 ..disk_fixture() 3000 } 3001 ); 3002 assert_eq!( 3003 DiskConfig::parse("path=/path/to_file,rate_limit_group=group0")?, 3004 DiskConfig { 3005 rate_limit_group: Some("group0".to_string()), 3006 ..disk_fixture() 3007 } 3008 ); 3009 assert_eq!( 3010 DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?, 3011 DiskConfig { 3012 queue_affinity: Some(vec![ 3013 VirtQueueAffinity { 3014 queue_index: 0, 3015 host_cpus: vec![1], 3016 }, 3017 VirtQueueAffinity { 3018 queue_index: 1, 3019 host_cpus: vec![2], 3020 }, 3021 VirtQueueAffinity { 3022 queue_index: 2, 3023 host_cpus: vec![3, 4], 3024 }, 3025 VirtQueueAffinity { 3026 queue_index: 3, 3027 host_cpus: vec![5, 6, 7, 8], 3028 } 3029 ]), 3030 ..disk_fixture() 3031 } 3032 ); 3033 Ok(()) 3034 } 3035 3036 fn net_fixture() -> NetConfig { 3037 NetConfig { 3038 tap: None, 3039 ip: Ipv4Addr::new(192, 168, 249, 1), 3040 mask: Ipv4Addr::new(255, 255, 255, 0), 3041 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 3042 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 3043 mtu: None, 3044 iommu: false, 3045 num_queues: 2, 3046 queue_size: 256, 3047 vhost_user: false, 3048 vhost_socket: None, 3049 vhost_mode: VhostMode::Client, 3050 id: None, 3051 fds: None, 3052 rate_limiter_config: None, 3053 pci_segment: 0, 3054 offload_tso: true, 3055 offload_ufo: true, 3056 offload_csum: true, 3057 } 3058 } 3059 3060 #[test] 3061 fn test_net_parsing() -> Result<()> { 3062 // mac address is random 3063 assert_eq!( 3064 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?, 3065 net_fixture(), 3066 ); 3067 3068 assert_eq!( 3069 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?, 3070 NetConfig { 3071 id: Some("mynet0".to_owned()), 3072 ..net_fixture() 3073 } 3074 ); 3075 3076 assert_eq!( 3077 NetConfig::parse( 3078 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,tap=tap0,ip=192.168.100.1,mask=255.255.255.128" 3079 )?, 3080 NetConfig { 3081 tap: Some("tap0".to_owned()), 3082 ip: "192.168.100.1".parse().unwrap(), 3083 mask: "255.255.255.128".parse().unwrap(), 3084 ..net_fixture() 3085 } 3086 ); 3087 3088 assert_eq!( 3089 NetConfig::parse( 3090 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock" 3091 )?, 3092 NetConfig { 3093 vhost_user: true, 3094 vhost_socket: Some("/tmp/sock".to_owned()), 3095 ..net_fixture() 3096 } 3097 ); 3098 3099 assert_eq!( 3100 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,num_queues=4,queue_size=1024,iommu=on")?, 3101 NetConfig { 3102 num_queues: 4, 3103 queue_size: 1024, 3104 iommu: true, 3105 ..net_fixture() 3106 } 3107 ); 3108 3109 assert_eq!( 3110 NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?, 3111 NetConfig { 3112 host_mac: None, 3113 fds: Some(vec![3, 7]), 3114 num_queues: 4, 3115 ..net_fixture() 3116 } 3117 ); 3118 3119 Ok(()) 3120 } 3121 3122 #[test] 3123 fn test_parse_rng() -> Result<()> { 3124 assert_eq!(RngConfig::parse("")?, RngConfig::default()); 3125 assert_eq!( 3126 RngConfig::parse("src=/dev/random")?, 3127 RngConfig { 3128 src: PathBuf::from("/dev/random"), 3129 ..Default::default() 3130 } 3131 ); 3132 assert_eq!( 3133 RngConfig::parse("src=/dev/random,iommu=on")?, 3134 RngConfig { 3135 src: PathBuf::from("/dev/random"), 3136 iommu: true, 3137 } 3138 ); 3139 assert_eq!( 3140 RngConfig::parse("iommu=on")?, 3141 RngConfig { 3142 iommu: true, 3143 ..Default::default() 3144 } 3145 ); 3146 Ok(()) 3147 } 3148 3149 fn fs_fixture() -> FsConfig { 3150 FsConfig { 3151 socket: PathBuf::from("/tmp/sock"), 3152 tag: "mytag".to_owned(), 3153 num_queues: 1, 3154 queue_size: 1024, 3155 id: None, 3156 pci_segment: 0, 3157 } 3158 } 3159 3160 #[test] 3161 fn test_parse_fs() -> Result<()> { 3162 // "tag" and "socket" must be supplied 3163 assert!(FsConfig::parse("").is_err()); 3164 assert!(FsConfig::parse("tag=mytag").is_err()); 3165 assert!(FsConfig::parse("socket=/tmp/sock").is_err()); 3166 assert_eq!(FsConfig::parse("tag=mytag,socket=/tmp/sock")?, fs_fixture()); 3167 assert_eq!( 3168 FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?, 3169 FsConfig { 3170 num_queues: 4, 3171 queue_size: 1024, 3172 ..fs_fixture() 3173 } 3174 ); 3175 3176 Ok(()) 3177 } 3178 3179 fn pmem_fixture() -> PmemConfig { 3180 PmemConfig { 3181 file: PathBuf::from("/tmp/pmem"), 3182 size: Some(128 << 20), 3183 iommu: false, 3184 discard_writes: false, 3185 id: None, 3186 pci_segment: 0, 3187 } 3188 } 3189 3190 #[test] 3191 fn test_pmem_parsing() -> Result<()> { 3192 // Must always give a file and size 3193 assert!(PmemConfig::parse("").is_err()); 3194 assert!(PmemConfig::parse("size=128M").is_err()); 3195 assert_eq!( 3196 PmemConfig::parse("file=/tmp/pmem,size=128M")?, 3197 pmem_fixture() 3198 ); 3199 assert_eq!( 3200 PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?, 3201 PmemConfig { 3202 id: Some("mypmem0".to_owned()), 3203 ..pmem_fixture() 3204 } 3205 ); 3206 assert_eq!( 3207 PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?, 3208 PmemConfig { 3209 discard_writes: true, 3210 iommu: true, 3211 ..pmem_fixture() 3212 } 3213 ); 3214 3215 Ok(()) 3216 } 3217 3218 #[test] 3219 fn test_console_parsing() -> Result<()> { 3220 assert!(ConsoleConfig::parse("").is_err()); 3221 assert!(ConsoleConfig::parse("badmode").is_err()); 3222 assert_eq!( 3223 ConsoleConfig::parse("off")?, 3224 ConsoleConfig { 3225 mode: ConsoleOutputMode::Off, 3226 iommu: false, 3227 file: None, 3228 socket: None, 3229 } 3230 ); 3231 assert_eq!( 3232 ConsoleConfig::parse("pty")?, 3233 ConsoleConfig { 3234 mode: ConsoleOutputMode::Pty, 3235 iommu: false, 3236 file: None, 3237 socket: None, 3238 } 3239 ); 3240 assert_eq!( 3241 ConsoleConfig::parse("tty")?, 3242 ConsoleConfig { 3243 mode: ConsoleOutputMode::Tty, 3244 iommu: false, 3245 file: None, 3246 socket: None, 3247 } 3248 ); 3249 assert_eq!( 3250 ConsoleConfig::parse("null")?, 3251 ConsoleConfig { 3252 mode: ConsoleOutputMode::Null, 3253 iommu: false, 3254 file: None, 3255 socket: None, 3256 } 3257 ); 3258 assert_eq!( 3259 ConsoleConfig::parse("file=/tmp/console")?, 3260 ConsoleConfig { 3261 mode: ConsoleOutputMode::File, 3262 iommu: false, 3263 file: Some(PathBuf::from("/tmp/console")), 3264 socket: None, 3265 } 3266 ); 3267 assert_eq!( 3268 ConsoleConfig::parse("null,iommu=on")?, 3269 ConsoleConfig { 3270 mode: ConsoleOutputMode::Null, 3271 iommu: true, 3272 file: None, 3273 socket: None, 3274 } 3275 ); 3276 assert_eq!( 3277 ConsoleConfig::parse("file=/tmp/console,iommu=on")?, 3278 ConsoleConfig { 3279 mode: ConsoleOutputMode::File, 3280 iommu: true, 3281 file: Some(PathBuf::from("/tmp/console")), 3282 socket: None, 3283 } 3284 ); 3285 assert_eq!( 3286 ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?, 3287 ConsoleConfig { 3288 mode: ConsoleOutputMode::Socket, 3289 iommu: true, 3290 file: None, 3291 socket: Some(PathBuf::from("/tmp/serial.sock")), 3292 } 3293 ); 3294 Ok(()) 3295 } 3296 3297 fn device_fixture() -> DeviceConfig { 3298 DeviceConfig { 3299 path: PathBuf::from("/path/to/device"), 3300 id: None, 3301 iommu: false, 3302 pci_segment: 0, 3303 } 3304 } 3305 3306 #[test] 3307 fn test_device_parsing() -> Result<()> { 3308 // Device must have a path provided 3309 assert!(DeviceConfig::parse("").is_err()); 3310 assert_eq!( 3311 DeviceConfig::parse("path=/path/to/device")?, 3312 device_fixture() 3313 ); 3314 3315 assert_eq!( 3316 DeviceConfig::parse("path=/path/to/device,iommu=on")?, 3317 DeviceConfig { 3318 iommu: true, 3319 ..device_fixture() 3320 } 3321 ); 3322 3323 assert_eq!( 3324 DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?, 3325 DeviceConfig { 3326 id: Some("mydevice0".to_owned()), 3327 iommu: true, 3328 ..device_fixture() 3329 } 3330 ); 3331 3332 Ok(()) 3333 } 3334 3335 fn vdpa_fixture() -> VdpaConfig { 3336 VdpaConfig { 3337 path: PathBuf::from("/dev/vhost-vdpa"), 3338 num_queues: 1, 3339 iommu: false, 3340 id: None, 3341 pci_segment: 0, 3342 } 3343 } 3344 3345 #[test] 3346 fn test_vdpa_parsing() -> Result<()> { 3347 // path is required 3348 assert!(VdpaConfig::parse("").is_err()); 3349 assert_eq!(VdpaConfig::parse("path=/dev/vhost-vdpa")?, vdpa_fixture()); 3350 assert_eq!( 3351 VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?, 3352 VdpaConfig { 3353 num_queues: 2, 3354 id: Some("my_vdpa".to_owned()), 3355 ..vdpa_fixture() 3356 } 3357 ); 3358 Ok(()) 3359 } 3360 3361 #[test] 3362 fn test_tpm_parsing() -> Result<()> { 3363 // path is required 3364 assert!(TpmConfig::parse("").is_err()); 3365 assert_eq!( 3366 TpmConfig::parse("socket=/var/run/tpm.sock")?, 3367 TpmConfig { 3368 socket: PathBuf::from("/var/run/tpm.sock"), 3369 } 3370 ); 3371 Ok(()) 3372 } 3373 3374 #[test] 3375 fn test_vsock_parsing() -> Result<()> { 3376 // socket and cid is required 3377 assert!(VsockConfig::parse("").is_err()); 3378 assert_eq!( 3379 VsockConfig::parse("socket=/tmp/sock,cid=3")?, 3380 VsockConfig { 3381 cid: 3, 3382 socket: PathBuf::from("/tmp/sock"), 3383 iommu: false, 3384 id: None, 3385 pci_segment: 0, 3386 } 3387 ); 3388 assert_eq!( 3389 VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?, 3390 VsockConfig { 3391 cid: 3, 3392 socket: PathBuf::from("/tmp/sock"), 3393 iommu: true, 3394 id: None, 3395 pci_segment: 0, 3396 } 3397 ); 3398 Ok(()) 3399 } 3400 3401 fn platform_fixture() -> PlatformConfig { 3402 PlatformConfig { 3403 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3404 iommu_segments: None, 3405 serial_number: None, 3406 uuid: None, 3407 oem_strings: None, 3408 #[cfg(feature = "tdx")] 3409 tdx: false, 3410 #[cfg(feature = "sev_snp")] 3411 sev_snp: false, 3412 } 3413 } 3414 3415 fn numa_fixture() -> NumaConfig { 3416 NumaConfig { 3417 guest_numa_id: 0, 3418 cpus: None, 3419 distances: None, 3420 memory_zones: None, 3421 #[cfg(target_arch = "x86_64")] 3422 sgx_epc_sections: None, 3423 pci_segments: None, 3424 } 3425 } 3426 3427 #[test] 3428 fn test_config_validation() { 3429 let mut valid_config = VmConfig { 3430 cpus: CpusConfig { 3431 boot_vcpus: 1, 3432 max_vcpus: 1, 3433 ..Default::default() 3434 }, 3435 memory: MemoryConfig { 3436 size: 536_870_912, 3437 mergeable: false, 3438 hotplug_method: HotplugMethod::Acpi, 3439 hotplug_size: None, 3440 hotplugged_size: None, 3441 shared: false, 3442 hugepages: false, 3443 hugepage_size: None, 3444 prefault: false, 3445 zones: None, 3446 thp: true, 3447 }, 3448 payload: Some(PayloadConfig { 3449 kernel: Some(PathBuf::from("/path/to/kernel")), 3450 firmware: None, 3451 cmdline: None, 3452 initramfs: None, 3453 #[cfg(feature = "igvm")] 3454 igvm: None, 3455 }), 3456 rate_limit_groups: None, 3457 disks: None, 3458 net: None, 3459 rng: RngConfig { 3460 src: PathBuf::from("/dev/urandom"), 3461 iommu: false, 3462 }, 3463 balloon: None, 3464 fs: None, 3465 pmem: None, 3466 serial: ConsoleConfig { 3467 file: None, 3468 mode: ConsoleOutputMode::Null, 3469 iommu: false, 3470 socket: None, 3471 }, 3472 console: ConsoleConfig { 3473 file: None, 3474 mode: ConsoleOutputMode::Tty, 3475 iommu: false, 3476 socket: None, 3477 }, 3478 #[cfg(target_arch = "x86_64")] 3479 debug_console: DebugConsoleConfig::default(), 3480 devices: None, 3481 user_devices: None, 3482 vdpa: None, 3483 vsock: None, 3484 pvpanic: false, 3485 iommu: false, 3486 #[cfg(target_arch = "x86_64")] 3487 sgx_epc: None, 3488 numa: None, 3489 watchdog: false, 3490 #[cfg(feature = "guest_debug")] 3491 gdb: false, 3492 platform: None, 3493 tpm: None, 3494 preserved_fds: None, 3495 }; 3496 3497 assert!(valid_config.validate().is_ok()); 3498 3499 let mut invalid_config = valid_config.clone(); 3500 invalid_config.serial.mode = ConsoleOutputMode::Tty; 3501 invalid_config.console.mode = ConsoleOutputMode::Tty; 3502 assert!(valid_config.validate().is_ok()); 3503 3504 let mut invalid_config = valid_config.clone(); 3505 invalid_config.payload = None; 3506 assert_eq!( 3507 invalid_config.validate(), 3508 Err(ValidationError::KernelMissing) 3509 ); 3510 3511 let mut invalid_config = valid_config.clone(); 3512 invalid_config.serial.mode = ConsoleOutputMode::File; 3513 invalid_config.serial.file = None; 3514 assert_eq!( 3515 invalid_config.validate(), 3516 Err(ValidationError::ConsoleFileMissing) 3517 ); 3518 3519 let mut invalid_config = valid_config.clone(); 3520 invalid_config.cpus.max_vcpus = 16; 3521 invalid_config.cpus.boot_vcpus = 32; 3522 assert_eq!( 3523 invalid_config.validate(), 3524 Err(ValidationError::CpusMaxLowerThanBoot) 3525 ); 3526 3527 let mut invalid_config = valid_config.clone(); 3528 invalid_config.cpus.max_vcpus = 16; 3529 invalid_config.cpus.boot_vcpus = 16; 3530 invalid_config.cpus.topology = Some(CpuTopology { 3531 threads_per_core: 2, 3532 cores_per_die: 8, 3533 dies_per_package: 1, 3534 packages: 2, 3535 }); 3536 assert_eq!( 3537 invalid_config.validate(), 3538 Err(ValidationError::CpuTopologyCount) 3539 ); 3540 3541 let mut invalid_config = valid_config.clone(); 3542 invalid_config.disks = Some(vec![DiskConfig { 3543 vhost_socket: Some("/path/to/sock".to_owned()), 3544 path: Some(PathBuf::from("/path/to/image")), 3545 ..disk_fixture() 3546 }]); 3547 assert_eq!( 3548 invalid_config.validate(), 3549 Err(ValidationError::DiskSocketAndPath) 3550 ); 3551 3552 let mut invalid_config = valid_config.clone(); 3553 invalid_config.memory.shared = true; 3554 invalid_config.disks = Some(vec![DiskConfig { 3555 path: None, 3556 vhost_user: true, 3557 ..disk_fixture() 3558 }]); 3559 assert_eq!( 3560 invalid_config.validate(), 3561 Err(ValidationError::VhostUserMissingSocket) 3562 ); 3563 3564 let mut invalid_config = valid_config.clone(); 3565 invalid_config.disks = Some(vec![DiskConfig { 3566 path: None, 3567 vhost_user: true, 3568 vhost_socket: Some("/path/to/sock".to_owned()), 3569 ..disk_fixture() 3570 }]); 3571 assert_eq!( 3572 invalid_config.validate(), 3573 Err(ValidationError::VhostUserRequiresSharedMemory) 3574 ); 3575 3576 let mut still_valid_config = valid_config.clone(); 3577 still_valid_config.disks = Some(vec![DiskConfig { 3578 path: None, 3579 vhost_user: true, 3580 vhost_socket: Some("/path/to/sock".to_owned()), 3581 ..disk_fixture() 3582 }]); 3583 still_valid_config.memory.shared = true; 3584 assert!(still_valid_config.validate().is_ok()); 3585 3586 let mut invalid_config = valid_config.clone(); 3587 invalid_config.net = Some(vec![NetConfig { 3588 vhost_user: true, 3589 ..net_fixture() 3590 }]); 3591 assert_eq!( 3592 invalid_config.validate(), 3593 Err(ValidationError::VhostUserRequiresSharedMemory) 3594 ); 3595 3596 let mut still_valid_config = valid_config.clone(); 3597 still_valid_config.net = Some(vec![NetConfig { 3598 vhost_user: true, 3599 vhost_socket: Some("/path/to/sock".to_owned()), 3600 ..net_fixture() 3601 }]); 3602 still_valid_config.memory.shared = true; 3603 assert!(still_valid_config.validate().is_ok()); 3604 3605 let mut invalid_config = valid_config.clone(); 3606 invalid_config.net = Some(vec![NetConfig { 3607 fds: Some(vec![0]), 3608 ..net_fixture() 3609 }]); 3610 assert_eq!( 3611 invalid_config.validate(), 3612 Err(ValidationError::VnetReservedFd) 3613 ); 3614 3615 let mut invalid_config = valid_config.clone(); 3616 invalid_config.net = Some(vec![NetConfig { 3617 offload_csum: false, 3618 ..net_fixture() 3619 }]); 3620 assert_eq!( 3621 invalid_config.validate(), 3622 Err(ValidationError::NoHardwareChecksumOffload) 3623 ); 3624 3625 let mut invalid_config = valid_config.clone(); 3626 invalid_config.fs = Some(vec![fs_fixture()]); 3627 assert_eq!( 3628 invalid_config.validate(), 3629 Err(ValidationError::VhostUserRequiresSharedMemory) 3630 ); 3631 3632 let mut still_valid_config = valid_config.clone(); 3633 still_valid_config.memory.shared = true; 3634 assert!(still_valid_config.validate().is_ok()); 3635 3636 let mut still_valid_config = valid_config.clone(); 3637 still_valid_config.memory.hugepages = true; 3638 assert!(still_valid_config.validate().is_ok()); 3639 3640 let mut still_valid_config = valid_config.clone(); 3641 still_valid_config.memory.hugepages = true; 3642 still_valid_config.memory.hugepage_size = Some(2 << 20); 3643 assert!(still_valid_config.validate().is_ok()); 3644 3645 let mut invalid_config = valid_config.clone(); 3646 invalid_config.memory.hugepages = false; 3647 invalid_config.memory.hugepage_size = Some(2 << 20); 3648 assert_eq!( 3649 invalid_config.validate(), 3650 Err(ValidationError::HugePageSizeWithoutHugePages) 3651 ); 3652 3653 let mut invalid_config = valid_config.clone(); 3654 invalid_config.memory.hugepages = true; 3655 invalid_config.memory.hugepage_size = Some(3 << 20); 3656 assert_eq!( 3657 invalid_config.validate(), 3658 Err(ValidationError::InvalidHugePageSize(3 << 20)) 3659 ); 3660 3661 let mut still_valid_config = valid_config.clone(); 3662 still_valid_config.platform = Some(platform_fixture()); 3663 assert!(still_valid_config.validate().is_ok()); 3664 3665 let mut invalid_config = valid_config.clone(); 3666 invalid_config.platform = Some(PlatformConfig { 3667 num_pci_segments: MAX_NUM_PCI_SEGMENTS + 1, 3668 ..platform_fixture() 3669 }); 3670 assert_eq!( 3671 invalid_config.validate(), 3672 Err(ValidationError::InvalidNumPciSegments( 3673 MAX_NUM_PCI_SEGMENTS + 1 3674 )) 3675 ); 3676 3677 let mut still_valid_config = valid_config.clone(); 3678 still_valid_config.platform = Some(PlatformConfig { 3679 iommu_segments: Some(vec![1, 2, 3]), 3680 ..platform_fixture() 3681 }); 3682 assert!(still_valid_config.validate().is_ok()); 3683 3684 let mut invalid_config = valid_config.clone(); 3685 invalid_config.platform = Some(PlatformConfig { 3686 iommu_segments: Some(vec![MAX_NUM_PCI_SEGMENTS + 1, MAX_NUM_PCI_SEGMENTS + 2]), 3687 ..platform_fixture() 3688 }); 3689 assert_eq!( 3690 invalid_config.validate(), 3691 Err(ValidationError::InvalidPciSegment(MAX_NUM_PCI_SEGMENTS + 1)) 3692 ); 3693 3694 let mut still_valid_config = valid_config.clone(); 3695 still_valid_config.platform = Some(PlatformConfig { 3696 iommu_segments: Some(vec![1, 2, 3]), 3697 ..platform_fixture() 3698 }); 3699 still_valid_config.disks = Some(vec![DiskConfig { 3700 iommu: true, 3701 pci_segment: 1, 3702 ..disk_fixture() 3703 }]); 3704 assert!(still_valid_config.validate().is_ok()); 3705 3706 let mut still_valid_config = valid_config.clone(); 3707 still_valid_config.platform = Some(PlatformConfig { 3708 iommu_segments: Some(vec![1, 2, 3]), 3709 ..platform_fixture() 3710 }); 3711 still_valid_config.net = Some(vec![NetConfig { 3712 iommu: true, 3713 pci_segment: 1, 3714 ..net_fixture() 3715 }]); 3716 assert!(still_valid_config.validate().is_ok()); 3717 3718 let mut still_valid_config = valid_config.clone(); 3719 still_valid_config.platform = Some(PlatformConfig { 3720 iommu_segments: Some(vec![1, 2, 3]), 3721 ..platform_fixture() 3722 }); 3723 still_valid_config.pmem = Some(vec![PmemConfig { 3724 iommu: true, 3725 pci_segment: 1, 3726 ..pmem_fixture() 3727 }]); 3728 assert!(still_valid_config.validate().is_ok()); 3729 3730 let mut still_valid_config = valid_config.clone(); 3731 still_valid_config.platform = Some(PlatformConfig { 3732 iommu_segments: Some(vec![1, 2, 3]), 3733 ..platform_fixture() 3734 }); 3735 still_valid_config.devices = Some(vec![DeviceConfig { 3736 iommu: true, 3737 pci_segment: 1, 3738 ..device_fixture() 3739 }]); 3740 assert!(still_valid_config.validate().is_ok()); 3741 3742 let mut still_valid_config = valid_config.clone(); 3743 still_valid_config.platform = Some(PlatformConfig { 3744 iommu_segments: Some(vec![1, 2, 3]), 3745 ..platform_fixture() 3746 }); 3747 still_valid_config.vsock = Some(VsockConfig { 3748 cid: 3, 3749 socket: PathBuf::new(), 3750 id: None, 3751 iommu: true, 3752 pci_segment: 1, 3753 }); 3754 assert!(still_valid_config.validate().is_ok()); 3755 3756 let mut invalid_config = valid_config.clone(); 3757 invalid_config.platform = Some(PlatformConfig { 3758 iommu_segments: Some(vec![1, 2, 3]), 3759 ..platform_fixture() 3760 }); 3761 invalid_config.disks = Some(vec![DiskConfig { 3762 iommu: false, 3763 pci_segment: 1, 3764 ..disk_fixture() 3765 }]); 3766 assert_eq!( 3767 invalid_config.validate(), 3768 Err(ValidationError::OnIommuSegment(1)) 3769 ); 3770 3771 let mut invalid_config = valid_config.clone(); 3772 invalid_config.platform = Some(PlatformConfig { 3773 iommu_segments: Some(vec![1, 2, 3]), 3774 ..platform_fixture() 3775 }); 3776 invalid_config.net = Some(vec![NetConfig { 3777 iommu: false, 3778 pci_segment: 1, 3779 ..net_fixture() 3780 }]); 3781 assert_eq!( 3782 invalid_config.validate(), 3783 Err(ValidationError::OnIommuSegment(1)) 3784 ); 3785 3786 let mut invalid_config = valid_config.clone(); 3787 invalid_config.platform = Some(PlatformConfig { 3788 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3789 iommu_segments: Some(vec![1, 2, 3]), 3790 ..platform_fixture() 3791 }); 3792 invalid_config.pmem = Some(vec![PmemConfig { 3793 iommu: false, 3794 pci_segment: 1, 3795 ..pmem_fixture() 3796 }]); 3797 assert_eq!( 3798 invalid_config.validate(), 3799 Err(ValidationError::OnIommuSegment(1)) 3800 ); 3801 3802 let mut invalid_config = valid_config.clone(); 3803 invalid_config.platform = Some(PlatformConfig { 3804 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3805 iommu_segments: Some(vec![1, 2, 3]), 3806 ..platform_fixture() 3807 }); 3808 invalid_config.devices = Some(vec![DeviceConfig { 3809 iommu: false, 3810 pci_segment: 1, 3811 ..device_fixture() 3812 }]); 3813 assert_eq!( 3814 invalid_config.validate(), 3815 Err(ValidationError::OnIommuSegment(1)) 3816 ); 3817 3818 let mut invalid_config = valid_config.clone(); 3819 invalid_config.platform = Some(PlatformConfig { 3820 iommu_segments: Some(vec![1, 2, 3]), 3821 ..platform_fixture() 3822 }); 3823 invalid_config.vsock = Some(VsockConfig { 3824 cid: 3, 3825 socket: PathBuf::new(), 3826 id: None, 3827 iommu: false, 3828 pci_segment: 1, 3829 }); 3830 assert_eq!( 3831 invalid_config.validate(), 3832 Err(ValidationError::OnIommuSegment(1)) 3833 ); 3834 3835 let mut invalid_config = valid_config.clone(); 3836 invalid_config.memory.shared = true; 3837 invalid_config.platform = Some(PlatformConfig { 3838 iommu_segments: Some(vec![1, 2, 3]), 3839 ..platform_fixture() 3840 }); 3841 invalid_config.user_devices = Some(vec![UserDeviceConfig { 3842 pci_segment: 1, 3843 socket: PathBuf::new(), 3844 id: None, 3845 }]); 3846 assert_eq!( 3847 invalid_config.validate(), 3848 Err(ValidationError::IommuNotSupportedOnSegment(1)) 3849 ); 3850 3851 let mut invalid_config = valid_config.clone(); 3852 invalid_config.platform = Some(PlatformConfig { 3853 iommu_segments: Some(vec![1, 2, 3]), 3854 ..platform_fixture() 3855 }); 3856 invalid_config.vdpa = Some(vec![VdpaConfig { 3857 pci_segment: 1, 3858 ..vdpa_fixture() 3859 }]); 3860 assert_eq!( 3861 invalid_config.validate(), 3862 Err(ValidationError::OnIommuSegment(1)) 3863 ); 3864 3865 let mut invalid_config = valid_config.clone(); 3866 invalid_config.memory.shared = true; 3867 invalid_config.platform = Some(PlatformConfig { 3868 iommu_segments: Some(vec![1, 2, 3]), 3869 ..platform_fixture() 3870 }); 3871 invalid_config.fs = Some(vec![FsConfig { 3872 pci_segment: 1, 3873 ..fs_fixture() 3874 }]); 3875 assert_eq!( 3876 invalid_config.validate(), 3877 Err(ValidationError::IommuNotSupportedOnSegment(1)) 3878 ); 3879 3880 let mut invalid_config = valid_config.clone(); 3881 invalid_config.platform = Some(PlatformConfig { 3882 num_pci_segments: 2, 3883 ..platform_fixture() 3884 }); 3885 invalid_config.numa = Some(vec![ 3886 NumaConfig { 3887 guest_numa_id: 0, 3888 pci_segments: Some(vec![1]), 3889 ..numa_fixture() 3890 }, 3891 NumaConfig { 3892 guest_numa_id: 1, 3893 pci_segments: Some(vec![1]), 3894 ..numa_fixture() 3895 }, 3896 ]); 3897 assert_eq!( 3898 invalid_config.validate(), 3899 Err(ValidationError::PciSegmentReused(1, 0, 1)) 3900 ); 3901 3902 let mut invalid_config = valid_config.clone(); 3903 invalid_config.numa = Some(vec![ 3904 NumaConfig { 3905 guest_numa_id: 0, 3906 ..numa_fixture() 3907 }, 3908 NumaConfig { 3909 guest_numa_id: 1, 3910 pci_segments: Some(vec![0]), 3911 ..numa_fixture() 3912 }, 3913 ]); 3914 assert_eq!( 3915 invalid_config.validate(), 3916 Err(ValidationError::DefaultPciSegmentInvalidNode(1)) 3917 ); 3918 3919 let mut invalid_config = valid_config.clone(); 3920 invalid_config.numa = Some(vec![ 3921 NumaConfig { 3922 guest_numa_id: 0, 3923 pci_segments: Some(vec![0]), 3924 ..numa_fixture() 3925 }, 3926 NumaConfig { 3927 guest_numa_id: 1, 3928 pci_segments: Some(vec![1]), 3929 ..numa_fixture() 3930 }, 3931 ]); 3932 assert_eq!( 3933 invalid_config.validate(), 3934 Err(ValidationError::InvalidPciSegment(1)) 3935 ); 3936 3937 let mut invalid_config = valid_config.clone(); 3938 invalid_config.disks = Some(vec![DiskConfig { 3939 rate_limit_group: Some("foo".into()), 3940 ..disk_fixture() 3941 }]); 3942 assert_eq!( 3943 invalid_config.validate(), 3944 Err(ValidationError::InvalidRateLimiterGroup) 3945 ); 3946 3947 let mut still_valid_config = valid_config.clone(); 3948 still_valid_config.devices = Some(vec![ 3949 DeviceConfig { 3950 path: "/device1".into(), 3951 ..device_fixture() 3952 }, 3953 DeviceConfig { 3954 path: "/device2".into(), 3955 ..device_fixture() 3956 }, 3957 ]); 3958 assert!(still_valid_config.validate().is_ok()); 3959 3960 let mut invalid_config = valid_config.clone(); 3961 invalid_config.devices = Some(vec![ 3962 DeviceConfig { 3963 path: "/device1".into(), 3964 ..device_fixture() 3965 }, 3966 DeviceConfig { 3967 path: "/device1".into(), 3968 ..device_fixture() 3969 }, 3970 ]); 3971 assert!(invalid_config.validate().is_err()); 3972 3973 let mut still_valid_config = valid_config; 3974 // SAFETY: Safe as the file was just opened 3975 let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 3976 // SAFETY: Safe as the file was just opened 3977 let fd2 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 3978 // SAFETY: safe as both FDs are valid 3979 unsafe { 3980 still_valid_config.add_preserved_fds(vec![fd1, fd2]); 3981 } 3982 let _still_valid_config = still_valid_config.clone(); 3983 } 3984 } 3985