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