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