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