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