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