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