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