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