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