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