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