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