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