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