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