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