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