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