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