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