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