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