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