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