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