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