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