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