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 1750 .add("path") 1751 .add("id") 1752 .add("iommu") 1753 .add("pci_segment") 1754 .add("x_nv_gpudirect_clique"); 1755 parser.parse(device).map_err(Error::ParseDevice)?; 1756 1757 let path = parser 1758 .get("path") 1759 .map(PathBuf::from) 1760 .ok_or(Error::ParseDevicePathMissing)?; 1761 let iommu = parser 1762 .convert::<Toggle>("iommu") 1763 .map_err(Error::ParseDevice)? 1764 .unwrap_or(Toggle(false)) 1765 .0; 1766 let id = parser.get("id"); 1767 let pci_segment = parser 1768 .convert::<u16>("pci_segment") 1769 .map_err(Error::ParseDevice)? 1770 .unwrap_or_default(); 1771 let x_nv_gpudirect_clique = parser 1772 .convert::<u8>("x_nv_gpudirect_clique") 1773 .map_err(Error::ParseDevice)?; 1774 Ok(DeviceConfig { 1775 path, 1776 iommu, 1777 id, 1778 pci_segment, 1779 x_nv_gpudirect_clique, 1780 }) 1781 } 1782 1783 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1784 if let Some(platform_config) = vm_config.platform.as_ref() { 1785 if self.pci_segment >= platform_config.num_pci_segments { 1786 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1787 } 1788 1789 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1790 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1791 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1792 } 1793 } 1794 } 1795 1796 Ok(()) 1797 } 1798 } 1799 1800 impl UserDeviceConfig { 1801 pub const SYNTAX: &'static str = 1802 "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\""; 1803 1804 pub fn parse(user_device: &str) -> Result<Self> { 1805 let mut parser = OptionParser::new(); 1806 parser.add("socket").add("id").add("pci_segment"); 1807 parser.parse(user_device).map_err(Error::ParseUserDevice)?; 1808 1809 let socket = parser 1810 .get("socket") 1811 .map(PathBuf::from) 1812 .ok_or(Error::ParseUserDeviceSocketMissing)?; 1813 let id = parser.get("id"); 1814 let pci_segment = parser 1815 .convert::<u16>("pci_segment") 1816 .map_err(Error::ParseUserDevice)? 1817 .unwrap_or_default(); 1818 1819 Ok(UserDeviceConfig { 1820 socket, 1821 id, 1822 pci_segment, 1823 }) 1824 } 1825 1826 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1827 if let Some(platform_config) = vm_config.platform.as_ref() { 1828 if self.pci_segment >= platform_config.num_pci_segments { 1829 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1830 } 1831 1832 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1833 if iommu_segments.contains(&self.pci_segment) { 1834 return Err(ValidationError::IommuNotSupportedOnSegment( 1835 self.pci_segment, 1836 )); 1837 } 1838 } 1839 } 1840 1841 Ok(()) 1842 } 1843 } 1844 1845 impl VdpaConfig { 1846 pub const SYNTAX: &'static str = "vDPA device \ 1847 \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\ 1848 id=<device_id>,pci_segment=<segment_id>\""; 1849 1850 pub fn parse(vdpa: &str) -> Result<Self> { 1851 let mut parser = OptionParser::new(); 1852 parser 1853 .add("path") 1854 .add("num_queues") 1855 .add("iommu") 1856 .add("id") 1857 .add("pci_segment"); 1858 parser.parse(vdpa).map_err(Error::ParseVdpa)?; 1859 1860 let path = parser 1861 .get("path") 1862 .map(PathBuf::from) 1863 .ok_or(Error::ParseVdpaPathMissing)?; 1864 let num_queues = parser 1865 .convert("num_queues") 1866 .map_err(Error::ParseVdpa)? 1867 .unwrap_or_else(default_vdpaconfig_num_queues); 1868 let iommu = parser 1869 .convert::<Toggle>("iommu") 1870 .map_err(Error::ParseVdpa)? 1871 .unwrap_or(Toggle(false)) 1872 .0; 1873 let id = parser.get("id"); 1874 let pci_segment = parser 1875 .convert("pci_segment") 1876 .map_err(Error::ParseVdpa)? 1877 .unwrap_or_default(); 1878 1879 Ok(VdpaConfig { 1880 path, 1881 num_queues, 1882 iommu, 1883 id, 1884 pci_segment, 1885 }) 1886 } 1887 1888 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1889 if let Some(platform_config) = vm_config.platform.as_ref() { 1890 if self.pci_segment >= platform_config.num_pci_segments { 1891 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1892 } 1893 1894 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1895 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1896 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1897 } 1898 } 1899 } 1900 1901 Ok(()) 1902 } 1903 } 1904 1905 impl VsockConfig { 1906 pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ 1907 \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\""; 1908 1909 pub fn parse(vsock: &str) -> Result<Self> { 1910 let mut parser = OptionParser::new(); 1911 parser 1912 .add("socket") 1913 .add("cid") 1914 .add("iommu") 1915 .add("id") 1916 .add("pci_segment"); 1917 parser.parse(vsock).map_err(Error::ParseVsock)?; 1918 1919 let socket = parser 1920 .get("socket") 1921 .map(PathBuf::from) 1922 .ok_or(Error::ParseVsockSockMissing)?; 1923 let iommu = parser 1924 .convert::<Toggle>("iommu") 1925 .map_err(Error::ParseVsock)? 1926 .unwrap_or(Toggle(false)) 1927 .0; 1928 let cid = parser 1929 .convert("cid") 1930 .map_err(Error::ParseVsock)? 1931 .ok_or(Error::ParseVsockCidMissing)?; 1932 let id = parser.get("id"); 1933 let pci_segment = parser 1934 .convert("pci_segment") 1935 .map_err(Error::ParseVsock)? 1936 .unwrap_or_default(); 1937 1938 Ok(VsockConfig { 1939 cid, 1940 socket, 1941 iommu, 1942 id, 1943 pci_segment, 1944 }) 1945 } 1946 1947 pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { 1948 if let Some(platform_config) = vm_config.platform.as_ref() { 1949 if self.pci_segment >= platform_config.num_pci_segments { 1950 return Err(ValidationError::InvalidPciSegment(self.pci_segment)); 1951 } 1952 1953 if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { 1954 if iommu_segments.contains(&self.pci_segment) && !self.iommu { 1955 return Err(ValidationError::OnIommuSegment(self.pci_segment)); 1956 } 1957 } 1958 } 1959 1960 Ok(()) 1961 } 1962 } 1963 1964 #[cfg(target_arch = "x86_64")] 1965 impl SgxEpcConfig { 1966 pub const SYNTAX: &'static str = "SGX EPC parameters \ 1967 \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\""; 1968 1969 pub fn parse(sgx_epc: &str) -> Result<Self> { 1970 let mut parser = OptionParser::new(); 1971 parser.add("id").add("size").add("prefault"); 1972 parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?; 1973 1974 let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?; 1975 let size = parser 1976 .convert::<ByteSized>("size") 1977 .map_err(Error::ParseSgxEpc)? 1978 .unwrap_or(ByteSized(0)) 1979 .0; 1980 let prefault = parser 1981 .convert::<Toggle>("prefault") 1982 .map_err(Error::ParseSgxEpc)? 1983 .unwrap_or(Toggle(false)) 1984 .0; 1985 1986 Ok(SgxEpcConfig { id, size, prefault }) 1987 } 1988 } 1989 1990 impl NumaConfig { 1991 pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ 1992 \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\ 1993 memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>,\ 1994 pci_segments=<list_of_pci_segments>\""; 1995 1996 pub fn parse(numa: &str) -> Result<Self> { 1997 let mut parser = OptionParser::new(); 1998 parser 1999 .add("guest_numa_id") 2000 .add("cpus") 2001 .add("distances") 2002 .add("memory_zones") 2003 .add("sgx_epc_sections") 2004 .add("pci_segments"); 2005 2006 parser.parse(numa).map_err(Error::ParseNuma)?; 2007 2008 let guest_numa_id = parser 2009 .convert::<u32>("guest_numa_id") 2010 .map_err(Error::ParseNuma)? 2011 .unwrap_or(0); 2012 let cpus = parser 2013 .convert::<IntegerList>("cpus") 2014 .map_err(Error::ParseNuma)? 2015 .map(|v| v.0.iter().map(|e| *e as u8).collect()); 2016 let distances = parser 2017 .convert::<Tuple<u64, u64>>("distances") 2018 .map_err(Error::ParseNuma)? 2019 .map(|v| { 2020 v.0.iter() 2021 .map(|(e1, e2)| NumaDistance { 2022 destination: *e1 as u32, 2023 distance: *e2 as u8, 2024 }) 2025 .collect() 2026 }); 2027 let memory_zones = parser 2028 .convert::<StringList>("memory_zones") 2029 .map_err(Error::ParseNuma)? 2030 .map(|v| v.0); 2031 #[cfg(target_arch = "x86_64")] 2032 let sgx_epc_sections = parser 2033 .convert::<StringList>("sgx_epc_sections") 2034 .map_err(Error::ParseNuma)? 2035 .map(|v| v.0); 2036 let pci_segments = parser 2037 .convert::<IntegerList>("pci_segments") 2038 .map_err(Error::ParseNuma)? 2039 .map(|v| v.0.iter().map(|e| *e as u16).collect()); 2040 Ok(NumaConfig { 2041 guest_numa_id, 2042 cpus, 2043 distances, 2044 memory_zones, 2045 #[cfg(target_arch = "x86_64")] 2046 sgx_epc_sections, 2047 pci_segments, 2048 }) 2049 } 2050 } 2051 2052 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] 2053 pub struct RestoreConfig { 2054 pub source_url: PathBuf, 2055 #[serde(default)] 2056 pub prefault: bool, 2057 } 2058 2059 impl RestoreConfig { 2060 pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ 2061 \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \ 2062 \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ 2063 \n`prefault` brings memory pages in when enabled (disabled by default)"; 2064 2065 pub fn parse(restore: &str) -> Result<Self> { 2066 let mut parser = OptionParser::new(); 2067 parser.add("source_url").add("prefault"); 2068 parser.parse(restore).map_err(Error::ParseRestore)?; 2069 2070 let source_url = parser 2071 .get("source_url") 2072 .map(PathBuf::from) 2073 .ok_or(Error::ParseRestoreSourceUrlMissing)?; 2074 let prefault = parser 2075 .convert::<Toggle>("prefault") 2076 .map_err(Error::ParseRestore)? 2077 .unwrap_or(Toggle(false)) 2078 .0; 2079 2080 Ok(RestoreConfig { 2081 source_url, 2082 prefault, 2083 }) 2084 } 2085 } 2086 2087 impl TpmConfig { 2088 pub const SYNTAX: &'static str = "TPM device \ 2089 \"(UNIX Domain Socket from swtpm) socket=</path/to/a/socket>\""; 2090 2091 pub fn parse(tpm: &str) -> Result<Self> { 2092 let mut parser = OptionParser::new(); 2093 parser.add("socket"); 2094 parser.parse(tpm).map_err(Error::ParseTpm)?; 2095 let socket = parser 2096 .get("socket") 2097 .map(PathBuf::from) 2098 .ok_or(Error::ParseTpmPathMissing)?; 2099 Ok(TpmConfig { socket }) 2100 } 2101 } 2102 2103 impl VmConfig { 2104 fn validate_identifier( 2105 id_list: &mut BTreeSet<String>, 2106 id: &Option<String>, 2107 ) -> ValidationResult<()> { 2108 if let Some(id) = id.as_ref() { 2109 if id.starts_with("__") { 2110 return Err(ValidationError::InvalidIdentifier(id.clone())); 2111 } 2112 2113 if !id_list.insert(id.clone()) { 2114 return Err(ValidationError::IdentifierNotUnique(id.clone())); 2115 } 2116 } 2117 2118 Ok(()) 2119 } 2120 2121 pub fn backed_by_shared_memory(&self) -> bool { 2122 if self.memory.shared || self.memory.hugepages { 2123 return true; 2124 } 2125 2126 if self.memory.size == 0 { 2127 for zone in self.memory.zones.as_ref().unwrap() { 2128 if !zone.shared && !zone.hugepages { 2129 return false; 2130 } 2131 } 2132 true 2133 } else { 2134 false 2135 } 2136 } 2137 2138 // Also enables virtio-iommu if the config needs it 2139 // Returns the list of unique identifiers provided through the 2140 // configuration. 2141 pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> { 2142 let mut id_list = BTreeSet::new(); 2143 2144 self.payload 2145 .as_ref() 2146 .ok_or(ValidationError::KernelMissing)?; 2147 2148 #[cfg(feature = "tdx")] 2149 { 2150 let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false); 2151 // At this point we know payload isn't None. 2152 if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() { 2153 return Err(ValidationError::TdxFirmwareMissing); 2154 } 2155 if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) { 2156 return Err(ValidationError::TdxNoCpuHotplug); 2157 } 2158 } 2159 2160 #[cfg(feature = "sev_snp")] 2161 { 2162 let host_data_opt = &self.payload.as_ref().unwrap().host_data; 2163 2164 if let Some(host_data) = host_data_opt { 2165 if host_data.len() != 64 { 2166 return Err(ValidationError::InvalidHostData); 2167 } 2168 } 2169 } 2170 // The 'conflict' check is introduced in commit 24438e0390d3 2171 // (vm-virtio: Enable the vmm support for virtio-console). 2172 // 2173 // Allow simultaneously set serial and console as TTY mode, for 2174 // someone want to use virtio console for better performance, and 2175 // want to keep legacy serial to catch boot stage logs for debug. 2176 // Using such double tty mode, you need to configure the kernel 2177 // properly, such as: 2178 // "console=hvc0 earlyprintk=ttyS0" 2179 2180 let mut tty_consoles = Vec::new(); 2181 if self.console.mode == ConsoleOutputMode::Tty { 2182 tty_consoles.push("virtio-console"); 2183 }; 2184 if self.serial.mode == ConsoleOutputMode::Tty { 2185 tty_consoles.push("serial-console"); 2186 }; 2187 #[cfg(target_arch = "x86_64")] 2188 if self.debug_console.mode == ConsoleOutputMode::Tty { 2189 tty_consoles.push("debug-console"); 2190 }; 2191 if tty_consoles.len() > 1 { 2192 warn!("Using TTY output for multiple consoles: {:?}", tty_consoles); 2193 } 2194 2195 if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { 2196 return Err(ValidationError::ConsoleFileMissing); 2197 } 2198 2199 if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() { 2200 return Err(ValidationError::ConsoleFileMissing); 2201 } 2202 2203 if self.cpus.max_vcpus < self.cpus.boot_vcpus { 2204 return Err(ValidationError::CpusMaxLowerThanBoot); 2205 } 2206 2207 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2208 for rate_limit_group in rate_limit_groups { 2209 rate_limit_group.validate(self)?; 2210 2211 Self::validate_identifier(&mut id_list, &Some(rate_limit_group.id.clone()))?; 2212 } 2213 } 2214 2215 if let Some(disks) = &self.disks { 2216 for disk in disks { 2217 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() { 2218 return Err(ValidationError::DiskSocketAndPath); 2219 } 2220 if disk.vhost_user && !self.backed_by_shared_memory() { 2221 return Err(ValidationError::VhostUserRequiresSharedMemory); 2222 } 2223 if disk.vhost_user && disk.vhost_socket.is_none() { 2224 return Err(ValidationError::VhostUserMissingSocket); 2225 } 2226 if let Some(rate_limit_group) = &disk.rate_limit_group { 2227 if let Some(rate_limit_groups) = &self.rate_limit_groups { 2228 if !rate_limit_groups 2229 .iter() 2230 .any(|cfg| &cfg.id == rate_limit_group) 2231 { 2232 return Err(ValidationError::InvalidRateLimiterGroup); 2233 } 2234 } else { 2235 return Err(ValidationError::InvalidRateLimiterGroup); 2236 } 2237 } 2238 2239 disk.validate(self)?; 2240 self.iommu |= disk.iommu; 2241 2242 Self::validate_identifier(&mut id_list, &disk.id)?; 2243 } 2244 } 2245 2246 if let Some(nets) = &self.net { 2247 for net in nets { 2248 if net.vhost_user && !self.backed_by_shared_memory() { 2249 return Err(ValidationError::VhostUserRequiresSharedMemory); 2250 } 2251 net.validate(self)?; 2252 self.iommu |= net.iommu; 2253 2254 Self::validate_identifier(&mut id_list, &net.id)?; 2255 } 2256 } 2257 2258 if let Some(fses) = &self.fs { 2259 if !fses.is_empty() && !self.backed_by_shared_memory() { 2260 return Err(ValidationError::VhostUserRequiresSharedMemory); 2261 } 2262 for fs in fses { 2263 fs.validate(self)?; 2264 2265 Self::validate_identifier(&mut id_list, &fs.id)?; 2266 } 2267 } 2268 2269 if let Some(pmems) = &self.pmem { 2270 for pmem in pmems { 2271 pmem.validate(self)?; 2272 self.iommu |= pmem.iommu; 2273 2274 Self::validate_identifier(&mut id_list, &pmem.id)?; 2275 } 2276 } 2277 2278 self.iommu |= self.rng.iommu; 2279 self.iommu |= self.console.iommu; 2280 2281 if let Some(t) = &self.cpus.topology { 2282 if t.threads_per_core == 0 2283 || t.cores_per_die == 0 2284 || t.dies_per_package == 0 2285 || t.packages == 0 2286 { 2287 return Err(ValidationError::CpuTopologyZeroPart); 2288 } 2289 2290 // The setting of dies doesen't apply on AArch64. 2291 // Only '1' value is accepted, so its impact on the vcpu topology 2292 // setting can be ignored. 2293 #[cfg(target_arch = "aarch64")] 2294 if t.dies_per_package != 1 { 2295 return Err(ValidationError::CpuTopologyDiesPerPackage); 2296 } 2297 2298 let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages; 2299 if total != self.cpus.max_vcpus { 2300 return Err(ValidationError::CpuTopologyCount); 2301 } 2302 } 2303 2304 if let Some(hugepage_size) = &self.memory.hugepage_size { 2305 if !self.memory.hugepages { 2306 return Err(ValidationError::HugePageSizeWithoutHugePages); 2307 } 2308 if !hugepage_size.is_power_of_two() { 2309 return Err(ValidationError::InvalidHugePageSize(*hugepage_size)); 2310 } 2311 } 2312 2313 if let Some(user_devices) = &self.user_devices { 2314 if !user_devices.is_empty() && !self.backed_by_shared_memory() { 2315 return Err(ValidationError::UserDevicesRequireSharedMemory); 2316 } 2317 2318 for user_device in user_devices { 2319 user_device.validate(self)?; 2320 2321 Self::validate_identifier(&mut id_list, &user_device.id)?; 2322 } 2323 } 2324 2325 if let Some(vdpa_devices) = &self.vdpa { 2326 for vdpa_device in vdpa_devices { 2327 vdpa_device.validate(self)?; 2328 self.iommu |= vdpa_device.iommu; 2329 2330 Self::validate_identifier(&mut id_list, &vdpa_device.id)?; 2331 } 2332 } 2333 2334 if let Some(vsock) = &self.vsock { 2335 if [!0, 0, 1, 2].contains(&vsock.cid) { 2336 return Err(ValidationError::VsockSpecialCid(vsock.cid)); 2337 } 2338 } 2339 2340 if let Some(balloon) = &self.balloon { 2341 let mut ram_size = self.memory.size; 2342 2343 if let Some(zones) = &self.memory.zones { 2344 for zone in zones { 2345 ram_size += zone.size; 2346 } 2347 } 2348 2349 if balloon.size >= ram_size { 2350 return Err(ValidationError::BalloonLargerThanRam( 2351 balloon.size, 2352 ram_size, 2353 )); 2354 } 2355 } 2356 2357 if let Some(devices) = &self.devices { 2358 let mut device_paths = BTreeSet::new(); 2359 for device in devices { 2360 if !device_paths.insert(device.path.to_string_lossy()) { 2361 return Err(ValidationError::DuplicateDevicePath( 2362 device.path.to_string_lossy().to_string(), 2363 )); 2364 } 2365 2366 device.validate(self)?; 2367 self.iommu |= device.iommu; 2368 2369 Self::validate_identifier(&mut id_list, &device.id)?; 2370 } 2371 } 2372 2373 if let Some(vsock) = &self.vsock { 2374 vsock.validate(self)?; 2375 self.iommu |= vsock.iommu; 2376 2377 Self::validate_identifier(&mut id_list, &vsock.id)?; 2378 } 2379 2380 let num_pci_segments = match &self.platform { 2381 Some(platform_config) => platform_config.num_pci_segments, 2382 None => 1, 2383 }; 2384 if let Some(numa) = &self.numa { 2385 let mut used_numa_node_memory_zones = HashMap::new(); 2386 let mut used_pci_segments = HashMap::new(); 2387 for numa_node in numa.iter() { 2388 if let Some(memory_zones) = numa_node.memory_zones.clone() { 2389 for memory_zone in memory_zones.iter() { 2390 if !used_numa_node_memory_zones.contains_key(memory_zone) { 2391 used_numa_node_memory_zones 2392 .insert(memory_zone.to_string(), numa_node.guest_numa_id); 2393 } else { 2394 return Err(ValidationError::MemoryZoneReused( 2395 memory_zone.to_string(), 2396 *used_numa_node_memory_zones.get(memory_zone).unwrap(), 2397 numa_node.guest_numa_id, 2398 )); 2399 } 2400 } 2401 } 2402 2403 if let Some(pci_segments) = numa_node.pci_segments.clone() { 2404 for pci_segment in pci_segments.iter() { 2405 if *pci_segment >= num_pci_segments { 2406 return Err(ValidationError::InvalidPciSegment(*pci_segment)); 2407 } 2408 if *pci_segment == 0 && numa_node.guest_numa_id != 0 { 2409 return Err(ValidationError::DefaultPciSegmentInvalidNode( 2410 numa_node.guest_numa_id, 2411 )); 2412 } 2413 if !used_pci_segments.contains_key(pci_segment) { 2414 used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id); 2415 } else { 2416 return Err(ValidationError::PciSegmentReused( 2417 *pci_segment, 2418 *used_pci_segments.get(pci_segment).unwrap(), 2419 numa_node.guest_numa_id, 2420 )); 2421 } 2422 } 2423 } 2424 } 2425 } 2426 2427 if let Some(zones) = &self.memory.zones { 2428 for zone in zones.iter() { 2429 let id = zone.id.clone(); 2430 Self::validate_identifier(&mut id_list, &Some(id))?; 2431 } 2432 } 2433 2434 #[cfg(target_arch = "x86_64")] 2435 if let Some(sgx_epcs) = &self.sgx_epc { 2436 for sgx_epc in sgx_epcs.iter() { 2437 let id = sgx_epc.id.clone(); 2438 Self::validate_identifier(&mut id_list, &Some(id))?; 2439 } 2440 } 2441 2442 self.platform.as_ref().map(|p| p.validate()).transpose()?; 2443 self.iommu |= self 2444 .platform 2445 .as_ref() 2446 .map(|p| p.iommu_segments.is_some()) 2447 .unwrap_or_default(); 2448 2449 Ok(id_list) 2450 } 2451 2452 pub fn parse(vm_params: VmParams) -> Result<Self> { 2453 let mut rate_limit_groups: Option<Vec<RateLimiterGroupConfig>> = None; 2454 if let Some(rate_limit_group_list) = &vm_params.rate_limit_groups { 2455 let mut rate_limit_group_config_list = Vec::new(); 2456 for item in rate_limit_group_list.iter() { 2457 let rate_limit_group_config = RateLimiterGroupConfig::parse(item)?; 2458 rate_limit_group_config_list.push(rate_limit_group_config); 2459 } 2460 rate_limit_groups = Some(rate_limit_group_config_list); 2461 } 2462 2463 let mut disks: Option<Vec<DiskConfig>> = None; 2464 if let Some(disk_list) = &vm_params.disks { 2465 let mut disk_config_list = Vec::new(); 2466 for item in disk_list.iter() { 2467 let disk_config = DiskConfig::parse(item)?; 2468 disk_config_list.push(disk_config); 2469 } 2470 disks = Some(disk_config_list); 2471 } 2472 2473 let mut net: Option<Vec<NetConfig>> = None; 2474 if let Some(net_list) = &vm_params.net { 2475 let mut net_config_list = Vec::new(); 2476 for item in net_list.iter() { 2477 let net_config = NetConfig::parse(item)?; 2478 net_config_list.push(net_config); 2479 } 2480 net = Some(net_config_list); 2481 } 2482 2483 let rng = RngConfig::parse(vm_params.rng)?; 2484 2485 let mut balloon: Option<BalloonConfig> = None; 2486 if let Some(balloon_params) = &vm_params.balloon { 2487 balloon = Some(BalloonConfig::parse(balloon_params)?); 2488 } 2489 2490 let mut fs: Option<Vec<FsConfig>> = None; 2491 if let Some(fs_list) = &vm_params.fs { 2492 let mut fs_config_list = Vec::new(); 2493 for item in fs_list.iter() { 2494 fs_config_list.push(FsConfig::parse(item)?); 2495 } 2496 fs = Some(fs_config_list); 2497 } 2498 2499 let mut pmem: Option<Vec<PmemConfig>> = None; 2500 if let Some(pmem_list) = &vm_params.pmem { 2501 let mut pmem_config_list = Vec::new(); 2502 for item in pmem_list.iter() { 2503 let pmem_config = PmemConfig::parse(item)?; 2504 pmem_config_list.push(pmem_config); 2505 } 2506 pmem = Some(pmem_config_list); 2507 } 2508 2509 let console = ConsoleConfig::parse(vm_params.console)?; 2510 let serial = ConsoleConfig::parse(vm_params.serial)?; 2511 #[cfg(target_arch = "x86_64")] 2512 let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?; 2513 2514 let mut devices: Option<Vec<DeviceConfig>> = None; 2515 if let Some(device_list) = &vm_params.devices { 2516 let mut device_config_list = Vec::new(); 2517 for item in device_list.iter() { 2518 let device_config = DeviceConfig::parse(item)?; 2519 device_config_list.push(device_config); 2520 } 2521 devices = Some(device_config_list); 2522 } 2523 2524 let mut user_devices: Option<Vec<UserDeviceConfig>> = None; 2525 if let Some(user_device_list) = &vm_params.user_devices { 2526 let mut user_device_config_list = Vec::new(); 2527 for item in user_device_list.iter() { 2528 let user_device_config = UserDeviceConfig::parse(item)?; 2529 user_device_config_list.push(user_device_config); 2530 } 2531 user_devices = Some(user_device_config_list); 2532 } 2533 2534 let mut vdpa: Option<Vec<VdpaConfig>> = None; 2535 if let Some(vdpa_list) = &vm_params.vdpa { 2536 let mut vdpa_config_list = Vec::new(); 2537 for item in vdpa_list.iter() { 2538 let vdpa_config = VdpaConfig::parse(item)?; 2539 vdpa_config_list.push(vdpa_config); 2540 } 2541 vdpa = Some(vdpa_config_list); 2542 } 2543 2544 let mut vsock: Option<VsockConfig> = None; 2545 if let Some(vs) = &vm_params.vsock { 2546 let vsock_config = VsockConfig::parse(vs)?; 2547 vsock = Some(vsock_config); 2548 } 2549 2550 let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?; 2551 2552 #[cfg(target_arch = "x86_64")] 2553 let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None; 2554 #[cfg(target_arch = "x86_64")] 2555 { 2556 if let Some(sgx_epc_list) = &vm_params.sgx_epc { 2557 let mut sgx_epc_config_list = Vec::new(); 2558 for item in sgx_epc_list.iter() { 2559 let sgx_epc_config = SgxEpcConfig::parse(item)?; 2560 sgx_epc_config_list.push(sgx_epc_config); 2561 } 2562 sgx_epc = Some(sgx_epc_config_list); 2563 } 2564 } 2565 2566 let mut numa: Option<Vec<NumaConfig>> = None; 2567 if let Some(numa_list) = &vm_params.numa { 2568 let mut numa_config_list = Vec::new(); 2569 for item in numa_list.iter() { 2570 let numa_config = NumaConfig::parse(item)?; 2571 numa_config_list.push(numa_config); 2572 } 2573 numa = Some(numa_config_list); 2574 } 2575 2576 #[cfg(not(feature = "igvm"))] 2577 let payload_present = vm_params.kernel.is_some() || vm_params.firmware.is_some(); 2578 2579 #[cfg(feature = "igvm")] 2580 let payload_present = 2581 vm_params.kernel.is_some() || vm_params.firmware.is_some() || vm_params.igvm.is_some(); 2582 2583 let payload = if payload_present { 2584 Some(PayloadConfig { 2585 kernel: vm_params.kernel.map(PathBuf::from), 2586 initramfs: vm_params.initramfs.map(PathBuf::from), 2587 cmdline: vm_params.cmdline.map(|s| s.to_string()), 2588 firmware: vm_params.firmware.map(PathBuf::from), 2589 #[cfg(feature = "igvm")] 2590 igvm: vm_params.igvm.map(PathBuf::from), 2591 #[cfg(feature = "sev_snp")] 2592 host_data: vm_params.host_data.map(|s| s.to_string()), 2593 }) 2594 } else { 2595 None 2596 }; 2597 2598 let mut tpm: Option<TpmConfig> = None; 2599 if let Some(tc) = vm_params.tpm { 2600 let tpm_conf = TpmConfig::parse(tc)?; 2601 tpm = Some(TpmConfig { 2602 socket: tpm_conf.socket, 2603 }); 2604 } 2605 2606 #[cfg(feature = "guest_debug")] 2607 let gdb = vm_params.gdb; 2608 2609 let mut config = VmConfig { 2610 cpus: CpusConfig::parse(vm_params.cpus)?, 2611 memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?, 2612 payload, 2613 rate_limit_groups, 2614 disks, 2615 net, 2616 rng, 2617 balloon, 2618 fs, 2619 pmem, 2620 serial, 2621 console, 2622 #[cfg(target_arch = "x86_64")] 2623 debug_console, 2624 devices, 2625 user_devices, 2626 vdpa, 2627 vsock, 2628 pvpanic: vm_params.pvpanic, 2629 iommu: false, // updated in VmConfig::validate() 2630 #[cfg(target_arch = "x86_64")] 2631 sgx_epc, 2632 numa, 2633 watchdog: vm_params.watchdog, 2634 #[cfg(feature = "guest_debug")] 2635 gdb, 2636 platform, 2637 tpm, 2638 preserved_fds: None, 2639 }; 2640 config.validate().map_err(Error::Validation)?; 2641 Ok(config) 2642 } 2643 2644 pub fn remove_device(&mut self, id: &str) -> bool { 2645 let mut removed = false; 2646 2647 // Remove if VFIO device 2648 if let Some(devices) = self.devices.as_mut() { 2649 let len = devices.len(); 2650 devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2651 removed |= devices.len() != len; 2652 } 2653 2654 // Remove if VFIO user device 2655 if let Some(user_devices) = self.user_devices.as_mut() { 2656 let len = user_devices.len(); 2657 user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2658 removed |= user_devices.len() != len; 2659 } 2660 2661 // Remove if disk device 2662 if let Some(disks) = self.disks.as_mut() { 2663 let len = disks.len(); 2664 disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2665 removed |= disks.len() != len; 2666 } 2667 2668 // Remove if fs device 2669 if let Some(fs) = self.fs.as_mut() { 2670 let len = fs.len(); 2671 fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2672 removed |= fs.len() != len; 2673 } 2674 2675 // Remove if net device 2676 if let Some(net) = self.net.as_mut() { 2677 let len = net.len(); 2678 net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2679 removed |= net.len() != len; 2680 } 2681 2682 // Remove if pmem device 2683 if let Some(pmem) = self.pmem.as_mut() { 2684 let len = pmem.len(); 2685 pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2686 removed |= pmem.len() != len; 2687 } 2688 2689 // Remove if vDPA device 2690 if let Some(vdpa) = self.vdpa.as_mut() { 2691 let len = vdpa.len(); 2692 vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); 2693 removed |= vdpa.len() != len; 2694 } 2695 2696 // Remove if vsock device 2697 if let Some(vsock) = self.vsock.as_ref() { 2698 if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) { 2699 self.vsock = None; 2700 removed = true; 2701 } 2702 } 2703 2704 removed 2705 } 2706 2707 /// # Safety 2708 /// To use this safely, the caller must guarantee that the input 2709 /// fds are all valid. 2710 pub unsafe fn add_preserved_fds(&mut self, mut fds: Vec<i32>) { 2711 if fds.is_empty() { 2712 return; 2713 } 2714 2715 if let Some(preserved_fds) = &self.preserved_fds { 2716 fds.append(&mut preserved_fds.clone()); 2717 } 2718 2719 self.preserved_fds = Some(fds); 2720 } 2721 2722 #[cfg(feature = "tdx")] 2723 pub fn is_tdx_enabled(&self) -> bool { 2724 self.platform.as_ref().map(|p| p.tdx).unwrap_or(false) 2725 } 2726 2727 #[cfg(feature = "sev_snp")] 2728 pub fn is_sev_snp_enabled(&self) -> bool { 2729 self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false) 2730 } 2731 } 2732 2733 impl Clone for VmConfig { 2734 fn clone(&self) -> Self { 2735 VmConfig { 2736 cpus: self.cpus.clone(), 2737 memory: self.memory.clone(), 2738 payload: self.payload.clone(), 2739 rate_limit_groups: self.rate_limit_groups.clone(), 2740 disks: self.disks.clone(), 2741 net: self.net.clone(), 2742 rng: self.rng.clone(), 2743 balloon: self.balloon.clone(), 2744 fs: self.fs.clone(), 2745 pmem: self.pmem.clone(), 2746 serial: self.serial.clone(), 2747 console: self.console.clone(), 2748 #[cfg(target_arch = "x86_64")] 2749 debug_console: self.debug_console.clone(), 2750 devices: self.devices.clone(), 2751 user_devices: self.user_devices.clone(), 2752 vdpa: self.vdpa.clone(), 2753 vsock: self.vsock.clone(), 2754 #[cfg(target_arch = "x86_64")] 2755 sgx_epc: self.sgx_epc.clone(), 2756 numa: self.numa.clone(), 2757 platform: self.platform.clone(), 2758 tpm: self.tpm.clone(), 2759 preserved_fds: self 2760 .preserved_fds 2761 .as_ref() 2762 // SAFETY: FFI call with valid FDs 2763 .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()), 2764 ..*self 2765 } 2766 } 2767 } 2768 2769 impl Drop for VmConfig { 2770 fn drop(&mut self) { 2771 if let Some(mut fds) = self.preserved_fds.take() { 2772 for fd in fds.drain(..) { 2773 // SAFETY: FFI call with valid FDs 2774 unsafe { libc::close(fd) }; 2775 } 2776 } 2777 } 2778 } 2779 2780 #[cfg(test)] 2781 mod tests { 2782 use super::*; 2783 use net_util::MacAddr; 2784 use std::fs::File; 2785 use std::net::Ipv4Addr; 2786 use std::os::unix::io::AsRawFd; 2787 2788 #[test] 2789 fn test_cpu_parsing() -> Result<()> { 2790 assert_eq!(CpusConfig::parse("")?, CpusConfig::default()); 2791 2792 assert_eq!( 2793 CpusConfig::parse("boot=1")?, 2794 CpusConfig { 2795 boot_vcpus: 1, 2796 max_vcpus: 1, 2797 ..Default::default() 2798 } 2799 ); 2800 assert_eq!( 2801 CpusConfig::parse("boot=1,max=2")?, 2802 CpusConfig { 2803 boot_vcpus: 1, 2804 max_vcpus: 2, 2805 ..Default::default() 2806 } 2807 ); 2808 assert_eq!( 2809 CpusConfig::parse("boot=8,topology=2:2:1:2")?, 2810 CpusConfig { 2811 boot_vcpus: 8, 2812 max_vcpus: 8, 2813 topology: Some(CpuTopology { 2814 threads_per_core: 2, 2815 cores_per_die: 2, 2816 dies_per_package: 1, 2817 packages: 2 2818 }), 2819 ..Default::default() 2820 } 2821 ); 2822 2823 assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err()); 2824 assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err()); 2825 assert_eq!( 2826 CpusConfig::parse("boot=1,kvm_hyperv=on")?, 2827 CpusConfig { 2828 boot_vcpus: 1, 2829 max_vcpus: 1, 2830 kvm_hyperv: true, 2831 ..Default::default() 2832 } 2833 ); 2834 assert_eq!( 2835 CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?, 2836 CpusConfig { 2837 boot_vcpus: 2, 2838 max_vcpus: 2, 2839 affinity: Some(vec![ 2840 CpuAffinity { 2841 vcpu: 0, 2842 host_cpus: vec![0, 2], 2843 }, 2844 CpuAffinity { 2845 vcpu: 1, 2846 host_cpus: vec![1, 3], 2847 } 2848 ]), 2849 ..Default::default() 2850 }, 2851 ); 2852 2853 Ok(()) 2854 } 2855 2856 #[test] 2857 fn test_mem_parsing() -> Result<()> { 2858 assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default()); 2859 // Default string 2860 assert_eq!( 2861 MemoryConfig::parse("size=512M", None)?, 2862 MemoryConfig::default() 2863 ); 2864 assert_eq!( 2865 MemoryConfig::parse("size=512M,mergeable=on", None)?, 2866 MemoryConfig { 2867 size: 512 << 20, 2868 mergeable: true, 2869 ..Default::default() 2870 } 2871 ); 2872 assert_eq!( 2873 MemoryConfig::parse("mergeable=on", None)?, 2874 MemoryConfig { 2875 mergeable: true, 2876 ..Default::default() 2877 } 2878 ); 2879 assert_eq!( 2880 MemoryConfig::parse("size=1G,mergeable=off", None)?, 2881 MemoryConfig { 2882 size: 1 << 30, 2883 mergeable: false, 2884 ..Default::default() 2885 } 2886 ); 2887 assert_eq!( 2888 MemoryConfig::parse("hotplug_method=acpi", None)?, 2889 MemoryConfig { 2890 ..Default::default() 2891 } 2892 ); 2893 assert_eq!( 2894 MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?, 2895 MemoryConfig { 2896 hotplug_size: Some(512 << 20), 2897 ..Default::default() 2898 } 2899 ); 2900 assert_eq!( 2901 MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?, 2902 MemoryConfig { 2903 hotplug_size: Some(512 << 20), 2904 hotplug_method: HotplugMethod::VirtioMem, 2905 ..Default::default() 2906 } 2907 ); 2908 assert_eq!( 2909 MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?, 2910 MemoryConfig { 2911 hugepage_size: Some(2 << 20), 2912 size: 1 << 30, 2913 hugepages: true, 2914 ..Default::default() 2915 } 2916 ); 2917 Ok(()) 2918 } 2919 2920 #[test] 2921 fn test_rate_limit_group_parsing() -> Result<()> { 2922 assert_eq!( 2923 RateLimiterGroupConfig::parse("id=group0,bw_size=1000,bw_refill_time=100")?, 2924 RateLimiterGroupConfig { 2925 id: "group0".to_string(), 2926 rate_limiter_config: RateLimiterConfig { 2927 bandwidth: Some(TokenBucketConfig { 2928 size: 1000, 2929 one_time_burst: Some(0), 2930 refill_time: 100, 2931 }), 2932 ops: None, 2933 } 2934 } 2935 ); 2936 assert_eq!( 2937 RateLimiterGroupConfig::parse("id=group0,ops_size=1000,ops_refill_time=100")?, 2938 RateLimiterGroupConfig { 2939 id: "group0".to_string(), 2940 rate_limiter_config: RateLimiterConfig { 2941 bandwidth: None, 2942 ops: Some(TokenBucketConfig { 2943 size: 1000, 2944 one_time_burst: Some(0), 2945 refill_time: 100, 2946 }), 2947 } 2948 } 2949 ); 2950 Ok(()) 2951 } 2952 2953 fn disk_fixture() -> DiskConfig { 2954 DiskConfig { 2955 path: Some(PathBuf::from("/path/to_file")), 2956 readonly: false, 2957 direct: false, 2958 iommu: false, 2959 num_queues: 1, 2960 queue_size: 128, 2961 vhost_user: false, 2962 vhost_socket: None, 2963 id: None, 2964 disable_io_uring: false, 2965 disable_aio: false, 2966 rate_limit_group: None, 2967 rate_limiter_config: None, 2968 pci_segment: 0, 2969 serial: None, 2970 queue_affinity: None, 2971 } 2972 } 2973 2974 #[test] 2975 fn test_disk_parsing() -> Result<()> { 2976 assert_eq!( 2977 DiskConfig::parse("path=/path/to_file")?, 2978 DiskConfig { ..disk_fixture() } 2979 ); 2980 assert_eq!( 2981 DiskConfig::parse("path=/path/to_file,id=mydisk0")?, 2982 DiskConfig { 2983 id: Some("mydisk0".to_owned()), 2984 ..disk_fixture() 2985 } 2986 ); 2987 assert_eq!( 2988 DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?, 2989 DiskConfig { 2990 path: None, 2991 vhost_socket: Some(String::from("/tmp/sock")), 2992 vhost_user: true, 2993 ..disk_fixture() 2994 } 2995 ); 2996 assert_eq!( 2997 DiskConfig::parse("path=/path/to_file,iommu=on")?, 2998 DiskConfig { 2999 iommu: true, 3000 ..disk_fixture() 3001 } 3002 ); 3003 assert_eq!( 3004 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?, 3005 DiskConfig { 3006 iommu: true, 3007 queue_size: 256, 3008 ..disk_fixture() 3009 } 3010 ); 3011 assert_eq!( 3012 DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?, 3013 DiskConfig { 3014 iommu: true, 3015 queue_size: 256, 3016 num_queues: 4, 3017 ..disk_fixture() 3018 } 3019 ); 3020 assert_eq!( 3021 DiskConfig::parse("path=/path/to_file,direct=on")?, 3022 DiskConfig { 3023 direct: true, 3024 ..disk_fixture() 3025 } 3026 ); 3027 assert_eq!( 3028 DiskConfig::parse("path=/path/to_file,serial=test")?, 3029 DiskConfig { 3030 serial: Some(String::from("test")), 3031 ..disk_fixture() 3032 } 3033 ); 3034 assert_eq!( 3035 DiskConfig::parse("path=/path/to_file,rate_limit_group=group0")?, 3036 DiskConfig { 3037 rate_limit_group: Some("group0".to_string()), 3038 ..disk_fixture() 3039 } 3040 ); 3041 assert_eq!( 3042 DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?, 3043 DiskConfig { 3044 queue_affinity: Some(vec![ 3045 VirtQueueAffinity { 3046 queue_index: 0, 3047 host_cpus: vec![1], 3048 }, 3049 VirtQueueAffinity { 3050 queue_index: 1, 3051 host_cpus: vec![2], 3052 }, 3053 VirtQueueAffinity { 3054 queue_index: 2, 3055 host_cpus: vec![3, 4], 3056 }, 3057 VirtQueueAffinity { 3058 queue_index: 3, 3059 host_cpus: vec![5, 6, 7, 8], 3060 } 3061 ]), 3062 ..disk_fixture() 3063 } 3064 ); 3065 Ok(()) 3066 } 3067 3068 fn net_fixture() -> NetConfig { 3069 NetConfig { 3070 tap: None, 3071 ip: Ipv4Addr::new(192, 168, 249, 1), 3072 mask: Ipv4Addr::new(255, 255, 255, 0), 3073 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), 3074 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), 3075 mtu: None, 3076 iommu: false, 3077 num_queues: 2, 3078 queue_size: 256, 3079 vhost_user: false, 3080 vhost_socket: None, 3081 vhost_mode: VhostMode::Client, 3082 id: None, 3083 fds: None, 3084 rate_limiter_config: None, 3085 pci_segment: 0, 3086 offload_tso: true, 3087 offload_ufo: true, 3088 offload_csum: true, 3089 } 3090 } 3091 3092 #[test] 3093 fn test_net_parsing() -> Result<()> { 3094 // mac address is random 3095 assert_eq!( 3096 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?, 3097 net_fixture(), 3098 ); 3099 3100 assert_eq!( 3101 NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?, 3102 NetConfig { 3103 id: Some("mynet0".to_owned()), 3104 ..net_fixture() 3105 } 3106 ); 3107 3108 assert_eq!( 3109 NetConfig::parse( 3110 "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" 3111 )?, 3112 NetConfig { 3113 tap: Some("tap0".to_owned()), 3114 ip: "192.168.100.1".parse().unwrap(), 3115 mask: "255.255.255.128".parse().unwrap(), 3116 ..net_fixture() 3117 } 3118 ); 3119 3120 assert_eq!( 3121 NetConfig::parse( 3122 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock" 3123 )?, 3124 NetConfig { 3125 vhost_user: true, 3126 vhost_socket: Some("/tmp/sock".to_owned()), 3127 ..net_fixture() 3128 } 3129 ); 3130 3131 assert_eq!( 3132 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")?, 3133 NetConfig { 3134 num_queues: 4, 3135 queue_size: 1024, 3136 iommu: true, 3137 ..net_fixture() 3138 } 3139 ); 3140 3141 assert_eq!( 3142 NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?, 3143 NetConfig { 3144 host_mac: None, 3145 fds: Some(vec![3, 7]), 3146 num_queues: 4, 3147 ..net_fixture() 3148 } 3149 ); 3150 3151 Ok(()) 3152 } 3153 3154 #[test] 3155 fn test_parse_rng() -> Result<()> { 3156 assert_eq!(RngConfig::parse("")?, RngConfig::default()); 3157 assert_eq!( 3158 RngConfig::parse("src=/dev/random")?, 3159 RngConfig { 3160 src: PathBuf::from("/dev/random"), 3161 ..Default::default() 3162 } 3163 ); 3164 assert_eq!( 3165 RngConfig::parse("src=/dev/random,iommu=on")?, 3166 RngConfig { 3167 src: PathBuf::from("/dev/random"), 3168 iommu: true, 3169 } 3170 ); 3171 assert_eq!( 3172 RngConfig::parse("iommu=on")?, 3173 RngConfig { 3174 iommu: true, 3175 ..Default::default() 3176 } 3177 ); 3178 Ok(()) 3179 } 3180 3181 fn fs_fixture() -> FsConfig { 3182 FsConfig { 3183 socket: PathBuf::from("/tmp/sock"), 3184 tag: "mytag".to_owned(), 3185 num_queues: 1, 3186 queue_size: 1024, 3187 id: None, 3188 pci_segment: 0, 3189 } 3190 } 3191 3192 #[test] 3193 fn test_parse_fs() -> Result<()> { 3194 // "tag" and "socket" must be supplied 3195 assert!(FsConfig::parse("").is_err()); 3196 assert!(FsConfig::parse("tag=mytag").is_err()); 3197 assert!(FsConfig::parse("socket=/tmp/sock").is_err()); 3198 assert_eq!(FsConfig::parse("tag=mytag,socket=/tmp/sock")?, fs_fixture()); 3199 assert_eq!( 3200 FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?, 3201 FsConfig { 3202 num_queues: 4, 3203 queue_size: 1024, 3204 ..fs_fixture() 3205 } 3206 ); 3207 3208 Ok(()) 3209 } 3210 3211 fn pmem_fixture() -> PmemConfig { 3212 PmemConfig { 3213 file: PathBuf::from("/tmp/pmem"), 3214 size: Some(128 << 20), 3215 iommu: false, 3216 discard_writes: false, 3217 id: None, 3218 pci_segment: 0, 3219 } 3220 } 3221 3222 #[test] 3223 fn test_pmem_parsing() -> Result<()> { 3224 // Must always give a file and size 3225 assert!(PmemConfig::parse("").is_err()); 3226 assert!(PmemConfig::parse("size=128M").is_err()); 3227 assert_eq!( 3228 PmemConfig::parse("file=/tmp/pmem,size=128M")?, 3229 pmem_fixture() 3230 ); 3231 assert_eq!( 3232 PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?, 3233 PmemConfig { 3234 id: Some("mypmem0".to_owned()), 3235 ..pmem_fixture() 3236 } 3237 ); 3238 assert_eq!( 3239 PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?, 3240 PmemConfig { 3241 discard_writes: true, 3242 iommu: true, 3243 ..pmem_fixture() 3244 } 3245 ); 3246 3247 Ok(()) 3248 } 3249 3250 #[test] 3251 fn test_console_parsing() -> Result<()> { 3252 assert!(ConsoleConfig::parse("").is_err()); 3253 assert!(ConsoleConfig::parse("badmode").is_err()); 3254 assert_eq!( 3255 ConsoleConfig::parse("off")?, 3256 ConsoleConfig { 3257 mode: ConsoleOutputMode::Off, 3258 iommu: false, 3259 file: None, 3260 socket: None, 3261 } 3262 ); 3263 assert_eq!( 3264 ConsoleConfig::parse("pty")?, 3265 ConsoleConfig { 3266 mode: ConsoleOutputMode::Pty, 3267 iommu: false, 3268 file: None, 3269 socket: None, 3270 } 3271 ); 3272 assert_eq!( 3273 ConsoleConfig::parse("tty")?, 3274 ConsoleConfig { 3275 mode: ConsoleOutputMode::Tty, 3276 iommu: false, 3277 file: None, 3278 socket: None, 3279 } 3280 ); 3281 assert_eq!( 3282 ConsoleConfig::parse("null")?, 3283 ConsoleConfig { 3284 mode: ConsoleOutputMode::Null, 3285 iommu: false, 3286 file: None, 3287 socket: None, 3288 } 3289 ); 3290 assert_eq!( 3291 ConsoleConfig::parse("file=/tmp/console")?, 3292 ConsoleConfig { 3293 mode: ConsoleOutputMode::File, 3294 iommu: false, 3295 file: Some(PathBuf::from("/tmp/console")), 3296 socket: None, 3297 } 3298 ); 3299 assert_eq!( 3300 ConsoleConfig::parse("null,iommu=on")?, 3301 ConsoleConfig { 3302 mode: ConsoleOutputMode::Null, 3303 iommu: true, 3304 file: None, 3305 socket: None, 3306 } 3307 ); 3308 assert_eq!( 3309 ConsoleConfig::parse("file=/tmp/console,iommu=on")?, 3310 ConsoleConfig { 3311 mode: ConsoleOutputMode::File, 3312 iommu: true, 3313 file: Some(PathBuf::from("/tmp/console")), 3314 socket: None, 3315 } 3316 ); 3317 assert_eq!( 3318 ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?, 3319 ConsoleConfig { 3320 mode: ConsoleOutputMode::Socket, 3321 iommu: true, 3322 file: None, 3323 socket: Some(PathBuf::from("/tmp/serial.sock")), 3324 } 3325 ); 3326 Ok(()) 3327 } 3328 3329 fn device_fixture() -> DeviceConfig { 3330 DeviceConfig { 3331 path: PathBuf::from("/path/to/device"), 3332 id: None, 3333 iommu: false, 3334 pci_segment: 0, 3335 x_nv_gpudirect_clique: None, 3336 } 3337 } 3338 3339 #[test] 3340 fn test_device_parsing() -> Result<()> { 3341 // Device must have a path provided 3342 assert!(DeviceConfig::parse("").is_err()); 3343 assert_eq!( 3344 DeviceConfig::parse("path=/path/to/device")?, 3345 device_fixture() 3346 ); 3347 3348 assert_eq!( 3349 DeviceConfig::parse("path=/path/to/device,iommu=on")?, 3350 DeviceConfig { 3351 iommu: true, 3352 ..device_fixture() 3353 } 3354 ); 3355 3356 assert_eq!( 3357 DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?, 3358 DeviceConfig { 3359 id: Some("mydevice0".to_owned()), 3360 iommu: true, 3361 ..device_fixture() 3362 } 3363 ); 3364 3365 Ok(()) 3366 } 3367 3368 fn vdpa_fixture() -> VdpaConfig { 3369 VdpaConfig { 3370 path: PathBuf::from("/dev/vhost-vdpa"), 3371 num_queues: 1, 3372 iommu: false, 3373 id: None, 3374 pci_segment: 0, 3375 } 3376 } 3377 3378 #[test] 3379 fn test_vdpa_parsing() -> Result<()> { 3380 // path is required 3381 assert!(VdpaConfig::parse("").is_err()); 3382 assert_eq!(VdpaConfig::parse("path=/dev/vhost-vdpa")?, vdpa_fixture()); 3383 assert_eq!( 3384 VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?, 3385 VdpaConfig { 3386 num_queues: 2, 3387 id: Some("my_vdpa".to_owned()), 3388 ..vdpa_fixture() 3389 } 3390 ); 3391 Ok(()) 3392 } 3393 3394 #[test] 3395 fn test_tpm_parsing() -> Result<()> { 3396 // path is required 3397 assert!(TpmConfig::parse("").is_err()); 3398 assert_eq!( 3399 TpmConfig::parse("socket=/var/run/tpm.sock")?, 3400 TpmConfig { 3401 socket: PathBuf::from("/var/run/tpm.sock"), 3402 } 3403 ); 3404 Ok(()) 3405 } 3406 3407 #[test] 3408 fn test_vsock_parsing() -> Result<()> { 3409 // socket and cid is required 3410 assert!(VsockConfig::parse("").is_err()); 3411 assert_eq!( 3412 VsockConfig::parse("socket=/tmp/sock,cid=3")?, 3413 VsockConfig { 3414 cid: 3, 3415 socket: PathBuf::from("/tmp/sock"), 3416 iommu: false, 3417 id: None, 3418 pci_segment: 0, 3419 } 3420 ); 3421 assert_eq!( 3422 VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?, 3423 VsockConfig { 3424 cid: 3, 3425 socket: PathBuf::from("/tmp/sock"), 3426 iommu: true, 3427 id: None, 3428 pci_segment: 0, 3429 } 3430 ); 3431 Ok(()) 3432 } 3433 3434 fn platform_fixture() -> PlatformConfig { 3435 PlatformConfig { 3436 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3437 iommu_segments: None, 3438 serial_number: None, 3439 uuid: None, 3440 oem_strings: None, 3441 #[cfg(feature = "tdx")] 3442 tdx: false, 3443 #[cfg(feature = "sev_snp")] 3444 sev_snp: false, 3445 } 3446 } 3447 3448 fn numa_fixture() -> NumaConfig { 3449 NumaConfig { 3450 guest_numa_id: 0, 3451 cpus: None, 3452 distances: None, 3453 memory_zones: None, 3454 #[cfg(target_arch = "x86_64")] 3455 sgx_epc_sections: None, 3456 pci_segments: None, 3457 } 3458 } 3459 3460 #[test] 3461 fn test_config_validation() { 3462 let mut valid_config = VmConfig { 3463 cpus: CpusConfig { 3464 boot_vcpus: 1, 3465 max_vcpus: 1, 3466 ..Default::default() 3467 }, 3468 memory: MemoryConfig { 3469 size: 536_870_912, 3470 mergeable: false, 3471 hotplug_method: HotplugMethod::Acpi, 3472 hotplug_size: None, 3473 hotplugged_size: None, 3474 shared: false, 3475 hugepages: false, 3476 hugepage_size: None, 3477 prefault: false, 3478 zones: None, 3479 thp: true, 3480 }, 3481 payload: Some(PayloadConfig { 3482 kernel: Some(PathBuf::from("/path/to/kernel")), 3483 firmware: None, 3484 cmdline: None, 3485 initramfs: None, 3486 #[cfg(feature = "igvm")] 3487 igvm: None, 3488 #[cfg(feature = "sev_snp")] 3489 host_data: Some( 3490 "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb431188673288c07".to_string(), 3491 ), 3492 }), 3493 rate_limit_groups: None, 3494 disks: None, 3495 net: None, 3496 rng: RngConfig { 3497 src: PathBuf::from("/dev/urandom"), 3498 iommu: false, 3499 }, 3500 balloon: None, 3501 fs: None, 3502 pmem: None, 3503 serial: ConsoleConfig { 3504 file: None, 3505 mode: ConsoleOutputMode::Null, 3506 iommu: false, 3507 socket: None, 3508 }, 3509 console: ConsoleConfig { 3510 file: None, 3511 mode: ConsoleOutputMode::Tty, 3512 iommu: false, 3513 socket: None, 3514 }, 3515 #[cfg(target_arch = "x86_64")] 3516 debug_console: DebugConsoleConfig::default(), 3517 devices: None, 3518 user_devices: None, 3519 vdpa: None, 3520 vsock: None, 3521 pvpanic: false, 3522 iommu: false, 3523 #[cfg(target_arch = "x86_64")] 3524 sgx_epc: None, 3525 numa: None, 3526 watchdog: false, 3527 #[cfg(feature = "guest_debug")] 3528 gdb: false, 3529 platform: None, 3530 tpm: None, 3531 preserved_fds: None, 3532 }; 3533 3534 assert!(valid_config.validate().is_ok()); 3535 3536 let mut invalid_config = valid_config.clone(); 3537 invalid_config.serial.mode = ConsoleOutputMode::Tty; 3538 invalid_config.console.mode = ConsoleOutputMode::Tty; 3539 assert!(valid_config.validate().is_ok()); 3540 3541 let mut invalid_config = valid_config.clone(); 3542 invalid_config.payload = None; 3543 assert_eq!( 3544 invalid_config.validate(), 3545 Err(ValidationError::KernelMissing) 3546 ); 3547 3548 let mut invalid_config = valid_config.clone(); 3549 invalid_config.serial.mode = ConsoleOutputMode::File; 3550 invalid_config.serial.file = None; 3551 assert_eq!( 3552 invalid_config.validate(), 3553 Err(ValidationError::ConsoleFileMissing) 3554 ); 3555 3556 let mut invalid_config = valid_config.clone(); 3557 invalid_config.cpus.max_vcpus = 16; 3558 invalid_config.cpus.boot_vcpus = 32; 3559 assert_eq!( 3560 invalid_config.validate(), 3561 Err(ValidationError::CpusMaxLowerThanBoot) 3562 ); 3563 3564 let mut invalid_config = valid_config.clone(); 3565 invalid_config.cpus.max_vcpus = 16; 3566 invalid_config.cpus.boot_vcpus = 16; 3567 invalid_config.cpus.topology = Some(CpuTopology { 3568 threads_per_core: 2, 3569 cores_per_die: 8, 3570 dies_per_package: 1, 3571 packages: 2, 3572 }); 3573 assert_eq!( 3574 invalid_config.validate(), 3575 Err(ValidationError::CpuTopologyCount) 3576 ); 3577 3578 let mut invalid_config = valid_config.clone(); 3579 invalid_config.disks = Some(vec![DiskConfig { 3580 vhost_socket: Some("/path/to/sock".to_owned()), 3581 path: Some(PathBuf::from("/path/to/image")), 3582 ..disk_fixture() 3583 }]); 3584 assert_eq!( 3585 invalid_config.validate(), 3586 Err(ValidationError::DiskSocketAndPath) 3587 ); 3588 3589 let mut invalid_config = valid_config.clone(); 3590 invalid_config.memory.shared = true; 3591 invalid_config.disks = Some(vec![DiskConfig { 3592 path: None, 3593 vhost_user: true, 3594 ..disk_fixture() 3595 }]); 3596 assert_eq!( 3597 invalid_config.validate(), 3598 Err(ValidationError::VhostUserMissingSocket) 3599 ); 3600 3601 let mut invalid_config = valid_config.clone(); 3602 invalid_config.disks = Some(vec![DiskConfig { 3603 path: None, 3604 vhost_user: true, 3605 vhost_socket: Some("/path/to/sock".to_owned()), 3606 ..disk_fixture() 3607 }]); 3608 assert_eq!( 3609 invalid_config.validate(), 3610 Err(ValidationError::VhostUserRequiresSharedMemory) 3611 ); 3612 3613 let mut still_valid_config = valid_config.clone(); 3614 still_valid_config.disks = Some(vec![DiskConfig { 3615 path: None, 3616 vhost_user: true, 3617 vhost_socket: Some("/path/to/sock".to_owned()), 3618 ..disk_fixture() 3619 }]); 3620 still_valid_config.memory.shared = true; 3621 assert!(still_valid_config.validate().is_ok()); 3622 3623 let mut invalid_config = valid_config.clone(); 3624 invalid_config.net = Some(vec![NetConfig { 3625 vhost_user: true, 3626 ..net_fixture() 3627 }]); 3628 assert_eq!( 3629 invalid_config.validate(), 3630 Err(ValidationError::VhostUserRequiresSharedMemory) 3631 ); 3632 3633 let mut still_valid_config = valid_config.clone(); 3634 still_valid_config.net = Some(vec![NetConfig { 3635 vhost_user: true, 3636 vhost_socket: Some("/path/to/sock".to_owned()), 3637 ..net_fixture() 3638 }]); 3639 still_valid_config.memory.shared = true; 3640 assert!(still_valid_config.validate().is_ok()); 3641 3642 let mut invalid_config = valid_config.clone(); 3643 invalid_config.net = Some(vec![NetConfig { 3644 fds: Some(vec![0]), 3645 ..net_fixture() 3646 }]); 3647 assert_eq!( 3648 invalid_config.validate(), 3649 Err(ValidationError::VnetReservedFd) 3650 ); 3651 3652 let mut invalid_config = valid_config.clone(); 3653 invalid_config.net = Some(vec![NetConfig { 3654 offload_csum: false, 3655 ..net_fixture() 3656 }]); 3657 assert_eq!( 3658 invalid_config.validate(), 3659 Err(ValidationError::NoHardwareChecksumOffload) 3660 ); 3661 3662 let mut invalid_config = valid_config.clone(); 3663 invalid_config.fs = Some(vec![fs_fixture()]); 3664 assert_eq!( 3665 invalid_config.validate(), 3666 Err(ValidationError::VhostUserRequiresSharedMemory) 3667 ); 3668 3669 let mut still_valid_config = valid_config.clone(); 3670 still_valid_config.memory.shared = true; 3671 assert!(still_valid_config.validate().is_ok()); 3672 3673 let mut still_valid_config = valid_config.clone(); 3674 still_valid_config.memory.hugepages = true; 3675 assert!(still_valid_config.validate().is_ok()); 3676 3677 let mut still_valid_config = valid_config.clone(); 3678 still_valid_config.memory.hugepages = true; 3679 still_valid_config.memory.hugepage_size = Some(2 << 20); 3680 assert!(still_valid_config.validate().is_ok()); 3681 3682 let mut invalid_config = valid_config.clone(); 3683 invalid_config.memory.hugepages = false; 3684 invalid_config.memory.hugepage_size = Some(2 << 20); 3685 assert_eq!( 3686 invalid_config.validate(), 3687 Err(ValidationError::HugePageSizeWithoutHugePages) 3688 ); 3689 3690 let mut invalid_config = valid_config.clone(); 3691 invalid_config.memory.hugepages = true; 3692 invalid_config.memory.hugepage_size = Some(3 << 20); 3693 assert_eq!( 3694 invalid_config.validate(), 3695 Err(ValidationError::InvalidHugePageSize(3 << 20)) 3696 ); 3697 3698 let mut still_valid_config = valid_config.clone(); 3699 still_valid_config.platform = Some(platform_fixture()); 3700 assert!(still_valid_config.validate().is_ok()); 3701 3702 let mut invalid_config = valid_config.clone(); 3703 invalid_config.platform = Some(PlatformConfig { 3704 num_pci_segments: MAX_NUM_PCI_SEGMENTS + 1, 3705 ..platform_fixture() 3706 }); 3707 assert_eq!( 3708 invalid_config.validate(), 3709 Err(ValidationError::InvalidNumPciSegments( 3710 MAX_NUM_PCI_SEGMENTS + 1 3711 )) 3712 ); 3713 3714 let mut still_valid_config = valid_config.clone(); 3715 still_valid_config.platform = Some(PlatformConfig { 3716 iommu_segments: Some(vec![1, 2, 3]), 3717 ..platform_fixture() 3718 }); 3719 assert!(still_valid_config.validate().is_ok()); 3720 3721 let mut invalid_config = valid_config.clone(); 3722 invalid_config.platform = Some(PlatformConfig { 3723 iommu_segments: Some(vec![MAX_NUM_PCI_SEGMENTS + 1, MAX_NUM_PCI_SEGMENTS + 2]), 3724 ..platform_fixture() 3725 }); 3726 assert_eq!( 3727 invalid_config.validate(), 3728 Err(ValidationError::InvalidPciSegment(MAX_NUM_PCI_SEGMENTS + 1)) 3729 ); 3730 3731 let mut still_valid_config = valid_config.clone(); 3732 still_valid_config.platform = Some(PlatformConfig { 3733 iommu_segments: Some(vec![1, 2, 3]), 3734 ..platform_fixture() 3735 }); 3736 still_valid_config.disks = Some(vec![DiskConfig { 3737 iommu: true, 3738 pci_segment: 1, 3739 ..disk_fixture() 3740 }]); 3741 assert!(still_valid_config.validate().is_ok()); 3742 3743 let mut still_valid_config = valid_config.clone(); 3744 still_valid_config.platform = Some(PlatformConfig { 3745 iommu_segments: Some(vec![1, 2, 3]), 3746 ..platform_fixture() 3747 }); 3748 still_valid_config.net = Some(vec![NetConfig { 3749 iommu: true, 3750 pci_segment: 1, 3751 ..net_fixture() 3752 }]); 3753 assert!(still_valid_config.validate().is_ok()); 3754 3755 let mut still_valid_config = valid_config.clone(); 3756 still_valid_config.platform = Some(PlatformConfig { 3757 iommu_segments: Some(vec![1, 2, 3]), 3758 ..platform_fixture() 3759 }); 3760 still_valid_config.pmem = Some(vec![PmemConfig { 3761 iommu: true, 3762 pci_segment: 1, 3763 ..pmem_fixture() 3764 }]); 3765 assert!(still_valid_config.validate().is_ok()); 3766 3767 let mut still_valid_config = valid_config.clone(); 3768 still_valid_config.platform = Some(PlatformConfig { 3769 iommu_segments: Some(vec![1, 2, 3]), 3770 ..platform_fixture() 3771 }); 3772 still_valid_config.devices = Some(vec![DeviceConfig { 3773 iommu: true, 3774 pci_segment: 1, 3775 ..device_fixture() 3776 }]); 3777 assert!(still_valid_config.validate().is_ok()); 3778 3779 let mut still_valid_config = valid_config.clone(); 3780 still_valid_config.platform = Some(PlatformConfig { 3781 iommu_segments: Some(vec![1, 2, 3]), 3782 ..platform_fixture() 3783 }); 3784 still_valid_config.vsock = Some(VsockConfig { 3785 cid: 3, 3786 socket: PathBuf::new(), 3787 id: None, 3788 iommu: true, 3789 pci_segment: 1, 3790 }); 3791 assert!(still_valid_config.validate().is_ok()); 3792 3793 let mut invalid_config = valid_config.clone(); 3794 invalid_config.platform = Some(PlatformConfig { 3795 iommu_segments: Some(vec![1, 2, 3]), 3796 ..platform_fixture() 3797 }); 3798 invalid_config.disks = Some(vec![DiskConfig { 3799 iommu: false, 3800 pci_segment: 1, 3801 ..disk_fixture() 3802 }]); 3803 assert_eq!( 3804 invalid_config.validate(), 3805 Err(ValidationError::OnIommuSegment(1)) 3806 ); 3807 3808 let mut invalid_config = valid_config.clone(); 3809 invalid_config.platform = Some(PlatformConfig { 3810 iommu_segments: Some(vec![1, 2, 3]), 3811 ..platform_fixture() 3812 }); 3813 invalid_config.net = Some(vec![NetConfig { 3814 iommu: false, 3815 pci_segment: 1, 3816 ..net_fixture() 3817 }]); 3818 assert_eq!( 3819 invalid_config.validate(), 3820 Err(ValidationError::OnIommuSegment(1)) 3821 ); 3822 3823 let mut invalid_config = valid_config.clone(); 3824 invalid_config.platform = Some(PlatformConfig { 3825 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3826 iommu_segments: Some(vec![1, 2, 3]), 3827 ..platform_fixture() 3828 }); 3829 invalid_config.pmem = Some(vec![PmemConfig { 3830 iommu: false, 3831 pci_segment: 1, 3832 ..pmem_fixture() 3833 }]); 3834 assert_eq!( 3835 invalid_config.validate(), 3836 Err(ValidationError::OnIommuSegment(1)) 3837 ); 3838 3839 let mut invalid_config = valid_config.clone(); 3840 invalid_config.platform = Some(PlatformConfig { 3841 num_pci_segments: MAX_NUM_PCI_SEGMENTS, 3842 iommu_segments: Some(vec![1, 2, 3]), 3843 ..platform_fixture() 3844 }); 3845 invalid_config.devices = Some(vec![DeviceConfig { 3846 iommu: false, 3847 pci_segment: 1, 3848 ..device_fixture() 3849 }]); 3850 assert_eq!( 3851 invalid_config.validate(), 3852 Err(ValidationError::OnIommuSegment(1)) 3853 ); 3854 3855 let mut invalid_config = valid_config.clone(); 3856 invalid_config.platform = Some(PlatformConfig { 3857 iommu_segments: Some(vec![1, 2, 3]), 3858 ..platform_fixture() 3859 }); 3860 invalid_config.vsock = Some(VsockConfig { 3861 cid: 3, 3862 socket: PathBuf::new(), 3863 id: None, 3864 iommu: false, 3865 pci_segment: 1, 3866 }); 3867 assert_eq!( 3868 invalid_config.validate(), 3869 Err(ValidationError::OnIommuSegment(1)) 3870 ); 3871 3872 let mut invalid_config = valid_config.clone(); 3873 invalid_config.memory.shared = true; 3874 invalid_config.platform = Some(PlatformConfig { 3875 iommu_segments: Some(vec![1, 2, 3]), 3876 ..platform_fixture() 3877 }); 3878 invalid_config.user_devices = Some(vec![UserDeviceConfig { 3879 pci_segment: 1, 3880 socket: PathBuf::new(), 3881 id: None, 3882 }]); 3883 assert_eq!( 3884 invalid_config.validate(), 3885 Err(ValidationError::IommuNotSupportedOnSegment(1)) 3886 ); 3887 3888 let mut invalid_config = valid_config.clone(); 3889 invalid_config.platform = Some(PlatformConfig { 3890 iommu_segments: Some(vec![1, 2, 3]), 3891 ..platform_fixture() 3892 }); 3893 invalid_config.vdpa = Some(vec![VdpaConfig { 3894 pci_segment: 1, 3895 ..vdpa_fixture() 3896 }]); 3897 assert_eq!( 3898 invalid_config.validate(), 3899 Err(ValidationError::OnIommuSegment(1)) 3900 ); 3901 3902 let mut invalid_config = valid_config.clone(); 3903 invalid_config.memory.shared = true; 3904 invalid_config.platform = Some(PlatformConfig { 3905 iommu_segments: Some(vec![1, 2, 3]), 3906 ..platform_fixture() 3907 }); 3908 invalid_config.fs = Some(vec![FsConfig { 3909 pci_segment: 1, 3910 ..fs_fixture() 3911 }]); 3912 assert_eq!( 3913 invalid_config.validate(), 3914 Err(ValidationError::IommuNotSupportedOnSegment(1)) 3915 ); 3916 3917 let mut invalid_config = valid_config.clone(); 3918 invalid_config.platform = Some(PlatformConfig { 3919 num_pci_segments: 2, 3920 ..platform_fixture() 3921 }); 3922 invalid_config.numa = Some(vec![ 3923 NumaConfig { 3924 guest_numa_id: 0, 3925 pci_segments: Some(vec![1]), 3926 ..numa_fixture() 3927 }, 3928 NumaConfig { 3929 guest_numa_id: 1, 3930 pci_segments: Some(vec![1]), 3931 ..numa_fixture() 3932 }, 3933 ]); 3934 assert_eq!( 3935 invalid_config.validate(), 3936 Err(ValidationError::PciSegmentReused(1, 0, 1)) 3937 ); 3938 3939 let mut invalid_config = valid_config.clone(); 3940 invalid_config.numa = Some(vec![ 3941 NumaConfig { 3942 guest_numa_id: 0, 3943 ..numa_fixture() 3944 }, 3945 NumaConfig { 3946 guest_numa_id: 1, 3947 pci_segments: Some(vec![0]), 3948 ..numa_fixture() 3949 }, 3950 ]); 3951 assert_eq!( 3952 invalid_config.validate(), 3953 Err(ValidationError::DefaultPciSegmentInvalidNode(1)) 3954 ); 3955 3956 let mut invalid_config = valid_config.clone(); 3957 invalid_config.numa = Some(vec![ 3958 NumaConfig { 3959 guest_numa_id: 0, 3960 pci_segments: Some(vec![0]), 3961 ..numa_fixture() 3962 }, 3963 NumaConfig { 3964 guest_numa_id: 1, 3965 pci_segments: Some(vec![1]), 3966 ..numa_fixture() 3967 }, 3968 ]); 3969 assert_eq!( 3970 invalid_config.validate(), 3971 Err(ValidationError::InvalidPciSegment(1)) 3972 ); 3973 3974 let mut invalid_config = valid_config.clone(); 3975 invalid_config.disks = Some(vec![DiskConfig { 3976 rate_limit_group: Some("foo".into()), 3977 ..disk_fixture() 3978 }]); 3979 assert_eq!( 3980 invalid_config.validate(), 3981 Err(ValidationError::InvalidRateLimiterGroup) 3982 ); 3983 3984 let mut still_valid_config = valid_config.clone(); 3985 still_valid_config.devices = Some(vec![ 3986 DeviceConfig { 3987 path: "/device1".into(), 3988 ..device_fixture() 3989 }, 3990 DeviceConfig { 3991 path: "/device2".into(), 3992 ..device_fixture() 3993 }, 3994 ]); 3995 assert!(still_valid_config.validate().is_ok()); 3996 3997 let mut invalid_config = valid_config.clone(); 3998 invalid_config.devices = Some(vec![ 3999 DeviceConfig { 4000 path: "/device1".into(), 4001 ..device_fixture() 4002 }, 4003 DeviceConfig { 4004 path: "/device1".into(), 4005 ..device_fixture() 4006 }, 4007 ]); 4008 assert!(invalid_config.validate().is_err()); 4009 #[cfg(feature = "sev_snp")] 4010 { 4011 // Payload with empty host data 4012 let mut config_with_no_host_data = valid_config.clone(); 4013 config_with_no_host_data.payload = Some(PayloadConfig { 4014 kernel: Some(PathBuf::from("/path/to/kernel")), 4015 firmware: None, 4016 cmdline: None, 4017 initramfs: None, 4018 #[cfg(feature = "igvm")] 4019 igvm: None, 4020 #[cfg(feature = "sev_snp")] 4021 host_data: Some("".to_string()), 4022 }); 4023 assert!(config_with_no_host_data.validate().is_err()); 4024 4025 // Payload with no host data provided 4026 let mut valid_config_with_no_host_data = valid_config.clone(); 4027 valid_config_with_no_host_data.payload = Some(PayloadConfig { 4028 kernel: Some(PathBuf::from("/path/to/kernel")), 4029 firmware: None, 4030 cmdline: None, 4031 initramfs: None, 4032 #[cfg(feature = "igvm")] 4033 igvm: None, 4034 #[cfg(feature = "sev_snp")] 4035 host_data: None, 4036 }); 4037 assert!(valid_config_with_no_host_data.validate().is_ok()); 4038 4039 // Payload with invalid host data length i.e less than 64 4040 let mut config_with_invalid_host_data = valid_config.clone(); 4041 config_with_invalid_host_data.payload = Some(PayloadConfig { 4042 kernel: Some(PathBuf::from("/path/to/kernel")), 4043 firmware: None, 4044 cmdline: None, 4045 initramfs: None, 4046 #[cfg(feature = "igvm")] 4047 igvm: None, 4048 #[cfg(feature = "sev_snp")] 4049 host_data: Some( 4050 "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb43118867328".to_string(), 4051 ), 4052 }); 4053 assert!(config_with_invalid_host_data.validate().is_err()); 4054 } 4055 4056 let mut still_valid_config = valid_config; 4057 // SAFETY: Safe as the file was just opened 4058 let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 4059 // SAFETY: Safe as the file was just opened 4060 let fd2 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) }; 4061 // SAFETY: safe as both FDs are valid 4062 unsafe { 4063 still_valid_config.add_preserved_fds(vec![fd1, fd2]); 4064 } 4065 let _still_valid_config = still_valid_config.clone(); 4066 } 4067 } 4068