xref: /cloud-hypervisor/vmm/src/config.rs (revision 30cf1eed5e63499f3101ed320fc384b59c60fc6b)
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          serial=<serial_number>";
1149 
1150     pub fn parse(disk: &str) -> Result<Self> {
1151         let mut parser = OptionParser::new();
1152         parser
1153             .add("path")
1154             .add("readonly")
1155             .add("direct")
1156             .add("iommu")
1157             .add("queue_size")
1158             .add("num_queues")
1159             .add("vhost_user")
1160             .add("socket")
1161             .add("bw_size")
1162             .add("bw_one_time_burst")
1163             .add("bw_refill_time")
1164             .add("ops_size")
1165             .add("ops_one_time_burst")
1166             .add("ops_refill_time")
1167             .add("id")
1168             .add("_disable_io_uring")
1169             .add("_disable_aio")
1170             .add("pci_segment")
1171             .add("serial")
1172             .add("rate_limit_group")
1173             .add("queue_affinity");
1174         parser.parse(disk).map_err(Error::ParseDisk)?;
1175 
1176         let path = parser.get("path").map(PathBuf::from);
1177         let readonly = parser
1178             .convert::<Toggle>("readonly")
1179             .map_err(Error::ParseDisk)?
1180             .unwrap_or(Toggle(false))
1181             .0;
1182         let direct = parser
1183             .convert::<Toggle>("direct")
1184             .map_err(Error::ParseDisk)?
1185             .unwrap_or(Toggle(false))
1186             .0;
1187         let iommu = parser
1188             .convert::<Toggle>("iommu")
1189             .map_err(Error::ParseDisk)?
1190             .unwrap_or(Toggle(false))
1191             .0;
1192         let queue_size = parser
1193             .convert("queue_size")
1194             .map_err(Error::ParseDisk)?
1195             .unwrap_or_else(default_diskconfig_queue_size);
1196         let num_queues = parser
1197             .convert("num_queues")
1198             .map_err(Error::ParseDisk)?
1199             .unwrap_or_else(default_diskconfig_num_queues);
1200         let vhost_user = parser
1201             .convert::<Toggle>("vhost_user")
1202             .map_err(Error::ParseDisk)?
1203             .unwrap_or(Toggle(false))
1204             .0;
1205         let vhost_socket = parser.get("socket");
1206         let id = parser.get("id");
1207         let disable_io_uring = parser
1208             .convert::<Toggle>("_disable_io_uring")
1209             .map_err(Error::ParseDisk)?
1210             .unwrap_or(Toggle(false))
1211             .0;
1212         let disable_aio = parser
1213             .convert::<Toggle>("_disable_aio")
1214             .map_err(Error::ParseDisk)?
1215             .unwrap_or(Toggle(false))
1216             .0;
1217         let pci_segment = parser
1218             .convert("pci_segment")
1219             .map_err(Error::ParseDisk)?
1220             .unwrap_or_default();
1221         let rate_limit_group = parser.get("rate_limit_group");
1222         let bw_size = parser
1223             .convert("bw_size")
1224             .map_err(Error::ParseDisk)?
1225             .unwrap_or_default();
1226         let bw_one_time_burst = parser
1227             .convert("bw_one_time_burst")
1228             .map_err(Error::ParseDisk)?
1229             .unwrap_or_default();
1230         let bw_refill_time = parser
1231             .convert("bw_refill_time")
1232             .map_err(Error::ParseDisk)?
1233             .unwrap_or_default();
1234         let ops_size = parser
1235             .convert("ops_size")
1236             .map_err(Error::ParseDisk)?
1237             .unwrap_or_default();
1238         let ops_one_time_burst = parser
1239             .convert("ops_one_time_burst")
1240             .map_err(Error::ParseDisk)?
1241             .unwrap_or_default();
1242         let ops_refill_time = parser
1243             .convert("ops_refill_time")
1244             .map_err(Error::ParseDisk)?
1245             .unwrap_or_default();
1246         let serial = parser.get("serial");
1247         let queue_affinity = parser
1248             .convert::<Tuple<u16, Vec<usize>>>("queue_affinity")
1249             .map_err(Error::ParseDisk)?
1250             .map(|v| {
1251                 v.0.iter()
1252                     .map(|(e1, e2)| VirtQueueAffinity {
1253                         queue_index: *e1,
1254                         host_cpus: e2.clone(),
1255                     })
1256                     .collect()
1257             });
1258         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
1259             Some(TokenBucketConfig {
1260                 size: bw_size,
1261                 one_time_burst: Some(bw_one_time_burst),
1262                 refill_time: bw_refill_time,
1263             })
1264         } else {
1265             None
1266         };
1267         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
1268             Some(TokenBucketConfig {
1269                 size: ops_size,
1270                 one_time_burst: Some(ops_one_time_burst),
1271                 refill_time: ops_refill_time,
1272             })
1273         } else {
1274             None
1275         };
1276         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
1277             Some(RateLimiterConfig {
1278                 bandwidth: bw_tb_config,
1279                 ops: ops_tb_config,
1280             })
1281         } else {
1282             None
1283         };
1284 
1285         Ok(DiskConfig {
1286             path,
1287             readonly,
1288             direct,
1289             iommu,
1290             num_queues,
1291             queue_size,
1292             vhost_user,
1293             vhost_socket,
1294             rate_limit_group,
1295             rate_limiter_config,
1296             id,
1297             disable_io_uring,
1298             disable_aio,
1299             pci_segment,
1300             serial,
1301             queue_affinity,
1302         })
1303     }
1304 
1305     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1306         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
1307             return Err(ValidationError::TooManyQueues);
1308         }
1309 
1310         if self.vhost_user && self.iommu {
1311             return Err(ValidationError::IommuNotSupported);
1312         }
1313 
1314         if let Some(platform_config) = vm_config.platform.as_ref() {
1315             if self.pci_segment >= platform_config.num_pci_segments {
1316                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1317             }
1318 
1319             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1320                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1321                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1322                 }
1323             }
1324         }
1325 
1326         if self.rate_limiter_config.is_some() && self.rate_limit_group.is_some() {
1327             return Err(ValidationError::InvalidRateLimiterGroup);
1328         }
1329 
1330         Ok(())
1331     }
1332 }
1333 
1334 #[derive(Debug)]
1335 pub enum ParseVhostModeError {
1336     InvalidValue(String),
1337 }
1338 
1339 impl FromStr for VhostMode {
1340     type Err = ParseVhostModeError;
1341 
1342     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1343         match s.to_lowercase().as_str() {
1344             "client" => Ok(VhostMode::Client),
1345             "server" => Ok(VhostMode::Server),
1346             _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())),
1347         }
1348     }
1349 }
1350 
1351 impl NetConfig {
1352     pub const SYNTAX: &'static str = "Network parameters \
1353     \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1,fd2...>,iommu=on|off,\
1354     num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\
1355     vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\
1356     bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\
1357     ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,pci_segment=<segment_id>\
1358     offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\"";
1359 
1360     pub fn parse(net: &str) -> Result<Self> {
1361         let mut parser = OptionParser::new();
1362 
1363         parser
1364             .add("tap")
1365             .add("ip")
1366             .add("mask")
1367             .add("mac")
1368             .add("host_mac")
1369             .add("offload_tso")
1370             .add("offload_ufo")
1371             .add("offload_csum")
1372             .add("mtu")
1373             .add("iommu")
1374             .add("queue_size")
1375             .add("num_queues")
1376             .add("vhost_user")
1377             .add("socket")
1378             .add("vhost_mode")
1379             .add("id")
1380             .add("fd")
1381             .add("bw_size")
1382             .add("bw_one_time_burst")
1383             .add("bw_refill_time")
1384             .add("ops_size")
1385             .add("ops_one_time_burst")
1386             .add("ops_refill_time")
1387             .add("pci_segment");
1388         parser.parse(net).map_err(Error::ParseNetwork)?;
1389 
1390         let tap = parser.get("tap");
1391         let ip = parser
1392             .convert("ip")
1393             .map_err(Error::ParseNetwork)?
1394             .unwrap_or_else(default_netconfig_ip);
1395         let mask = parser
1396             .convert("mask")
1397             .map_err(Error::ParseNetwork)?
1398             .unwrap_or_else(default_netconfig_mask);
1399         let mac = parser
1400             .convert("mac")
1401             .map_err(Error::ParseNetwork)?
1402             .unwrap_or_else(default_netconfig_mac);
1403         let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?;
1404         let offload_tso = parser
1405             .convert::<Toggle>("offload_tso")
1406             .map_err(Error::ParseNetwork)?
1407             .unwrap_or(Toggle(true))
1408             .0;
1409         let offload_ufo = parser
1410             .convert::<Toggle>("offload_ufo")
1411             .map_err(Error::ParseNetwork)?
1412             .unwrap_or(Toggle(true))
1413             .0;
1414         let offload_csum = parser
1415             .convert::<Toggle>("offload_csum")
1416             .map_err(Error::ParseNetwork)?
1417             .unwrap_or(Toggle(true))
1418             .0;
1419         let mtu = parser.convert("mtu").map_err(Error::ParseNetwork)?;
1420         let iommu = parser
1421             .convert::<Toggle>("iommu")
1422             .map_err(Error::ParseNetwork)?
1423             .unwrap_or(Toggle(false))
1424             .0;
1425         let queue_size = parser
1426             .convert("queue_size")
1427             .map_err(Error::ParseNetwork)?
1428             .unwrap_or_else(default_netconfig_queue_size);
1429         let num_queues = parser
1430             .convert("num_queues")
1431             .map_err(Error::ParseNetwork)?
1432             .unwrap_or_else(default_netconfig_num_queues);
1433         let vhost_user = parser
1434             .convert::<Toggle>("vhost_user")
1435             .map_err(Error::ParseNetwork)?
1436             .unwrap_or(Toggle(false))
1437             .0;
1438         let vhost_socket = parser.get("socket");
1439         let vhost_mode = parser
1440             .convert("vhost_mode")
1441             .map_err(Error::ParseNetwork)?
1442             .unwrap_or_default();
1443         let id = parser.get("id");
1444         let fds = parser
1445             .convert::<IntegerList>("fd")
1446             .map_err(Error::ParseNetwork)?
1447             .map(|v| v.0.iter().map(|e| *e as i32).collect());
1448         let pci_segment = parser
1449             .convert("pci_segment")
1450             .map_err(Error::ParseNetwork)?
1451             .unwrap_or_default();
1452         let bw_size = parser
1453             .convert("bw_size")
1454             .map_err(Error::ParseNetwork)?
1455             .unwrap_or_default();
1456         let bw_one_time_burst = parser
1457             .convert("bw_one_time_burst")
1458             .map_err(Error::ParseNetwork)?
1459             .unwrap_or_default();
1460         let bw_refill_time = parser
1461             .convert("bw_refill_time")
1462             .map_err(Error::ParseNetwork)?
1463             .unwrap_or_default();
1464         let ops_size = parser
1465             .convert("ops_size")
1466             .map_err(Error::ParseNetwork)?
1467             .unwrap_or_default();
1468         let ops_one_time_burst = parser
1469             .convert("ops_one_time_burst")
1470             .map_err(Error::ParseNetwork)?
1471             .unwrap_or_default();
1472         let ops_refill_time = parser
1473             .convert("ops_refill_time")
1474             .map_err(Error::ParseNetwork)?
1475             .unwrap_or_default();
1476         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
1477             Some(TokenBucketConfig {
1478                 size: bw_size,
1479                 one_time_burst: Some(bw_one_time_burst),
1480                 refill_time: bw_refill_time,
1481             })
1482         } else {
1483             None
1484         };
1485         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
1486             Some(TokenBucketConfig {
1487                 size: ops_size,
1488                 one_time_burst: Some(ops_one_time_burst),
1489                 refill_time: ops_refill_time,
1490             })
1491         } else {
1492             None
1493         };
1494         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
1495             Some(RateLimiterConfig {
1496                 bandwidth: bw_tb_config,
1497                 ops: ops_tb_config,
1498             })
1499         } else {
1500             None
1501         };
1502 
1503         let config = NetConfig {
1504             tap,
1505             ip,
1506             mask,
1507             mac,
1508             host_mac,
1509             mtu,
1510             iommu,
1511             num_queues,
1512             queue_size,
1513             vhost_user,
1514             vhost_socket,
1515             vhost_mode,
1516             id,
1517             fds,
1518             rate_limiter_config,
1519             pci_segment,
1520             offload_tso,
1521             offload_ufo,
1522             offload_csum,
1523         };
1524         Ok(config)
1525     }
1526 
1527     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1528         if self.num_queues < 2 {
1529             return Err(ValidationError::VnetQueueLowerThan2);
1530         }
1531 
1532         if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues {
1533             return Err(ValidationError::VnetQueueFdMismatch);
1534         }
1535 
1536         if let Some(fds) = self.fds.as_ref() {
1537             for fd in fds {
1538                 if *fd <= 2 {
1539                     return Err(ValidationError::VnetReservedFd);
1540                 }
1541             }
1542         }
1543 
1544         if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize {
1545             return Err(ValidationError::TooManyQueues);
1546         }
1547 
1548         if self.vhost_user && self.iommu {
1549             return Err(ValidationError::IommuNotSupported);
1550         }
1551 
1552         if let Some(platform_config) = vm_config.platform.as_ref() {
1553             if self.pci_segment >= platform_config.num_pci_segments {
1554                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1555             }
1556 
1557             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1558                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1559                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1560                 }
1561             }
1562         }
1563 
1564         if let Some(mtu) = self.mtu {
1565             if mtu < virtio_devices::net::MIN_MTU {
1566                 return Err(ValidationError::InvalidMtu(mtu));
1567             }
1568         }
1569 
1570         if !self.offload_csum && (self.offload_tso || self.offload_ufo) {
1571             return Err(ValidationError::NoHardwareChecksumOffload);
1572         }
1573 
1574         Ok(())
1575     }
1576 }
1577 
1578 impl RngConfig {
1579     pub fn parse(rng: &str) -> Result<Self> {
1580         let mut parser = OptionParser::new();
1581         parser.add("src").add("iommu");
1582         parser.parse(rng).map_err(Error::ParseRng)?;
1583 
1584         let src = PathBuf::from(
1585             parser
1586                 .get("src")
1587                 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()),
1588         );
1589         let iommu = parser
1590             .convert::<Toggle>("iommu")
1591             .map_err(Error::ParseRng)?
1592             .unwrap_or(Toggle(false))
1593             .0;
1594 
1595         Ok(RngConfig { src, iommu })
1596     }
1597 }
1598 
1599 impl BalloonConfig {
1600     pub const SYNTAX: &'static str =
1601         "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\
1602         free_page_reporting=on|off\"";
1603 
1604     pub fn parse(balloon: &str) -> Result<Self> {
1605         let mut parser = OptionParser::new();
1606         parser.add("size");
1607         parser.add("deflate_on_oom");
1608         parser.add("free_page_reporting");
1609         parser.parse(balloon).map_err(Error::ParseBalloon)?;
1610 
1611         let size = parser
1612             .convert::<ByteSized>("size")
1613             .map_err(Error::ParseBalloon)?
1614             .map(|v| v.0)
1615             .unwrap_or(0);
1616 
1617         let deflate_on_oom = parser
1618             .convert::<Toggle>("deflate_on_oom")
1619             .map_err(Error::ParseBalloon)?
1620             .unwrap_or(Toggle(false))
1621             .0;
1622 
1623         let free_page_reporting = parser
1624             .convert::<Toggle>("free_page_reporting")
1625             .map_err(Error::ParseBalloon)?
1626             .unwrap_or(Toggle(false))
1627             .0;
1628 
1629         Ok(BalloonConfig {
1630             size,
1631             deflate_on_oom,
1632             free_page_reporting,
1633         })
1634     }
1635 }
1636 
1637 impl FsConfig {
1638     pub const SYNTAX: &'static str = "virtio-fs parameters \
1639     \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\
1640     queue_size=<size_of_each_queue>,id=<device_id>,pci_segment=<segment_id>\"";
1641 
1642     pub fn parse(fs: &str) -> Result<Self> {
1643         let mut parser = OptionParser::new();
1644         parser
1645             .add("tag")
1646             .add("queue_size")
1647             .add("num_queues")
1648             .add("socket")
1649             .add("id")
1650             .add("pci_segment");
1651         parser.parse(fs).map_err(Error::ParseFileSystem)?;
1652 
1653         let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?;
1654         if tag.len() > virtio_devices::vhost_user::VIRTIO_FS_TAG_LEN {
1655             return Err(Error::ParseFsTagTooLong);
1656         }
1657         let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?);
1658 
1659         let queue_size = parser
1660             .convert("queue_size")
1661             .map_err(Error::ParseFileSystem)?
1662             .unwrap_or_else(default_fsconfig_queue_size);
1663         let num_queues = parser
1664             .convert("num_queues")
1665             .map_err(Error::ParseFileSystem)?
1666             .unwrap_or_else(default_fsconfig_num_queues);
1667 
1668         let id = parser.get("id");
1669 
1670         let pci_segment = parser
1671             .convert("pci_segment")
1672             .map_err(Error::ParseFileSystem)?
1673             .unwrap_or_default();
1674 
1675         Ok(FsConfig {
1676             tag,
1677             socket,
1678             num_queues,
1679             queue_size,
1680             id,
1681             pci_segment,
1682         })
1683     }
1684 
1685     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1686         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
1687             return Err(ValidationError::TooManyQueues);
1688         }
1689 
1690         if let Some(platform_config) = vm_config.platform.as_ref() {
1691             if self.pci_segment >= platform_config.num_pci_segments {
1692                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1693             }
1694 
1695             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1696                 if iommu_segments.contains(&self.pci_segment) {
1697                     return Err(ValidationError::IommuNotSupportedOnSegment(
1698                         self.pci_segment,
1699                     ));
1700                 }
1701             }
1702         }
1703 
1704         Ok(())
1705     }
1706 }
1707 
1708 impl PmemConfig {
1709     pub const SYNTAX: &'static str = "Persistent memory parameters \
1710     \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\
1711     discard_writes=on|off,id=<device_id>,pci_segment=<segment_id>\"";
1712 
1713     pub fn parse(pmem: &str) -> Result<Self> {
1714         let mut parser = OptionParser::new();
1715         parser
1716             .add("size")
1717             .add("file")
1718             .add("iommu")
1719             .add("discard_writes")
1720             .add("id")
1721             .add("pci_segment");
1722         parser.parse(pmem).map_err(Error::ParsePersistentMemory)?;
1723 
1724         let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?);
1725         let size = parser
1726             .convert::<ByteSized>("size")
1727             .map_err(Error::ParsePersistentMemory)?
1728             .map(|v| v.0);
1729         let iommu = parser
1730             .convert::<Toggle>("iommu")
1731             .map_err(Error::ParsePersistentMemory)?
1732             .unwrap_or(Toggle(false))
1733             .0;
1734         let discard_writes = parser
1735             .convert::<Toggle>("discard_writes")
1736             .map_err(Error::ParsePersistentMemory)?
1737             .unwrap_or(Toggle(false))
1738             .0;
1739         let id = parser.get("id");
1740         let pci_segment = parser
1741             .convert("pci_segment")
1742             .map_err(Error::ParsePersistentMemory)?
1743             .unwrap_or_default();
1744 
1745         Ok(PmemConfig {
1746             file,
1747             size,
1748             iommu,
1749             discard_writes,
1750             id,
1751             pci_segment,
1752         })
1753     }
1754 
1755     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1756         if let Some(platform_config) = vm_config.platform.as_ref() {
1757             if self.pci_segment >= platform_config.num_pci_segments {
1758                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1759             }
1760 
1761             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1762                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1763                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1764                 }
1765             }
1766         }
1767 
1768         Ok(())
1769     }
1770 }
1771 
1772 impl ConsoleConfig {
1773     pub fn parse(console: &str) -> Result<Self> {
1774         let mut parser = OptionParser::new();
1775         parser
1776             .add_valueless("off")
1777             .add_valueless("pty")
1778             .add_valueless("tty")
1779             .add_valueless("null")
1780             .add("file")
1781             .add("iommu")
1782             .add("socket");
1783         parser.parse(console).map_err(Error::ParseConsole)?;
1784 
1785         let mut file: Option<PathBuf> = default_consoleconfig_file();
1786         let mut socket: Option<PathBuf> = None;
1787         let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
1788 
1789         if parser.is_set("off") {
1790         } else if parser.is_set("pty") {
1791             mode = ConsoleOutputMode::Pty
1792         } else if parser.is_set("tty") {
1793             mode = ConsoleOutputMode::Tty
1794         } else if parser.is_set("null") {
1795             mode = ConsoleOutputMode::Null
1796         } else if parser.is_set("file") {
1797             mode = ConsoleOutputMode::File;
1798             file =
1799                 Some(PathBuf::from(parser.get("file").ok_or(
1800                     Error::Validation(ValidationError::ConsoleFileMissing),
1801                 )?));
1802         } else if parser.is_set("socket") {
1803             mode = ConsoleOutputMode::Socket;
1804             socket = Some(PathBuf::from(parser.get("socket").ok_or(
1805                 Error::Validation(ValidationError::ConsoleSocketPathMissing),
1806             )?));
1807         } else {
1808             return Err(Error::ParseConsoleInvalidModeGiven);
1809         }
1810         let iommu = parser
1811             .convert::<Toggle>("iommu")
1812             .map_err(Error::ParseConsole)?
1813             .unwrap_or(Toggle(false))
1814             .0;
1815 
1816         Ok(Self {
1817             file,
1818             mode,
1819             iommu,
1820             socket,
1821         })
1822     }
1823 }
1824 
1825 #[cfg(target_arch = "x86_64")]
1826 impl DebugConsoleConfig {
1827     pub fn parse(debug_console_ops: &str) -> Result<Self> {
1828         let mut parser = OptionParser::new();
1829         parser
1830             .add_valueless("off")
1831             .add_valueless("pty")
1832             .add_valueless("tty")
1833             .add_valueless("null")
1834             .add("file")
1835             .add("iobase");
1836         parser
1837             .parse(debug_console_ops)
1838             .map_err(Error::ParseConsole)?;
1839 
1840         let mut file: Option<PathBuf> = default_consoleconfig_file();
1841         let mut iobase: Option<u16> = None;
1842         let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
1843 
1844         if parser.is_set("off") {
1845         } else if parser.is_set("pty") {
1846             mode = ConsoleOutputMode::Pty
1847         } else if parser.is_set("tty") {
1848             mode = ConsoleOutputMode::Tty
1849         } else if parser.is_set("null") {
1850             mode = ConsoleOutputMode::Null
1851         } else if parser.is_set("file") {
1852             mode = ConsoleOutputMode::File;
1853             file =
1854                 Some(PathBuf::from(parser.get("file").ok_or(
1855                     Error::Validation(ValidationError::ConsoleFileMissing),
1856                 )?));
1857         } else {
1858             return Err(Error::ParseConsoleInvalidModeGiven);
1859         }
1860 
1861         if parser.is_set("iobase") {
1862             if let Some(iobase_opt) = parser.get("iobase") {
1863                 if !iobase_opt.starts_with("0x") {
1864                     return Err(Error::Validation(ValidationError::InvalidIoPortHex(
1865                         iobase_opt,
1866                     )));
1867                 }
1868                 iobase = Some(u16::from_str_radix(&iobase_opt[2..], 16).map_err(|_| {
1869                     Error::Validation(ValidationError::InvalidIoPortHex(iobase_opt))
1870                 })?);
1871             }
1872         }
1873 
1874         Ok(Self { file, mode, iobase })
1875     }
1876 }
1877 
1878 impl DeviceConfig {
1879     pub const SYNTAX: &'static str =
1880         "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\"";
1881 
1882     pub fn parse(device: &str) -> Result<Self> {
1883         let mut parser = OptionParser::new();
1884         parser
1885             .add("path")
1886             .add("id")
1887             .add("iommu")
1888             .add("pci_segment")
1889             .add("x_nv_gpudirect_clique");
1890         parser.parse(device).map_err(Error::ParseDevice)?;
1891 
1892         let path = parser
1893             .get("path")
1894             .map(PathBuf::from)
1895             .ok_or(Error::ParseDevicePathMissing)?;
1896         let iommu = parser
1897             .convert::<Toggle>("iommu")
1898             .map_err(Error::ParseDevice)?
1899             .unwrap_or(Toggle(false))
1900             .0;
1901         let id = parser.get("id");
1902         let pci_segment = parser
1903             .convert::<u16>("pci_segment")
1904             .map_err(Error::ParseDevice)?
1905             .unwrap_or_default();
1906         let x_nv_gpudirect_clique = parser
1907             .convert::<u8>("x_nv_gpudirect_clique")
1908             .map_err(Error::ParseDevice)?;
1909         Ok(DeviceConfig {
1910             path,
1911             iommu,
1912             id,
1913             pci_segment,
1914             x_nv_gpudirect_clique,
1915         })
1916     }
1917 
1918     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1919         if let Some(platform_config) = vm_config.platform.as_ref() {
1920             if self.pci_segment >= platform_config.num_pci_segments {
1921                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1922             }
1923 
1924             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1925                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1926                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1927                 }
1928             }
1929         }
1930 
1931         Ok(())
1932     }
1933 }
1934 
1935 impl UserDeviceConfig {
1936     pub const SYNTAX: &'static str =
1937         "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\"";
1938 
1939     pub fn parse(user_device: &str) -> Result<Self> {
1940         let mut parser = OptionParser::new();
1941         parser.add("socket").add("id").add("pci_segment");
1942         parser.parse(user_device).map_err(Error::ParseUserDevice)?;
1943 
1944         let socket = parser
1945             .get("socket")
1946             .map(PathBuf::from)
1947             .ok_or(Error::ParseUserDeviceSocketMissing)?;
1948         let id = parser.get("id");
1949         let pci_segment = parser
1950             .convert::<u16>("pci_segment")
1951             .map_err(Error::ParseUserDevice)?
1952             .unwrap_or_default();
1953 
1954         Ok(UserDeviceConfig {
1955             socket,
1956             id,
1957             pci_segment,
1958         })
1959     }
1960 
1961     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1962         if let Some(platform_config) = vm_config.platform.as_ref() {
1963             if self.pci_segment >= platform_config.num_pci_segments {
1964                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1965             }
1966 
1967             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1968                 if iommu_segments.contains(&self.pci_segment) {
1969                     return Err(ValidationError::IommuNotSupportedOnSegment(
1970                         self.pci_segment,
1971                     ));
1972                 }
1973             }
1974         }
1975 
1976         Ok(())
1977     }
1978 }
1979 
1980 impl VdpaConfig {
1981     pub const SYNTAX: &'static str = "vDPA device \
1982         \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\
1983         id=<device_id>,pci_segment=<segment_id>\"";
1984 
1985     pub fn parse(vdpa: &str) -> Result<Self> {
1986         let mut parser = OptionParser::new();
1987         parser
1988             .add("path")
1989             .add("num_queues")
1990             .add("iommu")
1991             .add("id")
1992             .add("pci_segment");
1993         parser.parse(vdpa).map_err(Error::ParseVdpa)?;
1994 
1995         let path = parser
1996             .get("path")
1997             .map(PathBuf::from)
1998             .ok_or(Error::ParseVdpaPathMissing)?;
1999         let num_queues = parser
2000             .convert("num_queues")
2001             .map_err(Error::ParseVdpa)?
2002             .unwrap_or_else(default_vdpaconfig_num_queues);
2003         let iommu = parser
2004             .convert::<Toggle>("iommu")
2005             .map_err(Error::ParseVdpa)?
2006             .unwrap_or(Toggle(false))
2007             .0;
2008         let id = parser.get("id");
2009         let pci_segment = parser
2010             .convert("pci_segment")
2011             .map_err(Error::ParseVdpa)?
2012             .unwrap_or_default();
2013 
2014         Ok(VdpaConfig {
2015             path,
2016             num_queues,
2017             iommu,
2018             id,
2019             pci_segment,
2020         })
2021     }
2022 
2023     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
2024         if let Some(platform_config) = vm_config.platform.as_ref() {
2025             if self.pci_segment >= platform_config.num_pci_segments {
2026                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
2027             }
2028 
2029             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
2030                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
2031                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
2032                 }
2033             }
2034         }
2035 
2036         Ok(())
2037     }
2038 }
2039 
2040 impl VsockConfig {
2041     pub const SYNTAX: &'static str = "Virtio VSOCK parameters \
2042         \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\"";
2043 
2044     pub fn parse(vsock: &str) -> Result<Self> {
2045         let mut parser = OptionParser::new();
2046         parser
2047             .add("socket")
2048             .add("cid")
2049             .add("iommu")
2050             .add("id")
2051             .add("pci_segment");
2052         parser.parse(vsock).map_err(Error::ParseVsock)?;
2053 
2054         let socket = parser
2055             .get("socket")
2056             .map(PathBuf::from)
2057             .ok_or(Error::ParseVsockSockMissing)?;
2058         let iommu = parser
2059             .convert::<Toggle>("iommu")
2060             .map_err(Error::ParseVsock)?
2061             .unwrap_or(Toggle(false))
2062             .0;
2063         let cid = parser
2064             .convert("cid")
2065             .map_err(Error::ParseVsock)?
2066             .ok_or(Error::ParseVsockCidMissing)?;
2067         let id = parser.get("id");
2068         let pci_segment = parser
2069             .convert("pci_segment")
2070             .map_err(Error::ParseVsock)?
2071             .unwrap_or_default();
2072 
2073         Ok(VsockConfig {
2074             cid,
2075             socket,
2076             iommu,
2077             id,
2078             pci_segment,
2079         })
2080     }
2081 
2082     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
2083         if let Some(platform_config) = vm_config.platform.as_ref() {
2084             if self.pci_segment >= platform_config.num_pci_segments {
2085                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
2086             }
2087 
2088             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
2089                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
2090                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
2091                 }
2092             }
2093         }
2094 
2095         Ok(())
2096     }
2097 }
2098 
2099 #[cfg(target_arch = "x86_64")]
2100 impl SgxEpcConfig {
2101     pub const SYNTAX: &'static str = "SGX EPC parameters \
2102         \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\"";
2103 
2104     pub fn parse(sgx_epc: &str) -> Result<Self> {
2105         let mut parser = OptionParser::new();
2106         parser.add("id").add("size").add("prefault");
2107         parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?;
2108 
2109         let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?;
2110         let size = parser
2111             .convert::<ByteSized>("size")
2112             .map_err(Error::ParseSgxEpc)?
2113             .unwrap_or(ByteSized(0))
2114             .0;
2115         let prefault = parser
2116             .convert::<Toggle>("prefault")
2117             .map_err(Error::ParseSgxEpc)?
2118             .unwrap_or(Toggle(false))
2119             .0;
2120 
2121         Ok(SgxEpcConfig { id, size, prefault })
2122     }
2123 }
2124 
2125 impl NumaConfig {
2126     pub const SYNTAX: &'static str = "Settings related to a given NUMA node \
2127         \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\
2128         memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>,\
2129         pci_segments=<list_of_pci_segments>\"";
2130 
2131     pub fn parse(numa: &str) -> Result<Self> {
2132         let mut parser = OptionParser::new();
2133         parser
2134             .add("guest_numa_id")
2135             .add("cpus")
2136             .add("distances")
2137             .add("memory_zones")
2138             .add("sgx_epc_sections")
2139             .add("pci_segments");
2140 
2141         parser.parse(numa).map_err(Error::ParseNuma)?;
2142 
2143         let guest_numa_id = parser
2144             .convert::<u32>("guest_numa_id")
2145             .map_err(Error::ParseNuma)?
2146             .unwrap_or(0);
2147         let cpus = parser
2148             .convert::<IntegerList>("cpus")
2149             .map_err(Error::ParseNuma)?
2150             .map(|v| v.0.iter().map(|e| *e as u8).collect());
2151         let distances = parser
2152             .convert::<Tuple<u64, u64>>("distances")
2153             .map_err(Error::ParseNuma)?
2154             .map(|v| {
2155                 v.0.iter()
2156                     .map(|(e1, e2)| NumaDistance {
2157                         destination: *e1 as u32,
2158                         distance: *e2 as u8,
2159                     })
2160                     .collect()
2161             });
2162         let memory_zones = parser
2163             .convert::<StringList>("memory_zones")
2164             .map_err(Error::ParseNuma)?
2165             .map(|v| v.0);
2166         #[cfg(target_arch = "x86_64")]
2167         let sgx_epc_sections = parser
2168             .convert::<StringList>("sgx_epc_sections")
2169             .map_err(Error::ParseNuma)?
2170             .map(|v| v.0);
2171         let pci_segments = parser
2172             .convert::<IntegerList>("pci_segments")
2173             .map_err(Error::ParseNuma)?
2174             .map(|v| v.0.iter().map(|e| *e as u16).collect());
2175         Ok(NumaConfig {
2176             guest_numa_id,
2177             cpus,
2178             distances,
2179             memory_zones,
2180             #[cfg(target_arch = "x86_64")]
2181             sgx_epc_sections,
2182             pci_segments,
2183         })
2184     }
2185 }
2186 
2187 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2188 pub struct RestoredNetConfig {
2189     pub id: String,
2190     #[serde(default)]
2191     pub num_fds: usize,
2192     #[serde(
2193         default,
2194         serialize_with = "serialize_restorednetconfig_fds",
2195         deserialize_with = "deserialize_restorednetconfig_fds"
2196     )]
2197     pub fds: Option<Vec<i32>>,
2198 }
2199 
2200 fn serialize_restorednetconfig_fds<S>(
2201     x: &Option<Vec<i32>>,
2202     s: S,
2203 ) -> std::result::Result<S::Ok, S::Error>
2204 where
2205     S: serde::Serializer,
2206 {
2207     if let Some(x) = x {
2208         warn!("'RestoredNetConfig' contains FDs that can't be serialized correctly. Serializing them as invalid FDs.");
2209         let invalid_fds = vec![-1; x.len()];
2210         s.serialize_some(&invalid_fds)
2211     } else {
2212         s.serialize_none()
2213     }
2214 }
2215 
2216 fn deserialize_restorednetconfig_fds<'de, D>(
2217     d: D,
2218 ) -> std::result::Result<Option<Vec<i32>>, D::Error>
2219 where
2220     D: serde::Deserializer<'de>,
2221 {
2222     let invalid_fds: Option<Vec<i32>> = Option::deserialize(d)?;
2223     if let Some(invalid_fds) = invalid_fds {
2224         warn!("'RestoredNetConfig' contains FDs that can't be deserialized correctly. Deserializing them as invalid FDs.");
2225         Ok(Some(vec![-1; invalid_fds.len()]))
2226     } else {
2227         Ok(None)
2228     }
2229 }
2230 
2231 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2232 pub struct RestoreConfig {
2233     pub source_url: PathBuf,
2234     #[serde(default)]
2235     pub prefault: bool,
2236     #[serde(default)]
2237     pub net_fds: Option<Vec<RestoredNetConfig>>,
2238 }
2239 
2240 impl RestoreConfig {
2241     pub const SYNTAX: &'static str = "Restore from a VM snapshot. \
2242         \nRestore parameters \"source_url=<source_url>,prefault=on|off,\
2243         net_fds=<list_of_net_ids_with_their_associated_fds>\" \
2244         \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \
2245         \n`prefault` brings memory pages in when enabled (disabled by default) \
2246         \n`net_fds` is a list of net ids with new file descriptors. \
2247         Only net devices backed by FDs directly are needed as input.";
2248 
2249     pub fn parse(restore: &str) -> Result<Self> {
2250         let mut parser = OptionParser::new();
2251         parser.add("source_url").add("prefault").add("net_fds");
2252         parser.parse(restore).map_err(Error::ParseRestore)?;
2253 
2254         let source_url = parser
2255             .get("source_url")
2256             .map(PathBuf::from)
2257             .ok_or(Error::ParseRestoreSourceUrlMissing)?;
2258         let prefault = parser
2259             .convert::<Toggle>("prefault")
2260             .map_err(Error::ParseRestore)?
2261             .unwrap_or(Toggle(false))
2262             .0;
2263         let net_fds = parser
2264             .convert::<Tuple<String, Vec<u64>>>("net_fds")
2265             .map_err(Error::ParseRestore)?
2266             .map(|v| {
2267                 v.0.iter()
2268                     .map(|(id, fds)| RestoredNetConfig {
2269                         id: id.clone(),
2270                         num_fds: fds.len(),
2271                         fds: Some(fds.iter().map(|e| *e as i32).collect()),
2272                     })
2273                     .collect()
2274             });
2275 
2276         Ok(RestoreConfig {
2277             source_url,
2278             prefault,
2279             net_fds,
2280         })
2281     }
2282 
2283     // Ensure all net devices from 'VmConfig' backed by FDs have a
2284     // corresponding 'RestoreNetConfig' with a matched 'id' and expected
2285     // number of FDs.
2286     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
2287         let mut restored_net_with_fds = HashMap::new();
2288         for n in self.net_fds.iter().flatten() {
2289             assert_eq!(
2290                 n.num_fds,
2291                 n.fds.as_ref().map_or(0, |f| f.len()),
2292                 "Invalid 'RestoredNetConfig' with conflicted fields."
2293             );
2294             if restored_net_with_fds.insert(n.id.clone(), n).is_some() {
2295                 return Err(ValidationError::IdentifierNotUnique(n.id.clone()));
2296             }
2297         }
2298 
2299         for net_fds in vm_config.net.iter().flatten() {
2300             if let Some(expected_fds) = &net_fds.fds {
2301                 let expected_id = net_fds
2302                     .id
2303                     .as_ref()
2304                     .expect("Invalid 'NetConfig' with empty 'id' for VM restore.");
2305                 if let Some(r) = restored_net_with_fds.remove(expected_id) {
2306                     if r.num_fds != expected_fds.len() {
2307                         return Err(ValidationError::RestoreNetFdCountMismatch(
2308                             expected_id.clone(),
2309                             r.num_fds,
2310                             expected_fds.len(),
2311                         ));
2312                     }
2313                 } else {
2314                     return Err(ValidationError::RestoreMissingRequiredNetId(
2315                         expected_id.clone(),
2316                     ));
2317                 }
2318             }
2319         }
2320 
2321         if !restored_net_with_fds.is_empty() {
2322             warn!("Ignoring unused 'net_fds' for VM restore.")
2323         }
2324 
2325         Ok(())
2326     }
2327 }
2328 
2329 impl TpmConfig {
2330     pub const SYNTAX: &'static str = "TPM device \
2331         \"(UNIX Domain Socket from swtpm) socket=</path/to/a/socket>\"";
2332 
2333     pub fn parse(tpm: &str) -> Result<Self> {
2334         let mut parser = OptionParser::new();
2335         parser.add("socket");
2336         parser.parse(tpm).map_err(Error::ParseTpm)?;
2337         let socket = parser
2338             .get("socket")
2339             .map(PathBuf::from)
2340             .ok_or(Error::ParseTpmPathMissing)?;
2341         Ok(TpmConfig { socket })
2342     }
2343 }
2344 
2345 impl LandlockConfig {
2346     pub const SYNTAX: &'static str = "Landlock parameters \
2347         \"path=<path/to/{file/dir}>,access=[rw]\"";
2348 
2349     pub fn parse(landlock_rule: &str) -> Result<Self> {
2350         let mut parser = OptionParser::new();
2351         parser.add("path").add("access");
2352         parser
2353             .parse(landlock_rule)
2354             .map_err(Error::ParseLandlockRules)?;
2355 
2356         let path = parser
2357             .get("path")
2358             .map(PathBuf::from)
2359             .ok_or(Error::ParseLandlockMissingFields)?;
2360 
2361         let access = parser
2362             .get("access")
2363             .ok_or(Error::ParseLandlockMissingFields)?;
2364 
2365         if access.chars().count() > 2 {
2366             return Err(Error::ParseLandlockRules(OptionParserError::InvalidValue(
2367                 access.to_string(),
2368             )));
2369         }
2370 
2371         Ok(LandlockConfig { path, access })
2372     }
2373 
2374     pub fn validate(&self) -> ValidationResult<()> {
2375         if !self.path.exists() {
2376             return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
2377         }
2378         LandlockAccess::try_from(self.access.as_str())
2379             .map_err(|e| ValidationError::InvalidLandlockAccess(e.to_string()))?;
2380         Ok(())
2381     }
2382 }
2383 
2384 impl VmConfig {
2385     fn validate_identifier(
2386         id_list: &mut BTreeSet<String>,
2387         id: &Option<String>,
2388     ) -> ValidationResult<()> {
2389         if let Some(id) = id.as_ref() {
2390             if id.starts_with("__") {
2391                 return Err(ValidationError::InvalidIdentifier(id.clone()));
2392             }
2393 
2394             if !id_list.insert(id.clone()) {
2395                 return Err(ValidationError::IdentifierNotUnique(id.clone()));
2396             }
2397         }
2398 
2399         Ok(())
2400     }
2401 
2402     pub fn backed_by_shared_memory(&self) -> bool {
2403         if self.memory.shared || self.memory.hugepages {
2404             return true;
2405         }
2406 
2407         if self.memory.size == 0 {
2408             for zone in self.memory.zones.as_ref().unwrap() {
2409                 if !zone.shared && !zone.hugepages {
2410                     return false;
2411                 }
2412             }
2413             true
2414         } else {
2415             false
2416         }
2417     }
2418 
2419     // Also enables virtio-iommu if the config needs it
2420     // Returns the list of unique identifiers provided through the
2421     // configuration.
2422     pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> {
2423         let mut id_list = BTreeSet::new();
2424 
2425         self.payload
2426             .as_ref()
2427             .ok_or(ValidationError::KernelMissing)?;
2428 
2429         #[cfg(feature = "tdx")]
2430         {
2431             let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false);
2432             // At this point we know payload isn't None.
2433             if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() {
2434                 return Err(ValidationError::TdxFirmwareMissing);
2435             }
2436             if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) {
2437                 return Err(ValidationError::TdxNoCpuHotplug);
2438             }
2439         }
2440 
2441         #[cfg(feature = "sev_snp")]
2442         {
2443             let host_data_opt = &self.payload.as_ref().unwrap().host_data;
2444 
2445             if let Some(host_data) = host_data_opt {
2446                 if host_data.len() != 64 {
2447                     return Err(ValidationError::InvalidHostData);
2448                 }
2449             }
2450         }
2451         // The 'conflict' check is introduced in commit 24438e0390d3
2452         // (vm-virtio: Enable the vmm support for virtio-console).
2453         //
2454         // Allow simultaneously set serial and console as TTY mode, for
2455         // someone want to use virtio console for better performance, and
2456         // want to keep legacy serial to catch boot stage logs for debug.
2457         // Using such double tty mode, you need to configure the kernel
2458         // properly, such as:
2459         // "console=hvc0 earlyprintk=ttyS0"
2460 
2461         let mut tty_consoles = Vec::new();
2462         if self.console.mode == ConsoleOutputMode::Tty {
2463             tty_consoles.push("virtio-console");
2464         };
2465         if self.serial.mode == ConsoleOutputMode::Tty {
2466             tty_consoles.push("serial-console");
2467         };
2468         #[cfg(target_arch = "x86_64")]
2469         if self.debug_console.mode == ConsoleOutputMode::Tty {
2470             tty_consoles.push("debug-console");
2471         };
2472         if tty_consoles.len() > 1 {
2473             warn!("Using TTY output for multiple consoles: {:?}", tty_consoles);
2474         }
2475 
2476         if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() {
2477             return Err(ValidationError::ConsoleFileMissing);
2478         }
2479 
2480         if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() {
2481             return Err(ValidationError::ConsoleFileMissing);
2482         }
2483 
2484         if self.cpus.max_vcpus < self.cpus.boot_vcpus {
2485             return Err(ValidationError::CpusMaxLowerThanBoot);
2486         }
2487 
2488         if let Some(rate_limit_groups) = &self.rate_limit_groups {
2489             for rate_limit_group in rate_limit_groups {
2490                 rate_limit_group.validate(self)?;
2491 
2492                 Self::validate_identifier(&mut id_list, &Some(rate_limit_group.id.clone()))?;
2493             }
2494         }
2495 
2496         if let Some(disks) = &self.disks {
2497             for disk in disks {
2498                 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() {
2499                     return Err(ValidationError::DiskSocketAndPath);
2500                 }
2501                 if disk.vhost_user && !self.backed_by_shared_memory() {
2502                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2503                 }
2504                 if disk.vhost_user && disk.vhost_socket.is_none() {
2505                     return Err(ValidationError::VhostUserMissingSocket);
2506                 }
2507                 if let Some(rate_limit_group) = &disk.rate_limit_group {
2508                     if let Some(rate_limit_groups) = &self.rate_limit_groups {
2509                         if !rate_limit_groups
2510                             .iter()
2511                             .any(|cfg| &cfg.id == rate_limit_group)
2512                         {
2513                             return Err(ValidationError::InvalidRateLimiterGroup);
2514                         }
2515                     } else {
2516                         return Err(ValidationError::InvalidRateLimiterGroup);
2517                     }
2518                 }
2519 
2520                 disk.validate(self)?;
2521                 self.iommu |= disk.iommu;
2522 
2523                 Self::validate_identifier(&mut id_list, &disk.id)?;
2524             }
2525         }
2526 
2527         if let Some(nets) = &self.net {
2528             for net in nets {
2529                 if net.vhost_user && !self.backed_by_shared_memory() {
2530                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2531                 }
2532                 net.validate(self)?;
2533                 self.iommu |= net.iommu;
2534 
2535                 Self::validate_identifier(&mut id_list, &net.id)?;
2536             }
2537         }
2538 
2539         if let Some(fses) = &self.fs {
2540             if !fses.is_empty() && !self.backed_by_shared_memory() {
2541                 return Err(ValidationError::VhostUserRequiresSharedMemory);
2542             }
2543             for fs in fses {
2544                 fs.validate(self)?;
2545 
2546                 Self::validate_identifier(&mut id_list, &fs.id)?;
2547             }
2548         }
2549 
2550         if let Some(pmems) = &self.pmem {
2551             for pmem in pmems {
2552                 pmem.validate(self)?;
2553                 self.iommu |= pmem.iommu;
2554 
2555                 Self::validate_identifier(&mut id_list, &pmem.id)?;
2556             }
2557         }
2558 
2559         self.iommu |= self.rng.iommu;
2560         self.iommu |= self.console.iommu;
2561 
2562         if let Some(t) = &self.cpus.topology {
2563             if t.threads_per_core == 0
2564                 || t.cores_per_die == 0
2565                 || t.dies_per_package == 0
2566                 || t.packages == 0
2567             {
2568                 return Err(ValidationError::CpuTopologyZeroPart);
2569             }
2570 
2571             // The setting of dies doesn't apply on AArch64.
2572             // Only '1' value is accepted, so its impact on the vcpu topology
2573             // setting can be ignored.
2574             #[cfg(target_arch = "aarch64")]
2575             if t.dies_per_package != 1 {
2576                 return Err(ValidationError::CpuTopologyDiesPerPackage);
2577             }
2578 
2579             let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages;
2580             if total != self.cpus.max_vcpus {
2581                 return Err(ValidationError::CpuTopologyCount);
2582             }
2583         }
2584 
2585         if let Some(hugepage_size) = &self.memory.hugepage_size {
2586             if !self.memory.hugepages {
2587                 return Err(ValidationError::HugePageSizeWithoutHugePages);
2588             }
2589             if !hugepage_size.is_power_of_two() {
2590                 return Err(ValidationError::InvalidHugePageSize(*hugepage_size));
2591             }
2592         }
2593 
2594         if let Some(user_devices) = &self.user_devices {
2595             if !user_devices.is_empty() && !self.backed_by_shared_memory() {
2596                 return Err(ValidationError::UserDevicesRequireSharedMemory);
2597             }
2598 
2599             for user_device in user_devices {
2600                 user_device.validate(self)?;
2601 
2602                 Self::validate_identifier(&mut id_list, &user_device.id)?;
2603             }
2604         }
2605 
2606         if let Some(vdpa_devices) = &self.vdpa {
2607             for vdpa_device in vdpa_devices {
2608                 vdpa_device.validate(self)?;
2609                 self.iommu |= vdpa_device.iommu;
2610 
2611                 Self::validate_identifier(&mut id_list, &vdpa_device.id)?;
2612             }
2613         }
2614 
2615         if let Some(vsock) = &self.vsock {
2616             if [!0, 0, 1, 2].contains(&vsock.cid) {
2617                 return Err(ValidationError::VsockSpecialCid(vsock.cid));
2618             }
2619         }
2620 
2621         if let Some(balloon) = &self.balloon {
2622             let mut ram_size = self.memory.size;
2623 
2624             if let Some(zones) = &self.memory.zones {
2625                 for zone in zones {
2626                     ram_size += zone.size;
2627                 }
2628             }
2629 
2630             if balloon.size >= ram_size {
2631                 return Err(ValidationError::BalloonLargerThanRam(
2632                     balloon.size,
2633                     ram_size,
2634                 ));
2635             }
2636         }
2637 
2638         if let Some(devices) = &self.devices {
2639             let mut device_paths = BTreeSet::new();
2640             for device in devices {
2641                 if !device_paths.insert(device.path.to_string_lossy()) {
2642                     return Err(ValidationError::DuplicateDevicePath(
2643                         device.path.to_string_lossy().to_string(),
2644                     ));
2645                 }
2646 
2647                 device.validate(self)?;
2648                 self.iommu |= device.iommu;
2649 
2650                 Self::validate_identifier(&mut id_list, &device.id)?;
2651             }
2652         }
2653 
2654         if let Some(vsock) = &self.vsock {
2655             vsock.validate(self)?;
2656             self.iommu |= vsock.iommu;
2657 
2658             Self::validate_identifier(&mut id_list, &vsock.id)?;
2659         }
2660 
2661         let num_pci_segments = match &self.platform {
2662             Some(platform_config) => platform_config.num_pci_segments,
2663             None => 1,
2664         };
2665         if let Some(numa) = &self.numa {
2666             let mut used_numa_node_memory_zones = HashMap::new();
2667             let mut used_pci_segments = HashMap::new();
2668             for numa_node in numa.iter() {
2669                 if let Some(memory_zones) = numa_node.memory_zones.clone() {
2670                     for memory_zone in memory_zones.iter() {
2671                         if !used_numa_node_memory_zones.contains_key(memory_zone) {
2672                             used_numa_node_memory_zones
2673                                 .insert(memory_zone.to_string(), numa_node.guest_numa_id);
2674                         } else {
2675                             return Err(ValidationError::MemoryZoneReused(
2676                                 memory_zone.to_string(),
2677                                 *used_numa_node_memory_zones.get(memory_zone).unwrap(),
2678                                 numa_node.guest_numa_id,
2679                             ));
2680                         }
2681                     }
2682                 }
2683 
2684                 if let Some(pci_segments) = numa_node.pci_segments.clone() {
2685                     for pci_segment in pci_segments.iter() {
2686                         if *pci_segment >= num_pci_segments {
2687                             return Err(ValidationError::InvalidPciSegment(*pci_segment));
2688                         }
2689                         if *pci_segment == 0 && numa_node.guest_numa_id != 0 {
2690                             return Err(ValidationError::DefaultPciSegmentInvalidNode(
2691                                 numa_node.guest_numa_id,
2692                             ));
2693                         }
2694                         if !used_pci_segments.contains_key(pci_segment) {
2695                             used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id);
2696                         } else {
2697                             return Err(ValidationError::PciSegmentReused(
2698                                 *pci_segment,
2699                                 *used_pci_segments.get(pci_segment).unwrap(),
2700                                 numa_node.guest_numa_id,
2701                             ));
2702                         }
2703                     }
2704                 }
2705             }
2706         }
2707 
2708         if let Some(zones) = &self.memory.zones {
2709             for zone in zones.iter() {
2710                 let id = zone.id.clone();
2711                 Self::validate_identifier(&mut id_list, &Some(id))?;
2712             }
2713         }
2714 
2715         #[cfg(target_arch = "x86_64")]
2716         if let Some(sgx_epcs) = &self.sgx_epc {
2717             for sgx_epc in sgx_epcs.iter() {
2718                 let id = sgx_epc.id.clone();
2719                 Self::validate_identifier(&mut id_list, &Some(id))?;
2720             }
2721         }
2722 
2723         if let Some(pci_segments) = &self.pci_segments {
2724             for pci_segment in pci_segments {
2725                 pci_segment.validate(self)?;
2726             }
2727         }
2728 
2729         self.platform.as_ref().map(|p| p.validate()).transpose()?;
2730         self.iommu |= self
2731             .platform
2732             .as_ref()
2733             .map(|p| p.iommu_segments.is_some())
2734             .unwrap_or_default();
2735 
2736         if let Some(landlock_rules) = &self.landlock_rules {
2737             for landlock_rule in landlock_rules {
2738                 landlock_rule.validate()?;
2739             }
2740         }
2741 
2742         Ok(id_list)
2743     }
2744 
2745     pub fn parse(vm_params: VmParams) -> Result<Self> {
2746         let mut rate_limit_groups: Option<Vec<RateLimiterGroupConfig>> = None;
2747         if let Some(rate_limit_group_list) = &vm_params.rate_limit_groups {
2748             let mut rate_limit_group_config_list = Vec::new();
2749             for item in rate_limit_group_list.iter() {
2750                 let rate_limit_group_config = RateLimiterGroupConfig::parse(item)?;
2751                 rate_limit_group_config_list.push(rate_limit_group_config);
2752             }
2753             rate_limit_groups = Some(rate_limit_group_config_list);
2754         }
2755 
2756         let mut disks: Option<Vec<DiskConfig>> = None;
2757         if let Some(disk_list) = &vm_params.disks {
2758             let mut disk_config_list = Vec::new();
2759             for item in disk_list.iter() {
2760                 let disk_config = DiskConfig::parse(item)?;
2761                 disk_config_list.push(disk_config);
2762             }
2763             disks = Some(disk_config_list);
2764         }
2765 
2766         let mut net: Option<Vec<NetConfig>> = None;
2767         if let Some(net_list) = &vm_params.net {
2768             let mut net_config_list = Vec::new();
2769             for item in net_list.iter() {
2770                 let net_config = NetConfig::parse(item)?;
2771                 net_config_list.push(net_config);
2772             }
2773             net = Some(net_config_list);
2774         }
2775 
2776         let rng = RngConfig::parse(vm_params.rng)?;
2777 
2778         let mut balloon: Option<BalloonConfig> = None;
2779         if let Some(balloon_params) = &vm_params.balloon {
2780             balloon = Some(BalloonConfig::parse(balloon_params)?);
2781         }
2782 
2783         #[cfg(feature = "pvmemcontrol")]
2784         let pvmemcontrol: Option<PvmemcontrolConfig> = vm_params
2785             .pvmemcontrol
2786             .then_some(PvmemcontrolConfig::default());
2787 
2788         let mut fs: Option<Vec<FsConfig>> = None;
2789         if let Some(fs_list) = &vm_params.fs {
2790             let mut fs_config_list = Vec::new();
2791             for item in fs_list.iter() {
2792                 fs_config_list.push(FsConfig::parse(item)?);
2793             }
2794             fs = Some(fs_config_list);
2795         }
2796 
2797         let mut pmem: Option<Vec<PmemConfig>> = None;
2798         if let Some(pmem_list) = &vm_params.pmem {
2799             let mut pmem_config_list = Vec::new();
2800             for item in pmem_list.iter() {
2801                 let pmem_config = PmemConfig::parse(item)?;
2802                 pmem_config_list.push(pmem_config);
2803             }
2804             pmem = Some(pmem_config_list);
2805         }
2806 
2807         let console = ConsoleConfig::parse(vm_params.console)?;
2808         let serial = ConsoleConfig::parse(vm_params.serial)?;
2809         #[cfg(target_arch = "x86_64")]
2810         let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?;
2811 
2812         let mut devices: Option<Vec<DeviceConfig>> = None;
2813         if let Some(device_list) = &vm_params.devices {
2814             let mut device_config_list = Vec::new();
2815             for item in device_list.iter() {
2816                 let device_config = DeviceConfig::parse(item)?;
2817                 device_config_list.push(device_config);
2818             }
2819             devices = Some(device_config_list);
2820         }
2821 
2822         let mut user_devices: Option<Vec<UserDeviceConfig>> = None;
2823         if let Some(user_device_list) = &vm_params.user_devices {
2824             let mut user_device_config_list = Vec::new();
2825             for item in user_device_list.iter() {
2826                 let user_device_config = UserDeviceConfig::parse(item)?;
2827                 user_device_config_list.push(user_device_config);
2828             }
2829             user_devices = Some(user_device_config_list);
2830         }
2831 
2832         let mut vdpa: Option<Vec<VdpaConfig>> = None;
2833         if let Some(vdpa_list) = &vm_params.vdpa {
2834             let mut vdpa_config_list = Vec::new();
2835             for item in vdpa_list.iter() {
2836                 let vdpa_config = VdpaConfig::parse(item)?;
2837                 vdpa_config_list.push(vdpa_config);
2838             }
2839             vdpa = Some(vdpa_config_list);
2840         }
2841 
2842         let mut vsock: Option<VsockConfig> = None;
2843         if let Some(vs) = &vm_params.vsock {
2844             let vsock_config = VsockConfig::parse(vs)?;
2845             vsock = Some(vsock_config);
2846         }
2847 
2848         let mut pci_segments: Option<Vec<PciSegmentConfig>> = None;
2849         if let Some(pci_segment_list) = &vm_params.pci_segments {
2850             let mut pci_segment_config_list = Vec::new();
2851             for item in pci_segment_list.iter() {
2852                 let pci_segment_config = PciSegmentConfig::parse(item)?;
2853                 pci_segment_config_list.push(pci_segment_config);
2854             }
2855             pci_segments = Some(pci_segment_config_list);
2856         }
2857 
2858         let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?;
2859 
2860         #[cfg(target_arch = "x86_64")]
2861         let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None;
2862         #[cfg(target_arch = "x86_64")]
2863         {
2864             if let Some(sgx_epc_list) = &vm_params.sgx_epc {
2865                 let mut sgx_epc_config_list = Vec::new();
2866                 for item in sgx_epc_list.iter() {
2867                     let sgx_epc_config = SgxEpcConfig::parse(item)?;
2868                     sgx_epc_config_list.push(sgx_epc_config);
2869                 }
2870                 sgx_epc = Some(sgx_epc_config_list);
2871             }
2872         }
2873 
2874         let mut numa: Option<Vec<NumaConfig>> = None;
2875         if let Some(numa_list) = &vm_params.numa {
2876             let mut numa_config_list = Vec::new();
2877             for item in numa_list.iter() {
2878                 let numa_config = NumaConfig::parse(item)?;
2879                 numa_config_list.push(numa_config);
2880             }
2881             numa = Some(numa_config_list);
2882         }
2883 
2884         #[cfg(not(feature = "igvm"))]
2885         let payload_present = vm_params.kernel.is_some() || vm_params.firmware.is_some();
2886 
2887         #[cfg(feature = "igvm")]
2888         let payload_present =
2889             vm_params.kernel.is_some() || vm_params.firmware.is_some() || vm_params.igvm.is_some();
2890 
2891         let payload = if payload_present {
2892             Some(PayloadConfig {
2893                 kernel: vm_params.kernel.map(PathBuf::from),
2894                 initramfs: vm_params.initramfs.map(PathBuf::from),
2895                 cmdline: vm_params.cmdline.map(|s| s.to_string()),
2896                 firmware: vm_params.firmware.map(PathBuf::from),
2897                 #[cfg(feature = "igvm")]
2898                 igvm: vm_params.igvm.map(PathBuf::from),
2899                 #[cfg(feature = "sev_snp")]
2900                 host_data: vm_params.host_data.map(|s| s.to_string()),
2901             })
2902         } else {
2903             None
2904         };
2905 
2906         let mut tpm: Option<TpmConfig> = None;
2907         if let Some(tc) = vm_params.tpm {
2908             let tpm_conf = TpmConfig::parse(tc)?;
2909             tpm = Some(TpmConfig {
2910                 socket: tpm_conf.socket,
2911             });
2912         }
2913 
2914         #[cfg(feature = "guest_debug")]
2915         let gdb = vm_params.gdb;
2916 
2917         let mut landlock_rules: Option<Vec<LandlockConfig>> = None;
2918         if let Some(ll_rules) = vm_params.landlock_rules {
2919             landlock_rules = Some(
2920                 ll_rules
2921                     .iter()
2922                     .map(|rule| LandlockConfig::parse(rule))
2923                     .collect::<Result<Vec<LandlockConfig>>>()?,
2924             );
2925         }
2926 
2927         let mut config = VmConfig {
2928             cpus: CpusConfig::parse(vm_params.cpus)?,
2929             memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
2930             payload,
2931             rate_limit_groups,
2932             disks,
2933             net,
2934             rng,
2935             balloon,
2936             fs,
2937             pmem,
2938             serial,
2939             console,
2940             #[cfg(target_arch = "x86_64")]
2941             debug_console,
2942             devices,
2943             user_devices,
2944             vdpa,
2945             vsock,
2946             #[cfg(feature = "pvmemcontrol")]
2947             pvmemcontrol,
2948             pvpanic: vm_params.pvpanic,
2949             iommu: false, // updated in VmConfig::validate()
2950             #[cfg(target_arch = "x86_64")]
2951             sgx_epc,
2952             numa,
2953             watchdog: vm_params.watchdog,
2954             #[cfg(feature = "guest_debug")]
2955             gdb,
2956             pci_segments,
2957             platform,
2958             tpm,
2959             preserved_fds: None,
2960             landlock_enable: vm_params.landlock_enable,
2961             landlock_rules,
2962         };
2963         config.validate().map_err(Error::Validation)?;
2964         Ok(config)
2965     }
2966 
2967     pub fn remove_device(&mut self, id: &str) -> bool {
2968         let mut removed = false;
2969 
2970         // Remove if VFIO device
2971         if let Some(devices) = self.devices.as_mut() {
2972             let len = devices.len();
2973             devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2974             removed |= devices.len() != len;
2975         }
2976 
2977         // Remove if VFIO user device
2978         if let Some(user_devices) = self.user_devices.as_mut() {
2979             let len = user_devices.len();
2980             user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2981             removed |= user_devices.len() != len;
2982         }
2983 
2984         // Remove if disk device
2985         if let Some(disks) = self.disks.as_mut() {
2986             let len = disks.len();
2987             disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2988             removed |= disks.len() != len;
2989         }
2990 
2991         // Remove if fs device
2992         if let Some(fs) = self.fs.as_mut() {
2993             let len = fs.len();
2994             fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2995             removed |= fs.len() != len;
2996         }
2997 
2998         // Remove if net device
2999         if let Some(net) = self.net.as_mut() {
3000             let len = net.len();
3001             net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
3002             removed |= net.len() != len;
3003         }
3004 
3005         // Remove if pmem device
3006         if let Some(pmem) = self.pmem.as_mut() {
3007             let len = pmem.len();
3008             pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
3009             removed |= pmem.len() != len;
3010         }
3011 
3012         // Remove if vDPA device
3013         if let Some(vdpa) = self.vdpa.as_mut() {
3014             let len = vdpa.len();
3015             vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
3016             removed |= vdpa.len() != len;
3017         }
3018 
3019         // Remove if vsock device
3020         if let Some(vsock) = self.vsock.as_ref() {
3021             if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) {
3022                 self.vsock = None;
3023                 removed = true;
3024             }
3025         }
3026 
3027         removed
3028     }
3029 
3030     /// # Safety
3031     /// To use this safely, the caller must guarantee that the input
3032     /// fds are all valid.
3033     pub unsafe fn add_preserved_fds(&mut self, mut fds: Vec<i32>) {
3034         if fds.is_empty() {
3035             return;
3036         }
3037 
3038         if let Some(preserved_fds) = &self.preserved_fds {
3039             fds.append(&mut preserved_fds.clone());
3040         }
3041 
3042         self.preserved_fds = Some(fds);
3043     }
3044 
3045     #[cfg(feature = "tdx")]
3046     pub fn is_tdx_enabled(&self) -> bool {
3047         self.platform.as_ref().map(|p| p.tdx).unwrap_or(false)
3048     }
3049 
3050     #[cfg(feature = "sev_snp")]
3051     pub fn is_sev_snp_enabled(&self) -> bool {
3052         self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false)
3053     }
3054 }
3055 
3056 impl Clone for VmConfig {
3057     fn clone(&self) -> Self {
3058         VmConfig {
3059             cpus: self.cpus.clone(),
3060             memory: self.memory.clone(),
3061             payload: self.payload.clone(),
3062             rate_limit_groups: self.rate_limit_groups.clone(),
3063             disks: self.disks.clone(),
3064             net: self.net.clone(),
3065             rng: self.rng.clone(),
3066             balloon: self.balloon.clone(),
3067             #[cfg(feature = "pvmemcontrol")]
3068             pvmemcontrol: self.pvmemcontrol.clone(),
3069             fs: self.fs.clone(),
3070             pmem: self.pmem.clone(),
3071             serial: self.serial.clone(),
3072             console: self.console.clone(),
3073             #[cfg(target_arch = "x86_64")]
3074             debug_console: self.debug_console.clone(),
3075             devices: self.devices.clone(),
3076             user_devices: self.user_devices.clone(),
3077             vdpa: self.vdpa.clone(),
3078             vsock: self.vsock.clone(),
3079             #[cfg(target_arch = "x86_64")]
3080             sgx_epc: self.sgx_epc.clone(),
3081             numa: self.numa.clone(),
3082             pci_segments: self.pci_segments.clone(),
3083             platform: self.platform.clone(),
3084             tpm: self.tpm.clone(),
3085             preserved_fds: self
3086                 .preserved_fds
3087                 .as_ref()
3088                 // SAFETY: FFI call with valid FDs
3089                 .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()),
3090             landlock_rules: self.landlock_rules.clone(),
3091             ..*self
3092         }
3093     }
3094 }
3095 
3096 impl Drop for VmConfig {
3097     fn drop(&mut self) {
3098         if let Some(mut fds) = self.preserved_fds.take() {
3099             for fd in fds.drain(..) {
3100                 // SAFETY: FFI call with valid FDs
3101                 unsafe { libc::close(fd) };
3102             }
3103         }
3104     }
3105 }
3106 
3107 #[cfg(test)]
3108 mod tests {
3109     use std::fs::File;
3110     use std::net::Ipv4Addr;
3111     use std::os::unix::io::AsRawFd;
3112 
3113     use net_util::MacAddr;
3114 
3115     use super::*;
3116 
3117     #[test]
3118     fn test_cpu_parsing() -> Result<()> {
3119         assert_eq!(CpusConfig::parse("")?, CpusConfig::default());
3120 
3121         assert_eq!(
3122             CpusConfig::parse("boot=1")?,
3123             CpusConfig {
3124                 boot_vcpus: 1,
3125                 max_vcpus: 1,
3126                 ..Default::default()
3127             }
3128         );
3129         assert_eq!(
3130             CpusConfig::parse("boot=1,max=2")?,
3131             CpusConfig {
3132                 boot_vcpus: 1,
3133                 max_vcpus: 2,
3134                 ..Default::default()
3135             }
3136         );
3137         assert_eq!(
3138             CpusConfig::parse("boot=8,topology=2:2:1:2")?,
3139             CpusConfig {
3140                 boot_vcpus: 8,
3141                 max_vcpus: 8,
3142                 topology: Some(CpuTopology {
3143                     threads_per_core: 2,
3144                     cores_per_die: 2,
3145                     dies_per_package: 1,
3146                     packages: 2
3147                 }),
3148                 ..Default::default()
3149             }
3150         );
3151 
3152         CpusConfig::parse("boot=8,topology=2:2:1").unwrap_err();
3153         CpusConfig::parse("boot=8,topology=2:2:1:x").unwrap_err();
3154         assert_eq!(
3155             CpusConfig::parse("boot=1,kvm_hyperv=on")?,
3156             CpusConfig {
3157                 boot_vcpus: 1,
3158                 max_vcpus: 1,
3159                 kvm_hyperv: true,
3160                 ..Default::default()
3161             }
3162         );
3163         assert_eq!(
3164             CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?,
3165             CpusConfig {
3166                 boot_vcpus: 2,
3167                 max_vcpus: 2,
3168                 affinity: Some(vec![
3169                     CpuAffinity {
3170                         vcpu: 0,
3171                         host_cpus: vec![0, 2],
3172                     },
3173                     CpuAffinity {
3174                         vcpu: 1,
3175                         host_cpus: vec![1, 3],
3176                     }
3177                 ]),
3178                 ..Default::default()
3179             },
3180         );
3181 
3182         Ok(())
3183     }
3184 
3185     #[test]
3186     fn test_mem_parsing() -> Result<()> {
3187         assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default());
3188         // Default string
3189         assert_eq!(
3190             MemoryConfig::parse("size=512M", None)?,
3191             MemoryConfig::default()
3192         );
3193         assert_eq!(
3194             MemoryConfig::parse("size=512M,mergeable=on", None)?,
3195             MemoryConfig {
3196                 size: 512 << 20,
3197                 mergeable: true,
3198                 ..Default::default()
3199             }
3200         );
3201         assert_eq!(
3202             MemoryConfig::parse("mergeable=on", None)?,
3203             MemoryConfig {
3204                 mergeable: true,
3205                 ..Default::default()
3206             }
3207         );
3208         assert_eq!(
3209             MemoryConfig::parse("size=1G,mergeable=off", None)?,
3210             MemoryConfig {
3211                 size: 1 << 30,
3212                 mergeable: false,
3213                 ..Default::default()
3214             }
3215         );
3216         assert_eq!(
3217             MemoryConfig::parse("hotplug_method=acpi", None)?,
3218             MemoryConfig {
3219                 ..Default::default()
3220             }
3221         );
3222         assert_eq!(
3223             MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?,
3224             MemoryConfig {
3225                 hotplug_size: Some(512 << 20),
3226                 ..Default::default()
3227             }
3228         );
3229         assert_eq!(
3230             MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?,
3231             MemoryConfig {
3232                 hotplug_size: Some(512 << 20),
3233                 hotplug_method: HotplugMethod::VirtioMem,
3234                 ..Default::default()
3235             }
3236         );
3237         assert_eq!(
3238             MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?,
3239             MemoryConfig {
3240                 hugepage_size: Some(2 << 20),
3241                 size: 1 << 30,
3242                 hugepages: true,
3243                 ..Default::default()
3244             }
3245         );
3246         Ok(())
3247     }
3248 
3249     #[test]
3250     fn test_rate_limit_group_parsing() -> Result<()> {
3251         assert_eq!(
3252             RateLimiterGroupConfig::parse("id=group0,bw_size=1000,bw_refill_time=100")?,
3253             RateLimiterGroupConfig {
3254                 id: "group0".to_string(),
3255                 rate_limiter_config: RateLimiterConfig {
3256                     bandwidth: Some(TokenBucketConfig {
3257                         size: 1000,
3258                         one_time_burst: Some(0),
3259                         refill_time: 100,
3260                     }),
3261                     ops: None,
3262                 }
3263             }
3264         );
3265         assert_eq!(
3266             RateLimiterGroupConfig::parse("id=group0,ops_size=1000,ops_refill_time=100")?,
3267             RateLimiterGroupConfig {
3268                 id: "group0".to_string(),
3269                 rate_limiter_config: RateLimiterConfig {
3270                     bandwidth: None,
3271                     ops: Some(TokenBucketConfig {
3272                         size: 1000,
3273                         one_time_burst: Some(0),
3274                         refill_time: 100,
3275                     }),
3276                 }
3277             }
3278         );
3279         Ok(())
3280     }
3281 
3282     #[test]
3283     fn test_pci_segment_parsing() -> Result<()> {
3284         assert_eq!(
3285             PciSegmentConfig::parse("pci_segment=0")?,
3286             PciSegmentConfig {
3287                 pci_segment: 0,
3288                 mmio32_aperture_weight: 1,
3289                 mmio64_aperture_weight: 1,
3290             }
3291         );
3292         assert_eq!(
3293             PciSegmentConfig::parse(
3294                 "pci_segment=0,mmio32_aperture_weight=1,mmio64_aperture_weight=1"
3295             )?,
3296             PciSegmentConfig {
3297                 pci_segment: 0,
3298                 mmio32_aperture_weight: 1,
3299                 mmio64_aperture_weight: 1,
3300             }
3301         );
3302         assert_eq!(
3303             PciSegmentConfig::parse("pci_segment=0,mmio32_aperture_weight=2")?,
3304             PciSegmentConfig {
3305                 pci_segment: 0,
3306                 mmio32_aperture_weight: 2,
3307                 mmio64_aperture_weight: 1,
3308             }
3309         );
3310         assert_eq!(
3311             PciSegmentConfig::parse("pci_segment=0,mmio64_aperture_weight=2")?,
3312             PciSegmentConfig {
3313                 pci_segment: 0,
3314                 mmio32_aperture_weight: 1,
3315                 mmio64_aperture_weight: 2,
3316             }
3317         );
3318 
3319         Ok(())
3320     }
3321 
3322     fn disk_fixture() -> DiskConfig {
3323         DiskConfig {
3324             path: Some(PathBuf::from("/path/to_file")),
3325             readonly: false,
3326             direct: false,
3327             iommu: false,
3328             num_queues: 1,
3329             queue_size: 128,
3330             vhost_user: false,
3331             vhost_socket: None,
3332             id: None,
3333             disable_io_uring: false,
3334             disable_aio: false,
3335             rate_limit_group: None,
3336             rate_limiter_config: None,
3337             pci_segment: 0,
3338             serial: None,
3339             queue_affinity: None,
3340         }
3341     }
3342 
3343     #[test]
3344     fn test_disk_parsing() -> Result<()> {
3345         assert_eq!(
3346             DiskConfig::parse("path=/path/to_file")?,
3347             DiskConfig { ..disk_fixture() }
3348         );
3349         assert_eq!(
3350             DiskConfig::parse("path=/path/to_file,id=mydisk0")?,
3351             DiskConfig {
3352                 id: Some("mydisk0".to_owned()),
3353                 ..disk_fixture()
3354             }
3355         );
3356         assert_eq!(
3357             DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
3358             DiskConfig {
3359                 path: None,
3360                 vhost_socket: Some(String::from("/tmp/sock")),
3361                 vhost_user: true,
3362                 ..disk_fixture()
3363             }
3364         );
3365         assert_eq!(
3366             DiskConfig::parse("path=/path/to_file,iommu=on")?,
3367             DiskConfig {
3368                 iommu: true,
3369                 ..disk_fixture()
3370             }
3371         );
3372         assert_eq!(
3373             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?,
3374             DiskConfig {
3375                 iommu: true,
3376                 queue_size: 256,
3377                 ..disk_fixture()
3378             }
3379         );
3380         assert_eq!(
3381             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?,
3382             DiskConfig {
3383                 iommu: true,
3384                 queue_size: 256,
3385                 num_queues: 4,
3386                 ..disk_fixture()
3387             }
3388         );
3389         assert_eq!(
3390             DiskConfig::parse("path=/path/to_file,direct=on")?,
3391             DiskConfig {
3392                 direct: true,
3393                 ..disk_fixture()
3394             }
3395         );
3396         assert_eq!(
3397             DiskConfig::parse("path=/path/to_file,serial=test")?,
3398             DiskConfig {
3399                 serial: Some(String::from("test")),
3400                 ..disk_fixture()
3401             }
3402         );
3403         assert_eq!(
3404             DiskConfig::parse("path=/path/to_file,rate_limit_group=group0")?,
3405             DiskConfig {
3406                 rate_limit_group: Some("group0".to_string()),
3407                 ..disk_fixture()
3408             }
3409         );
3410         assert_eq!(
3411             DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?,
3412             DiskConfig {
3413                 queue_affinity: Some(vec![
3414                     VirtQueueAffinity {
3415                         queue_index: 0,
3416                         host_cpus: vec![1],
3417                     },
3418                     VirtQueueAffinity {
3419                         queue_index: 1,
3420                         host_cpus: vec![2],
3421                     },
3422                     VirtQueueAffinity {
3423                         queue_index: 2,
3424                         host_cpus: vec![3, 4],
3425                     },
3426                     VirtQueueAffinity {
3427                         queue_index: 3,
3428                         host_cpus: vec![5, 6, 7, 8],
3429                     }
3430                 ]),
3431                 ..disk_fixture()
3432             }
3433         );
3434         Ok(())
3435     }
3436 
3437     fn net_fixture() -> NetConfig {
3438         NetConfig {
3439             tap: None,
3440             ip: Ipv4Addr::new(192, 168, 249, 1),
3441             mask: Ipv4Addr::new(255, 255, 255, 0),
3442             mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
3443             host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
3444             mtu: None,
3445             iommu: false,
3446             num_queues: 2,
3447             queue_size: 256,
3448             vhost_user: false,
3449             vhost_socket: None,
3450             vhost_mode: VhostMode::Client,
3451             id: None,
3452             fds: None,
3453             rate_limiter_config: None,
3454             pci_segment: 0,
3455             offload_tso: true,
3456             offload_ufo: true,
3457             offload_csum: true,
3458         }
3459     }
3460 
3461     #[test]
3462     fn test_net_parsing() -> Result<()> {
3463         // mac address is random
3464         assert_eq!(
3465             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?,
3466             net_fixture(),
3467         );
3468 
3469         assert_eq!(
3470             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?,
3471             NetConfig {
3472                 id: Some("mynet0".to_owned()),
3473                 ..net_fixture()
3474             }
3475         );
3476 
3477         assert_eq!(
3478             NetConfig::parse(
3479                 "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"
3480             )?,
3481             NetConfig {
3482                 tap: Some("tap0".to_owned()),
3483                 ip: "192.168.100.1".parse().unwrap(),
3484                 mask: "255.255.255.128".parse().unwrap(),
3485                 ..net_fixture()
3486             }
3487         );
3488 
3489         assert_eq!(
3490             NetConfig::parse(
3491                 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock"
3492             )?,
3493             NetConfig {
3494                 vhost_user: true,
3495                 vhost_socket: Some("/tmp/sock".to_owned()),
3496                 ..net_fixture()
3497             }
3498         );
3499 
3500         assert_eq!(
3501             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")?,
3502             NetConfig {
3503                 num_queues: 4,
3504                 queue_size: 1024,
3505                 iommu: true,
3506                 ..net_fixture()
3507             }
3508         );
3509 
3510         assert_eq!(
3511             NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?,
3512             NetConfig {
3513                 host_mac: None,
3514                 fds: Some(vec![3, 7]),
3515                 num_queues: 4,
3516                 ..net_fixture()
3517             }
3518         );
3519 
3520         Ok(())
3521     }
3522 
3523     #[test]
3524     fn test_parse_rng() -> Result<()> {
3525         assert_eq!(RngConfig::parse("")?, RngConfig::default());
3526         assert_eq!(
3527             RngConfig::parse("src=/dev/random")?,
3528             RngConfig {
3529                 src: PathBuf::from("/dev/random"),
3530                 ..Default::default()
3531             }
3532         );
3533         assert_eq!(
3534             RngConfig::parse("src=/dev/random,iommu=on")?,
3535             RngConfig {
3536                 src: PathBuf::from("/dev/random"),
3537                 iommu: true,
3538             }
3539         );
3540         assert_eq!(
3541             RngConfig::parse("iommu=on")?,
3542             RngConfig {
3543                 iommu: true,
3544                 ..Default::default()
3545             }
3546         );
3547         Ok(())
3548     }
3549 
3550     fn fs_fixture() -> FsConfig {
3551         FsConfig {
3552             socket: PathBuf::from("/tmp/sock"),
3553             tag: "mytag".to_owned(),
3554             num_queues: 1,
3555             queue_size: 1024,
3556             id: None,
3557             pci_segment: 0,
3558         }
3559     }
3560 
3561     #[test]
3562     fn test_parse_fs() -> Result<()> {
3563         // "tag" and "socket" must be supplied
3564         FsConfig::parse("").unwrap_err();
3565         FsConfig::parse("tag=mytag").unwrap_err();
3566         FsConfig::parse("socket=/tmp/sock").unwrap_err();
3567         assert_eq!(FsConfig::parse("tag=mytag,socket=/tmp/sock")?, fs_fixture());
3568         assert_eq!(
3569             FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?,
3570             FsConfig {
3571                 num_queues: 4,
3572                 queue_size: 1024,
3573                 ..fs_fixture()
3574             }
3575         );
3576 
3577         Ok(())
3578     }
3579 
3580     fn pmem_fixture() -> PmemConfig {
3581         PmemConfig {
3582             file: PathBuf::from("/tmp/pmem"),
3583             size: Some(128 << 20),
3584             iommu: false,
3585             discard_writes: false,
3586             id: None,
3587             pci_segment: 0,
3588         }
3589     }
3590 
3591     #[test]
3592     fn test_pmem_parsing() -> Result<()> {
3593         // Must always give a file and size
3594         PmemConfig::parse("").unwrap_err();
3595         PmemConfig::parse("size=128M").unwrap_err();
3596         assert_eq!(
3597             PmemConfig::parse("file=/tmp/pmem,size=128M")?,
3598             pmem_fixture()
3599         );
3600         assert_eq!(
3601             PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?,
3602             PmemConfig {
3603                 id: Some("mypmem0".to_owned()),
3604                 ..pmem_fixture()
3605             }
3606         );
3607         assert_eq!(
3608             PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?,
3609             PmemConfig {
3610                 discard_writes: true,
3611                 iommu: true,
3612                 ..pmem_fixture()
3613             }
3614         );
3615 
3616         Ok(())
3617     }
3618 
3619     #[test]
3620     fn test_console_parsing() -> Result<()> {
3621         ConsoleConfig::parse("").unwrap_err();
3622         ConsoleConfig::parse("badmode").unwrap_err();
3623         assert_eq!(
3624             ConsoleConfig::parse("off")?,
3625             ConsoleConfig {
3626                 mode: ConsoleOutputMode::Off,
3627                 iommu: false,
3628                 file: None,
3629                 socket: None,
3630             }
3631         );
3632         assert_eq!(
3633             ConsoleConfig::parse("pty")?,
3634             ConsoleConfig {
3635                 mode: ConsoleOutputMode::Pty,
3636                 iommu: false,
3637                 file: None,
3638                 socket: None,
3639             }
3640         );
3641         assert_eq!(
3642             ConsoleConfig::parse("tty")?,
3643             ConsoleConfig {
3644                 mode: ConsoleOutputMode::Tty,
3645                 iommu: false,
3646                 file: None,
3647                 socket: None,
3648             }
3649         );
3650         assert_eq!(
3651             ConsoleConfig::parse("null")?,
3652             ConsoleConfig {
3653                 mode: ConsoleOutputMode::Null,
3654                 iommu: false,
3655                 file: None,
3656                 socket: None,
3657             }
3658         );
3659         assert_eq!(
3660             ConsoleConfig::parse("file=/tmp/console")?,
3661             ConsoleConfig {
3662                 mode: ConsoleOutputMode::File,
3663                 iommu: false,
3664                 file: Some(PathBuf::from("/tmp/console")),
3665                 socket: None,
3666             }
3667         );
3668         assert_eq!(
3669             ConsoleConfig::parse("null,iommu=on")?,
3670             ConsoleConfig {
3671                 mode: ConsoleOutputMode::Null,
3672                 iommu: true,
3673                 file: None,
3674                 socket: None,
3675             }
3676         );
3677         assert_eq!(
3678             ConsoleConfig::parse("file=/tmp/console,iommu=on")?,
3679             ConsoleConfig {
3680                 mode: ConsoleOutputMode::File,
3681                 iommu: true,
3682                 file: Some(PathBuf::from("/tmp/console")),
3683                 socket: None,
3684             }
3685         );
3686         assert_eq!(
3687             ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?,
3688             ConsoleConfig {
3689                 mode: ConsoleOutputMode::Socket,
3690                 iommu: true,
3691                 file: None,
3692                 socket: Some(PathBuf::from("/tmp/serial.sock")),
3693             }
3694         );
3695         Ok(())
3696     }
3697 
3698     fn device_fixture() -> DeviceConfig {
3699         DeviceConfig {
3700             path: PathBuf::from("/path/to/device"),
3701             id: None,
3702             iommu: false,
3703             pci_segment: 0,
3704             x_nv_gpudirect_clique: None,
3705         }
3706     }
3707 
3708     #[test]
3709     fn test_device_parsing() -> Result<()> {
3710         // Device must have a path provided
3711         DeviceConfig::parse("").unwrap_err();
3712         assert_eq!(
3713             DeviceConfig::parse("path=/path/to/device")?,
3714             device_fixture()
3715         );
3716 
3717         assert_eq!(
3718             DeviceConfig::parse("path=/path/to/device,iommu=on")?,
3719             DeviceConfig {
3720                 iommu: true,
3721                 ..device_fixture()
3722             }
3723         );
3724 
3725         assert_eq!(
3726             DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?,
3727             DeviceConfig {
3728                 id: Some("mydevice0".to_owned()),
3729                 iommu: true,
3730                 ..device_fixture()
3731             }
3732         );
3733 
3734         Ok(())
3735     }
3736 
3737     fn vdpa_fixture() -> VdpaConfig {
3738         VdpaConfig {
3739             path: PathBuf::from("/dev/vhost-vdpa"),
3740             num_queues: 1,
3741             iommu: false,
3742             id: None,
3743             pci_segment: 0,
3744         }
3745     }
3746 
3747     #[test]
3748     fn test_vdpa_parsing() -> Result<()> {
3749         // path is required
3750         VdpaConfig::parse("").unwrap_err();
3751         assert_eq!(VdpaConfig::parse("path=/dev/vhost-vdpa")?, vdpa_fixture());
3752         assert_eq!(
3753             VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?,
3754             VdpaConfig {
3755                 num_queues: 2,
3756                 id: Some("my_vdpa".to_owned()),
3757                 ..vdpa_fixture()
3758             }
3759         );
3760         Ok(())
3761     }
3762 
3763     #[test]
3764     fn test_tpm_parsing() -> Result<()> {
3765         // path is required
3766         TpmConfig::parse("").unwrap_err();
3767         assert_eq!(
3768             TpmConfig::parse("socket=/var/run/tpm.sock")?,
3769             TpmConfig {
3770                 socket: PathBuf::from("/var/run/tpm.sock"),
3771             }
3772         );
3773         Ok(())
3774     }
3775 
3776     #[test]
3777     fn test_vsock_parsing() -> Result<()> {
3778         // socket and cid is required
3779         VsockConfig::parse("").unwrap_err();
3780         assert_eq!(
3781             VsockConfig::parse("socket=/tmp/sock,cid=3")?,
3782             VsockConfig {
3783                 cid: 3,
3784                 socket: PathBuf::from("/tmp/sock"),
3785                 iommu: false,
3786                 id: None,
3787                 pci_segment: 0,
3788             }
3789         );
3790         assert_eq!(
3791             VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?,
3792             VsockConfig {
3793                 cid: 3,
3794                 socket: PathBuf::from("/tmp/sock"),
3795                 iommu: true,
3796                 id: None,
3797                 pci_segment: 0,
3798             }
3799         );
3800         Ok(())
3801     }
3802 
3803     #[test]
3804     fn test_restore_parsing() -> Result<()> {
3805         assert_eq!(
3806             RestoreConfig::parse("source_url=/path/to/snapshot")?,
3807             RestoreConfig {
3808                 source_url: PathBuf::from("/path/to/snapshot"),
3809                 prefault: false,
3810                 net_fds: None,
3811             }
3812         );
3813         assert_eq!(
3814             RestoreConfig::parse(
3815                 "source_url=/path/to/snapshot,prefault=off,net_fds=[net0@[3,4],net1@[5,6,7,8]]"
3816             )?,
3817             RestoreConfig {
3818                 source_url: PathBuf::from("/path/to/snapshot"),
3819                 prefault: false,
3820                 net_fds: Some(vec![
3821                     RestoredNetConfig {
3822                         id: "net0".to_string(),
3823                         num_fds: 2,
3824                         fds: Some(vec![3, 4]),
3825                     },
3826                     RestoredNetConfig {
3827                         id: "net1".to_string(),
3828                         num_fds: 4,
3829                         fds: Some(vec![5, 6, 7, 8]),
3830                     }
3831                 ]),
3832             }
3833         );
3834         // Parsing should fail as source_url is a required field
3835         RestoreConfig::parse("prefault=off").unwrap_err();
3836         Ok(())
3837     }
3838 
3839     #[test]
3840     fn test_restore_config_validation() {
3841         // interested in only VmConfig.net, so set rest to default values
3842         let mut snapshot_vm_config = VmConfig {
3843             cpus: CpusConfig::default(),
3844             memory: MemoryConfig::default(),
3845             payload: None,
3846             rate_limit_groups: None,
3847             disks: None,
3848             rng: RngConfig::default(),
3849             balloon: None,
3850             fs: None,
3851             pmem: None,
3852             serial: default_serial(),
3853             console: default_console(),
3854             #[cfg(target_arch = "x86_64")]
3855             debug_console: DebugConsoleConfig::default(),
3856             devices: None,
3857             user_devices: None,
3858             vdpa: None,
3859             vsock: None,
3860             #[cfg(feature = "pvmemcontrol")]
3861             pvmemcontrol: None,
3862             pvpanic: false,
3863             iommu: false,
3864             #[cfg(target_arch = "x86_64")]
3865             sgx_epc: None,
3866             numa: None,
3867             watchdog: false,
3868             #[cfg(feature = "guest_debug")]
3869             gdb: false,
3870             pci_segments: None,
3871             platform: None,
3872             tpm: None,
3873             preserved_fds: None,
3874             net: Some(vec![
3875                 NetConfig {
3876                     id: Some("net0".to_owned()),
3877                     num_queues: 2,
3878                     fds: Some(vec![-1, -1, -1, -1]),
3879                     ..net_fixture()
3880                 },
3881                 NetConfig {
3882                     id: Some("net1".to_owned()),
3883                     num_queues: 1,
3884                     fds: Some(vec![-1, -1]),
3885                     ..net_fixture()
3886                 },
3887                 NetConfig {
3888                     id: Some("net2".to_owned()),
3889                     fds: None,
3890                     ..net_fixture()
3891                 },
3892             ]),
3893             landlock_enable: false,
3894             landlock_rules: None,
3895         };
3896 
3897         let valid_config = RestoreConfig {
3898             source_url: PathBuf::from("/path/to/snapshot"),
3899             prefault: false,
3900             net_fds: Some(vec![
3901                 RestoredNetConfig {
3902                     id: "net0".to_string(),
3903                     num_fds: 4,
3904                     fds: Some(vec![3, 4, 5, 6]),
3905                 },
3906                 RestoredNetConfig {
3907                     id: "net1".to_string(),
3908                     num_fds: 2,
3909                     fds: Some(vec![7, 8]),
3910                 },
3911             ]),
3912         };
3913         valid_config.validate(&snapshot_vm_config).unwrap();
3914 
3915         let mut invalid_config = valid_config.clone();
3916         invalid_config.net_fds = Some(vec![RestoredNetConfig {
3917             id: "netx".to_string(),
3918             num_fds: 4,
3919             fds: Some(vec![3, 4, 5, 6]),
3920         }]);
3921         assert_eq!(
3922             invalid_config.validate(&snapshot_vm_config),
3923             Err(ValidationError::RestoreMissingRequiredNetId(
3924                 "net0".to_string()
3925             ))
3926         );
3927 
3928         invalid_config.net_fds = Some(vec![
3929             RestoredNetConfig {
3930                 id: "net0".to_string(),
3931                 num_fds: 4,
3932                 fds: Some(vec![3, 4, 5, 6]),
3933             },
3934             RestoredNetConfig {
3935                 id: "net0".to_string(),
3936                 num_fds: 4,
3937                 fds: Some(vec![3, 4, 5, 6]),
3938             },
3939         ]);
3940         assert_eq!(
3941             invalid_config.validate(&snapshot_vm_config),
3942             Err(ValidationError::IdentifierNotUnique("net0".to_string()))
3943         );
3944 
3945         invalid_config.net_fds = Some(vec![RestoredNetConfig {
3946             id: "net0".to_string(),
3947             num_fds: 4,
3948             fds: Some(vec![3, 4, 5, 6]),
3949         }]);
3950         assert_eq!(
3951             invalid_config.validate(&snapshot_vm_config),
3952             Err(ValidationError::RestoreMissingRequiredNetId(
3953                 "net1".to_string()
3954             ))
3955         );
3956 
3957         invalid_config.net_fds = Some(vec![RestoredNetConfig {
3958             id: "net0".to_string(),
3959             num_fds: 2,
3960             fds: Some(vec![3, 4]),
3961         }]);
3962         assert_eq!(
3963             invalid_config.validate(&snapshot_vm_config),
3964             Err(ValidationError::RestoreNetFdCountMismatch(
3965                 "net0".to_string(),
3966                 2,
3967                 4
3968             ))
3969         );
3970 
3971         let another_valid_config = RestoreConfig {
3972             source_url: PathBuf::from("/path/to/snapshot"),
3973             prefault: false,
3974             net_fds: None,
3975         };
3976         snapshot_vm_config.net = Some(vec![NetConfig {
3977             id: Some("net2".to_owned()),
3978             fds: None,
3979             ..net_fixture()
3980         }]);
3981         another_valid_config.validate(&snapshot_vm_config).unwrap();
3982     }
3983 
3984     fn platform_fixture() -> PlatformConfig {
3985         PlatformConfig {
3986             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
3987             iommu_segments: None,
3988             serial_number: None,
3989             uuid: None,
3990             oem_strings: None,
3991             #[cfg(feature = "tdx")]
3992             tdx: false,
3993             #[cfg(feature = "sev_snp")]
3994             sev_snp: false,
3995         }
3996     }
3997 
3998     fn numa_fixture() -> NumaConfig {
3999         NumaConfig {
4000             guest_numa_id: 0,
4001             cpus: None,
4002             distances: None,
4003             memory_zones: None,
4004             #[cfg(target_arch = "x86_64")]
4005             sgx_epc_sections: None,
4006             pci_segments: None,
4007         }
4008     }
4009 
4010     #[test]
4011     fn test_config_validation() {
4012         let mut valid_config = VmConfig {
4013             cpus: CpusConfig {
4014                 boot_vcpus: 1,
4015                 max_vcpus: 1,
4016                 ..Default::default()
4017             },
4018             memory: MemoryConfig {
4019                 size: 536_870_912,
4020                 mergeable: false,
4021                 hotplug_method: HotplugMethod::Acpi,
4022                 hotplug_size: None,
4023                 hotplugged_size: None,
4024                 shared: false,
4025                 hugepages: false,
4026                 hugepage_size: None,
4027                 prefault: false,
4028                 zones: None,
4029                 thp: true,
4030             },
4031             payload: Some(PayloadConfig {
4032                 kernel: Some(PathBuf::from("/path/to/kernel")),
4033                 firmware: None,
4034                 cmdline: None,
4035                 initramfs: None,
4036                 #[cfg(feature = "igvm")]
4037                 igvm: None,
4038                 #[cfg(feature = "sev_snp")]
4039                 host_data: Some(
4040                     "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb431188673288c07".to_string(),
4041                 ),
4042             }),
4043             rate_limit_groups: None,
4044             disks: None,
4045             net: None,
4046             rng: RngConfig {
4047                 src: PathBuf::from("/dev/urandom"),
4048                 iommu: false,
4049             },
4050             balloon: None,
4051             fs: None,
4052             pmem: None,
4053             serial: ConsoleConfig {
4054                 file: None,
4055                 mode: ConsoleOutputMode::Null,
4056                 iommu: false,
4057                 socket: None,
4058             },
4059             console: ConsoleConfig {
4060                 file: None,
4061                 mode: ConsoleOutputMode::Tty,
4062                 iommu: false,
4063                 socket: None,
4064             },
4065             #[cfg(target_arch = "x86_64")]
4066             debug_console: DebugConsoleConfig::default(),
4067             devices: None,
4068             user_devices: None,
4069             vdpa: None,
4070             vsock: None,
4071             #[cfg(feature = "pvmemcontrol")]
4072             pvmemcontrol: None,
4073             pvpanic: false,
4074             iommu: false,
4075             #[cfg(target_arch = "x86_64")]
4076             sgx_epc: None,
4077             numa: None,
4078             watchdog: false,
4079             #[cfg(feature = "guest_debug")]
4080             gdb: false,
4081             pci_segments: None,
4082             platform: None,
4083             tpm: None,
4084             preserved_fds: None,
4085             landlock_enable: false,
4086             landlock_rules: None,
4087         };
4088 
4089         valid_config.validate().unwrap();
4090 
4091         let mut invalid_config = valid_config.clone();
4092         invalid_config.serial.mode = ConsoleOutputMode::Tty;
4093         invalid_config.console.mode = ConsoleOutputMode::Tty;
4094         valid_config.validate().unwrap();
4095 
4096         let mut invalid_config = valid_config.clone();
4097         invalid_config.payload = None;
4098         assert_eq!(
4099             invalid_config.validate(),
4100             Err(ValidationError::KernelMissing)
4101         );
4102 
4103         let mut invalid_config = valid_config.clone();
4104         invalid_config.serial.mode = ConsoleOutputMode::File;
4105         invalid_config.serial.file = None;
4106         assert_eq!(
4107             invalid_config.validate(),
4108             Err(ValidationError::ConsoleFileMissing)
4109         );
4110 
4111         let mut invalid_config = valid_config.clone();
4112         invalid_config.cpus.max_vcpus = 16;
4113         invalid_config.cpus.boot_vcpus = 32;
4114         assert_eq!(
4115             invalid_config.validate(),
4116             Err(ValidationError::CpusMaxLowerThanBoot)
4117         );
4118 
4119         let mut invalid_config = valid_config.clone();
4120         invalid_config.cpus.max_vcpus = 16;
4121         invalid_config.cpus.boot_vcpus = 16;
4122         invalid_config.cpus.topology = Some(CpuTopology {
4123             threads_per_core: 2,
4124             cores_per_die: 8,
4125             dies_per_package: 1,
4126             packages: 2,
4127         });
4128         assert_eq!(
4129             invalid_config.validate(),
4130             Err(ValidationError::CpuTopologyCount)
4131         );
4132 
4133         let mut invalid_config = valid_config.clone();
4134         invalid_config.disks = Some(vec![DiskConfig {
4135             vhost_socket: Some("/path/to/sock".to_owned()),
4136             path: Some(PathBuf::from("/path/to/image")),
4137             ..disk_fixture()
4138         }]);
4139         assert_eq!(
4140             invalid_config.validate(),
4141             Err(ValidationError::DiskSocketAndPath)
4142         );
4143 
4144         let mut invalid_config = valid_config.clone();
4145         invalid_config.memory.shared = true;
4146         invalid_config.disks = Some(vec![DiskConfig {
4147             path: None,
4148             vhost_user: true,
4149             ..disk_fixture()
4150         }]);
4151         assert_eq!(
4152             invalid_config.validate(),
4153             Err(ValidationError::VhostUserMissingSocket)
4154         );
4155 
4156         let mut invalid_config = valid_config.clone();
4157         invalid_config.disks = Some(vec![DiskConfig {
4158             path: None,
4159             vhost_user: true,
4160             vhost_socket: Some("/path/to/sock".to_owned()),
4161             ..disk_fixture()
4162         }]);
4163         assert_eq!(
4164             invalid_config.validate(),
4165             Err(ValidationError::VhostUserRequiresSharedMemory)
4166         );
4167 
4168         let mut still_valid_config = valid_config.clone();
4169         still_valid_config.disks = Some(vec![DiskConfig {
4170             path: None,
4171             vhost_user: true,
4172             vhost_socket: Some("/path/to/sock".to_owned()),
4173             ..disk_fixture()
4174         }]);
4175         still_valid_config.memory.shared = true;
4176         still_valid_config.validate().unwrap();
4177 
4178         let mut invalid_config = valid_config.clone();
4179         invalid_config.net = Some(vec![NetConfig {
4180             vhost_user: true,
4181             ..net_fixture()
4182         }]);
4183         assert_eq!(
4184             invalid_config.validate(),
4185             Err(ValidationError::VhostUserRequiresSharedMemory)
4186         );
4187 
4188         let mut still_valid_config = valid_config.clone();
4189         still_valid_config.net = Some(vec![NetConfig {
4190             vhost_user: true,
4191             vhost_socket: Some("/path/to/sock".to_owned()),
4192             ..net_fixture()
4193         }]);
4194         still_valid_config.memory.shared = true;
4195         still_valid_config.validate().unwrap();
4196 
4197         let mut invalid_config = valid_config.clone();
4198         invalid_config.net = Some(vec![NetConfig {
4199             fds: Some(vec![0]),
4200             ..net_fixture()
4201         }]);
4202         assert_eq!(
4203             invalid_config.validate(),
4204             Err(ValidationError::VnetReservedFd)
4205         );
4206 
4207         let mut invalid_config = valid_config.clone();
4208         invalid_config.net = Some(vec![NetConfig {
4209             offload_csum: false,
4210             ..net_fixture()
4211         }]);
4212         assert_eq!(
4213             invalid_config.validate(),
4214             Err(ValidationError::NoHardwareChecksumOffload)
4215         );
4216 
4217         let mut invalid_config = valid_config.clone();
4218         invalid_config.fs = Some(vec![fs_fixture()]);
4219         assert_eq!(
4220             invalid_config.validate(),
4221             Err(ValidationError::VhostUserRequiresSharedMemory)
4222         );
4223 
4224         let mut still_valid_config = valid_config.clone();
4225         still_valid_config.memory.shared = true;
4226         still_valid_config.validate().unwrap();
4227 
4228         let mut still_valid_config = valid_config.clone();
4229         still_valid_config.memory.hugepages = true;
4230         still_valid_config.validate().unwrap();
4231 
4232         let mut still_valid_config = valid_config.clone();
4233         still_valid_config.memory.hugepages = true;
4234         still_valid_config.memory.hugepage_size = Some(2 << 20);
4235         still_valid_config.validate().unwrap();
4236 
4237         let mut invalid_config = valid_config.clone();
4238         invalid_config.memory.hugepages = false;
4239         invalid_config.memory.hugepage_size = Some(2 << 20);
4240         assert_eq!(
4241             invalid_config.validate(),
4242             Err(ValidationError::HugePageSizeWithoutHugePages)
4243         );
4244 
4245         let mut invalid_config = valid_config.clone();
4246         invalid_config.memory.hugepages = true;
4247         invalid_config.memory.hugepage_size = Some(3 << 20);
4248         assert_eq!(
4249             invalid_config.validate(),
4250             Err(ValidationError::InvalidHugePageSize(3 << 20))
4251         );
4252 
4253         let mut still_valid_config = valid_config.clone();
4254         still_valid_config.platform = Some(platform_fixture());
4255         still_valid_config.validate().unwrap();
4256 
4257         let mut invalid_config = valid_config.clone();
4258         invalid_config.platform = Some(PlatformConfig {
4259             num_pci_segments: MAX_NUM_PCI_SEGMENTS + 1,
4260             ..platform_fixture()
4261         });
4262         assert_eq!(
4263             invalid_config.validate(),
4264             Err(ValidationError::InvalidNumPciSegments(
4265                 MAX_NUM_PCI_SEGMENTS + 1
4266             ))
4267         );
4268 
4269         let mut still_valid_config = valid_config.clone();
4270         still_valid_config.platform = Some(PlatformConfig {
4271             iommu_segments: Some(vec![1, 2, 3]),
4272             ..platform_fixture()
4273         });
4274         still_valid_config.validate().unwrap();
4275 
4276         let mut invalid_config = valid_config.clone();
4277         invalid_config.platform = Some(PlatformConfig {
4278             iommu_segments: Some(vec![MAX_NUM_PCI_SEGMENTS + 1, MAX_NUM_PCI_SEGMENTS + 2]),
4279             ..platform_fixture()
4280         });
4281         assert_eq!(
4282             invalid_config.validate(),
4283             Err(ValidationError::InvalidPciSegment(MAX_NUM_PCI_SEGMENTS + 1))
4284         );
4285 
4286         let mut still_valid_config = valid_config.clone();
4287         still_valid_config.platform = Some(PlatformConfig {
4288             iommu_segments: Some(vec![1, 2, 3]),
4289             ..platform_fixture()
4290         });
4291         still_valid_config.disks = Some(vec![DiskConfig {
4292             iommu: true,
4293             pci_segment: 1,
4294             ..disk_fixture()
4295         }]);
4296         still_valid_config.validate().unwrap();
4297 
4298         let mut still_valid_config = valid_config.clone();
4299         still_valid_config.platform = Some(PlatformConfig {
4300             iommu_segments: Some(vec![1, 2, 3]),
4301             ..platform_fixture()
4302         });
4303         still_valid_config.net = Some(vec![NetConfig {
4304             iommu: true,
4305             pci_segment: 1,
4306             ..net_fixture()
4307         }]);
4308         still_valid_config.validate().unwrap();
4309 
4310         let mut still_valid_config = valid_config.clone();
4311         still_valid_config.platform = Some(PlatformConfig {
4312             iommu_segments: Some(vec![1, 2, 3]),
4313             ..platform_fixture()
4314         });
4315         still_valid_config.pmem = Some(vec![PmemConfig {
4316             iommu: true,
4317             pci_segment: 1,
4318             ..pmem_fixture()
4319         }]);
4320         still_valid_config.validate().unwrap();
4321 
4322         let mut still_valid_config = valid_config.clone();
4323         still_valid_config.platform = Some(PlatformConfig {
4324             iommu_segments: Some(vec![1, 2, 3]),
4325             ..platform_fixture()
4326         });
4327         still_valid_config.devices = Some(vec![DeviceConfig {
4328             iommu: true,
4329             pci_segment: 1,
4330             ..device_fixture()
4331         }]);
4332         still_valid_config.validate().unwrap();
4333 
4334         let mut still_valid_config = valid_config.clone();
4335         still_valid_config.platform = Some(PlatformConfig {
4336             iommu_segments: Some(vec![1, 2, 3]),
4337             ..platform_fixture()
4338         });
4339         still_valid_config.vsock = Some(VsockConfig {
4340             cid: 3,
4341             socket: PathBuf::new(),
4342             id: None,
4343             iommu: true,
4344             pci_segment: 1,
4345         });
4346         still_valid_config.validate().unwrap();
4347 
4348         let mut invalid_config = valid_config.clone();
4349         invalid_config.platform = Some(PlatformConfig {
4350             iommu_segments: Some(vec![1, 2, 3]),
4351             ..platform_fixture()
4352         });
4353         invalid_config.disks = Some(vec![DiskConfig {
4354             iommu: false,
4355             pci_segment: 1,
4356             ..disk_fixture()
4357         }]);
4358         assert_eq!(
4359             invalid_config.validate(),
4360             Err(ValidationError::OnIommuSegment(1))
4361         );
4362 
4363         let mut invalid_config = valid_config.clone();
4364         invalid_config.platform = Some(PlatformConfig {
4365             iommu_segments: Some(vec![1, 2, 3]),
4366             ..platform_fixture()
4367         });
4368         invalid_config.net = Some(vec![NetConfig {
4369             iommu: false,
4370             pci_segment: 1,
4371             ..net_fixture()
4372         }]);
4373         assert_eq!(
4374             invalid_config.validate(),
4375             Err(ValidationError::OnIommuSegment(1))
4376         );
4377 
4378         let mut invalid_config = valid_config.clone();
4379         invalid_config.platform = Some(PlatformConfig {
4380             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
4381             iommu_segments: Some(vec![1, 2, 3]),
4382             ..platform_fixture()
4383         });
4384         invalid_config.pmem = Some(vec![PmemConfig {
4385             iommu: false,
4386             pci_segment: 1,
4387             ..pmem_fixture()
4388         }]);
4389         assert_eq!(
4390             invalid_config.validate(),
4391             Err(ValidationError::OnIommuSegment(1))
4392         );
4393 
4394         let mut invalid_config = valid_config.clone();
4395         invalid_config.platform = Some(PlatformConfig {
4396             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
4397             iommu_segments: Some(vec![1, 2, 3]),
4398             ..platform_fixture()
4399         });
4400         invalid_config.devices = Some(vec![DeviceConfig {
4401             iommu: false,
4402             pci_segment: 1,
4403             ..device_fixture()
4404         }]);
4405         assert_eq!(
4406             invalid_config.validate(),
4407             Err(ValidationError::OnIommuSegment(1))
4408         );
4409 
4410         let mut invalid_config = valid_config.clone();
4411         invalid_config.platform = Some(PlatformConfig {
4412             iommu_segments: Some(vec![1, 2, 3]),
4413             ..platform_fixture()
4414         });
4415         invalid_config.vsock = Some(VsockConfig {
4416             cid: 3,
4417             socket: PathBuf::new(),
4418             id: None,
4419             iommu: false,
4420             pci_segment: 1,
4421         });
4422         assert_eq!(
4423             invalid_config.validate(),
4424             Err(ValidationError::OnIommuSegment(1))
4425         );
4426 
4427         let mut invalid_config = valid_config.clone();
4428         invalid_config.memory.shared = true;
4429         invalid_config.platform = Some(PlatformConfig {
4430             iommu_segments: Some(vec![1, 2, 3]),
4431             ..platform_fixture()
4432         });
4433         invalid_config.user_devices = Some(vec![UserDeviceConfig {
4434             pci_segment: 1,
4435             socket: PathBuf::new(),
4436             id: None,
4437         }]);
4438         assert_eq!(
4439             invalid_config.validate(),
4440             Err(ValidationError::IommuNotSupportedOnSegment(1))
4441         );
4442 
4443         let mut invalid_config = valid_config.clone();
4444         invalid_config.platform = Some(PlatformConfig {
4445             iommu_segments: Some(vec![1, 2, 3]),
4446             ..platform_fixture()
4447         });
4448         invalid_config.vdpa = Some(vec![VdpaConfig {
4449             pci_segment: 1,
4450             ..vdpa_fixture()
4451         }]);
4452         assert_eq!(
4453             invalid_config.validate(),
4454             Err(ValidationError::OnIommuSegment(1))
4455         );
4456 
4457         let mut invalid_config = valid_config.clone();
4458         invalid_config.memory.shared = true;
4459         invalid_config.platform = Some(PlatformConfig {
4460             iommu_segments: Some(vec![1, 2, 3]),
4461             ..platform_fixture()
4462         });
4463         invalid_config.fs = Some(vec![FsConfig {
4464             pci_segment: 1,
4465             ..fs_fixture()
4466         }]);
4467         assert_eq!(
4468             invalid_config.validate(),
4469             Err(ValidationError::IommuNotSupportedOnSegment(1))
4470         );
4471 
4472         let mut invalid_config = valid_config.clone();
4473         invalid_config.platform = Some(PlatformConfig {
4474             num_pci_segments: 2,
4475             ..platform_fixture()
4476         });
4477         invalid_config.numa = Some(vec![
4478             NumaConfig {
4479                 guest_numa_id: 0,
4480                 pci_segments: Some(vec![1]),
4481                 ..numa_fixture()
4482             },
4483             NumaConfig {
4484                 guest_numa_id: 1,
4485                 pci_segments: Some(vec![1]),
4486                 ..numa_fixture()
4487             },
4488         ]);
4489         assert_eq!(
4490             invalid_config.validate(),
4491             Err(ValidationError::PciSegmentReused(1, 0, 1))
4492         );
4493 
4494         let mut invalid_config = valid_config.clone();
4495         invalid_config.pci_segments = Some(vec![PciSegmentConfig {
4496             pci_segment: 0,
4497             mmio32_aperture_weight: 1,
4498             mmio64_aperture_weight: 0,
4499         }]);
4500         assert_eq!(
4501             invalid_config.validate(),
4502             Err(ValidationError::InvalidPciSegmentApertureWeight(0))
4503         );
4504 
4505         let mut invalid_config = valid_config.clone();
4506         invalid_config.pci_segments = Some(vec![PciSegmentConfig {
4507             pci_segment: 0,
4508             mmio32_aperture_weight: 0,
4509             mmio64_aperture_weight: 1,
4510         }]);
4511         assert_eq!(
4512             invalid_config.validate(),
4513             Err(ValidationError::InvalidPciSegmentApertureWeight(0))
4514         );
4515 
4516         let mut invalid_config = valid_config.clone();
4517         invalid_config.numa = Some(vec![
4518             NumaConfig {
4519                 guest_numa_id: 0,
4520                 ..numa_fixture()
4521             },
4522             NumaConfig {
4523                 guest_numa_id: 1,
4524                 pci_segments: Some(vec![0]),
4525                 ..numa_fixture()
4526             },
4527         ]);
4528         assert_eq!(
4529             invalid_config.validate(),
4530             Err(ValidationError::DefaultPciSegmentInvalidNode(1))
4531         );
4532 
4533         let mut invalid_config = valid_config.clone();
4534         invalid_config.numa = Some(vec![
4535             NumaConfig {
4536                 guest_numa_id: 0,
4537                 pci_segments: Some(vec![0]),
4538                 ..numa_fixture()
4539             },
4540             NumaConfig {
4541                 guest_numa_id: 1,
4542                 pci_segments: Some(vec![1]),
4543                 ..numa_fixture()
4544             },
4545         ]);
4546         assert_eq!(
4547             invalid_config.validate(),
4548             Err(ValidationError::InvalidPciSegment(1))
4549         );
4550 
4551         let mut invalid_config = valid_config.clone();
4552         invalid_config.disks = Some(vec![DiskConfig {
4553             rate_limit_group: Some("foo".into()),
4554             ..disk_fixture()
4555         }]);
4556         assert_eq!(
4557             invalid_config.validate(),
4558             Err(ValidationError::InvalidRateLimiterGroup)
4559         );
4560 
4561         let mut still_valid_config = valid_config.clone();
4562         still_valid_config.devices = Some(vec![
4563             DeviceConfig {
4564                 path: "/device1".into(),
4565                 ..device_fixture()
4566             },
4567             DeviceConfig {
4568                 path: "/device2".into(),
4569                 ..device_fixture()
4570             },
4571         ]);
4572         still_valid_config.validate().unwrap();
4573 
4574         let mut invalid_config = valid_config.clone();
4575         invalid_config.devices = Some(vec![
4576             DeviceConfig {
4577                 path: "/device1".into(),
4578                 ..device_fixture()
4579             },
4580             DeviceConfig {
4581                 path: "/device1".into(),
4582                 ..device_fixture()
4583             },
4584         ]);
4585         invalid_config.validate().unwrap_err();
4586         #[cfg(feature = "sev_snp")]
4587         {
4588             // Payload with empty host data
4589             let mut config_with_no_host_data = valid_config.clone();
4590             config_with_no_host_data.payload = Some(PayloadConfig {
4591                 kernel: Some(PathBuf::from("/path/to/kernel")),
4592                 firmware: None,
4593                 cmdline: None,
4594                 initramfs: None,
4595                 #[cfg(feature = "igvm")]
4596                 igvm: None,
4597                 #[cfg(feature = "sev_snp")]
4598                 host_data: Some("".to_string()),
4599             });
4600             config_with_no_host_data.validate().unwrap_err();
4601 
4602             // Payload with no host data provided
4603             let mut valid_config_with_no_host_data = valid_config.clone();
4604             valid_config_with_no_host_data.payload = Some(PayloadConfig {
4605                 kernel: Some(PathBuf::from("/path/to/kernel")),
4606                 firmware: None,
4607                 cmdline: None,
4608                 initramfs: None,
4609                 #[cfg(feature = "igvm")]
4610                 igvm: None,
4611                 #[cfg(feature = "sev_snp")]
4612                 host_data: None,
4613             });
4614             valid_config_with_no_host_data.validate().unwrap();
4615 
4616             // Payload with invalid host data length i.e less than 64
4617             let mut config_with_invalid_host_data = valid_config.clone();
4618             config_with_invalid_host_data.payload = Some(PayloadConfig {
4619                 kernel: Some(PathBuf::from("/path/to/kernel")),
4620                 firmware: None,
4621                 cmdline: None,
4622                 initramfs: None,
4623                 #[cfg(feature = "igvm")]
4624                 igvm: None,
4625                 #[cfg(feature = "sev_snp")]
4626                 host_data: Some(
4627                     "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb43118867328".to_string(),
4628                 ),
4629             });
4630             config_with_invalid_host_data.validate().unwrap_err();
4631         }
4632 
4633         let mut still_valid_config = valid_config;
4634         // SAFETY: Safe as the file was just opened
4635         let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) };
4636         // SAFETY: Safe as the file was just opened
4637         let fd2 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) };
4638         // SAFETY: safe as both FDs are valid
4639         unsafe {
4640             still_valid_config.add_preserved_fds(vec![fd1, fd2]);
4641         }
4642         let _still_valid_config = still_valid_config.clone();
4643     }
4644     #[test]
4645     fn test_landlock_parsing() -> Result<()> {
4646         // should not be empty
4647         LandlockConfig::parse("").unwrap_err();
4648         // access should not be empty
4649         LandlockConfig::parse("path=/dir/path1").unwrap_err();
4650         LandlockConfig::parse("path=/dir/path1,access=rwr").unwrap_err();
4651         assert_eq!(
4652             LandlockConfig::parse("path=/dir/path1,access=rw")?,
4653             LandlockConfig {
4654                 path: PathBuf::from("/dir/path1"),
4655                 access: "rw".to_string(),
4656             }
4657         );
4658         Ok(())
4659     }
4660 }
4661