xref: /cloud-hypervisor/vmm/src/config.rs (revision 0310c5726f4d1e560ec39e0a6cd4298bbfe8dc07)
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.add("path").add("id").add("iommu").add("pci_segment");
1750         parser.parse(device).map_err(Error::ParseDevice)?;
1751 
1752         let path = parser
1753             .get("path")
1754             .map(PathBuf::from)
1755             .ok_or(Error::ParseDevicePathMissing)?;
1756         let iommu = parser
1757             .convert::<Toggle>("iommu")
1758             .map_err(Error::ParseDevice)?
1759             .unwrap_or(Toggle(false))
1760             .0;
1761         let id = parser.get("id");
1762         let pci_segment = parser
1763             .convert::<u16>("pci_segment")
1764             .map_err(Error::ParseDevice)?
1765             .unwrap_or_default();
1766 
1767         Ok(DeviceConfig {
1768             path,
1769             iommu,
1770             id,
1771             pci_segment,
1772         })
1773     }
1774 
1775     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1776         if let Some(platform_config) = vm_config.platform.as_ref() {
1777             if self.pci_segment >= platform_config.num_pci_segments {
1778                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1779             }
1780 
1781             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1782                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1783                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1784                 }
1785             }
1786         }
1787 
1788         Ok(())
1789     }
1790 }
1791 
1792 impl UserDeviceConfig {
1793     pub const SYNTAX: &'static str =
1794         "Userspace device socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\"";
1795 
1796     pub fn parse(user_device: &str) -> Result<Self> {
1797         let mut parser = OptionParser::new();
1798         parser.add("socket").add("id").add("pci_segment");
1799         parser.parse(user_device).map_err(Error::ParseUserDevice)?;
1800 
1801         let socket = parser
1802             .get("socket")
1803             .map(PathBuf::from)
1804             .ok_or(Error::ParseUserDeviceSocketMissing)?;
1805         let id = parser.get("id");
1806         let pci_segment = parser
1807             .convert::<u16>("pci_segment")
1808             .map_err(Error::ParseUserDevice)?
1809             .unwrap_or_default();
1810 
1811         Ok(UserDeviceConfig {
1812             socket,
1813             id,
1814             pci_segment,
1815         })
1816     }
1817 
1818     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1819         if let Some(platform_config) = vm_config.platform.as_ref() {
1820             if self.pci_segment >= platform_config.num_pci_segments {
1821                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1822             }
1823 
1824             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1825                 if iommu_segments.contains(&self.pci_segment) {
1826                     return Err(ValidationError::IommuNotSupportedOnSegment(
1827                         self.pci_segment,
1828                     ));
1829                 }
1830             }
1831         }
1832 
1833         Ok(())
1834     }
1835 }
1836 
1837 impl VdpaConfig {
1838     pub const SYNTAX: &'static str = "vDPA device \
1839         \"path=<device_path>,num_queues=<number_of_queues>,iommu=on|off,\
1840         id=<device_id>,pci_segment=<segment_id>\"";
1841 
1842     pub fn parse(vdpa: &str) -> Result<Self> {
1843         let mut parser = OptionParser::new();
1844         parser
1845             .add("path")
1846             .add("num_queues")
1847             .add("iommu")
1848             .add("id")
1849             .add("pci_segment");
1850         parser.parse(vdpa).map_err(Error::ParseVdpa)?;
1851 
1852         let path = parser
1853             .get("path")
1854             .map(PathBuf::from)
1855             .ok_or(Error::ParseVdpaPathMissing)?;
1856         let num_queues = parser
1857             .convert("num_queues")
1858             .map_err(Error::ParseVdpa)?
1859             .unwrap_or_else(default_vdpaconfig_num_queues);
1860         let iommu = parser
1861             .convert::<Toggle>("iommu")
1862             .map_err(Error::ParseVdpa)?
1863             .unwrap_or(Toggle(false))
1864             .0;
1865         let id = parser.get("id");
1866         let pci_segment = parser
1867             .convert("pci_segment")
1868             .map_err(Error::ParseVdpa)?
1869             .unwrap_or_default();
1870 
1871         Ok(VdpaConfig {
1872             path,
1873             num_queues,
1874             iommu,
1875             id,
1876             pci_segment,
1877         })
1878     }
1879 
1880     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1881         if let Some(platform_config) = vm_config.platform.as_ref() {
1882             if self.pci_segment >= platform_config.num_pci_segments {
1883                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1884             }
1885 
1886             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1887                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1888                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1889                 }
1890             }
1891         }
1892 
1893         Ok(())
1894     }
1895 }
1896 
1897 impl VsockConfig {
1898     pub const SYNTAX: &'static str = "Virtio VSOCK parameters \
1899         \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>,pci_segment=<segment_id>\"";
1900 
1901     pub fn parse(vsock: &str) -> Result<Self> {
1902         let mut parser = OptionParser::new();
1903         parser
1904             .add("socket")
1905             .add("cid")
1906             .add("iommu")
1907             .add("id")
1908             .add("pci_segment");
1909         parser.parse(vsock).map_err(Error::ParseVsock)?;
1910 
1911         let socket = parser
1912             .get("socket")
1913             .map(PathBuf::from)
1914             .ok_or(Error::ParseVsockSockMissing)?;
1915         let iommu = parser
1916             .convert::<Toggle>("iommu")
1917             .map_err(Error::ParseVsock)?
1918             .unwrap_or(Toggle(false))
1919             .0;
1920         let cid = parser
1921             .convert("cid")
1922             .map_err(Error::ParseVsock)?
1923             .ok_or(Error::ParseVsockCidMissing)?;
1924         let id = parser.get("id");
1925         let pci_segment = parser
1926             .convert("pci_segment")
1927             .map_err(Error::ParseVsock)?
1928             .unwrap_or_default();
1929 
1930         Ok(VsockConfig {
1931             cid,
1932             socket,
1933             iommu,
1934             id,
1935             pci_segment,
1936         })
1937     }
1938 
1939     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1940         if let Some(platform_config) = vm_config.platform.as_ref() {
1941             if self.pci_segment >= platform_config.num_pci_segments {
1942                 return Err(ValidationError::InvalidPciSegment(self.pci_segment));
1943             }
1944 
1945             if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
1946                 if iommu_segments.contains(&self.pci_segment) && !self.iommu {
1947                     return Err(ValidationError::OnIommuSegment(self.pci_segment));
1948                 }
1949             }
1950         }
1951 
1952         Ok(())
1953     }
1954 }
1955 
1956 #[cfg(target_arch = "x86_64")]
1957 impl SgxEpcConfig {
1958     pub const SYNTAX: &'static str = "SGX EPC parameters \
1959         \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\"";
1960 
1961     pub fn parse(sgx_epc: &str) -> Result<Self> {
1962         let mut parser = OptionParser::new();
1963         parser.add("id").add("size").add("prefault");
1964         parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?;
1965 
1966         let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?;
1967         let size = parser
1968             .convert::<ByteSized>("size")
1969             .map_err(Error::ParseSgxEpc)?
1970             .unwrap_or(ByteSized(0))
1971             .0;
1972         let prefault = parser
1973             .convert::<Toggle>("prefault")
1974             .map_err(Error::ParseSgxEpc)?
1975             .unwrap_or(Toggle(false))
1976             .0;
1977 
1978         Ok(SgxEpcConfig { id, size, prefault })
1979     }
1980 }
1981 
1982 impl NumaConfig {
1983     pub const SYNTAX: &'static str = "Settings related to a given NUMA node \
1984         \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\
1985         memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>,\
1986         pci_segments=<list_of_pci_segments>\"";
1987 
1988     pub fn parse(numa: &str) -> Result<Self> {
1989         let mut parser = OptionParser::new();
1990         parser
1991             .add("guest_numa_id")
1992             .add("cpus")
1993             .add("distances")
1994             .add("memory_zones")
1995             .add("sgx_epc_sections")
1996             .add("pci_segments");
1997 
1998         parser.parse(numa).map_err(Error::ParseNuma)?;
1999 
2000         let guest_numa_id = parser
2001             .convert::<u32>("guest_numa_id")
2002             .map_err(Error::ParseNuma)?
2003             .unwrap_or(0);
2004         let cpus = parser
2005             .convert::<IntegerList>("cpus")
2006             .map_err(Error::ParseNuma)?
2007             .map(|v| v.0.iter().map(|e| *e as u8).collect());
2008         let distances = parser
2009             .convert::<Tuple<u64, u64>>("distances")
2010             .map_err(Error::ParseNuma)?
2011             .map(|v| {
2012                 v.0.iter()
2013                     .map(|(e1, e2)| NumaDistance {
2014                         destination: *e1 as u32,
2015                         distance: *e2 as u8,
2016                     })
2017                     .collect()
2018             });
2019         let memory_zones = parser
2020             .convert::<StringList>("memory_zones")
2021             .map_err(Error::ParseNuma)?
2022             .map(|v| v.0);
2023         #[cfg(target_arch = "x86_64")]
2024         let sgx_epc_sections = parser
2025             .convert::<StringList>("sgx_epc_sections")
2026             .map_err(Error::ParseNuma)?
2027             .map(|v| v.0);
2028         let pci_segments = parser
2029             .convert::<IntegerList>("pci_segments")
2030             .map_err(Error::ParseNuma)?
2031             .map(|v| v.0.iter().map(|e| *e as u16).collect());
2032         Ok(NumaConfig {
2033             guest_numa_id,
2034             cpus,
2035             distances,
2036             memory_zones,
2037             #[cfg(target_arch = "x86_64")]
2038             sgx_epc_sections,
2039             pci_segments,
2040         })
2041     }
2042 }
2043 
2044 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2045 pub struct RestoreConfig {
2046     pub source_url: PathBuf,
2047     #[serde(default)]
2048     pub prefault: bool,
2049 }
2050 
2051 impl RestoreConfig {
2052     pub const SYNTAX: &'static str = "Restore from a VM snapshot. \
2053         \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \
2054         \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \
2055         \n`prefault` brings memory pages in when enabled (disabled by default)";
2056 
2057     pub fn parse(restore: &str) -> Result<Self> {
2058         let mut parser = OptionParser::new();
2059         parser.add("source_url").add("prefault");
2060         parser.parse(restore).map_err(Error::ParseRestore)?;
2061 
2062         let source_url = parser
2063             .get("source_url")
2064             .map(PathBuf::from)
2065             .ok_or(Error::ParseRestoreSourceUrlMissing)?;
2066         let prefault = parser
2067             .convert::<Toggle>("prefault")
2068             .map_err(Error::ParseRestore)?
2069             .unwrap_or(Toggle(false))
2070             .0;
2071 
2072         Ok(RestoreConfig {
2073             source_url,
2074             prefault,
2075         })
2076     }
2077 }
2078 
2079 impl TpmConfig {
2080     pub const SYNTAX: &'static str = "TPM device \
2081         \"(UNIX Domain Socket from swtpm) socket=</path/to/a/socket>\"";
2082 
2083     pub fn parse(tpm: &str) -> Result<Self> {
2084         let mut parser = OptionParser::new();
2085         parser.add("socket");
2086         parser.parse(tpm).map_err(Error::ParseTpm)?;
2087         let socket = parser
2088             .get("socket")
2089             .map(PathBuf::from)
2090             .ok_or(Error::ParseTpmPathMissing)?;
2091         Ok(TpmConfig { socket })
2092     }
2093 }
2094 
2095 impl VmConfig {
2096     fn validate_identifier(
2097         id_list: &mut BTreeSet<String>,
2098         id: &Option<String>,
2099     ) -> ValidationResult<()> {
2100         if let Some(id) = id.as_ref() {
2101             if id.starts_with("__") {
2102                 return Err(ValidationError::InvalidIdentifier(id.clone()));
2103             }
2104 
2105             if !id_list.insert(id.clone()) {
2106                 return Err(ValidationError::IdentifierNotUnique(id.clone()));
2107             }
2108         }
2109 
2110         Ok(())
2111     }
2112 
2113     pub fn backed_by_shared_memory(&self) -> bool {
2114         if self.memory.shared || self.memory.hugepages {
2115             return true;
2116         }
2117 
2118         if self.memory.size == 0 {
2119             for zone in self.memory.zones.as_ref().unwrap() {
2120                 if !zone.shared && !zone.hugepages {
2121                     return false;
2122                 }
2123             }
2124             true
2125         } else {
2126             false
2127         }
2128     }
2129 
2130     // Also enables virtio-iommu if the config needs it
2131     // Returns the list of unique identifiers provided through the
2132     // configuration.
2133     pub fn validate(&mut self) -> ValidationResult<BTreeSet<String>> {
2134         let mut id_list = BTreeSet::new();
2135 
2136         self.payload
2137             .as_ref()
2138             .ok_or(ValidationError::KernelMissing)?;
2139 
2140         #[cfg(feature = "tdx")]
2141         {
2142             let tdx_enabled = self.platform.as_ref().map(|p| p.tdx).unwrap_or(false);
2143             // At this point we know payload isn't None.
2144             if tdx_enabled && self.payload.as_ref().unwrap().firmware.is_none() {
2145                 return Err(ValidationError::TdxFirmwareMissing);
2146             }
2147             if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) {
2148                 return Err(ValidationError::TdxNoCpuHotplug);
2149             }
2150         }
2151 
2152         #[cfg(feature = "sev_snp")]
2153         {
2154             let host_data_opt = &self.payload.as_ref().unwrap().host_data;
2155 
2156             if let Some(host_data) = host_data_opt {
2157                 if host_data.len() != 64 {
2158                     return Err(ValidationError::InvalidHostData);
2159                 }
2160             }
2161         }
2162         // The 'conflict' check is introduced in commit 24438e0390d3
2163         // (vm-virtio: Enable the vmm support for virtio-console).
2164         //
2165         // Allow simultaneously set serial and console as TTY mode, for
2166         // someone want to use virtio console for better performance, and
2167         // want to keep legacy serial to catch boot stage logs for debug.
2168         // Using such double tty mode, you need to configure the kernel
2169         // properly, such as:
2170         // "console=hvc0 earlyprintk=ttyS0"
2171 
2172         let mut tty_consoles = Vec::new();
2173         if self.console.mode == ConsoleOutputMode::Tty {
2174             tty_consoles.push("virtio-console");
2175         };
2176         if self.serial.mode == ConsoleOutputMode::Tty {
2177             tty_consoles.push("serial-console");
2178         };
2179         #[cfg(target_arch = "x86_64")]
2180         if self.debug_console.mode == ConsoleOutputMode::Tty {
2181             tty_consoles.push("debug-console");
2182         };
2183         if tty_consoles.len() > 1 {
2184             warn!("Using TTY output for multiple consoles: {:?}", tty_consoles);
2185         }
2186 
2187         if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() {
2188             return Err(ValidationError::ConsoleFileMissing);
2189         }
2190 
2191         if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() {
2192             return Err(ValidationError::ConsoleFileMissing);
2193         }
2194 
2195         if self.cpus.max_vcpus < self.cpus.boot_vcpus {
2196             return Err(ValidationError::CpusMaxLowerThanBoot);
2197         }
2198 
2199         if let Some(rate_limit_groups) = &self.rate_limit_groups {
2200             for rate_limit_group in rate_limit_groups {
2201                 rate_limit_group.validate(self)?;
2202 
2203                 Self::validate_identifier(&mut id_list, &Some(rate_limit_group.id.clone()))?;
2204             }
2205         }
2206 
2207         if let Some(disks) = &self.disks {
2208             for disk in disks {
2209                 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() {
2210                     return Err(ValidationError::DiskSocketAndPath);
2211                 }
2212                 if disk.vhost_user && !self.backed_by_shared_memory() {
2213                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2214                 }
2215                 if disk.vhost_user && disk.vhost_socket.is_none() {
2216                     return Err(ValidationError::VhostUserMissingSocket);
2217                 }
2218                 if let Some(rate_limit_group) = &disk.rate_limit_group {
2219                     if let Some(rate_limit_groups) = &self.rate_limit_groups {
2220                         if !rate_limit_groups
2221                             .iter()
2222                             .any(|cfg| &cfg.id == rate_limit_group)
2223                         {
2224                             return Err(ValidationError::InvalidRateLimiterGroup);
2225                         }
2226                     } else {
2227                         return Err(ValidationError::InvalidRateLimiterGroup);
2228                     }
2229                 }
2230 
2231                 disk.validate(self)?;
2232                 self.iommu |= disk.iommu;
2233 
2234                 Self::validate_identifier(&mut id_list, &disk.id)?;
2235             }
2236         }
2237 
2238         if let Some(nets) = &self.net {
2239             for net in nets {
2240                 if net.vhost_user && !self.backed_by_shared_memory() {
2241                     return Err(ValidationError::VhostUserRequiresSharedMemory);
2242                 }
2243                 net.validate(self)?;
2244                 self.iommu |= net.iommu;
2245 
2246                 Self::validate_identifier(&mut id_list, &net.id)?;
2247             }
2248         }
2249 
2250         if let Some(fses) = &self.fs {
2251             if !fses.is_empty() && !self.backed_by_shared_memory() {
2252                 return Err(ValidationError::VhostUserRequiresSharedMemory);
2253             }
2254             for fs in fses {
2255                 fs.validate(self)?;
2256 
2257                 Self::validate_identifier(&mut id_list, &fs.id)?;
2258             }
2259         }
2260 
2261         if let Some(pmems) = &self.pmem {
2262             for pmem in pmems {
2263                 pmem.validate(self)?;
2264                 self.iommu |= pmem.iommu;
2265 
2266                 Self::validate_identifier(&mut id_list, &pmem.id)?;
2267             }
2268         }
2269 
2270         self.iommu |= self.rng.iommu;
2271         self.iommu |= self.console.iommu;
2272 
2273         if let Some(t) = &self.cpus.topology {
2274             if t.threads_per_core == 0
2275                 || t.cores_per_die == 0
2276                 || t.dies_per_package == 0
2277                 || t.packages == 0
2278             {
2279                 return Err(ValidationError::CpuTopologyZeroPart);
2280             }
2281 
2282             // The setting of dies doesen't apply on AArch64.
2283             // Only '1' value is accepted, so its impact on the vcpu topology
2284             // setting can be ignored.
2285             #[cfg(target_arch = "aarch64")]
2286             if t.dies_per_package != 1 {
2287                 return Err(ValidationError::CpuTopologyDiesPerPackage);
2288             }
2289 
2290             let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages;
2291             if total != self.cpus.max_vcpus {
2292                 return Err(ValidationError::CpuTopologyCount);
2293             }
2294         }
2295 
2296         if let Some(hugepage_size) = &self.memory.hugepage_size {
2297             if !self.memory.hugepages {
2298                 return Err(ValidationError::HugePageSizeWithoutHugePages);
2299             }
2300             if !hugepage_size.is_power_of_two() {
2301                 return Err(ValidationError::InvalidHugePageSize(*hugepage_size));
2302             }
2303         }
2304 
2305         if let Some(user_devices) = &self.user_devices {
2306             if !user_devices.is_empty() && !self.backed_by_shared_memory() {
2307                 return Err(ValidationError::UserDevicesRequireSharedMemory);
2308             }
2309 
2310             for user_device in user_devices {
2311                 user_device.validate(self)?;
2312 
2313                 Self::validate_identifier(&mut id_list, &user_device.id)?;
2314             }
2315         }
2316 
2317         if let Some(vdpa_devices) = &self.vdpa {
2318             for vdpa_device in vdpa_devices {
2319                 vdpa_device.validate(self)?;
2320                 self.iommu |= vdpa_device.iommu;
2321 
2322                 Self::validate_identifier(&mut id_list, &vdpa_device.id)?;
2323             }
2324         }
2325 
2326         if let Some(vsock) = &self.vsock {
2327             if [!0, 0, 1, 2].contains(&vsock.cid) {
2328                 return Err(ValidationError::VsockSpecialCid(vsock.cid));
2329             }
2330         }
2331 
2332         if let Some(balloon) = &self.balloon {
2333             let mut ram_size = self.memory.size;
2334 
2335             if let Some(zones) = &self.memory.zones {
2336                 for zone in zones {
2337                     ram_size += zone.size;
2338                 }
2339             }
2340 
2341             if balloon.size >= ram_size {
2342                 return Err(ValidationError::BalloonLargerThanRam(
2343                     balloon.size,
2344                     ram_size,
2345                 ));
2346             }
2347         }
2348 
2349         if let Some(devices) = &self.devices {
2350             let mut device_paths = BTreeSet::new();
2351             for device in devices {
2352                 if !device_paths.insert(device.path.to_string_lossy()) {
2353                     return Err(ValidationError::DuplicateDevicePath(
2354                         device.path.to_string_lossy().to_string(),
2355                     ));
2356                 }
2357 
2358                 device.validate(self)?;
2359                 self.iommu |= device.iommu;
2360 
2361                 Self::validate_identifier(&mut id_list, &device.id)?;
2362             }
2363         }
2364 
2365         if let Some(vsock) = &self.vsock {
2366             vsock.validate(self)?;
2367             self.iommu |= vsock.iommu;
2368 
2369             Self::validate_identifier(&mut id_list, &vsock.id)?;
2370         }
2371 
2372         let num_pci_segments = match &self.platform {
2373             Some(platform_config) => platform_config.num_pci_segments,
2374             None => 1,
2375         };
2376         if let Some(numa) = &self.numa {
2377             let mut used_numa_node_memory_zones = HashMap::new();
2378             let mut used_pci_segments = HashMap::new();
2379             for numa_node in numa.iter() {
2380                 if let Some(memory_zones) = numa_node.memory_zones.clone() {
2381                     for memory_zone in memory_zones.iter() {
2382                         if !used_numa_node_memory_zones.contains_key(memory_zone) {
2383                             used_numa_node_memory_zones
2384                                 .insert(memory_zone.to_string(), numa_node.guest_numa_id);
2385                         } else {
2386                             return Err(ValidationError::MemoryZoneReused(
2387                                 memory_zone.to_string(),
2388                                 *used_numa_node_memory_zones.get(memory_zone).unwrap(),
2389                                 numa_node.guest_numa_id,
2390                             ));
2391                         }
2392                     }
2393                 }
2394 
2395                 if let Some(pci_segments) = numa_node.pci_segments.clone() {
2396                     for pci_segment in pci_segments.iter() {
2397                         if *pci_segment >= num_pci_segments {
2398                             return Err(ValidationError::InvalidPciSegment(*pci_segment));
2399                         }
2400                         if *pci_segment == 0 && numa_node.guest_numa_id != 0 {
2401                             return Err(ValidationError::DefaultPciSegmentInvalidNode(
2402                                 numa_node.guest_numa_id,
2403                             ));
2404                         }
2405                         if !used_pci_segments.contains_key(pci_segment) {
2406                             used_pci_segments.insert(*pci_segment, numa_node.guest_numa_id);
2407                         } else {
2408                             return Err(ValidationError::PciSegmentReused(
2409                                 *pci_segment,
2410                                 *used_pci_segments.get(pci_segment).unwrap(),
2411                                 numa_node.guest_numa_id,
2412                             ));
2413                         }
2414                     }
2415                 }
2416             }
2417         }
2418 
2419         if let Some(zones) = &self.memory.zones {
2420             for zone in zones.iter() {
2421                 let id = zone.id.clone();
2422                 Self::validate_identifier(&mut id_list, &Some(id))?;
2423             }
2424         }
2425 
2426         #[cfg(target_arch = "x86_64")]
2427         if let Some(sgx_epcs) = &self.sgx_epc {
2428             for sgx_epc in sgx_epcs.iter() {
2429                 let id = sgx_epc.id.clone();
2430                 Self::validate_identifier(&mut id_list, &Some(id))?;
2431             }
2432         }
2433 
2434         self.platform.as_ref().map(|p| p.validate()).transpose()?;
2435         self.iommu |= self
2436             .platform
2437             .as_ref()
2438             .map(|p| p.iommu_segments.is_some())
2439             .unwrap_or_default();
2440 
2441         Ok(id_list)
2442     }
2443 
2444     pub fn parse(vm_params: VmParams) -> Result<Self> {
2445         let mut rate_limit_groups: Option<Vec<RateLimiterGroupConfig>> = None;
2446         if let Some(rate_limit_group_list) = &vm_params.rate_limit_groups {
2447             let mut rate_limit_group_config_list = Vec::new();
2448             for item in rate_limit_group_list.iter() {
2449                 let rate_limit_group_config = RateLimiterGroupConfig::parse(item)?;
2450                 rate_limit_group_config_list.push(rate_limit_group_config);
2451             }
2452             rate_limit_groups = Some(rate_limit_group_config_list);
2453         }
2454 
2455         let mut disks: Option<Vec<DiskConfig>> = None;
2456         if let Some(disk_list) = &vm_params.disks {
2457             let mut disk_config_list = Vec::new();
2458             for item in disk_list.iter() {
2459                 let disk_config = DiskConfig::parse(item)?;
2460                 disk_config_list.push(disk_config);
2461             }
2462             disks = Some(disk_config_list);
2463         }
2464 
2465         let mut net: Option<Vec<NetConfig>> = None;
2466         if let Some(net_list) = &vm_params.net {
2467             let mut net_config_list = Vec::new();
2468             for item in net_list.iter() {
2469                 let net_config = NetConfig::parse(item)?;
2470                 net_config_list.push(net_config);
2471             }
2472             net = Some(net_config_list);
2473         }
2474 
2475         let rng = RngConfig::parse(vm_params.rng)?;
2476 
2477         let mut balloon: Option<BalloonConfig> = None;
2478         if let Some(balloon_params) = &vm_params.balloon {
2479             balloon = Some(BalloonConfig::parse(balloon_params)?);
2480         }
2481 
2482         let mut fs: Option<Vec<FsConfig>> = None;
2483         if let Some(fs_list) = &vm_params.fs {
2484             let mut fs_config_list = Vec::new();
2485             for item in fs_list.iter() {
2486                 fs_config_list.push(FsConfig::parse(item)?);
2487             }
2488             fs = Some(fs_config_list);
2489         }
2490 
2491         let mut pmem: Option<Vec<PmemConfig>> = None;
2492         if let Some(pmem_list) = &vm_params.pmem {
2493             let mut pmem_config_list = Vec::new();
2494             for item in pmem_list.iter() {
2495                 let pmem_config = PmemConfig::parse(item)?;
2496                 pmem_config_list.push(pmem_config);
2497             }
2498             pmem = Some(pmem_config_list);
2499         }
2500 
2501         let console = ConsoleConfig::parse(vm_params.console)?;
2502         let serial = ConsoleConfig::parse(vm_params.serial)?;
2503         #[cfg(target_arch = "x86_64")]
2504         let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?;
2505 
2506         let mut devices: Option<Vec<DeviceConfig>> = None;
2507         if let Some(device_list) = &vm_params.devices {
2508             let mut device_config_list = Vec::new();
2509             for item in device_list.iter() {
2510                 let device_config = DeviceConfig::parse(item)?;
2511                 device_config_list.push(device_config);
2512             }
2513             devices = Some(device_config_list);
2514         }
2515 
2516         let mut user_devices: Option<Vec<UserDeviceConfig>> = None;
2517         if let Some(user_device_list) = &vm_params.user_devices {
2518             let mut user_device_config_list = Vec::new();
2519             for item in user_device_list.iter() {
2520                 let user_device_config = UserDeviceConfig::parse(item)?;
2521                 user_device_config_list.push(user_device_config);
2522             }
2523             user_devices = Some(user_device_config_list);
2524         }
2525 
2526         let mut vdpa: Option<Vec<VdpaConfig>> = None;
2527         if let Some(vdpa_list) = &vm_params.vdpa {
2528             let mut vdpa_config_list = Vec::new();
2529             for item in vdpa_list.iter() {
2530                 let vdpa_config = VdpaConfig::parse(item)?;
2531                 vdpa_config_list.push(vdpa_config);
2532             }
2533             vdpa = Some(vdpa_config_list);
2534         }
2535 
2536         let mut vsock: Option<VsockConfig> = None;
2537         if let Some(vs) = &vm_params.vsock {
2538             let vsock_config = VsockConfig::parse(vs)?;
2539             vsock = Some(vsock_config);
2540         }
2541 
2542         let platform = vm_params.platform.map(PlatformConfig::parse).transpose()?;
2543 
2544         #[cfg(target_arch = "x86_64")]
2545         let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None;
2546         #[cfg(target_arch = "x86_64")]
2547         {
2548             if let Some(sgx_epc_list) = &vm_params.sgx_epc {
2549                 let mut sgx_epc_config_list = Vec::new();
2550                 for item in sgx_epc_list.iter() {
2551                     let sgx_epc_config = SgxEpcConfig::parse(item)?;
2552                     sgx_epc_config_list.push(sgx_epc_config);
2553                 }
2554                 sgx_epc = Some(sgx_epc_config_list);
2555             }
2556         }
2557 
2558         let mut numa: Option<Vec<NumaConfig>> = None;
2559         if let Some(numa_list) = &vm_params.numa {
2560             let mut numa_config_list = Vec::new();
2561             for item in numa_list.iter() {
2562                 let numa_config = NumaConfig::parse(item)?;
2563                 numa_config_list.push(numa_config);
2564             }
2565             numa = Some(numa_config_list);
2566         }
2567 
2568         #[cfg(not(feature = "igvm"))]
2569         let payload_present = vm_params.kernel.is_some() || vm_params.firmware.is_some();
2570 
2571         #[cfg(feature = "igvm")]
2572         let payload_present =
2573             vm_params.kernel.is_some() || vm_params.firmware.is_some() || vm_params.igvm.is_some();
2574 
2575         let payload = if payload_present {
2576             Some(PayloadConfig {
2577                 kernel: vm_params.kernel.map(PathBuf::from),
2578                 initramfs: vm_params.initramfs.map(PathBuf::from),
2579                 cmdline: vm_params.cmdline.map(|s| s.to_string()),
2580                 firmware: vm_params.firmware.map(PathBuf::from),
2581                 #[cfg(feature = "igvm")]
2582                 igvm: vm_params.igvm.map(PathBuf::from),
2583                 #[cfg(feature = "sev_snp")]
2584                 host_data: vm_params.host_data.map(|s| s.to_string()),
2585             })
2586         } else {
2587             None
2588         };
2589 
2590         let mut tpm: Option<TpmConfig> = None;
2591         if let Some(tc) = vm_params.tpm {
2592             let tpm_conf = TpmConfig::parse(tc)?;
2593             tpm = Some(TpmConfig {
2594                 socket: tpm_conf.socket,
2595             });
2596         }
2597 
2598         #[cfg(feature = "guest_debug")]
2599         let gdb = vm_params.gdb;
2600 
2601         let mut config = VmConfig {
2602             cpus: CpusConfig::parse(vm_params.cpus)?,
2603             memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
2604             payload,
2605             rate_limit_groups,
2606             disks,
2607             net,
2608             rng,
2609             balloon,
2610             fs,
2611             pmem,
2612             serial,
2613             console,
2614             #[cfg(target_arch = "x86_64")]
2615             debug_console,
2616             devices,
2617             user_devices,
2618             vdpa,
2619             vsock,
2620             pvpanic: vm_params.pvpanic,
2621             iommu: false, // updated in VmConfig::validate()
2622             #[cfg(target_arch = "x86_64")]
2623             sgx_epc,
2624             numa,
2625             watchdog: vm_params.watchdog,
2626             #[cfg(feature = "guest_debug")]
2627             gdb,
2628             platform,
2629             tpm,
2630             preserved_fds: None,
2631         };
2632         config.validate().map_err(Error::Validation)?;
2633         Ok(config)
2634     }
2635 
2636     pub fn remove_device(&mut self, id: &str) -> bool {
2637         let mut removed = false;
2638 
2639         // Remove if VFIO device
2640         if let Some(devices) = self.devices.as_mut() {
2641             let len = devices.len();
2642             devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2643             removed |= devices.len() != len;
2644         }
2645 
2646         // Remove if VFIO user device
2647         if let Some(user_devices) = self.user_devices.as_mut() {
2648             let len = user_devices.len();
2649             user_devices.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2650             removed |= user_devices.len() != len;
2651         }
2652 
2653         // Remove if disk device
2654         if let Some(disks) = self.disks.as_mut() {
2655             let len = disks.len();
2656             disks.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2657             removed |= disks.len() != len;
2658         }
2659 
2660         // Remove if fs device
2661         if let Some(fs) = self.fs.as_mut() {
2662             let len = fs.len();
2663             fs.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2664             removed |= fs.len() != len;
2665         }
2666 
2667         // Remove if net device
2668         if let Some(net) = self.net.as_mut() {
2669             let len = net.len();
2670             net.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2671             removed |= net.len() != len;
2672         }
2673 
2674         // Remove if pmem device
2675         if let Some(pmem) = self.pmem.as_mut() {
2676             let len = pmem.len();
2677             pmem.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2678             removed |= pmem.len() != len;
2679         }
2680 
2681         // Remove if vDPA device
2682         if let Some(vdpa) = self.vdpa.as_mut() {
2683             let len = vdpa.len();
2684             vdpa.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
2685             removed |= vdpa.len() != len;
2686         }
2687 
2688         // Remove if vsock device
2689         if let Some(vsock) = self.vsock.as_ref() {
2690             if vsock.id.as_ref().map(|id| id.as_ref()) == Some(id) {
2691                 self.vsock = None;
2692                 removed = true;
2693             }
2694         }
2695 
2696         removed
2697     }
2698 
2699     /// # Safety
2700     /// To use this safely, the caller must guarantee that the input
2701     /// fds are all valid.
2702     pub unsafe fn add_preserved_fds(&mut self, mut fds: Vec<i32>) {
2703         if fds.is_empty() {
2704             return;
2705         }
2706 
2707         if let Some(preserved_fds) = &self.preserved_fds {
2708             fds.append(&mut preserved_fds.clone());
2709         }
2710 
2711         self.preserved_fds = Some(fds);
2712     }
2713 
2714     #[cfg(feature = "tdx")]
2715     pub fn is_tdx_enabled(&self) -> bool {
2716         self.platform.as_ref().map(|p| p.tdx).unwrap_or(false)
2717     }
2718 
2719     #[cfg(feature = "sev_snp")]
2720     pub fn is_sev_snp_enabled(&self) -> bool {
2721         self.platform.as_ref().map(|p| p.sev_snp).unwrap_or(false)
2722     }
2723 }
2724 
2725 impl Clone for VmConfig {
2726     fn clone(&self) -> Self {
2727         VmConfig {
2728             cpus: self.cpus.clone(),
2729             memory: self.memory.clone(),
2730             payload: self.payload.clone(),
2731             rate_limit_groups: self.rate_limit_groups.clone(),
2732             disks: self.disks.clone(),
2733             net: self.net.clone(),
2734             rng: self.rng.clone(),
2735             balloon: self.balloon.clone(),
2736             fs: self.fs.clone(),
2737             pmem: self.pmem.clone(),
2738             serial: self.serial.clone(),
2739             console: self.console.clone(),
2740             #[cfg(target_arch = "x86_64")]
2741             debug_console: self.debug_console.clone(),
2742             devices: self.devices.clone(),
2743             user_devices: self.user_devices.clone(),
2744             vdpa: self.vdpa.clone(),
2745             vsock: self.vsock.clone(),
2746             #[cfg(target_arch = "x86_64")]
2747             sgx_epc: self.sgx_epc.clone(),
2748             numa: self.numa.clone(),
2749             platform: self.platform.clone(),
2750             tpm: self.tpm.clone(),
2751             preserved_fds: self
2752                 .preserved_fds
2753                 .as_ref()
2754                 // SAFETY: FFI call with valid FDs
2755                 .map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()),
2756             ..*self
2757         }
2758     }
2759 }
2760 
2761 impl Drop for VmConfig {
2762     fn drop(&mut self) {
2763         if let Some(mut fds) = self.preserved_fds.take() {
2764             for fd in fds.drain(..) {
2765                 // SAFETY: FFI call with valid FDs
2766                 unsafe { libc::close(fd) };
2767             }
2768         }
2769     }
2770 }
2771 
2772 #[cfg(test)]
2773 mod tests {
2774     use super::*;
2775     use net_util::MacAddr;
2776     use std::fs::File;
2777     use std::net::Ipv4Addr;
2778     use std::os::unix::io::AsRawFd;
2779 
2780     #[test]
2781     fn test_cpu_parsing() -> Result<()> {
2782         assert_eq!(CpusConfig::parse("")?, CpusConfig::default());
2783 
2784         assert_eq!(
2785             CpusConfig::parse("boot=1")?,
2786             CpusConfig {
2787                 boot_vcpus: 1,
2788                 max_vcpus: 1,
2789                 ..Default::default()
2790             }
2791         );
2792         assert_eq!(
2793             CpusConfig::parse("boot=1,max=2")?,
2794             CpusConfig {
2795                 boot_vcpus: 1,
2796                 max_vcpus: 2,
2797                 ..Default::default()
2798             }
2799         );
2800         assert_eq!(
2801             CpusConfig::parse("boot=8,topology=2:2:1:2")?,
2802             CpusConfig {
2803                 boot_vcpus: 8,
2804                 max_vcpus: 8,
2805                 topology: Some(CpuTopology {
2806                     threads_per_core: 2,
2807                     cores_per_die: 2,
2808                     dies_per_package: 1,
2809                     packages: 2
2810                 }),
2811                 ..Default::default()
2812             }
2813         );
2814 
2815         assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err());
2816         assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err());
2817         assert_eq!(
2818             CpusConfig::parse("boot=1,kvm_hyperv=on")?,
2819             CpusConfig {
2820                 boot_vcpus: 1,
2821                 max_vcpus: 1,
2822                 kvm_hyperv: true,
2823                 ..Default::default()
2824             }
2825         );
2826         assert_eq!(
2827             CpusConfig::parse("boot=2,affinity=[0@[0,2],1@[1,3]]")?,
2828             CpusConfig {
2829                 boot_vcpus: 2,
2830                 max_vcpus: 2,
2831                 affinity: Some(vec![
2832                     CpuAffinity {
2833                         vcpu: 0,
2834                         host_cpus: vec![0, 2],
2835                     },
2836                     CpuAffinity {
2837                         vcpu: 1,
2838                         host_cpus: vec![1, 3],
2839                     }
2840                 ]),
2841                 ..Default::default()
2842             },
2843         );
2844 
2845         Ok(())
2846     }
2847 
2848     #[test]
2849     fn test_mem_parsing() -> Result<()> {
2850         assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default());
2851         // Default string
2852         assert_eq!(
2853             MemoryConfig::parse("size=512M", None)?,
2854             MemoryConfig::default()
2855         );
2856         assert_eq!(
2857             MemoryConfig::parse("size=512M,mergeable=on", None)?,
2858             MemoryConfig {
2859                 size: 512 << 20,
2860                 mergeable: true,
2861                 ..Default::default()
2862             }
2863         );
2864         assert_eq!(
2865             MemoryConfig::parse("mergeable=on", None)?,
2866             MemoryConfig {
2867                 mergeable: true,
2868                 ..Default::default()
2869             }
2870         );
2871         assert_eq!(
2872             MemoryConfig::parse("size=1G,mergeable=off", None)?,
2873             MemoryConfig {
2874                 size: 1 << 30,
2875                 mergeable: false,
2876                 ..Default::default()
2877             }
2878         );
2879         assert_eq!(
2880             MemoryConfig::parse("hotplug_method=acpi", None)?,
2881             MemoryConfig {
2882                 ..Default::default()
2883             }
2884         );
2885         assert_eq!(
2886             MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?,
2887             MemoryConfig {
2888                 hotplug_size: Some(512 << 20),
2889                 ..Default::default()
2890             }
2891         );
2892         assert_eq!(
2893             MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?,
2894             MemoryConfig {
2895                 hotplug_size: Some(512 << 20),
2896                 hotplug_method: HotplugMethod::VirtioMem,
2897                 ..Default::default()
2898             }
2899         );
2900         assert_eq!(
2901             MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?,
2902             MemoryConfig {
2903                 hugepage_size: Some(2 << 20),
2904                 size: 1 << 30,
2905                 hugepages: true,
2906                 ..Default::default()
2907             }
2908         );
2909         Ok(())
2910     }
2911 
2912     #[test]
2913     fn test_rate_limit_group_parsing() -> Result<()> {
2914         assert_eq!(
2915             RateLimiterGroupConfig::parse("id=group0,bw_size=1000,bw_refill_time=100")?,
2916             RateLimiterGroupConfig {
2917                 id: "group0".to_string(),
2918                 rate_limiter_config: RateLimiterConfig {
2919                     bandwidth: Some(TokenBucketConfig {
2920                         size: 1000,
2921                         one_time_burst: Some(0),
2922                         refill_time: 100,
2923                     }),
2924                     ops: None,
2925                 }
2926             }
2927         );
2928         assert_eq!(
2929             RateLimiterGroupConfig::parse("id=group0,ops_size=1000,ops_refill_time=100")?,
2930             RateLimiterGroupConfig {
2931                 id: "group0".to_string(),
2932                 rate_limiter_config: RateLimiterConfig {
2933                     bandwidth: None,
2934                     ops: Some(TokenBucketConfig {
2935                         size: 1000,
2936                         one_time_burst: Some(0),
2937                         refill_time: 100,
2938                     }),
2939                 }
2940             }
2941         );
2942         Ok(())
2943     }
2944 
2945     fn disk_fixture() -> DiskConfig {
2946         DiskConfig {
2947             path: Some(PathBuf::from("/path/to_file")),
2948             readonly: false,
2949             direct: false,
2950             iommu: false,
2951             num_queues: 1,
2952             queue_size: 128,
2953             vhost_user: false,
2954             vhost_socket: None,
2955             id: None,
2956             disable_io_uring: false,
2957             disable_aio: false,
2958             rate_limit_group: None,
2959             rate_limiter_config: None,
2960             pci_segment: 0,
2961             serial: None,
2962             queue_affinity: None,
2963         }
2964     }
2965 
2966     #[test]
2967     fn test_disk_parsing() -> Result<()> {
2968         assert_eq!(
2969             DiskConfig::parse("path=/path/to_file")?,
2970             DiskConfig { ..disk_fixture() }
2971         );
2972         assert_eq!(
2973             DiskConfig::parse("path=/path/to_file,id=mydisk0")?,
2974             DiskConfig {
2975                 id: Some("mydisk0".to_owned()),
2976                 ..disk_fixture()
2977             }
2978         );
2979         assert_eq!(
2980             DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
2981             DiskConfig {
2982                 path: None,
2983                 vhost_socket: Some(String::from("/tmp/sock")),
2984                 vhost_user: true,
2985                 ..disk_fixture()
2986             }
2987         );
2988         assert_eq!(
2989             DiskConfig::parse("path=/path/to_file,iommu=on")?,
2990             DiskConfig {
2991                 iommu: true,
2992                 ..disk_fixture()
2993             }
2994         );
2995         assert_eq!(
2996             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?,
2997             DiskConfig {
2998                 iommu: true,
2999                 queue_size: 256,
3000                 ..disk_fixture()
3001             }
3002         );
3003         assert_eq!(
3004             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?,
3005             DiskConfig {
3006                 iommu: true,
3007                 queue_size: 256,
3008                 num_queues: 4,
3009                 ..disk_fixture()
3010             }
3011         );
3012         assert_eq!(
3013             DiskConfig::parse("path=/path/to_file,direct=on")?,
3014             DiskConfig {
3015                 direct: true,
3016                 ..disk_fixture()
3017             }
3018         );
3019         assert_eq!(
3020             DiskConfig::parse("path=/path/to_file,serial=test")?,
3021             DiskConfig {
3022                 serial: Some(String::from("test")),
3023                 ..disk_fixture()
3024             }
3025         );
3026         assert_eq!(
3027             DiskConfig::parse("path=/path/to_file,rate_limit_group=group0")?,
3028             DiskConfig {
3029                 rate_limit_group: Some("group0".to_string()),
3030                 ..disk_fixture()
3031             }
3032         );
3033         assert_eq!(
3034             DiskConfig::parse("path=/path/to_file,queue_affinity=[0@[1],1@[2],2@[3,4],3@[5-8]]")?,
3035             DiskConfig {
3036                 queue_affinity: Some(vec![
3037                     VirtQueueAffinity {
3038                         queue_index: 0,
3039                         host_cpus: vec![1],
3040                     },
3041                     VirtQueueAffinity {
3042                         queue_index: 1,
3043                         host_cpus: vec![2],
3044                     },
3045                     VirtQueueAffinity {
3046                         queue_index: 2,
3047                         host_cpus: vec![3, 4],
3048                     },
3049                     VirtQueueAffinity {
3050                         queue_index: 3,
3051                         host_cpus: vec![5, 6, 7, 8],
3052                     }
3053                 ]),
3054                 ..disk_fixture()
3055             }
3056         );
3057         Ok(())
3058     }
3059 
3060     fn net_fixture() -> NetConfig {
3061         NetConfig {
3062             tap: None,
3063             ip: Ipv4Addr::new(192, 168, 249, 1),
3064             mask: Ipv4Addr::new(255, 255, 255, 0),
3065             mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
3066             host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
3067             mtu: None,
3068             iommu: false,
3069             num_queues: 2,
3070             queue_size: 256,
3071             vhost_user: false,
3072             vhost_socket: None,
3073             vhost_mode: VhostMode::Client,
3074             id: None,
3075             fds: None,
3076             rate_limiter_config: None,
3077             pci_segment: 0,
3078             offload_tso: true,
3079             offload_ufo: true,
3080             offload_csum: true,
3081         }
3082     }
3083 
3084     #[test]
3085     fn test_net_parsing() -> Result<()> {
3086         // mac address is random
3087         assert_eq!(
3088             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?,
3089             net_fixture(),
3090         );
3091 
3092         assert_eq!(
3093             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?,
3094             NetConfig {
3095                 id: Some("mynet0".to_owned()),
3096                 ..net_fixture()
3097             }
3098         );
3099 
3100         assert_eq!(
3101             NetConfig::parse(
3102                 "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"
3103             )?,
3104             NetConfig {
3105                 tap: Some("tap0".to_owned()),
3106                 ip: "192.168.100.1".parse().unwrap(),
3107                 mask: "255.255.255.128".parse().unwrap(),
3108                 ..net_fixture()
3109             }
3110         );
3111 
3112         assert_eq!(
3113             NetConfig::parse(
3114                 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock"
3115             )?,
3116             NetConfig {
3117                 vhost_user: true,
3118                 vhost_socket: Some("/tmp/sock".to_owned()),
3119                 ..net_fixture()
3120             }
3121         );
3122 
3123         assert_eq!(
3124             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")?,
3125             NetConfig {
3126                 num_queues: 4,
3127                 queue_size: 1024,
3128                 iommu: true,
3129                 ..net_fixture()
3130             }
3131         );
3132 
3133         assert_eq!(
3134             NetConfig::parse("mac=de:ad:be:ef:12:34,fd=[3,7],num_queues=4")?,
3135             NetConfig {
3136                 host_mac: None,
3137                 fds: Some(vec![3, 7]),
3138                 num_queues: 4,
3139                 ..net_fixture()
3140             }
3141         );
3142 
3143         Ok(())
3144     }
3145 
3146     #[test]
3147     fn test_parse_rng() -> Result<()> {
3148         assert_eq!(RngConfig::parse("")?, RngConfig::default());
3149         assert_eq!(
3150             RngConfig::parse("src=/dev/random")?,
3151             RngConfig {
3152                 src: PathBuf::from("/dev/random"),
3153                 ..Default::default()
3154             }
3155         );
3156         assert_eq!(
3157             RngConfig::parse("src=/dev/random,iommu=on")?,
3158             RngConfig {
3159                 src: PathBuf::from("/dev/random"),
3160                 iommu: true,
3161             }
3162         );
3163         assert_eq!(
3164             RngConfig::parse("iommu=on")?,
3165             RngConfig {
3166                 iommu: true,
3167                 ..Default::default()
3168             }
3169         );
3170         Ok(())
3171     }
3172 
3173     fn fs_fixture() -> FsConfig {
3174         FsConfig {
3175             socket: PathBuf::from("/tmp/sock"),
3176             tag: "mytag".to_owned(),
3177             num_queues: 1,
3178             queue_size: 1024,
3179             id: None,
3180             pci_segment: 0,
3181         }
3182     }
3183 
3184     #[test]
3185     fn test_parse_fs() -> Result<()> {
3186         // "tag" and "socket" must be supplied
3187         assert!(FsConfig::parse("").is_err());
3188         assert!(FsConfig::parse("tag=mytag").is_err());
3189         assert!(FsConfig::parse("socket=/tmp/sock").is_err());
3190         assert_eq!(FsConfig::parse("tag=mytag,socket=/tmp/sock")?, fs_fixture());
3191         assert_eq!(
3192             FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?,
3193             FsConfig {
3194                 num_queues: 4,
3195                 queue_size: 1024,
3196                 ..fs_fixture()
3197             }
3198         );
3199 
3200         Ok(())
3201     }
3202 
3203     fn pmem_fixture() -> PmemConfig {
3204         PmemConfig {
3205             file: PathBuf::from("/tmp/pmem"),
3206             size: Some(128 << 20),
3207             iommu: false,
3208             discard_writes: false,
3209             id: None,
3210             pci_segment: 0,
3211         }
3212     }
3213 
3214     #[test]
3215     fn test_pmem_parsing() -> Result<()> {
3216         // Must always give a file and size
3217         assert!(PmemConfig::parse("").is_err());
3218         assert!(PmemConfig::parse("size=128M").is_err());
3219         assert_eq!(
3220             PmemConfig::parse("file=/tmp/pmem,size=128M")?,
3221             pmem_fixture()
3222         );
3223         assert_eq!(
3224             PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?,
3225             PmemConfig {
3226                 id: Some("mypmem0".to_owned()),
3227                 ..pmem_fixture()
3228             }
3229         );
3230         assert_eq!(
3231             PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,discard_writes=on")?,
3232             PmemConfig {
3233                 discard_writes: true,
3234                 iommu: true,
3235                 ..pmem_fixture()
3236             }
3237         );
3238 
3239         Ok(())
3240     }
3241 
3242     #[test]
3243     fn test_console_parsing() -> Result<()> {
3244         assert!(ConsoleConfig::parse("").is_err());
3245         assert!(ConsoleConfig::parse("badmode").is_err());
3246         assert_eq!(
3247             ConsoleConfig::parse("off")?,
3248             ConsoleConfig {
3249                 mode: ConsoleOutputMode::Off,
3250                 iommu: false,
3251                 file: None,
3252                 socket: None,
3253             }
3254         );
3255         assert_eq!(
3256             ConsoleConfig::parse("pty")?,
3257             ConsoleConfig {
3258                 mode: ConsoleOutputMode::Pty,
3259                 iommu: false,
3260                 file: None,
3261                 socket: None,
3262             }
3263         );
3264         assert_eq!(
3265             ConsoleConfig::parse("tty")?,
3266             ConsoleConfig {
3267                 mode: ConsoleOutputMode::Tty,
3268                 iommu: false,
3269                 file: None,
3270                 socket: None,
3271             }
3272         );
3273         assert_eq!(
3274             ConsoleConfig::parse("null")?,
3275             ConsoleConfig {
3276                 mode: ConsoleOutputMode::Null,
3277                 iommu: false,
3278                 file: None,
3279                 socket: None,
3280             }
3281         );
3282         assert_eq!(
3283             ConsoleConfig::parse("file=/tmp/console")?,
3284             ConsoleConfig {
3285                 mode: ConsoleOutputMode::File,
3286                 iommu: false,
3287                 file: Some(PathBuf::from("/tmp/console")),
3288                 socket: None,
3289             }
3290         );
3291         assert_eq!(
3292             ConsoleConfig::parse("null,iommu=on")?,
3293             ConsoleConfig {
3294                 mode: ConsoleOutputMode::Null,
3295                 iommu: true,
3296                 file: None,
3297                 socket: None,
3298             }
3299         );
3300         assert_eq!(
3301             ConsoleConfig::parse("file=/tmp/console,iommu=on")?,
3302             ConsoleConfig {
3303                 mode: ConsoleOutputMode::File,
3304                 iommu: true,
3305                 file: Some(PathBuf::from("/tmp/console")),
3306                 socket: None,
3307             }
3308         );
3309         assert_eq!(
3310             ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?,
3311             ConsoleConfig {
3312                 mode: ConsoleOutputMode::Socket,
3313                 iommu: true,
3314                 file: None,
3315                 socket: Some(PathBuf::from("/tmp/serial.sock")),
3316             }
3317         );
3318         Ok(())
3319     }
3320 
3321     fn device_fixture() -> DeviceConfig {
3322         DeviceConfig {
3323             path: PathBuf::from("/path/to/device"),
3324             id: None,
3325             iommu: false,
3326             pci_segment: 0,
3327         }
3328     }
3329 
3330     #[test]
3331     fn test_device_parsing() -> Result<()> {
3332         // Device must have a path provided
3333         assert!(DeviceConfig::parse("").is_err());
3334         assert_eq!(
3335             DeviceConfig::parse("path=/path/to/device")?,
3336             device_fixture()
3337         );
3338 
3339         assert_eq!(
3340             DeviceConfig::parse("path=/path/to/device,iommu=on")?,
3341             DeviceConfig {
3342                 iommu: true,
3343                 ..device_fixture()
3344             }
3345         );
3346 
3347         assert_eq!(
3348             DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?,
3349             DeviceConfig {
3350                 id: Some("mydevice0".to_owned()),
3351                 iommu: true,
3352                 ..device_fixture()
3353             }
3354         );
3355 
3356         Ok(())
3357     }
3358 
3359     fn vdpa_fixture() -> VdpaConfig {
3360         VdpaConfig {
3361             path: PathBuf::from("/dev/vhost-vdpa"),
3362             num_queues: 1,
3363             iommu: false,
3364             id: None,
3365             pci_segment: 0,
3366         }
3367     }
3368 
3369     #[test]
3370     fn test_vdpa_parsing() -> Result<()> {
3371         // path is required
3372         assert!(VdpaConfig::parse("").is_err());
3373         assert_eq!(VdpaConfig::parse("path=/dev/vhost-vdpa")?, vdpa_fixture());
3374         assert_eq!(
3375             VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?,
3376             VdpaConfig {
3377                 num_queues: 2,
3378                 id: Some("my_vdpa".to_owned()),
3379                 ..vdpa_fixture()
3380             }
3381         );
3382         Ok(())
3383     }
3384 
3385     #[test]
3386     fn test_tpm_parsing() -> Result<()> {
3387         // path is required
3388         assert!(TpmConfig::parse("").is_err());
3389         assert_eq!(
3390             TpmConfig::parse("socket=/var/run/tpm.sock")?,
3391             TpmConfig {
3392                 socket: PathBuf::from("/var/run/tpm.sock"),
3393             }
3394         );
3395         Ok(())
3396     }
3397 
3398     #[test]
3399     fn test_vsock_parsing() -> Result<()> {
3400         // socket and cid is required
3401         assert!(VsockConfig::parse("").is_err());
3402         assert_eq!(
3403             VsockConfig::parse("socket=/tmp/sock,cid=3")?,
3404             VsockConfig {
3405                 cid: 3,
3406                 socket: PathBuf::from("/tmp/sock"),
3407                 iommu: false,
3408                 id: None,
3409                 pci_segment: 0,
3410             }
3411         );
3412         assert_eq!(
3413             VsockConfig::parse("socket=/tmp/sock,cid=3,iommu=on")?,
3414             VsockConfig {
3415                 cid: 3,
3416                 socket: PathBuf::from("/tmp/sock"),
3417                 iommu: true,
3418                 id: None,
3419                 pci_segment: 0,
3420             }
3421         );
3422         Ok(())
3423     }
3424 
3425     fn platform_fixture() -> PlatformConfig {
3426         PlatformConfig {
3427             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
3428             iommu_segments: None,
3429             serial_number: None,
3430             uuid: None,
3431             oem_strings: None,
3432             #[cfg(feature = "tdx")]
3433             tdx: false,
3434             #[cfg(feature = "sev_snp")]
3435             sev_snp: false,
3436         }
3437     }
3438 
3439     fn numa_fixture() -> NumaConfig {
3440         NumaConfig {
3441             guest_numa_id: 0,
3442             cpus: None,
3443             distances: None,
3444             memory_zones: None,
3445             #[cfg(target_arch = "x86_64")]
3446             sgx_epc_sections: None,
3447             pci_segments: None,
3448         }
3449     }
3450 
3451     #[test]
3452     fn test_config_validation() {
3453         let mut valid_config = VmConfig {
3454             cpus: CpusConfig {
3455                 boot_vcpus: 1,
3456                 max_vcpus: 1,
3457                 ..Default::default()
3458             },
3459             memory: MemoryConfig {
3460                 size: 536_870_912,
3461                 mergeable: false,
3462                 hotplug_method: HotplugMethod::Acpi,
3463                 hotplug_size: None,
3464                 hotplugged_size: None,
3465                 shared: false,
3466                 hugepages: false,
3467                 hugepage_size: None,
3468                 prefault: false,
3469                 zones: None,
3470                 thp: true,
3471             },
3472             payload: Some(PayloadConfig {
3473                 kernel: Some(PathBuf::from("/path/to/kernel")),
3474                 firmware: None,
3475                 cmdline: None,
3476                 initramfs: None,
3477                 #[cfg(feature = "igvm")]
3478                 igvm: None,
3479                 #[cfg(feature = "sev_snp")]
3480                 host_data: Some(
3481                     "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb431188673288c07".to_string(),
3482                 ),
3483             }),
3484             rate_limit_groups: None,
3485             disks: None,
3486             net: None,
3487             rng: RngConfig {
3488                 src: PathBuf::from("/dev/urandom"),
3489                 iommu: false,
3490             },
3491             balloon: None,
3492             fs: None,
3493             pmem: None,
3494             serial: ConsoleConfig {
3495                 file: None,
3496                 mode: ConsoleOutputMode::Null,
3497                 iommu: false,
3498                 socket: None,
3499             },
3500             console: ConsoleConfig {
3501                 file: None,
3502                 mode: ConsoleOutputMode::Tty,
3503                 iommu: false,
3504                 socket: None,
3505             },
3506             #[cfg(target_arch = "x86_64")]
3507             debug_console: DebugConsoleConfig::default(),
3508             devices: None,
3509             user_devices: None,
3510             vdpa: None,
3511             vsock: None,
3512             pvpanic: false,
3513             iommu: false,
3514             #[cfg(target_arch = "x86_64")]
3515             sgx_epc: None,
3516             numa: None,
3517             watchdog: false,
3518             #[cfg(feature = "guest_debug")]
3519             gdb: false,
3520             platform: None,
3521             tpm: None,
3522             preserved_fds: None,
3523         };
3524 
3525         assert!(valid_config.validate().is_ok());
3526 
3527         let mut invalid_config = valid_config.clone();
3528         invalid_config.serial.mode = ConsoleOutputMode::Tty;
3529         invalid_config.console.mode = ConsoleOutputMode::Tty;
3530         assert!(valid_config.validate().is_ok());
3531 
3532         let mut invalid_config = valid_config.clone();
3533         invalid_config.payload = None;
3534         assert_eq!(
3535             invalid_config.validate(),
3536             Err(ValidationError::KernelMissing)
3537         );
3538 
3539         let mut invalid_config = valid_config.clone();
3540         invalid_config.serial.mode = ConsoleOutputMode::File;
3541         invalid_config.serial.file = None;
3542         assert_eq!(
3543             invalid_config.validate(),
3544             Err(ValidationError::ConsoleFileMissing)
3545         );
3546 
3547         let mut invalid_config = valid_config.clone();
3548         invalid_config.cpus.max_vcpus = 16;
3549         invalid_config.cpus.boot_vcpus = 32;
3550         assert_eq!(
3551             invalid_config.validate(),
3552             Err(ValidationError::CpusMaxLowerThanBoot)
3553         );
3554 
3555         let mut invalid_config = valid_config.clone();
3556         invalid_config.cpus.max_vcpus = 16;
3557         invalid_config.cpus.boot_vcpus = 16;
3558         invalid_config.cpus.topology = Some(CpuTopology {
3559             threads_per_core: 2,
3560             cores_per_die: 8,
3561             dies_per_package: 1,
3562             packages: 2,
3563         });
3564         assert_eq!(
3565             invalid_config.validate(),
3566             Err(ValidationError::CpuTopologyCount)
3567         );
3568 
3569         let mut invalid_config = valid_config.clone();
3570         invalid_config.disks = Some(vec![DiskConfig {
3571             vhost_socket: Some("/path/to/sock".to_owned()),
3572             path: Some(PathBuf::from("/path/to/image")),
3573             ..disk_fixture()
3574         }]);
3575         assert_eq!(
3576             invalid_config.validate(),
3577             Err(ValidationError::DiskSocketAndPath)
3578         );
3579 
3580         let mut invalid_config = valid_config.clone();
3581         invalid_config.memory.shared = true;
3582         invalid_config.disks = Some(vec![DiskConfig {
3583             path: None,
3584             vhost_user: true,
3585             ..disk_fixture()
3586         }]);
3587         assert_eq!(
3588             invalid_config.validate(),
3589             Err(ValidationError::VhostUserMissingSocket)
3590         );
3591 
3592         let mut invalid_config = valid_config.clone();
3593         invalid_config.disks = Some(vec![DiskConfig {
3594             path: None,
3595             vhost_user: true,
3596             vhost_socket: Some("/path/to/sock".to_owned()),
3597             ..disk_fixture()
3598         }]);
3599         assert_eq!(
3600             invalid_config.validate(),
3601             Err(ValidationError::VhostUserRequiresSharedMemory)
3602         );
3603 
3604         let mut still_valid_config = valid_config.clone();
3605         still_valid_config.disks = Some(vec![DiskConfig {
3606             path: None,
3607             vhost_user: true,
3608             vhost_socket: Some("/path/to/sock".to_owned()),
3609             ..disk_fixture()
3610         }]);
3611         still_valid_config.memory.shared = true;
3612         assert!(still_valid_config.validate().is_ok());
3613 
3614         let mut invalid_config = valid_config.clone();
3615         invalid_config.net = Some(vec![NetConfig {
3616             vhost_user: true,
3617             ..net_fixture()
3618         }]);
3619         assert_eq!(
3620             invalid_config.validate(),
3621             Err(ValidationError::VhostUserRequiresSharedMemory)
3622         );
3623 
3624         let mut still_valid_config = valid_config.clone();
3625         still_valid_config.net = Some(vec![NetConfig {
3626             vhost_user: true,
3627             vhost_socket: Some("/path/to/sock".to_owned()),
3628             ..net_fixture()
3629         }]);
3630         still_valid_config.memory.shared = true;
3631         assert!(still_valid_config.validate().is_ok());
3632 
3633         let mut invalid_config = valid_config.clone();
3634         invalid_config.net = Some(vec![NetConfig {
3635             fds: Some(vec![0]),
3636             ..net_fixture()
3637         }]);
3638         assert_eq!(
3639             invalid_config.validate(),
3640             Err(ValidationError::VnetReservedFd)
3641         );
3642 
3643         let mut invalid_config = valid_config.clone();
3644         invalid_config.net = Some(vec![NetConfig {
3645             offload_csum: false,
3646             ..net_fixture()
3647         }]);
3648         assert_eq!(
3649             invalid_config.validate(),
3650             Err(ValidationError::NoHardwareChecksumOffload)
3651         );
3652 
3653         let mut invalid_config = valid_config.clone();
3654         invalid_config.fs = Some(vec![fs_fixture()]);
3655         assert_eq!(
3656             invalid_config.validate(),
3657             Err(ValidationError::VhostUserRequiresSharedMemory)
3658         );
3659 
3660         let mut still_valid_config = valid_config.clone();
3661         still_valid_config.memory.shared = true;
3662         assert!(still_valid_config.validate().is_ok());
3663 
3664         let mut still_valid_config = valid_config.clone();
3665         still_valid_config.memory.hugepages = true;
3666         assert!(still_valid_config.validate().is_ok());
3667 
3668         let mut still_valid_config = valid_config.clone();
3669         still_valid_config.memory.hugepages = true;
3670         still_valid_config.memory.hugepage_size = Some(2 << 20);
3671         assert!(still_valid_config.validate().is_ok());
3672 
3673         let mut invalid_config = valid_config.clone();
3674         invalid_config.memory.hugepages = false;
3675         invalid_config.memory.hugepage_size = Some(2 << 20);
3676         assert_eq!(
3677             invalid_config.validate(),
3678             Err(ValidationError::HugePageSizeWithoutHugePages)
3679         );
3680 
3681         let mut invalid_config = valid_config.clone();
3682         invalid_config.memory.hugepages = true;
3683         invalid_config.memory.hugepage_size = Some(3 << 20);
3684         assert_eq!(
3685             invalid_config.validate(),
3686             Err(ValidationError::InvalidHugePageSize(3 << 20))
3687         );
3688 
3689         let mut still_valid_config = valid_config.clone();
3690         still_valid_config.platform = Some(platform_fixture());
3691         assert!(still_valid_config.validate().is_ok());
3692 
3693         let mut invalid_config = valid_config.clone();
3694         invalid_config.platform = Some(PlatformConfig {
3695             num_pci_segments: MAX_NUM_PCI_SEGMENTS + 1,
3696             ..platform_fixture()
3697         });
3698         assert_eq!(
3699             invalid_config.validate(),
3700             Err(ValidationError::InvalidNumPciSegments(
3701                 MAX_NUM_PCI_SEGMENTS + 1
3702             ))
3703         );
3704 
3705         let mut still_valid_config = valid_config.clone();
3706         still_valid_config.platform = Some(PlatformConfig {
3707             iommu_segments: Some(vec![1, 2, 3]),
3708             ..platform_fixture()
3709         });
3710         assert!(still_valid_config.validate().is_ok());
3711 
3712         let mut invalid_config = valid_config.clone();
3713         invalid_config.platform = Some(PlatformConfig {
3714             iommu_segments: Some(vec![MAX_NUM_PCI_SEGMENTS + 1, MAX_NUM_PCI_SEGMENTS + 2]),
3715             ..platform_fixture()
3716         });
3717         assert_eq!(
3718             invalid_config.validate(),
3719             Err(ValidationError::InvalidPciSegment(MAX_NUM_PCI_SEGMENTS + 1))
3720         );
3721 
3722         let mut still_valid_config = valid_config.clone();
3723         still_valid_config.platform = Some(PlatformConfig {
3724             iommu_segments: Some(vec![1, 2, 3]),
3725             ..platform_fixture()
3726         });
3727         still_valid_config.disks = Some(vec![DiskConfig {
3728             iommu: true,
3729             pci_segment: 1,
3730             ..disk_fixture()
3731         }]);
3732         assert!(still_valid_config.validate().is_ok());
3733 
3734         let mut still_valid_config = valid_config.clone();
3735         still_valid_config.platform = Some(PlatformConfig {
3736             iommu_segments: Some(vec![1, 2, 3]),
3737             ..platform_fixture()
3738         });
3739         still_valid_config.net = Some(vec![NetConfig {
3740             iommu: true,
3741             pci_segment: 1,
3742             ..net_fixture()
3743         }]);
3744         assert!(still_valid_config.validate().is_ok());
3745 
3746         let mut still_valid_config = valid_config.clone();
3747         still_valid_config.platform = Some(PlatformConfig {
3748             iommu_segments: Some(vec![1, 2, 3]),
3749             ..platform_fixture()
3750         });
3751         still_valid_config.pmem = Some(vec![PmemConfig {
3752             iommu: true,
3753             pci_segment: 1,
3754             ..pmem_fixture()
3755         }]);
3756         assert!(still_valid_config.validate().is_ok());
3757 
3758         let mut still_valid_config = valid_config.clone();
3759         still_valid_config.platform = Some(PlatformConfig {
3760             iommu_segments: Some(vec![1, 2, 3]),
3761             ..platform_fixture()
3762         });
3763         still_valid_config.devices = Some(vec![DeviceConfig {
3764             iommu: true,
3765             pci_segment: 1,
3766             ..device_fixture()
3767         }]);
3768         assert!(still_valid_config.validate().is_ok());
3769 
3770         let mut still_valid_config = valid_config.clone();
3771         still_valid_config.platform = Some(PlatformConfig {
3772             iommu_segments: Some(vec![1, 2, 3]),
3773             ..platform_fixture()
3774         });
3775         still_valid_config.vsock = Some(VsockConfig {
3776             cid: 3,
3777             socket: PathBuf::new(),
3778             id: None,
3779             iommu: true,
3780             pci_segment: 1,
3781         });
3782         assert!(still_valid_config.validate().is_ok());
3783 
3784         let mut invalid_config = valid_config.clone();
3785         invalid_config.platform = Some(PlatformConfig {
3786             iommu_segments: Some(vec![1, 2, 3]),
3787             ..platform_fixture()
3788         });
3789         invalid_config.disks = Some(vec![DiskConfig {
3790             iommu: false,
3791             pci_segment: 1,
3792             ..disk_fixture()
3793         }]);
3794         assert_eq!(
3795             invalid_config.validate(),
3796             Err(ValidationError::OnIommuSegment(1))
3797         );
3798 
3799         let mut invalid_config = valid_config.clone();
3800         invalid_config.platform = Some(PlatformConfig {
3801             iommu_segments: Some(vec![1, 2, 3]),
3802             ..platform_fixture()
3803         });
3804         invalid_config.net = Some(vec![NetConfig {
3805             iommu: false,
3806             pci_segment: 1,
3807             ..net_fixture()
3808         }]);
3809         assert_eq!(
3810             invalid_config.validate(),
3811             Err(ValidationError::OnIommuSegment(1))
3812         );
3813 
3814         let mut invalid_config = valid_config.clone();
3815         invalid_config.platform = Some(PlatformConfig {
3816             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
3817             iommu_segments: Some(vec![1, 2, 3]),
3818             ..platform_fixture()
3819         });
3820         invalid_config.pmem = Some(vec![PmemConfig {
3821             iommu: false,
3822             pci_segment: 1,
3823             ..pmem_fixture()
3824         }]);
3825         assert_eq!(
3826             invalid_config.validate(),
3827             Err(ValidationError::OnIommuSegment(1))
3828         );
3829 
3830         let mut invalid_config = valid_config.clone();
3831         invalid_config.platform = Some(PlatformConfig {
3832             num_pci_segments: MAX_NUM_PCI_SEGMENTS,
3833             iommu_segments: Some(vec![1, 2, 3]),
3834             ..platform_fixture()
3835         });
3836         invalid_config.devices = Some(vec![DeviceConfig {
3837             iommu: false,
3838             pci_segment: 1,
3839             ..device_fixture()
3840         }]);
3841         assert_eq!(
3842             invalid_config.validate(),
3843             Err(ValidationError::OnIommuSegment(1))
3844         );
3845 
3846         let mut invalid_config = valid_config.clone();
3847         invalid_config.platform = Some(PlatformConfig {
3848             iommu_segments: Some(vec![1, 2, 3]),
3849             ..platform_fixture()
3850         });
3851         invalid_config.vsock = Some(VsockConfig {
3852             cid: 3,
3853             socket: PathBuf::new(),
3854             id: None,
3855             iommu: false,
3856             pci_segment: 1,
3857         });
3858         assert_eq!(
3859             invalid_config.validate(),
3860             Err(ValidationError::OnIommuSegment(1))
3861         );
3862 
3863         let mut invalid_config = valid_config.clone();
3864         invalid_config.memory.shared = true;
3865         invalid_config.platform = Some(PlatformConfig {
3866             iommu_segments: Some(vec![1, 2, 3]),
3867             ..platform_fixture()
3868         });
3869         invalid_config.user_devices = Some(vec![UserDeviceConfig {
3870             pci_segment: 1,
3871             socket: PathBuf::new(),
3872             id: None,
3873         }]);
3874         assert_eq!(
3875             invalid_config.validate(),
3876             Err(ValidationError::IommuNotSupportedOnSegment(1))
3877         );
3878 
3879         let mut invalid_config = valid_config.clone();
3880         invalid_config.platform = Some(PlatformConfig {
3881             iommu_segments: Some(vec![1, 2, 3]),
3882             ..platform_fixture()
3883         });
3884         invalid_config.vdpa = Some(vec![VdpaConfig {
3885             pci_segment: 1,
3886             ..vdpa_fixture()
3887         }]);
3888         assert_eq!(
3889             invalid_config.validate(),
3890             Err(ValidationError::OnIommuSegment(1))
3891         );
3892 
3893         let mut invalid_config = valid_config.clone();
3894         invalid_config.memory.shared = true;
3895         invalid_config.platform = Some(PlatformConfig {
3896             iommu_segments: Some(vec![1, 2, 3]),
3897             ..platform_fixture()
3898         });
3899         invalid_config.fs = Some(vec![FsConfig {
3900             pci_segment: 1,
3901             ..fs_fixture()
3902         }]);
3903         assert_eq!(
3904             invalid_config.validate(),
3905             Err(ValidationError::IommuNotSupportedOnSegment(1))
3906         );
3907 
3908         let mut invalid_config = valid_config.clone();
3909         invalid_config.platform = Some(PlatformConfig {
3910             num_pci_segments: 2,
3911             ..platform_fixture()
3912         });
3913         invalid_config.numa = Some(vec![
3914             NumaConfig {
3915                 guest_numa_id: 0,
3916                 pci_segments: Some(vec![1]),
3917                 ..numa_fixture()
3918             },
3919             NumaConfig {
3920                 guest_numa_id: 1,
3921                 pci_segments: Some(vec![1]),
3922                 ..numa_fixture()
3923             },
3924         ]);
3925         assert_eq!(
3926             invalid_config.validate(),
3927             Err(ValidationError::PciSegmentReused(1, 0, 1))
3928         );
3929 
3930         let mut invalid_config = valid_config.clone();
3931         invalid_config.numa = Some(vec![
3932             NumaConfig {
3933                 guest_numa_id: 0,
3934                 ..numa_fixture()
3935             },
3936             NumaConfig {
3937                 guest_numa_id: 1,
3938                 pci_segments: Some(vec![0]),
3939                 ..numa_fixture()
3940             },
3941         ]);
3942         assert_eq!(
3943             invalid_config.validate(),
3944             Err(ValidationError::DefaultPciSegmentInvalidNode(1))
3945         );
3946 
3947         let mut invalid_config = valid_config.clone();
3948         invalid_config.numa = Some(vec![
3949             NumaConfig {
3950                 guest_numa_id: 0,
3951                 pci_segments: Some(vec![0]),
3952                 ..numa_fixture()
3953             },
3954             NumaConfig {
3955                 guest_numa_id: 1,
3956                 pci_segments: Some(vec![1]),
3957                 ..numa_fixture()
3958             },
3959         ]);
3960         assert_eq!(
3961             invalid_config.validate(),
3962             Err(ValidationError::InvalidPciSegment(1))
3963         );
3964 
3965         let mut invalid_config = valid_config.clone();
3966         invalid_config.disks = Some(vec![DiskConfig {
3967             rate_limit_group: Some("foo".into()),
3968             ..disk_fixture()
3969         }]);
3970         assert_eq!(
3971             invalid_config.validate(),
3972             Err(ValidationError::InvalidRateLimiterGroup)
3973         );
3974 
3975         let mut still_valid_config = valid_config.clone();
3976         still_valid_config.devices = Some(vec![
3977             DeviceConfig {
3978                 path: "/device1".into(),
3979                 ..device_fixture()
3980             },
3981             DeviceConfig {
3982                 path: "/device2".into(),
3983                 ..device_fixture()
3984             },
3985         ]);
3986         assert!(still_valid_config.validate().is_ok());
3987 
3988         let mut invalid_config = valid_config.clone();
3989         invalid_config.devices = Some(vec![
3990             DeviceConfig {
3991                 path: "/device1".into(),
3992                 ..device_fixture()
3993             },
3994             DeviceConfig {
3995                 path: "/device1".into(),
3996                 ..device_fixture()
3997             },
3998         ]);
3999         assert!(invalid_config.validate().is_err());
4000         #[cfg(feature = "sev_snp")]
4001         {
4002             // Payload with empty host data
4003             let mut config_with_no_host_data = valid_config.clone();
4004             config_with_no_host_data.payload = Some(PayloadConfig {
4005                 kernel: Some(PathBuf::from("/path/to/kernel")),
4006                 firmware: None,
4007                 cmdline: None,
4008                 initramfs: None,
4009                 #[cfg(feature = "igvm")]
4010                 igvm: None,
4011                 #[cfg(feature = "sev_snp")]
4012                 host_data: Some("".to_string()),
4013             });
4014             assert!(config_with_no_host_data.validate().is_err());
4015 
4016             // Payload with no host data provided
4017             let mut valid_config_with_no_host_data = valid_config.clone();
4018             valid_config_with_no_host_data.payload = Some(PayloadConfig {
4019                 kernel: Some(PathBuf::from("/path/to/kernel")),
4020                 firmware: None,
4021                 cmdline: None,
4022                 initramfs: None,
4023                 #[cfg(feature = "igvm")]
4024                 igvm: None,
4025                 #[cfg(feature = "sev_snp")]
4026                 host_data: None,
4027             });
4028             assert!(valid_config_with_no_host_data.validate().is_ok());
4029 
4030             // Payload with invalid host data length i.e less than 64
4031             let mut config_with_invalid_host_data = valid_config.clone();
4032             config_with_invalid_host_data.payload = Some(PayloadConfig {
4033                 kernel: Some(PathBuf::from("/path/to/kernel")),
4034                 firmware: None,
4035                 cmdline: None,
4036                 initramfs: None,
4037                 #[cfg(feature = "igvm")]
4038                 igvm: None,
4039                 #[cfg(feature = "sev_snp")]
4040                 host_data: Some(
4041                     "243eb7dc1a21129caa91dcbb794922b933baecb5823a377eb43118867328".to_string(),
4042                 ),
4043             });
4044             assert!(config_with_invalid_host_data.validate().is_err());
4045         }
4046 
4047         let mut still_valid_config = valid_config;
4048         // SAFETY: Safe as the file was just opened
4049         let fd1 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) };
4050         // SAFETY: Safe as the file was just opened
4051         let fd2 = unsafe { libc::dup(File::open("/dev/null").unwrap().as_raw_fd()) };
4052         // SAFETY: safe as both FDs are valid
4053         unsafe {
4054             still_valid_config.add_preserved_fds(vec![fd1, fd2]);
4055         }
4056         let _still_valid_config = still_valid_config.clone();
4057     }
4058 }
4059