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