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