xref: /cloud-hypervisor/vmm/src/config.rs (revision f67b3f79ea19c9a66e04074cbbf5d292f6529e43)
1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use clap::ArgMatches;
7 use net_util::MacAddr;
8 use option_parser::{
9     ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, TupleTwoIntegers,
10 };
11 use std::collections::HashMap;
12 use std::convert::From;
13 use std::fmt;
14 use std::net::Ipv4Addr;
15 use std::path::PathBuf;
16 use std::result;
17 use std::str::FromStr;
18 
19 use virtio_devices::{RateLimiterConfig, TokenBucketConfig};
20 
21 pub const DEFAULT_VCPUS: u8 = 1;
22 pub const DEFAULT_MEMORY_MB: u64 = 512;
23 pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom";
24 pub const DEFAULT_NUM_QUEUES_VUNET: usize = 2;
25 pub const DEFAULT_QUEUE_SIZE_VUNET: u16 = 256;
26 pub const DEFAULT_NUM_QUEUES_VUBLK: usize = 1;
27 pub const DEFAULT_QUEUE_SIZE_VUBLK: u16 = 128;
28 
29 /// Errors associated with VM configuration parameters.
30 #[derive(Debug)]
31 pub enum Error {
32     /// Filesystem tag is missing
33     ParseFsTagMissing,
34     /// Filesystem socket is missing
35     ParseFsSockMissing,
36     /// Cannot have dax=off along with cache_size parameter.
37     InvalidCacheSizeWithDaxOff,
38     /// Missing persistent memory file parameter.
39     ParsePmemFileMissing,
40     /// Missing vsock socket path parameter.
41     ParseVsockSockMissing,
42     /// Missing vsock cid parameter.
43     ParseVsockCidMissing,
44     /// Missing restore source_url parameter.
45     ParseRestoreSourceUrlMissing,
46     /// Error parsing CPU options
47     ParseCpus(OptionParserError),
48     /// Error parsing memory options
49     ParseMemory(OptionParserError),
50     /// Error parsing memory zone options
51     ParseMemoryZone(OptionParserError),
52     /// Missing 'id' from memory zone
53     ParseMemoryZoneIdMissing,
54     /// Error parsing disk options
55     ParseDisk(OptionParserError),
56     /// Error parsing network options
57     ParseNetwork(OptionParserError),
58     /// Error parsing RNG options
59     ParseRng(OptionParserError),
60     /// Error parsing balloon options
61     ParseBalloon(OptionParserError),
62     /// Error parsing filesystem parameters
63     ParseFileSystem(OptionParserError),
64     /// Error parsing persistent memory parameters
65     ParsePersistentMemory(OptionParserError),
66     /// Failed parsing console
67     ParseConsole(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 to parse vsock parameters
75     ParseVsock(OptionParserError),
76     /// Failed to parse restore parameters
77     ParseRestore(OptionParserError),
78     /// Failed to parse 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 to parse NUMA parameters
85     ParseNuma(OptionParserError),
86     /// Failed to validate configuration
87     Validation(ValidationError),
88     #[cfg(feature = "tdx")]
89     /// Failed to parse TDX config
90     ParseTdx(OptionParserError),
91     #[cfg(feature = "tdx")]
92     // No TDX firmware
93     FirmwarePathMissing,
94     /// Failed to parse userspace device
95     ParseUserDevice(OptionParserError),
96     /// Missing socket for userspace device
97     ParseUserDeviceSocketMissing,
98 }
99 
100 #[derive(Debug)]
101 pub enum ValidationError {
102     /// Both console and serial are tty.
103     DoubleTtyMode,
104     /// No kernel specified
105     KernelMissing,
106     /// Missing file value for console
107     ConsoleFileMissing,
108     /// Max is less than boot
109     CpusMaxLowerThanBoot,
110     /// Both socket and path specified
111     DiskSocketAndPath,
112     /// Using vhost user requires shared memory
113     VhostUserRequiresSharedMemory,
114     /// No socket provided for vhost_use
115     VhostUserMissingSocket,
116     /// Trying to use IOMMU without PCI
117     IommuUnsupported,
118     /// Trying to use VFIO without PCI
119     VfioUnsupported,
120     /// CPU topology count doesn't match max
121     CpuTopologyCount,
122     /// One part of the CPU topology was zero
123     CpuTopologyZeroPart,
124     /// Virtio needs a min of 2 queues
125     VnetQueueLowerThan2,
126     /// The input queue number for virtio_net must match the number of input fds
127     VnetQueueFdMismatch,
128     /// Using reserved fd
129     VnetReservedFd,
130     /// Hugepages not turned on
131     HugePageSizeWithoutHugePages,
132     /// Huge page size is not power of 2
133     InvalidHugePageSize(u64),
134     /// CPU Hotplug not permitted with TDX
135     #[cfg(feature = "tdx")]
136     TdxNoCpuHotplug,
137     /// Specifying kernel not permitted with TDX
138     #[cfg(feature = "tdx")]
139     TdxKernelSpecified,
140     /// Insuffient vCPUs for queues
141     TooManyQueues,
142     /// Need shared memory for vfio-user
143     UserDevicesRequireSharedMemory,
144     /// Memory zone is reused across NUMA nodes
145     MemoryZoneReused(String, u32, u32),
146 }
147 
148 type ValidationResult<T> = std::result::Result<T, ValidationError>;
149 
150 impl fmt::Display for ValidationError {
151     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152         use self::ValidationError::*;
153         match self {
154             DoubleTtyMode => write!(f, "Console mode tty specified for both serial and console"),
155             KernelMissing => write!(f, "No kernel specified"),
156             ConsoleFileMissing => write!(f, "Path missing when using file console mode"),
157             CpusMaxLowerThanBoot => write!(f, "Max CPUs greater than boot CPUs"),
158             DiskSocketAndPath => write!(f, "Disk path and vhost socket both provided"),
159             VhostUserRequiresSharedMemory => {
160                 write!(f, "Using vhost-user requires using shared memory")
161             }
162             VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"),
163             IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"),
164             VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"),
165             CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"),
166             CpuTopologyCount => write!(
167                 f,
168                 "Product of CPU topology parts does not match maximum vCPUs"
169             ),
170             VnetQueueLowerThan2 => write!(f, "Number of queues to virtio_net less than 2"),
171             VnetQueueFdMismatch => write!(
172                 f,
173                 "Number of queues to virtio_net does not match the number of input FDs"
174             ),
175             VnetReservedFd => write!(f, "Reserved fd number (<= 2)"),
176             HugePageSizeWithoutHugePages => {
177                 write!(f, "Huge page size specified but huge pages not enabled")
178             }
179             InvalidHugePageSize(s) => {
180                 write!(f, "Huge page size is not power of 2: {}", s)
181             }
182             #[cfg(feature = "tdx")]
183             TdxNoCpuHotplug => {
184                 write!(f, "CPU hotplug not possible with TDX")
185             }
186             #[cfg(feature = "tdx")]
187             TdxKernelSpecified => {
188                 write!(f, "Direct kernel boot not possible with TDX")
189             }
190             TooManyQueues => {
191                 write!(f, "Number of vCPUs is insufficient for number of queues")
192             }
193             UserDevicesRequireSharedMemory => {
194                 write!(f, "Using user devices requires using shared memory")
195             }
196             MemoryZoneReused(s, u1, u2) => {
197                 write!(
198                     f,
199                     "Memory zone: {} belongs to multiple NUMA nodes {} and {}",
200                     s, u1, u2
201                 )
202             }
203         }
204     }
205 }
206 
207 impl fmt::Display for Error {
208     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
209         use self::Error::*;
210         match self {
211             ParseConsole(o) => write!(f, "Error parsing --console: {}", o),
212             ParseConsoleInvalidModeGiven => {
213                 write!(f, "Error parsing --console: invalid console mode given")
214             }
215             ParseCpus(o) => write!(f, "Error parsing --cpus: {}", o),
216 
217             ParseDevice(o) => write!(f, "Error parsing --device: {}", o),
218             ParseDevicePathMissing => write!(f, "Error parsing --device: path missing"),
219             ParseFileSystem(o) => write!(f, "Error parsing --fs: {}", o),
220             ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"),
221             ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"),
222             InvalidCacheSizeWithDaxOff => {
223                 write!(f, "Error parsing --fs: cache_size used with dax=on")
224             }
225             ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {}", o),
226             ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"),
227             ParseVsock(o) => write!(f, "Error parsing --vsock: {}", o),
228             ParseVsockCidMissing => write!(f, "Error parsing --vsock: cid missing"),
229             ParseVsockSockMissing => write!(f, "Error parsing --vsock: socket missing"),
230             ParseMemory(o) => write!(f, "Error parsing --memory: {}", o),
231             ParseMemoryZone(o) => write!(f, "Error parsing --memory-zone: {}", o),
232             ParseMemoryZoneIdMissing => write!(f, "Error parsing --memory-zone: id missing"),
233             ParseNetwork(o) => write!(f, "Error parsing --net: {}", o),
234             ParseDisk(o) => write!(f, "Error parsing --disk: {}", o),
235             ParseRng(o) => write!(f, "Error parsing --rng: {}", o),
236             ParseBalloon(o) => write!(f, "Error parsing --balloon: {}", o),
237             ParseRestore(o) => write!(f, "Error parsing --restore: {}", o),
238             #[cfg(target_arch = "x86_64")]
239             ParseSgxEpc(o) => write!(f, "Error parsing --sgx-epc: {}", o),
240             #[cfg(target_arch = "x86_64")]
241             ParseSgxEpcIdMissing => write!(f, "Error parsing --sgx-epc: id missing"),
242             ParseNuma(o) => write!(f, "Error parsing --numa: {}", o),
243             ParseRestoreSourceUrlMissing => {
244                 write!(f, "Error parsing --restore: source_url missing")
245             }
246             ParseUserDeviceSocketMissing => {
247                 write!(f, "Error parsing --user-device: socket missing")
248             }
249             ParseUserDevice(o) => write!(f, "Error parsing --user-device: {}", o),
250             Validation(v) => write!(f, "Error validating configuration: {}", v),
251             #[cfg(feature = "tdx")]
252             ParseTdx(o) => write!(f, "Error parsing --tdx: {}", o),
253             #[cfg(feature = "tdx")]
254             FirmwarePathMissing => write!(f, "TDX firmware missing"),
255         }
256     }
257 }
258 
259 pub type Result<T> = result::Result<T, Error>;
260 
261 pub struct VmParams<'a> {
262     pub cpus: &'a str,
263     pub memory: &'a str,
264     pub memory_zones: Option<Vec<&'a str>>,
265     pub kernel: Option<&'a str>,
266     pub initramfs: Option<&'a str>,
267     pub cmdline: Option<&'a str>,
268     pub disks: Option<Vec<&'a str>>,
269     pub net: Option<Vec<&'a str>>,
270     pub rng: &'a str,
271     pub balloon: Option<&'a str>,
272     pub fs: Option<Vec<&'a str>>,
273     pub pmem: Option<Vec<&'a str>>,
274     pub serial: &'a str,
275     pub console: &'a str,
276     pub devices: Option<Vec<&'a str>>,
277     pub user_devices: Option<Vec<&'a str>>,
278     pub vsock: Option<&'a str>,
279     #[cfg(target_arch = "x86_64")]
280     pub sgx_epc: Option<Vec<&'a str>>,
281     pub numa: Option<Vec<&'a str>>,
282     pub watchdog: bool,
283     #[cfg(feature = "tdx")]
284     pub tdx: Option<&'a str>,
285 }
286 
287 impl<'a> VmParams<'a> {
288     pub fn from_arg_matches(args: &'a ArgMatches) -> Self {
289         // These .unwrap()s cannot fail as there is a default value defined
290         let cpus = args.value_of("cpus").unwrap();
291         let memory = args.value_of("memory").unwrap();
292         let memory_zones: Option<Vec<&str>> = args.values_of("memory-zone").map(|x| x.collect());
293         let rng = args.value_of("rng").unwrap();
294         let serial = args.value_of("serial").unwrap();
295 
296         let kernel = args.value_of("kernel");
297         let initramfs = args.value_of("initramfs");
298         let cmdline = args.value_of("cmdline");
299 
300         let disks: Option<Vec<&str>> = args.values_of("disk").map(|x| x.collect());
301         let net: Option<Vec<&str>> = args.values_of("net").map(|x| x.collect());
302         let console = args.value_of("console").unwrap();
303         let balloon = args.value_of("balloon");
304         let fs: Option<Vec<&str>> = args.values_of("fs").map(|x| x.collect());
305         let pmem: Option<Vec<&str>> = args.values_of("pmem").map(|x| x.collect());
306         let devices: Option<Vec<&str>> = args.values_of("device").map(|x| x.collect());
307         let user_devices: Option<Vec<&str>> = args.values_of("user-device").map(|x| x.collect());
308         let vsock: Option<&str> = args.value_of("vsock");
309         #[cfg(target_arch = "x86_64")]
310         let sgx_epc: Option<Vec<&str>> = args.values_of("sgx-epc").map(|x| x.collect());
311         let numa: Option<Vec<&str>> = args.values_of("numa").map(|x| x.collect());
312         let watchdog = args.is_present("watchdog");
313         #[cfg(feature = "tdx")]
314         let tdx = args.value_of("tdx");
315         VmParams {
316             cpus,
317             memory,
318             memory_zones,
319             kernel,
320             initramfs,
321             cmdline,
322             disks,
323             net,
324             rng,
325             balloon,
326             fs,
327             pmem,
328             serial,
329             console,
330             devices,
331             user_devices,
332             vsock,
333             #[cfg(target_arch = "x86_64")]
334             sgx_epc,
335             numa,
336             watchdog,
337             #[cfg(feature = "tdx")]
338             tdx,
339         }
340     }
341 }
342 
343 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
344 pub enum HotplugMethod {
345     Acpi,
346     VirtioMem,
347 }
348 
349 impl Default for HotplugMethod {
350     fn default() -> Self {
351         HotplugMethod::Acpi
352     }
353 }
354 
355 #[derive(Debug)]
356 pub enum ParseHotplugMethodError {
357     InvalidValue(String),
358 }
359 
360 impl FromStr for HotplugMethod {
361     type Err = ParseHotplugMethodError;
362 
363     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
364         match s.to_lowercase().as_str() {
365             "acpi" => Ok(HotplugMethod::Acpi),
366             "virtio-mem" => Ok(HotplugMethod::VirtioMem),
367             _ => Err(ParseHotplugMethodError::InvalidValue(s.to_owned())),
368         }
369     }
370 }
371 
372 pub enum CpuTopologyParseError {
373     InvalidValue(String),
374 }
375 
376 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
377 pub struct CpuTopology {
378     pub threads_per_core: u8,
379     pub cores_per_die: u8,
380     pub dies_per_package: u8,
381     pub packages: u8,
382 }
383 
384 impl FromStr for CpuTopology {
385     type Err = CpuTopologyParseError;
386 
387     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
388         let parts: Vec<&str> = s.split(':').collect();
389 
390         if parts.len() != 4 {
391             return Err(Self::Err::InvalidValue(s.to_owned()));
392         }
393 
394         let t = CpuTopology {
395             threads_per_core: parts[0]
396                 .parse()
397                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
398             cores_per_die: parts[1]
399                 .parse()
400                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
401             dies_per_package: parts[2]
402                 .parse()
403                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
404             packages: parts[3]
405                 .parse()
406                 .map_err(|_| Self::Err::InvalidValue(s.to_owned()))?,
407         };
408 
409         Ok(t)
410     }
411 }
412 
413 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
414 pub struct CpusConfig {
415     pub boot_vcpus: u8,
416     pub max_vcpus: u8,
417     #[serde(default)]
418     pub topology: Option<CpuTopology>,
419     #[serde(default)]
420     pub kvm_hyperv: bool,
421     #[serde(default)]
422     pub max_phys_bits: Option<u8>,
423 }
424 
425 impl CpusConfig {
426     pub fn parse(cpus: &str) -> Result<Self> {
427         let mut parser = OptionParser::new();
428         parser
429             .add("boot")
430             .add("max")
431             .add("topology")
432             .add("kvm_hyperv")
433             .add("max_phys_bits");
434         parser.parse(cpus).map_err(Error::ParseCpus)?;
435 
436         let boot_vcpus: u8 = parser
437             .convert("boot")
438             .map_err(Error::ParseCpus)?
439             .unwrap_or(DEFAULT_VCPUS);
440         let max_vcpus: u8 = parser
441             .convert("max")
442             .map_err(Error::ParseCpus)?
443             .unwrap_or(boot_vcpus);
444         let topology = parser.convert("topology").map_err(Error::ParseCpus)?;
445         let kvm_hyperv = parser
446             .convert::<Toggle>("kvm_hyperv")
447             .map_err(Error::ParseCpus)?
448             .unwrap_or(Toggle(false))
449             .0;
450         let max_phys_bits = parser
451             .convert::<u8>("max_phys_bits")
452             .map_err(Error::ParseCpus)?;
453 
454         Ok(CpusConfig {
455             boot_vcpus,
456             max_vcpus,
457             topology,
458             kvm_hyperv,
459             max_phys_bits,
460         })
461     }
462 }
463 
464 impl Default for CpusConfig {
465     fn default() -> Self {
466         CpusConfig {
467             boot_vcpus: DEFAULT_VCPUS,
468             max_vcpus: DEFAULT_VCPUS,
469             topology: None,
470             kvm_hyperv: false,
471             max_phys_bits: None,
472         }
473     }
474 }
475 
476 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
477 pub struct MemoryZoneConfig {
478     pub id: String,
479     pub size: u64,
480     #[serde(default)]
481     pub file: Option<PathBuf>,
482     #[serde(default)]
483     pub shared: bool,
484     #[serde(default)]
485     pub hugepages: bool,
486     #[serde(default)]
487     pub hugepage_size: Option<u64>,
488     #[serde(default)]
489     pub host_numa_node: Option<u32>,
490     #[serde(default)]
491     pub hotplug_size: Option<u64>,
492     #[serde(default)]
493     pub hotplugged_size: Option<u64>,
494 }
495 
496 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
497 pub struct MemoryConfig {
498     pub size: u64,
499     #[serde(default)]
500     pub mergeable: bool,
501     #[serde(default)]
502     pub hotplug_method: HotplugMethod,
503     #[serde(default)]
504     pub hotplug_size: Option<u64>,
505     #[serde(default)]
506     pub hotplugged_size: Option<u64>,
507     #[serde(default)]
508     pub shared: bool,
509     #[serde(default)]
510     pub hugepages: bool,
511     #[serde(default)]
512     pub hugepage_size: Option<u64>,
513     #[serde(default)]
514     pub zones: Option<Vec<MemoryZoneConfig>>,
515 }
516 
517 impl MemoryConfig {
518     pub fn parse(memory: &str, memory_zones: Option<Vec<&str>>) -> Result<Self> {
519         let mut parser = OptionParser::new();
520         parser
521             .add("size")
522             .add("file")
523             .add("mergeable")
524             .add("hotplug_method")
525             .add("hotplug_size")
526             .add("hotplugged_size")
527             .add("shared")
528             .add("hugepages")
529             .add("hugepage_size");
530         parser.parse(memory).map_err(Error::ParseMemory)?;
531 
532         let size = parser
533             .convert::<ByteSized>("size")
534             .map_err(Error::ParseMemory)?
535             .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20))
536             .0;
537         let mergeable = parser
538             .convert::<Toggle>("mergeable")
539             .map_err(Error::ParseMemory)?
540             .unwrap_or(Toggle(false))
541             .0;
542         let hotplug_method = parser
543             .convert("hotplug_method")
544             .map_err(Error::ParseMemory)?
545             .unwrap_or_default();
546         let hotplug_size = parser
547             .convert::<ByteSized>("hotplug_size")
548             .map_err(Error::ParseMemory)?
549             .map(|v| v.0);
550         let hotplugged_size = parser
551             .convert::<ByteSized>("hotplugged_size")
552             .map_err(Error::ParseMemory)?
553             .map(|v| v.0);
554         let shared = parser
555             .convert::<Toggle>("shared")
556             .map_err(Error::ParseMemory)?
557             .unwrap_or(Toggle(false))
558             .0;
559         let hugepages = parser
560             .convert::<Toggle>("hugepages")
561             .map_err(Error::ParseMemory)?
562             .unwrap_or(Toggle(false))
563             .0;
564         let hugepage_size = parser
565             .convert::<ByteSized>("hugepage_size")
566             .map_err(Error::ParseMemory)?
567             .map(|v| v.0);
568 
569         let zones: Option<Vec<MemoryZoneConfig>> = if let Some(memory_zones) = &memory_zones {
570             let mut zones = Vec::new();
571             for memory_zone in memory_zones.iter() {
572                 let mut parser = OptionParser::new();
573                 parser
574                     .add("id")
575                     .add("size")
576                     .add("file")
577                     .add("shared")
578                     .add("hugepages")
579                     .add("hugepage_size")
580                     .add("host_numa_node")
581                     .add("hotplug_size")
582                     .add("hotplugged_size");
583                 parser.parse(memory_zone).map_err(Error::ParseMemoryZone)?;
584 
585                 let id = parser.get("id").ok_or(Error::ParseMemoryZoneIdMissing)?;
586                 let size = parser
587                     .convert::<ByteSized>("size")
588                     .map_err(Error::ParseMemoryZone)?
589                     .unwrap_or(ByteSized(DEFAULT_MEMORY_MB << 20))
590                     .0;
591                 let file = parser.get("file").map(PathBuf::from);
592                 let shared = parser
593                     .convert::<Toggle>("shared")
594                     .map_err(Error::ParseMemoryZone)?
595                     .unwrap_or(Toggle(false))
596                     .0;
597                 let hugepages = parser
598                     .convert::<Toggle>("hugepages")
599                     .map_err(Error::ParseMemoryZone)?
600                     .unwrap_or(Toggle(false))
601                     .0;
602                 let hugepage_size = parser
603                     .convert::<ByteSized>("hugepage_size")
604                     .map_err(Error::ParseMemoryZone)?
605                     .map(|v| v.0);
606 
607                 let host_numa_node = parser
608                     .convert::<u32>("host_numa_node")
609                     .map_err(Error::ParseMemoryZone)?;
610                 let hotplug_size = parser
611                     .convert::<ByteSized>("hotplug_size")
612                     .map_err(Error::ParseMemoryZone)?
613                     .map(|v| v.0);
614                 let hotplugged_size = parser
615                     .convert::<ByteSized>("hotplugged_size")
616                     .map_err(Error::ParseMemoryZone)?
617                     .map(|v| v.0);
618 
619                 zones.push(MemoryZoneConfig {
620                     id,
621                     size,
622                     file,
623                     shared,
624                     hugepages,
625                     hugepage_size,
626                     host_numa_node,
627                     hotplug_size,
628                     hotplugged_size,
629                 });
630             }
631             Some(zones)
632         } else {
633             None
634         };
635 
636         Ok(MemoryConfig {
637             size,
638             mergeable,
639             hotplug_method,
640             hotplug_size,
641             hotplugged_size,
642             shared,
643             hugepages,
644             hugepage_size,
645             zones,
646         })
647     }
648 
649     pub fn total_size(&self) -> u64 {
650         let mut size = self.size;
651         if let Some(hotplugged_size) = self.hotplugged_size {
652             size += hotplugged_size;
653         }
654 
655         if let Some(zones) = &self.zones {
656             for zone in zones.iter() {
657                 size += zone.size;
658                 if let Some(hotplugged_size) = zone.hotplugged_size {
659                     size += hotplugged_size;
660                 }
661             }
662         }
663 
664         size
665     }
666 }
667 
668 impl Default for MemoryConfig {
669     fn default() -> Self {
670         MemoryConfig {
671             size: DEFAULT_MEMORY_MB << 20,
672             mergeable: false,
673             hotplug_method: HotplugMethod::Acpi,
674             hotplug_size: None,
675             hotplugged_size: None,
676             shared: false,
677             hugepages: false,
678             hugepage_size: None,
679             zones: None,
680         }
681     }
682 }
683 
684 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
685 pub struct KernelConfig {
686     pub path: PathBuf,
687 }
688 
689 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
690 pub struct InitramfsConfig {
691     pub path: PathBuf,
692 }
693 
694 #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
695 pub struct CmdlineConfig {
696     pub args: String,
697 }
698 
699 impl CmdlineConfig {
700     pub fn parse(cmdline: Option<&str>) -> Result<Self> {
701         let args = cmdline
702             .map(std::string::ToString::to_string)
703             .unwrap_or_else(String::new);
704 
705         Ok(CmdlineConfig { args })
706     }
707 }
708 
709 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
710 pub struct DiskConfig {
711     pub path: Option<PathBuf>,
712     #[serde(default)]
713     pub readonly: bool,
714     #[serde(default)]
715     pub direct: bool,
716     #[serde(default)]
717     pub iommu: bool,
718     #[serde(default = "default_diskconfig_num_queues")]
719     pub num_queues: usize,
720     #[serde(default = "default_diskconfig_queue_size")]
721     pub queue_size: u16,
722     #[serde(default)]
723     pub vhost_user: bool,
724     pub vhost_socket: Option<String>,
725     #[serde(default = "default_diskconfig_poll_queue")]
726     pub poll_queue: bool,
727     #[serde(default)]
728     pub rate_limiter_config: Option<RateLimiterConfig>,
729     #[serde(default)]
730     pub id: Option<String>,
731     // For testing use only. Not exposed in API.
732     #[serde(default)]
733     pub disable_io_uring: bool,
734 }
735 
736 fn default_diskconfig_num_queues() -> usize {
737     DEFAULT_NUM_QUEUES_VUBLK
738 }
739 
740 fn default_diskconfig_queue_size() -> u16 {
741     DEFAULT_QUEUE_SIZE_VUBLK
742 }
743 
744 fn default_diskconfig_poll_queue() -> bool {
745     true
746 }
747 
748 impl Default for DiskConfig {
749     fn default() -> Self {
750         Self {
751             path: None,
752             readonly: false,
753             direct: false,
754             iommu: false,
755             num_queues: default_diskconfig_num_queues(),
756             queue_size: default_diskconfig_queue_size(),
757             vhost_user: false,
758             vhost_socket: None,
759             poll_queue: default_diskconfig_poll_queue(),
760             id: None,
761             disable_io_uring: false,
762             rate_limiter_config: None,
763         }
764     }
765 }
766 
767 impl DiskConfig {
768     pub const SYNTAX: &'static str = "Disk parameters \
769          \"path=<disk_image_path>,readonly=on|off,direct=on|off,iommu=on|off,\
770          num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,\
771          vhost_user=on|off,socket=<vhost_user_socket_path>,poll_queue=on|off,\
772          bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\
773          ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>,\
774          id=<device_id>\"";
775 
776     pub fn parse(disk: &str) -> Result<Self> {
777         let mut parser = OptionParser::new();
778         parser
779             .add("path")
780             .add("readonly")
781             .add("direct")
782             .add("iommu")
783             .add("queue_size")
784             .add("num_queues")
785             .add("vhost_user")
786             .add("socket")
787             .add("poll_queue")
788             .add("bw_size")
789             .add("bw_one_time_burst")
790             .add("bw_refill_time")
791             .add("ops_size")
792             .add("ops_one_time_burst")
793             .add("ops_refill_time")
794             .add("id")
795             .add("_disable_io_uring");
796         parser.parse(disk).map_err(Error::ParseDisk)?;
797 
798         let path = parser.get("path").map(PathBuf::from);
799         let readonly = parser
800             .convert::<Toggle>("readonly")
801             .map_err(Error::ParseDisk)?
802             .unwrap_or(Toggle(false))
803             .0;
804         let direct = parser
805             .convert::<Toggle>("direct")
806             .map_err(Error::ParseDisk)?
807             .unwrap_or(Toggle(false))
808             .0;
809         let iommu = parser
810             .convert::<Toggle>("iommu")
811             .map_err(Error::ParseDisk)?
812             .unwrap_or(Toggle(false))
813             .0;
814         let queue_size = parser
815             .convert("queue_size")
816             .map_err(Error::ParseDisk)?
817             .unwrap_or_else(default_diskconfig_queue_size);
818         let num_queues = parser
819             .convert("num_queues")
820             .map_err(Error::ParseDisk)?
821             .unwrap_or_else(default_diskconfig_num_queues);
822         let vhost_user = parser
823             .convert::<Toggle>("vhost_user")
824             .map_err(Error::ParseDisk)?
825             .unwrap_or(Toggle(false))
826             .0;
827         let vhost_socket = parser.get("socket");
828         let poll_queue = parser
829             .convert::<Toggle>("poll_queue")
830             .map_err(Error::ParseDisk)?
831             .unwrap_or_else(|| Toggle(default_diskconfig_poll_queue()))
832             .0;
833         let id = parser.get("id");
834         let disable_io_uring = parser
835             .convert::<Toggle>("_disable_io_uring")
836             .map_err(Error::ParseDisk)?
837             .unwrap_or(Toggle(false))
838             .0;
839         let bw_size = parser
840             .convert("bw_size")
841             .map_err(Error::ParseDisk)?
842             .unwrap_or_default();
843         let bw_one_time_burst = parser
844             .convert("bw_one_time_burst")
845             .map_err(Error::ParseDisk)?
846             .unwrap_or_default();
847         let bw_refill_time = parser
848             .convert("bw_refill_time")
849             .map_err(Error::ParseDisk)?
850             .unwrap_or_default();
851         let ops_size = parser
852             .convert("ops_size")
853             .map_err(Error::ParseDisk)?
854             .unwrap_or_default();
855         let ops_one_time_burst = parser
856             .convert("ops_one_time_burst")
857             .map_err(Error::ParseDisk)?
858             .unwrap_or_default();
859         let ops_refill_time = parser
860             .convert("ops_refill_time")
861             .map_err(Error::ParseDisk)?
862             .unwrap_or_default();
863         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
864             Some(TokenBucketConfig {
865                 size: bw_size,
866                 one_time_burst: Some(bw_one_time_burst),
867                 refill_time: bw_refill_time,
868             })
869         } else {
870             None
871         };
872         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
873             Some(TokenBucketConfig {
874                 size: ops_size,
875                 one_time_burst: Some(ops_one_time_burst),
876                 refill_time: ops_refill_time,
877             })
878         } else {
879             None
880         };
881         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
882             Some(RateLimiterConfig {
883                 bandwidth: bw_tb_config,
884                 ops: ops_tb_config,
885             })
886         } else {
887             None
888         };
889 
890         if parser.is_set("poll_queue") && !vhost_user {
891             warn!("poll_queue parameter currently only has effect when used vhost_user=true");
892         }
893 
894         Ok(DiskConfig {
895             path,
896             readonly,
897             direct,
898             iommu,
899             num_queues,
900             queue_size,
901             vhost_user,
902             vhost_socket,
903             poll_queue,
904             rate_limiter_config,
905             id,
906             disable_io_uring,
907         })
908     }
909 
910     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
911         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
912             return Err(ValidationError::TooManyQueues);
913         }
914 
915         Ok(())
916     }
917 }
918 
919 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
920 pub enum VhostMode {
921     Client,
922     Server,
923 }
924 
925 impl Default for VhostMode {
926     fn default() -> Self {
927         VhostMode::Client
928     }
929 }
930 
931 #[derive(Debug)]
932 pub enum ParseVhostModeError {
933     InvalidValue(String),
934 }
935 
936 impl FromStr for VhostMode {
937     type Err = ParseVhostModeError;
938 
939     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
940         match s.to_lowercase().as_str() {
941             "client" => Ok(VhostMode::Client),
942             "server" => Ok(VhostMode::Server),
943             _ => Err(ParseVhostModeError::InvalidValue(s.to_owned())),
944         }
945     }
946 }
947 
948 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
949 pub struct NetConfig {
950     #[serde(default = "default_netconfig_tap")]
951     pub tap: Option<String>,
952     #[serde(default = "default_netconfig_ip")]
953     pub ip: Ipv4Addr,
954     #[serde(default = "default_netconfig_mask")]
955     pub mask: Ipv4Addr,
956     #[serde(default = "default_netconfig_mac")]
957     pub mac: MacAddr,
958     #[serde(default)]
959     pub host_mac: Option<MacAddr>,
960     #[serde(default)]
961     pub iommu: bool,
962     #[serde(default = "default_netconfig_num_queues")]
963     pub num_queues: usize,
964     #[serde(default = "default_netconfig_queue_size")]
965     pub queue_size: u16,
966     #[serde(default)]
967     pub vhost_user: bool,
968     pub vhost_socket: Option<String>,
969     #[serde(default)]
970     pub vhost_mode: VhostMode,
971     #[serde(default)]
972     pub id: Option<String>,
973     #[serde(default)]
974     pub fds: Option<Vec<i32>>,
975     #[serde(default)]
976     pub rate_limiter_config: Option<RateLimiterConfig>,
977 }
978 
979 fn default_netconfig_tap() -> Option<String> {
980     None
981 }
982 
983 fn default_netconfig_ip() -> Ipv4Addr {
984     Ipv4Addr::new(192, 168, 249, 1)
985 }
986 
987 fn default_netconfig_mask() -> Ipv4Addr {
988     Ipv4Addr::new(255, 255, 255, 0)
989 }
990 
991 fn default_netconfig_mac() -> MacAddr {
992     MacAddr::local_random()
993 }
994 
995 fn default_netconfig_num_queues() -> usize {
996     DEFAULT_NUM_QUEUES_VUNET
997 }
998 
999 fn default_netconfig_queue_size() -> u16 {
1000     DEFAULT_QUEUE_SIZE_VUNET
1001 }
1002 
1003 impl Default for NetConfig {
1004     fn default() -> Self {
1005         Self {
1006             tap: default_netconfig_tap(),
1007             ip: default_netconfig_ip(),
1008             mask: default_netconfig_mask(),
1009             mac: default_netconfig_mac(),
1010             host_mac: None,
1011             iommu: false,
1012             num_queues: default_netconfig_num_queues(),
1013             queue_size: default_netconfig_queue_size(),
1014             vhost_user: false,
1015             vhost_socket: None,
1016             vhost_mode: VhostMode::Client,
1017             id: None,
1018             fds: None,
1019             rate_limiter_config: None,
1020         }
1021     }
1022 }
1023 
1024 impl NetConfig {
1025     pub const SYNTAX: &'static str = "Network parameters \
1026     \"tap=<if_name>,ip=<ip_addr>,mask=<net_mask>,mac=<mac_addr>,fd=<fd1:fd2...>,iommu=on|off,\
1027     num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,id=<device_id>,\
1028     vhost_user=<vhost_user_enable>,socket=<vhost_user_socket_path>,vhost_mode=client|server,\
1029     bw_size=<bytes>,bw_one_time_burst=<bytes>,bw_refill_time=<ms>,\
1030     ops_size=<io_ops>,ops_one_time_burst=<io_ops>,ops_refill_time=<ms>\"";
1031 
1032     pub fn parse(net: &str) -> Result<Self> {
1033         let mut parser = OptionParser::new();
1034 
1035         parser
1036             .add("tap")
1037             .add("ip")
1038             .add("mask")
1039             .add("mac")
1040             .add("host_mac")
1041             .add("iommu")
1042             .add("queue_size")
1043             .add("num_queues")
1044             .add("vhost_user")
1045             .add("socket")
1046             .add("vhost_mode")
1047             .add("id")
1048             .add("fd")
1049             .add("bw_size")
1050             .add("bw_one_time_burst")
1051             .add("bw_refill_time")
1052             .add("ops_size")
1053             .add("ops_one_time_burst")
1054             .add("ops_refill_time");
1055         parser.parse(net).map_err(Error::ParseNetwork)?;
1056 
1057         let tap = parser.get("tap");
1058         let ip = parser
1059             .convert("ip")
1060             .map_err(Error::ParseNetwork)?
1061             .unwrap_or_else(default_netconfig_ip);
1062         let mask = parser
1063             .convert("mask")
1064             .map_err(Error::ParseNetwork)?
1065             .unwrap_or_else(default_netconfig_mask);
1066         let mac = parser
1067             .convert("mac")
1068             .map_err(Error::ParseNetwork)?
1069             .unwrap_or_else(default_netconfig_mac);
1070         let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?;
1071         let iommu = parser
1072             .convert::<Toggle>("iommu")
1073             .map_err(Error::ParseNetwork)?
1074             .unwrap_or(Toggle(false))
1075             .0;
1076         let queue_size = parser
1077             .convert("queue_size")
1078             .map_err(Error::ParseNetwork)?
1079             .unwrap_or_else(default_netconfig_queue_size);
1080         let num_queues = parser
1081             .convert("num_queues")
1082             .map_err(Error::ParseNetwork)?
1083             .unwrap_or_else(default_netconfig_num_queues);
1084         let vhost_user = parser
1085             .convert::<Toggle>("vhost_user")
1086             .map_err(Error::ParseNetwork)?
1087             .unwrap_or(Toggle(false))
1088             .0;
1089         let vhost_socket = parser.get("socket");
1090         let vhost_mode = parser
1091             .convert("vhost_mode")
1092             .map_err(Error::ParseNetwork)?
1093             .unwrap_or_default();
1094         let id = parser.get("id");
1095         let fds = parser
1096             .convert::<IntegerList>("fd")
1097             .map_err(Error::ParseNetwork)?
1098             .map(|v| v.0.iter().map(|e| *e as i32).collect());
1099 
1100         let bw_size = parser
1101             .convert("bw_size")
1102             .map_err(Error::ParseDisk)?
1103             .unwrap_or_default();
1104         let bw_one_time_burst = parser
1105             .convert("bw_one_time_burst")
1106             .map_err(Error::ParseDisk)?
1107             .unwrap_or_default();
1108         let bw_refill_time = parser
1109             .convert("bw_refill_time")
1110             .map_err(Error::ParseDisk)?
1111             .unwrap_or_default();
1112         let ops_size = parser
1113             .convert("ops_size")
1114             .map_err(Error::ParseDisk)?
1115             .unwrap_or_default();
1116         let ops_one_time_burst = parser
1117             .convert("ops_one_time_burst")
1118             .map_err(Error::ParseDisk)?
1119             .unwrap_or_default();
1120         let ops_refill_time = parser
1121             .convert("ops_refill_time")
1122             .map_err(Error::ParseDisk)?
1123             .unwrap_or_default();
1124         let bw_tb_config = if bw_size != 0 && bw_refill_time != 0 {
1125             Some(TokenBucketConfig {
1126                 size: bw_size,
1127                 one_time_burst: Some(bw_one_time_burst),
1128                 refill_time: bw_refill_time,
1129             })
1130         } else {
1131             None
1132         };
1133         let ops_tb_config = if ops_size != 0 && ops_refill_time != 0 {
1134             Some(TokenBucketConfig {
1135                 size: ops_size,
1136                 one_time_burst: Some(ops_one_time_burst),
1137                 refill_time: ops_refill_time,
1138             })
1139         } else {
1140             None
1141         };
1142         let rate_limiter_config = if bw_tb_config.is_some() || ops_tb_config.is_some() {
1143             Some(RateLimiterConfig {
1144                 bandwidth: bw_tb_config,
1145                 ops: ops_tb_config,
1146             })
1147         } else {
1148             None
1149         };
1150 
1151         let config = NetConfig {
1152             tap,
1153             ip,
1154             mask,
1155             mac,
1156             host_mac,
1157             iommu,
1158             num_queues,
1159             queue_size,
1160             vhost_user,
1161             vhost_socket,
1162             vhost_mode,
1163             id,
1164             fds,
1165             rate_limiter_config,
1166         };
1167         Ok(config)
1168     }
1169 
1170     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1171         if self.num_queues < 2 {
1172             return Err(ValidationError::VnetQueueLowerThan2);
1173         }
1174 
1175         if self.fds.is_some() && self.fds.as_ref().unwrap().len() * 2 != self.num_queues {
1176             return Err(ValidationError::VnetQueueFdMismatch);
1177         }
1178 
1179         if let Some(fds) = self.fds.as_ref() {
1180             for fd in fds {
1181                 if *fd <= 2 {
1182                     return Err(ValidationError::VnetReservedFd);
1183                 }
1184             }
1185         }
1186 
1187         if (self.num_queues / 2) > vm_config.cpus.boot_vcpus as usize {
1188             return Err(ValidationError::TooManyQueues);
1189         }
1190 
1191         Ok(())
1192     }
1193 }
1194 
1195 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1196 pub struct RngConfig {
1197     pub src: PathBuf,
1198     #[serde(default)]
1199     pub iommu: bool,
1200 }
1201 
1202 impl RngConfig {
1203     pub fn parse(rng: &str) -> Result<Self> {
1204         let mut parser = OptionParser::new();
1205         parser.add("src").add("iommu");
1206         parser.parse(rng).map_err(Error::ParseRng)?;
1207 
1208         let src = PathBuf::from(
1209             parser
1210                 .get("src")
1211                 .unwrap_or_else(|| DEFAULT_RNG_SOURCE.to_owned()),
1212         );
1213         let iommu = parser
1214             .convert::<Toggle>("iommu")
1215             .map_err(Error::ParseRng)?
1216             .unwrap_or(Toggle(false))
1217             .0;
1218 
1219         Ok(RngConfig { src, iommu })
1220     }
1221 }
1222 
1223 impl Default for RngConfig {
1224     fn default() -> Self {
1225         RngConfig {
1226             src: PathBuf::from(DEFAULT_RNG_SOURCE),
1227             iommu: false,
1228         }
1229     }
1230 }
1231 
1232 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1233 pub struct BalloonConfig {
1234     pub size: u64,
1235     /// Option to deflate the balloon in case the guest is out of memory.
1236     #[serde(default)]
1237     pub deflate_on_oom: bool,
1238 }
1239 
1240 impl BalloonConfig {
1241     pub const SYNTAX: &'static str =
1242         "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off\"";
1243 
1244     pub fn parse(balloon: &str) -> Result<Self> {
1245         let mut parser = OptionParser::new();
1246         parser.add("size");
1247         parser.add("deflate_on_oom");
1248         parser.parse(balloon).map_err(Error::ParseBalloon)?;
1249 
1250         let size = parser
1251             .convert::<ByteSized>("size")
1252             .map_err(Error::ParseBalloon)?
1253             .map(|v| v.0)
1254             .unwrap_or(0);
1255 
1256         let deflate_on_oom = parser
1257             .convert::<Toggle>("deflate_on_oom")
1258             .map_err(Error::ParseBalloon)?
1259             .unwrap_or(Toggle(false))
1260             .0;
1261 
1262         Ok(BalloonConfig {
1263             size,
1264             deflate_on_oom,
1265         })
1266     }
1267 }
1268 
1269 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1270 pub struct FsConfig {
1271     pub tag: String,
1272     pub socket: PathBuf,
1273     #[serde(default = "default_fsconfig_num_queues")]
1274     pub num_queues: usize,
1275     #[serde(default = "default_fsconfig_queue_size")]
1276     pub queue_size: u16,
1277     #[serde(default = "default_fsconfig_dax")]
1278     pub dax: bool,
1279     #[serde(default = "default_fsconfig_cache_size")]
1280     pub cache_size: u64,
1281     #[serde(default)]
1282     pub id: Option<String>,
1283 }
1284 
1285 fn default_fsconfig_num_queues() -> usize {
1286     1
1287 }
1288 
1289 fn default_fsconfig_queue_size() -> u16 {
1290     1024
1291 }
1292 
1293 fn default_fsconfig_dax() -> bool {
1294     true
1295 }
1296 
1297 fn default_fsconfig_cache_size() -> u64 {
1298     0x0002_0000_0000
1299 }
1300 
1301 impl Default for FsConfig {
1302     fn default() -> Self {
1303         Self {
1304             tag: "".to_owned(),
1305             socket: PathBuf::new(),
1306             num_queues: default_fsconfig_num_queues(),
1307             queue_size: default_fsconfig_queue_size(),
1308             dax: default_fsconfig_dax(),
1309             cache_size: default_fsconfig_cache_size(),
1310             id: None,
1311         }
1312     }
1313 }
1314 
1315 impl FsConfig {
1316     pub const SYNTAX: &'static str = "virtio-fs parameters \
1317     \"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\
1318     queue_size=<size_of_each_queue>,dax=on|off,cache_size=<DAX cache size: \
1319     default 8Gib>,id=<device_id>\"";
1320 
1321     pub fn parse(fs: &str) -> Result<Self> {
1322         let mut parser = OptionParser::new();
1323         parser
1324             .add("tag")
1325             .add("dax")
1326             .add("cache_size")
1327             .add("queue_size")
1328             .add("num_queues")
1329             .add("socket")
1330             .add("id");
1331         parser.parse(fs).map_err(Error::ParseFileSystem)?;
1332 
1333         let tag = parser.get("tag").ok_or(Error::ParseFsTagMissing)?;
1334         let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseFsSockMissing)?);
1335 
1336         let queue_size = parser
1337             .convert("queue_size")
1338             .map_err(Error::ParseFileSystem)?
1339             .unwrap_or_else(default_fsconfig_queue_size);
1340         let num_queues = parser
1341             .convert("num_queues")
1342             .map_err(Error::ParseFileSystem)?
1343             .unwrap_or_else(default_fsconfig_num_queues);
1344 
1345         let dax = parser
1346             .convert::<Toggle>("dax")
1347             .map_err(Error::ParseFileSystem)?
1348             .unwrap_or_else(|| Toggle(default_fsconfig_dax()))
1349             .0;
1350 
1351         if parser.is_set("cache_size") && !dax {
1352             return Err(Error::InvalidCacheSizeWithDaxOff);
1353         }
1354 
1355         let cache_size = parser
1356             .convert::<ByteSized>("cache_size")
1357             .map_err(Error::ParseFileSystem)?
1358             .unwrap_or_else(|| ByteSized(default_fsconfig_cache_size()))
1359             .0;
1360 
1361         let id = parser.get("id");
1362 
1363         Ok(FsConfig {
1364             tag,
1365             socket,
1366             num_queues,
1367             queue_size,
1368             dax,
1369             cache_size,
1370             id,
1371         })
1372     }
1373 
1374     pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
1375         if self.num_queues > vm_config.cpus.boot_vcpus as usize {
1376             return Err(ValidationError::TooManyQueues);
1377         }
1378 
1379         Ok(())
1380     }
1381 }
1382 
1383 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1384 pub struct PmemConfig {
1385     pub file: PathBuf,
1386     #[serde(default)]
1387     pub size: Option<u64>,
1388     #[serde(default)]
1389     pub iommu: bool,
1390     #[serde(default)]
1391     pub mergeable: bool,
1392     #[serde(default)]
1393     pub discard_writes: bool,
1394     #[serde(default)]
1395     pub id: Option<String>,
1396 }
1397 
1398 impl PmemConfig {
1399     pub const SYNTAX: &'static str = "Persistent memory parameters \
1400     \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\
1401     mergeable=on|off,discard_writes=on|off,id=<device_id>\"";
1402     pub fn parse(pmem: &str) -> Result<Self> {
1403         let mut parser = OptionParser::new();
1404         parser
1405             .add("size")
1406             .add("file")
1407             .add("mergeable")
1408             .add("iommu")
1409             .add("discard_writes")
1410             .add("id");
1411         parser.parse(pmem).map_err(Error::ParsePersistentMemory)?;
1412 
1413         let file = PathBuf::from(parser.get("file").ok_or(Error::ParsePmemFileMissing)?);
1414         let size = parser
1415             .convert::<ByteSized>("size")
1416             .map_err(Error::ParsePersistentMemory)?
1417             .map(|v| v.0);
1418         let mergeable = parser
1419             .convert::<Toggle>("mergeable")
1420             .map_err(Error::ParsePersistentMemory)?
1421             .unwrap_or(Toggle(false))
1422             .0;
1423         let iommu = parser
1424             .convert::<Toggle>("iommu")
1425             .map_err(Error::ParsePersistentMemory)?
1426             .unwrap_or(Toggle(false))
1427             .0;
1428         let discard_writes = parser
1429             .convert::<Toggle>("discard_writes")
1430             .map_err(Error::ParsePersistentMemory)?
1431             .unwrap_or(Toggle(false))
1432             .0;
1433         let id = parser.get("id");
1434 
1435         Ok(PmemConfig {
1436             file,
1437             size,
1438             iommu,
1439             mergeable,
1440             discard_writes,
1441             id,
1442         })
1443     }
1444 }
1445 
1446 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1447 pub enum ConsoleOutputMode {
1448     Off,
1449     Pty,
1450     Tty,
1451     File,
1452     Null,
1453 }
1454 
1455 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1456 pub struct ConsoleConfig {
1457     #[serde(default = "default_consoleconfig_file")]
1458     pub file: Option<PathBuf>,
1459     pub mode: ConsoleOutputMode,
1460     #[serde(default)]
1461     pub iommu: bool,
1462 }
1463 
1464 fn default_consoleconfig_file() -> Option<PathBuf> {
1465     None
1466 }
1467 
1468 impl ConsoleConfig {
1469     pub fn parse(console: &str) -> Result<Self> {
1470         let mut parser = OptionParser::new();
1471         parser
1472             .add_valueless("off")
1473             .add_valueless("pty")
1474             .add_valueless("tty")
1475             .add_valueless("null")
1476             .add("file")
1477             .add("iommu");
1478         parser.parse(console).map_err(Error::ParseConsole)?;
1479 
1480         let mut file: Option<PathBuf> = default_consoleconfig_file();
1481         let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
1482 
1483         if parser.is_set("off") {
1484         } else if parser.is_set("pty") {
1485             mode = ConsoleOutputMode::Pty
1486         } else if parser.is_set("tty") {
1487             mode = ConsoleOutputMode::Tty
1488         } else if parser.is_set("null") {
1489             mode = ConsoleOutputMode::Null
1490         } else if parser.is_set("file") {
1491             mode = ConsoleOutputMode::File;
1492             file =
1493                 Some(PathBuf::from(parser.get("file").ok_or(
1494                     Error::Validation(ValidationError::ConsoleFileMissing),
1495                 )?));
1496         } else {
1497             return Err(Error::ParseConsoleInvalidModeGiven);
1498         }
1499         let iommu = parser
1500             .convert::<Toggle>("iommu")
1501             .map_err(Error::ParseConsole)?
1502             .unwrap_or(Toggle(false))
1503             .0;
1504 
1505         Ok(Self { file, mode, iommu })
1506     }
1507 
1508     pub fn default_serial() -> Self {
1509         ConsoleConfig {
1510             file: None,
1511             mode: ConsoleOutputMode::Null,
1512             iommu: false,
1513         }
1514     }
1515 
1516     pub fn default_console() -> Self {
1517         ConsoleConfig {
1518             file: None,
1519             mode: ConsoleOutputMode::Tty,
1520             iommu: false,
1521         }
1522     }
1523 }
1524 
1525 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1526 pub struct DeviceConfig {
1527     pub path: PathBuf,
1528     #[serde(default)]
1529     pub iommu: bool,
1530     #[serde(default)]
1531     pub id: Option<String>,
1532 }
1533 
1534 impl DeviceConfig {
1535     pub const SYNTAX: &'static str =
1536         "Direct device assignment parameters \"path=<device_path>,iommu=on|off,id=<device_id>\"";
1537     pub fn parse(device: &str) -> Result<Self> {
1538         let mut parser = OptionParser::new();
1539         parser.add("path").add("id").add("iommu");
1540         parser.parse(device).map_err(Error::ParseDevice)?;
1541 
1542         let path = parser
1543             .get("path")
1544             .map(PathBuf::from)
1545             .ok_or(Error::ParseDevicePathMissing)?;
1546         let iommu = parser
1547             .convert::<Toggle>("iommu")
1548             .map_err(Error::ParseDevice)?
1549             .unwrap_or(Toggle(false))
1550             .0;
1551         let id = parser.get("id");
1552         Ok(DeviceConfig { path, iommu, id })
1553     }
1554 }
1555 
1556 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1557 pub struct UserDeviceConfig {
1558     pub socket: PathBuf,
1559     #[serde(default)]
1560     pub id: Option<String>,
1561 }
1562 
1563 impl UserDeviceConfig {
1564     pub const SYNTAX: &'static str = "Userspace device socket=<socket_path>,id=<device_id>\"";
1565     pub fn parse(user_device: &str) -> Result<Self> {
1566         let mut parser = OptionParser::new();
1567         parser.add("socket").add("id");
1568         parser.parse(user_device).map_err(Error::ParseUserDevice)?;
1569 
1570         let socket = parser
1571             .get("socket")
1572             .map(PathBuf::from)
1573             .ok_or(Error::ParseUserDeviceSocketMissing)?;
1574 
1575         let id = parser.get("id");
1576 
1577         Ok(UserDeviceConfig { socket, id })
1578     }
1579 }
1580 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1581 pub struct VsockConfig {
1582     pub cid: u64,
1583     pub socket: PathBuf,
1584     #[serde(default)]
1585     pub iommu: bool,
1586     #[serde(default)]
1587     pub id: Option<String>,
1588 }
1589 
1590 impl VsockConfig {
1591     pub const SYNTAX: &'static str = "Virtio VSOCK parameters \
1592         \"cid=<context_id>,socket=<socket_path>,iommu=on|off,id=<device_id>\"";
1593     pub fn parse(vsock: &str) -> Result<Self> {
1594         let mut parser = OptionParser::new();
1595         parser.add("socket").add("cid").add("iommu").add("id");
1596         parser.parse(vsock).map_err(Error::ParseVsock)?;
1597 
1598         let socket = parser
1599             .get("socket")
1600             .map(PathBuf::from)
1601             .ok_or(Error::ParseVsockSockMissing)?;
1602         let iommu = parser
1603             .convert::<Toggle>("iommu")
1604             .map_err(Error::ParseVsock)?
1605             .unwrap_or(Toggle(false))
1606             .0;
1607         let cid = parser
1608             .convert("cid")
1609             .map_err(Error::ParseVsock)?
1610             .ok_or(Error::ParseVsockCidMissing)?;
1611         let id = parser.get("id");
1612 
1613         Ok(VsockConfig {
1614             cid,
1615             socket,
1616             iommu,
1617             id,
1618         })
1619     }
1620 }
1621 
1622 #[cfg(feature = "tdx")]
1623 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1624 pub struct TdxConfig {
1625     pub firmware: PathBuf,
1626 }
1627 
1628 #[cfg(feature = "tdx")]
1629 impl TdxConfig {
1630     pub fn parse(tdx: &str) -> Result<Self> {
1631         let mut parser = OptionParser::new();
1632         parser.add("firmware");
1633         parser.parse(tdx).map_err(Error::ParseTdx)?;
1634         let firmware = parser
1635             .get("firmware")
1636             .map(PathBuf::from)
1637             .ok_or(Error::FirmwarePathMissing)?;
1638         Ok(TdxConfig { firmware })
1639     }
1640 }
1641 
1642 #[cfg(target_arch = "x86_64")]
1643 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1644 pub struct SgxEpcConfig {
1645     pub id: String,
1646     #[serde(default)]
1647     pub size: u64,
1648     #[serde(default)]
1649     pub prefault: bool,
1650 }
1651 
1652 #[cfg(target_arch = "x86_64")]
1653 impl SgxEpcConfig {
1654     pub const SYNTAX: &'static str = "SGX EPC parameters \
1655         \"id=<epc_section_identifier>,size=<epc_section_size>,prefault=on|off\"";
1656     pub fn parse(sgx_epc: &str) -> Result<Self> {
1657         let mut parser = OptionParser::new();
1658         parser.add("id").add("size").add("prefault");
1659         parser.parse(sgx_epc).map_err(Error::ParseSgxEpc)?;
1660 
1661         let id = parser.get("id").ok_or(Error::ParseSgxEpcIdMissing)?;
1662         let size = parser
1663             .convert::<ByteSized>("size")
1664             .map_err(Error::ParseSgxEpc)?
1665             .unwrap_or(ByteSized(0))
1666             .0;
1667         let prefault = parser
1668             .convert::<Toggle>("prefault")
1669             .map_err(Error::ParseSgxEpc)?
1670             .unwrap_or(Toggle(false))
1671             .0;
1672 
1673         Ok(SgxEpcConfig { id, size, prefault })
1674     }
1675 }
1676 
1677 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1678 pub struct NumaDistance {
1679     #[serde(default)]
1680     pub destination: u32,
1681     #[serde(default)]
1682     pub distance: u8,
1683 }
1684 
1685 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1686 pub struct NumaConfig {
1687     #[serde(default)]
1688     pub guest_numa_id: u32,
1689     #[serde(default)]
1690     pub cpus: Option<Vec<u8>>,
1691     #[serde(default)]
1692     pub distances: Option<Vec<NumaDistance>>,
1693     #[serde(default)]
1694     pub memory_zones: Option<Vec<String>>,
1695     #[cfg(target_arch = "x86_64")]
1696     #[serde(default)]
1697     pub sgx_epc_sections: Option<Vec<String>>,
1698 }
1699 
1700 impl NumaConfig {
1701     pub const SYNTAX: &'static str = "Settings related to a given NUMA node \
1702         \"guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,\
1703         memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>\"";
1704     pub fn parse(numa: &str) -> Result<Self> {
1705         let mut parser = OptionParser::new();
1706         parser
1707             .add("guest_numa_id")
1708             .add("cpus")
1709             .add("distances")
1710             .add("memory_zones")
1711             .add("sgx_epc_sections");
1712         parser.parse(numa).map_err(Error::ParseNuma)?;
1713 
1714         let guest_numa_id = parser
1715             .convert::<u32>("guest_numa_id")
1716             .map_err(Error::ParseNuma)?
1717             .unwrap_or(0);
1718         let cpus = parser
1719             .convert::<IntegerList>("cpus")
1720             .map_err(Error::ParseNuma)?
1721             .map(|v| v.0.iter().map(|e| *e as u8).collect());
1722         let distances = parser
1723             .convert::<TupleTwoIntegers>("distances")
1724             .map_err(Error::ParseNuma)?
1725             .map(|v| {
1726                 v.0.iter()
1727                     .map(|(e1, e2)| NumaDistance {
1728                         destination: *e1 as u32,
1729                         distance: *e2 as u8,
1730                     })
1731                     .collect()
1732             });
1733         let memory_zones = parser
1734             .convert::<StringList>("memory_zones")
1735             .map_err(Error::ParseNuma)?
1736             .map(|v| v.0);
1737         #[cfg(target_arch = "x86_64")]
1738         let sgx_epc_sections = parser
1739             .convert::<StringList>("sgx_epc_sections")
1740             .map_err(Error::ParseNuma)?
1741             .map(|v| v.0);
1742 
1743         Ok(NumaConfig {
1744             guest_numa_id,
1745             cpus,
1746             distances,
1747             memory_zones,
1748             #[cfg(target_arch = "x86_64")]
1749             sgx_epc_sections,
1750         })
1751     }
1752 }
1753 
1754 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
1755 pub struct RestoreConfig {
1756     pub source_url: PathBuf,
1757     #[serde(default)]
1758     pub prefault: bool,
1759 }
1760 
1761 impl RestoreConfig {
1762     pub const SYNTAX: &'static str = "Restore from a VM snapshot. \
1763         \nRestore parameters \"source_url=<source_url>,prefault=on|off\" \
1764         \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \
1765         \n`prefault` brings memory pages in when enabled (disabled by default)";
1766     pub fn parse(restore: &str) -> Result<Self> {
1767         let mut parser = OptionParser::new();
1768         parser.add("source_url").add("prefault");
1769         parser.parse(restore).map_err(Error::ParseRestore)?;
1770 
1771         let source_url = parser
1772             .get("source_url")
1773             .map(PathBuf::from)
1774             .ok_or(Error::ParseRestoreSourceUrlMissing)?;
1775         let prefault = parser
1776             .convert::<Toggle>("prefault")
1777             .map_err(Error::ParseRestore)?
1778             .unwrap_or(Toggle(false))
1779             .0;
1780 
1781         Ok(RestoreConfig {
1782             source_url,
1783             prefault,
1784         })
1785     }
1786 }
1787 
1788 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1789 pub struct VmConfig {
1790     #[serde(default)]
1791     pub cpus: CpusConfig,
1792     #[serde(default)]
1793     pub memory: MemoryConfig,
1794     pub kernel: Option<KernelConfig>,
1795     #[serde(default)]
1796     pub initramfs: Option<InitramfsConfig>,
1797     #[serde(default)]
1798     pub cmdline: CmdlineConfig,
1799     pub disks: Option<Vec<DiskConfig>>,
1800     pub net: Option<Vec<NetConfig>>,
1801     #[serde(default)]
1802     pub rng: RngConfig,
1803     pub balloon: Option<BalloonConfig>,
1804     pub fs: Option<Vec<FsConfig>>,
1805     pub pmem: Option<Vec<PmemConfig>>,
1806     #[serde(default = "ConsoleConfig::default_serial")]
1807     pub serial: ConsoleConfig,
1808     #[serde(default = "ConsoleConfig::default_console")]
1809     pub console: ConsoleConfig,
1810     pub devices: Option<Vec<DeviceConfig>>,
1811     pub user_devices: Option<Vec<UserDeviceConfig>>,
1812     pub vsock: Option<VsockConfig>,
1813     #[serde(default)]
1814     pub iommu: bool,
1815     #[cfg(target_arch = "x86_64")]
1816     pub sgx_epc: Option<Vec<SgxEpcConfig>>,
1817     pub numa: Option<Vec<NumaConfig>>,
1818     #[serde(default)]
1819     pub watchdog: bool,
1820     #[cfg(feature = "tdx")]
1821     pub tdx: Option<TdxConfig>,
1822 }
1823 
1824 impl VmConfig {
1825     pub fn validate(&self) -> ValidationResult<()> {
1826         #[cfg(not(feature = "tdx"))]
1827         self.kernel.as_ref().ok_or(ValidationError::KernelMissing)?;
1828 
1829         #[cfg(feature = "tdx")]
1830         {
1831             let tdx_enabled = self.tdx.is_some();
1832             if !tdx_enabled && self.kernel.is_none() {
1833                 return Err(ValidationError::KernelMissing);
1834             }
1835             if tdx_enabled && (self.cpus.max_vcpus != self.cpus.boot_vcpus) {
1836                 return Err(ValidationError::TdxNoCpuHotplug);
1837             }
1838             if tdx_enabled && self.kernel.is_some() {
1839                 return Err(ValidationError::TdxKernelSpecified);
1840             }
1841         }
1842 
1843         if self.console.mode == ConsoleOutputMode::Tty && self.serial.mode == ConsoleOutputMode::Tty
1844         {
1845             return Err(ValidationError::DoubleTtyMode);
1846         }
1847 
1848         if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() {
1849             return Err(ValidationError::ConsoleFileMissing);
1850         }
1851 
1852         if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() {
1853             return Err(ValidationError::ConsoleFileMissing);
1854         }
1855 
1856         if self.cpus.max_vcpus < self.cpus.boot_vcpus {
1857             return Err(ValidationError::CpusMaxLowerThanBoot);
1858         }
1859 
1860         if let Some(disks) = &self.disks {
1861             for disk in disks {
1862                 if disk.vhost_socket.as_ref().and(disk.path.as_ref()).is_some() {
1863                     return Err(ValidationError::DiskSocketAndPath);
1864                 }
1865                 if disk.vhost_user && !self.memory.shared {
1866                     return Err(ValidationError::VhostUserRequiresSharedMemory);
1867                 }
1868                 if disk.vhost_user && disk.vhost_socket.is_none() {
1869                     return Err(ValidationError::VhostUserMissingSocket);
1870                 }
1871                 disk.validate(self)?;
1872             }
1873         }
1874 
1875         if let Some(nets) = &self.net {
1876             for net in nets {
1877                 if net.vhost_user && !self.memory.shared {
1878                     return Err(ValidationError::VhostUserRequiresSharedMemory);
1879                 }
1880                 net.validate(self)?;
1881             }
1882         }
1883 
1884         if let Some(fses) = &self.fs {
1885             if !fses.is_empty() && !self.memory.shared {
1886                 return Err(ValidationError::VhostUserRequiresSharedMemory);
1887             }
1888             for fs in fses {
1889                 fs.validate(self)?;
1890             }
1891         }
1892 
1893         if let Some(t) = &self.cpus.topology {
1894             if t.threads_per_core == 0
1895                 || t.cores_per_die == 0
1896                 || t.dies_per_package == 0
1897                 || t.packages == 0
1898             {
1899                 return Err(ValidationError::CpuTopologyZeroPart);
1900             }
1901 
1902             let total = t.threads_per_core * t.cores_per_die * t.dies_per_package * t.packages;
1903             if total != self.cpus.max_vcpus {
1904                 return Err(ValidationError::CpuTopologyCount);
1905             }
1906         }
1907 
1908         if let Some(hugepage_size) = &self.memory.hugepage_size {
1909             if !self.memory.hugepages {
1910                 return Err(ValidationError::HugePageSizeWithoutHugePages);
1911             }
1912             if !hugepage_size.is_power_of_two() {
1913                 return Err(ValidationError::InvalidHugePageSize(*hugepage_size));
1914             }
1915         }
1916 
1917         if let Some(user_devices) = &self.user_devices {
1918             if !user_devices.is_empty() && !self.memory.shared {
1919                 return Err(ValidationError::UserDevicesRequireSharedMemory);
1920             }
1921         }
1922 
1923         if let Some(numa) = &self.numa {
1924             let mut used_numa_node_memory_zones = HashMap::new();
1925             for numa_node in numa.iter() {
1926                 for memory_zone in numa_node.memory_zones.clone().unwrap().iter() {
1927                     if !used_numa_node_memory_zones.contains_key(memory_zone) {
1928                         used_numa_node_memory_zones
1929                             .insert(memory_zone.to_string(), numa_node.guest_numa_id);
1930                     } else {
1931                         return Err(ValidationError::MemoryZoneReused(
1932                             memory_zone.to_string(),
1933                             *used_numa_node_memory_zones
1934                                 .get(&memory_zone.to_string())
1935                                 .unwrap(),
1936                             numa_node.guest_numa_id,
1937                         ));
1938                     }
1939                 }
1940             }
1941         }
1942 
1943         Ok(())
1944     }
1945 
1946     pub fn parse(vm_params: VmParams) -> Result<Self> {
1947         let mut iommu = false;
1948 
1949         let mut disks: Option<Vec<DiskConfig>> = None;
1950         if let Some(disk_list) = &vm_params.disks {
1951             let mut disk_config_list = Vec::new();
1952             for item in disk_list.iter() {
1953                 let disk_config = DiskConfig::parse(item)?;
1954                 if disk_config.iommu {
1955                     iommu = true;
1956                 }
1957                 disk_config_list.push(disk_config);
1958             }
1959             disks = Some(disk_config_list);
1960         }
1961 
1962         let mut net: Option<Vec<NetConfig>> = None;
1963         if let Some(net_list) = &vm_params.net {
1964             let mut net_config_list = Vec::new();
1965             for item in net_list.iter() {
1966                 let net_config = NetConfig::parse(item)?;
1967                 if net_config.iommu {
1968                     iommu = true;
1969                 }
1970                 net_config_list.push(net_config);
1971             }
1972             net = Some(net_config_list);
1973         }
1974 
1975         let rng = RngConfig::parse(vm_params.rng)?;
1976         if rng.iommu {
1977             iommu = true;
1978         }
1979 
1980         let mut balloon: Option<BalloonConfig> = None;
1981         if let Some(balloon_params) = &vm_params.balloon {
1982             balloon = Some(BalloonConfig::parse(balloon_params)?);
1983         }
1984 
1985         let mut fs: Option<Vec<FsConfig>> = None;
1986         if let Some(fs_list) = &vm_params.fs {
1987             let mut fs_config_list = Vec::new();
1988             for item in fs_list.iter() {
1989                 fs_config_list.push(FsConfig::parse(item)?);
1990             }
1991             fs = Some(fs_config_list);
1992         }
1993 
1994         let mut pmem: Option<Vec<PmemConfig>> = None;
1995         if let Some(pmem_list) = &vm_params.pmem {
1996             let mut pmem_config_list = Vec::new();
1997             for item in pmem_list.iter() {
1998                 let pmem_config = PmemConfig::parse(item)?;
1999                 if pmem_config.iommu {
2000                     iommu = true;
2001                 }
2002                 pmem_config_list.push(pmem_config);
2003             }
2004             pmem = Some(pmem_config_list);
2005         }
2006 
2007         let console = ConsoleConfig::parse(vm_params.console)?;
2008         if console.iommu {
2009             iommu = true;
2010         }
2011         let serial = ConsoleConfig::parse(vm_params.serial)?;
2012 
2013         let mut devices: Option<Vec<DeviceConfig>> = None;
2014         if let Some(device_list) = &vm_params.devices {
2015             let mut device_config_list = Vec::new();
2016             for item in device_list.iter() {
2017                 let device_config = DeviceConfig::parse(item)?;
2018                 if device_config.iommu {
2019                     iommu = true;
2020                 }
2021                 device_config_list.push(device_config);
2022             }
2023             devices = Some(device_config_list);
2024         }
2025 
2026         let mut user_devices: Option<Vec<UserDeviceConfig>> = None;
2027         if let Some(user_device_list) = &vm_params.user_devices {
2028             let mut user_device_config_list = Vec::new();
2029             for item in user_device_list.iter() {
2030                 let user_device_config = UserDeviceConfig::parse(item)?;
2031                 user_device_config_list.push(user_device_config);
2032             }
2033             user_devices = Some(user_device_config_list);
2034         }
2035 
2036         let mut vsock: Option<VsockConfig> = None;
2037         if let Some(vs) = &vm_params.vsock {
2038             let vsock_config = VsockConfig::parse(vs)?;
2039             if vsock_config.iommu {
2040                 iommu = true;
2041             }
2042             vsock = Some(vsock_config);
2043         }
2044 
2045         #[cfg(target_arch = "x86_64")]
2046         let mut sgx_epc: Option<Vec<SgxEpcConfig>> = None;
2047         #[cfg(target_arch = "x86_64")]
2048         {
2049             if let Some(sgx_epc_list) = &vm_params.sgx_epc {
2050                 let mut sgx_epc_config_list = Vec::new();
2051                 for item in sgx_epc_list.iter() {
2052                     let sgx_epc_config = SgxEpcConfig::parse(item)?;
2053                     sgx_epc_config_list.push(sgx_epc_config);
2054                 }
2055                 sgx_epc = Some(sgx_epc_config_list);
2056             }
2057         }
2058 
2059         let mut numa: Option<Vec<NumaConfig>> = None;
2060         if let Some(numa_list) = &vm_params.numa {
2061             let mut numa_config_list = Vec::new();
2062             for item in numa_list.iter() {
2063                 let numa_config = NumaConfig::parse(item)?;
2064                 numa_config_list.push(numa_config);
2065             }
2066             numa = Some(numa_config_list);
2067         }
2068 
2069         let mut kernel: Option<KernelConfig> = None;
2070         if let Some(k) = vm_params.kernel {
2071             kernel = Some(KernelConfig {
2072                 path: PathBuf::from(k),
2073             });
2074         }
2075 
2076         let mut initramfs: Option<InitramfsConfig> = None;
2077         if let Some(k) = vm_params.initramfs {
2078             initramfs = Some(InitramfsConfig {
2079                 path: PathBuf::from(k),
2080             });
2081         }
2082 
2083         #[cfg(feature = "tdx")]
2084         let tdx = vm_params.tdx.map(TdxConfig::parse).transpose()?;
2085 
2086         let config = VmConfig {
2087             cpus: CpusConfig::parse(vm_params.cpus)?,
2088             memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
2089             kernel,
2090             initramfs,
2091             cmdline: CmdlineConfig::parse(vm_params.cmdline)?,
2092             disks,
2093             net,
2094             rng,
2095             balloon,
2096             fs,
2097             pmem,
2098             serial,
2099             console,
2100             devices,
2101             user_devices,
2102             vsock,
2103             iommu,
2104             #[cfg(target_arch = "x86_64")]
2105             sgx_epc,
2106             numa,
2107             watchdog: vm_params.watchdog,
2108             #[cfg(feature = "tdx")]
2109             tdx,
2110         };
2111         config.validate().map_err(Error::Validation)?;
2112         Ok(config)
2113     }
2114 }
2115 
2116 #[cfg(test)]
2117 mod tests {
2118     use super::*;
2119 
2120     #[test]
2121     fn test_option_parser() {
2122         let mut parser = OptionParser::new();
2123         parser
2124             .add("size")
2125             .add("mergeable")
2126             .add("hotplug_method")
2127             .add("hotplug_size");
2128 
2129         assert!(parser.parse("size=128M,hanging_param").is_err());
2130         assert!(parser.parse("size=128M,too_many_equals=foo=bar").is_err());
2131         assert!(parser.parse("size=128M,file=/dev/shm").is_err());
2132         assert!(parser.parse("size=128M").is_ok());
2133 
2134         assert_eq!(parser.get("size"), Some("128M".to_owned()));
2135         assert!(!parser.is_set("mergeable"));
2136         assert!(parser.is_set("size"));
2137     }
2138 
2139     #[test]
2140     fn test_cpu_parsing() -> Result<()> {
2141         assert_eq!(CpusConfig::parse("")?, CpusConfig::default());
2142 
2143         assert_eq!(
2144             CpusConfig::parse("boot=1")?,
2145             CpusConfig {
2146                 boot_vcpus: 1,
2147                 max_vcpus: 1,
2148                 ..Default::default()
2149             }
2150         );
2151         assert_eq!(
2152             CpusConfig::parse("boot=1,max=2")?,
2153             CpusConfig {
2154                 boot_vcpus: 1,
2155                 max_vcpus: 2,
2156                 ..Default::default()
2157             }
2158         );
2159         assert_eq!(
2160             CpusConfig::parse("boot=8,topology=2:2:1:2")?,
2161             CpusConfig {
2162                 boot_vcpus: 8,
2163                 max_vcpus: 8,
2164                 topology: Some(CpuTopology {
2165                     threads_per_core: 2,
2166                     cores_per_die: 2,
2167                     dies_per_package: 1,
2168                     packages: 2
2169                 }),
2170                 ..Default::default()
2171             }
2172         );
2173 
2174         assert!(CpusConfig::parse("boot=8,topology=2:2:1").is_err());
2175         assert!(CpusConfig::parse("boot=8,topology=2:2:1:x").is_err());
2176         assert_eq!(
2177             CpusConfig::parse("boot=1,kvm_hyperv=on")?,
2178             CpusConfig {
2179                 boot_vcpus: 1,
2180                 max_vcpus: 1,
2181                 kvm_hyperv: true,
2182                 ..Default::default()
2183             }
2184         );
2185         Ok(())
2186     }
2187 
2188     #[test]
2189     fn test_mem_parsing() -> Result<()> {
2190         assert_eq!(MemoryConfig::parse("", None)?, MemoryConfig::default());
2191         // Default string
2192         assert_eq!(
2193             MemoryConfig::parse("size=512M", None)?,
2194             MemoryConfig::default()
2195         );
2196         assert_eq!(
2197             MemoryConfig::parse("size=512M,mergeable=on", None)?,
2198             MemoryConfig {
2199                 size: 512 << 20,
2200                 mergeable: true,
2201                 ..Default::default()
2202             }
2203         );
2204         assert_eq!(
2205             MemoryConfig::parse("mergeable=on", None)?,
2206             MemoryConfig {
2207                 mergeable: true,
2208                 ..Default::default()
2209             }
2210         );
2211         assert_eq!(
2212             MemoryConfig::parse("size=1G,mergeable=off", None)?,
2213             MemoryConfig {
2214                 size: 1 << 30,
2215                 mergeable: false,
2216                 ..Default::default()
2217             }
2218         );
2219         assert_eq!(
2220             MemoryConfig::parse("hotplug_method=acpi", None)?,
2221             MemoryConfig {
2222                 ..Default::default()
2223             }
2224         );
2225         assert_eq!(
2226             MemoryConfig::parse("hotplug_method=acpi,hotplug_size=512M", None)?,
2227             MemoryConfig {
2228                 hotplug_size: Some(512 << 20),
2229                 ..Default::default()
2230             }
2231         );
2232         assert_eq!(
2233             MemoryConfig::parse("hotplug_method=virtio-mem,hotplug_size=512M", None)?,
2234             MemoryConfig {
2235                 hotplug_size: Some(512 << 20),
2236                 hotplug_method: HotplugMethod::VirtioMem,
2237                 ..Default::default()
2238             }
2239         );
2240         assert_eq!(
2241             MemoryConfig::parse("hugepages=on,size=1G,hugepage_size=2M", None)?,
2242             MemoryConfig {
2243                 hugepage_size: Some(2 << 20),
2244                 size: 1 << 30,
2245                 hugepages: true,
2246                 ..Default::default()
2247             }
2248         );
2249         Ok(())
2250     }
2251 
2252     #[test]
2253     fn test_disk_parsing() -> Result<()> {
2254         assert_eq!(
2255             DiskConfig::parse("path=/path/to_file")?,
2256             DiskConfig {
2257                 path: Some(PathBuf::from("/path/to_file")),
2258                 ..Default::default()
2259             }
2260         );
2261         assert_eq!(
2262             DiskConfig::parse("path=/path/to_file,id=mydisk0")?,
2263             DiskConfig {
2264                 path: Some(PathBuf::from("/path/to_file")),
2265                 id: Some("mydisk0".to_owned()),
2266                 ..Default::default()
2267             }
2268         );
2269         assert_eq!(
2270             DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
2271             DiskConfig {
2272                 vhost_socket: Some(String::from("/tmp/sock")),
2273                 vhost_user: true,
2274                 ..Default::default()
2275             }
2276         );
2277         assert_eq!(
2278             DiskConfig::parse("path=/path/to_file,iommu=on")?,
2279             DiskConfig {
2280                 path: Some(PathBuf::from("/path/to_file")),
2281                 iommu: true,
2282                 ..Default::default()
2283             }
2284         );
2285         assert_eq!(
2286             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256")?,
2287             DiskConfig {
2288                 path: Some(PathBuf::from("/path/to_file")),
2289                 iommu: true,
2290                 queue_size: 256,
2291                 ..Default::default()
2292             }
2293         );
2294         assert_eq!(
2295             DiskConfig::parse("path=/path/to_file,iommu=on,queue_size=256,num_queues=4")?,
2296             DiskConfig {
2297                 path: Some(PathBuf::from("/path/to_file")),
2298                 iommu: true,
2299                 queue_size: 256,
2300                 num_queues: 4,
2301                 ..Default::default()
2302             }
2303         );
2304         assert_eq!(
2305             DiskConfig::parse("path=/path/to_file,direct=on")?,
2306             DiskConfig {
2307                 path: Some(PathBuf::from("/path/to_file")),
2308                 direct: true,
2309                 ..Default::default()
2310             }
2311         );
2312         assert_eq!(
2313             DiskConfig::parse("path=/path/to_file")?,
2314             DiskConfig {
2315                 path: Some(PathBuf::from("/path/to_file")),
2316                 ..Default::default()
2317             }
2318         );
2319         assert_eq!(
2320             DiskConfig::parse("path=/path/to_file")?,
2321             DiskConfig {
2322                 path: Some(PathBuf::from("/path/to_file")),
2323                 ..Default::default()
2324             }
2325         );
2326         assert_eq!(
2327             DiskConfig::parse("path=/path/to_file,poll_queue=false")?,
2328             DiskConfig {
2329                 path: Some(PathBuf::from("/path/to_file")),
2330                 poll_queue: false,
2331                 ..Default::default()
2332             }
2333         );
2334         assert_eq!(
2335             DiskConfig::parse("path=/path/to_file,poll_queue=true")?,
2336             DiskConfig {
2337                 path: Some(PathBuf::from("/path/to_file")),
2338                 poll_queue: true,
2339                 ..Default::default()
2340             }
2341         );
2342 
2343         Ok(())
2344     }
2345 
2346     #[test]
2347     fn test_net_parsing() -> Result<()> {
2348         // mac address is random
2349         assert_eq!(
2350             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef")?,
2351             NetConfig {
2352                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2353                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2354                 ..Default::default()
2355             }
2356         );
2357 
2358         assert_eq!(
2359             NetConfig::parse("mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,id=mynet0")?,
2360             NetConfig {
2361                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2362                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2363                 id: Some("mynet0".to_owned()),
2364                 ..Default::default()
2365             }
2366         );
2367 
2368         assert_eq!(
2369             NetConfig::parse(
2370                 "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"
2371             )?,
2372             NetConfig {
2373                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2374                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2375                 tap: Some("tap0".to_owned()),
2376                 ip: "192.168.100.1".parse().unwrap(),
2377                 mask: "255.255.255.128".parse().unwrap(),
2378                 ..Default::default()
2379             }
2380         );
2381 
2382         assert_eq!(
2383             NetConfig::parse(
2384                 "mac=de:ad:be:ef:12:34,host_mac=12:34:de:ad:be:ef,vhost_user=true,socket=/tmp/sock"
2385             )?,
2386             NetConfig {
2387                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2388                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2389                 vhost_user: true,
2390                 vhost_socket: Some("/tmp/sock".to_owned()),
2391                 ..Default::default()
2392             }
2393         );
2394 
2395         assert_eq!(
2396             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")?,
2397             NetConfig {
2398                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2399                 host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
2400                 num_queues: 4,
2401                 queue_size: 1024,
2402                 iommu: true,
2403                 ..Default::default()
2404             }
2405         );
2406 
2407         assert_eq!(
2408             NetConfig::parse("mac=de:ad:be:ef:12:34,fd=3:7,num_queues=4")?,
2409             NetConfig {
2410                 mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
2411                 fds: Some(vec![3, 7]),
2412                 num_queues: 4,
2413                 ..Default::default()
2414             }
2415         );
2416 
2417         Ok(())
2418     }
2419 
2420     #[test]
2421     fn test_parse_rng() -> Result<()> {
2422         assert_eq!(RngConfig::parse("")?, RngConfig::default());
2423         assert_eq!(
2424             RngConfig::parse("src=/dev/random")?,
2425             RngConfig {
2426                 src: PathBuf::from("/dev/random"),
2427                 ..Default::default()
2428             }
2429         );
2430         assert_eq!(
2431             RngConfig::parse("src=/dev/random,iommu=on")?,
2432             RngConfig {
2433                 src: PathBuf::from("/dev/random"),
2434                 iommu: true,
2435             }
2436         );
2437         assert_eq!(
2438             RngConfig::parse("iommu=on")?,
2439             RngConfig {
2440                 iommu: true,
2441                 ..Default::default()
2442             }
2443         );
2444         Ok(())
2445     }
2446 
2447     #[test]
2448     fn test_parse_fs() -> Result<()> {
2449         // "tag" and "socket" must be supplied
2450         assert!(FsConfig::parse("").is_err());
2451         assert!(FsConfig::parse("tag=mytag").is_err());
2452         assert!(FsConfig::parse("socket=/tmp/sock").is_err());
2453         assert_eq!(
2454             FsConfig::parse("tag=mytag,socket=/tmp/sock")?,
2455             FsConfig {
2456                 socket: PathBuf::from("/tmp/sock"),
2457                 tag: "mytag".to_owned(),
2458                 ..Default::default()
2459             }
2460         );
2461         assert_eq!(
2462             FsConfig::parse("tag=mytag,socket=/tmp/sock")?,
2463             FsConfig {
2464                 socket: PathBuf::from("/tmp/sock"),
2465                 tag: "mytag".to_owned(),
2466                 ..Default::default()
2467             }
2468         );
2469         assert_eq!(
2470             FsConfig::parse("tag=mytag,socket=/tmp/sock,num_queues=4,queue_size=1024")?,
2471             FsConfig {
2472                 socket: PathBuf::from("/tmp/sock"),
2473                 tag: "mytag".to_owned(),
2474                 num_queues: 4,
2475                 queue_size: 1024,
2476                 ..Default::default()
2477             }
2478         );
2479         // DAX on -> default cache size
2480         assert_eq!(
2481             FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=on")?,
2482             FsConfig {
2483                 socket: PathBuf::from("/tmp/sock"),
2484                 tag: "mytag".to_owned(),
2485                 dax: true,
2486                 cache_size: default_fsconfig_cache_size(),
2487                 ..Default::default()
2488             }
2489         );
2490         assert_eq!(
2491             FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=on,cache_size=4G")?,
2492             FsConfig {
2493                 socket: PathBuf::from("/tmp/sock"),
2494                 tag: "mytag".to_owned(),
2495                 dax: true,
2496                 cache_size: 4 << 30,
2497                 ..Default::default()
2498             }
2499         );
2500         // Cache size without DAX is an error
2501         assert!(FsConfig::parse("tag=mytag,socket=/tmp/sock,dax=off,cache_size=4G").is_err());
2502         Ok(())
2503     }
2504 
2505     #[test]
2506     fn test_pmem_parsing() -> Result<()> {
2507         // Must always give a file and size
2508         assert!(PmemConfig::parse("").is_err());
2509         assert!(PmemConfig::parse("size=128M").is_err());
2510         assert_eq!(
2511             PmemConfig::parse("file=/tmp/pmem,size=128M")?,
2512             PmemConfig {
2513                 file: PathBuf::from("/tmp/pmem"),
2514                 size: Some(128 << 20),
2515                 ..Default::default()
2516             }
2517         );
2518         assert_eq!(
2519             PmemConfig::parse("file=/tmp/pmem,size=128M,id=mypmem0")?,
2520             PmemConfig {
2521                 file: PathBuf::from("/tmp/pmem"),
2522                 size: Some(128 << 20),
2523                 id: Some("mypmem0".to_owned()),
2524                 ..Default::default()
2525             }
2526         );
2527         assert_eq!(
2528             PmemConfig::parse("file=/tmp/pmem,size=128M,iommu=on,mergeable=on,discard_writes=on")?,
2529             PmemConfig {
2530                 file: PathBuf::from("/tmp/pmem"),
2531                 size: Some(128 << 20),
2532                 mergeable: true,
2533                 discard_writes: true,
2534                 iommu: true,
2535                 ..Default::default()
2536             }
2537         );
2538 
2539         Ok(())
2540     }
2541 
2542     #[test]
2543     fn test_console_parsing() -> Result<()> {
2544         assert!(ConsoleConfig::parse("").is_err());
2545         assert!(ConsoleConfig::parse("badmode").is_err());
2546         assert_eq!(
2547             ConsoleConfig::parse("off")?,
2548             ConsoleConfig {
2549                 mode: ConsoleOutputMode::Off,
2550                 iommu: false,
2551                 file: None,
2552             }
2553         );
2554         assert_eq!(
2555             ConsoleConfig::parse("pty")?,
2556             ConsoleConfig {
2557                 mode: ConsoleOutputMode::Pty,
2558                 iommu: false,
2559                 file: None,
2560             }
2561         );
2562         assert_eq!(
2563             ConsoleConfig::parse("tty")?,
2564             ConsoleConfig {
2565                 mode: ConsoleOutputMode::Tty,
2566                 iommu: false,
2567                 file: None,
2568             }
2569         );
2570         assert_eq!(
2571             ConsoleConfig::parse("null")?,
2572             ConsoleConfig {
2573                 mode: ConsoleOutputMode::Null,
2574                 iommu: false,
2575                 file: None,
2576             }
2577         );
2578         assert_eq!(
2579             ConsoleConfig::parse("file=/tmp/console")?,
2580             ConsoleConfig {
2581                 mode: ConsoleOutputMode::File,
2582                 iommu: false,
2583                 file: Some(PathBuf::from("/tmp/console"))
2584             }
2585         );
2586         assert_eq!(
2587             ConsoleConfig::parse("null,iommu=on")?,
2588             ConsoleConfig {
2589                 mode: ConsoleOutputMode::Null,
2590                 iommu: true,
2591                 file: None,
2592             }
2593         );
2594         assert_eq!(
2595             ConsoleConfig::parse("file=/tmp/console,iommu=on")?,
2596             ConsoleConfig {
2597                 mode: ConsoleOutputMode::File,
2598                 iommu: true,
2599                 file: Some(PathBuf::from("/tmp/console"))
2600             }
2601         );
2602         Ok(())
2603     }
2604 
2605     #[test]
2606     fn test_device_parsing() -> Result<()> {
2607         // Device must have a path provided
2608         assert!(DeviceConfig::parse("").is_err());
2609         assert_eq!(
2610             DeviceConfig::parse("path=/path/to/device")?,
2611             DeviceConfig {
2612                 path: PathBuf::from("/path/to/device"),
2613                 id: None,
2614                 iommu: false
2615             }
2616         );
2617 
2618         assert_eq!(
2619             DeviceConfig::parse("path=/path/to/device,iommu=on")?,
2620             DeviceConfig {
2621                 path: PathBuf::from("/path/to/device"),
2622                 id: None,
2623                 iommu: true
2624             }
2625         );
2626 
2627         assert_eq!(
2628             DeviceConfig::parse("path=/path/to/device,iommu=on,id=mydevice0")?,
2629             DeviceConfig {
2630                 path: PathBuf::from("/path/to/device"),
2631                 id: Some("mydevice0".to_owned()),
2632                 iommu: true
2633             }
2634         );
2635 
2636         Ok(())
2637     }
2638 
2639     #[test]
2640     fn test_vsock_parsing() -> Result<()> {
2641         // socket and cid is required
2642         assert!(VsockConfig::parse("").is_err());
2643         assert_eq!(
2644             VsockConfig::parse("socket=/tmp/sock,cid=1")?,
2645             VsockConfig {
2646                 cid: 1,
2647                 socket: PathBuf::from("/tmp/sock"),
2648                 iommu: false,
2649                 id: None,
2650             }
2651         );
2652         assert_eq!(
2653             VsockConfig::parse("socket=/tmp/sock,cid=1,iommu=on")?,
2654             VsockConfig {
2655                 cid: 1,
2656                 socket: PathBuf::from("/tmp/sock"),
2657                 iommu: true,
2658                 id: None,
2659             }
2660         );
2661         Ok(())
2662     }
2663 
2664     #[test]
2665     fn test_config_validation() {
2666         let valid_config = VmConfig {
2667             cpus: CpusConfig {
2668                 boot_vcpus: 1,
2669                 max_vcpus: 1,
2670                 ..Default::default()
2671             },
2672             memory: MemoryConfig {
2673                 size: 536_870_912,
2674                 mergeable: false,
2675                 hotplug_method: HotplugMethod::Acpi,
2676                 hotplug_size: None,
2677                 hotplugged_size: None,
2678                 shared: false,
2679                 hugepages: false,
2680                 hugepage_size: None,
2681                 zones: None,
2682             },
2683             kernel: Some(KernelConfig {
2684                 path: PathBuf::from("/path/to/kernel"),
2685             }),
2686             initramfs: None,
2687             cmdline: CmdlineConfig {
2688                 args: String::from(""),
2689             },
2690             disks: None,
2691             net: None,
2692             rng: RngConfig {
2693                 src: PathBuf::from("/dev/urandom"),
2694                 iommu: false,
2695             },
2696             balloon: None,
2697             fs: None,
2698             pmem: None,
2699             serial: ConsoleConfig {
2700                 file: None,
2701                 mode: ConsoleOutputMode::Null,
2702                 iommu: false,
2703             },
2704             console: ConsoleConfig {
2705                 file: None,
2706                 mode: ConsoleOutputMode::Tty,
2707                 iommu: false,
2708             },
2709             devices: None,
2710             user_devices: None,
2711             vsock: None,
2712             iommu: false,
2713             #[cfg(target_arch = "x86_64")]
2714             sgx_epc: None,
2715             numa: None,
2716             watchdog: false,
2717             #[cfg(feature = "tdx")]
2718             tdx: None,
2719         };
2720 
2721         assert!(valid_config.validate().is_ok());
2722 
2723         let mut invalid_config = valid_config.clone();
2724         invalid_config.serial.mode = ConsoleOutputMode::Tty;
2725         invalid_config.console.mode = ConsoleOutputMode::Tty;
2726         assert!(invalid_config.validate().is_err());
2727 
2728         let mut invalid_config = valid_config.clone();
2729         invalid_config.kernel = None;
2730         assert!(invalid_config.validate().is_err());
2731 
2732         let mut invalid_config = valid_config.clone();
2733         invalid_config.serial.mode = ConsoleOutputMode::File;
2734         invalid_config.serial.file = None;
2735         assert!(invalid_config.validate().is_err());
2736 
2737         let mut invalid_config = valid_config.clone();
2738         invalid_config.cpus.max_vcpus = 16;
2739         invalid_config.cpus.boot_vcpus = 32;
2740         assert!(invalid_config.validate().is_err());
2741 
2742         let mut invalid_config = valid_config.clone();
2743         invalid_config.cpus.max_vcpus = 16;
2744         invalid_config.cpus.boot_vcpus = 16;
2745         invalid_config.cpus.topology = Some(CpuTopology {
2746             threads_per_core: 2,
2747             cores_per_die: 8,
2748             dies_per_package: 1,
2749             packages: 2,
2750         });
2751         assert!(invalid_config.validate().is_err());
2752 
2753         let mut invalid_config = valid_config.clone();
2754         invalid_config.disks = Some(vec![DiskConfig {
2755             vhost_socket: Some("/path/to/sock".to_owned()),
2756             path: Some(PathBuf::from("/path/to/image")),
2757             ..Default::default()
2758         }]);
2759         assert!(invalid_config.validate().is_err());
2760 
2761         let mut invalid_config = valid_config.clone();
2762         invalid_config.disks = Some(vec![DiskConfig {
2763             vhost_user: true,
2764             ..Default::default()
2765         }]);
2766         assert!(invalid_config.validate().is_err());
2767 
2768         let mut invalid_config = valid_config.clone();
2769         invalid_config.disks = Some(vec![DiskConfig {
2770             vhost_user: true,
2771             vhost_socket: Some("/path/to/sock".to_owned()),
2772             ..Default::default()
2773         }]);
2774         assert!(invalid_config.validate().is_err());
2775 
2776         let mut still_valid_config = valid_config.clone();
2777         still_valid_config.disks = Some(vec![DiskConfig {
2778             vhost_user: true,
2779             vhost_socket: Some("/path/to/sock".to_owned()),
2780             ..Default::default()
2781         }]);
2782         still_valid_config.memory.shared = true;
2783         assert!(still_valid_config.validate().is_ok());
2784 
2785         let mut invalid_config = valid_config.clone();
2786         invalid_config.net = Some(vec![NetConfig {
2787             vhost_user: true,
2788             ..Default::default()
2789         }]);
2790         assert!(invalid_config.validate().is_err());
2791 
2792         let mut still_valid_config = valid_config.clone();
2793         still_valid_config.net = Some(vec![NetConfig {
2794             vhost_user: true,
2795             vhost_socket: Some("/path/to/sock".to_owned()),
2796             ..Default::default()
2797         }]);
2798         still_valid_config.memory.shared = true;
2799         assert!(still_valid_config.validate().is_ok());
2800 
2801         let mut invalid_config = valid_config.clone();
2802         invalid_config.net = Some(vec![NetConfig {
2803             fds: Some(vec![0]),
2804             ..Default::default()
2805         }]);
2806         assert!(invalid_config.validate().is_err());
2807 
2808         let mut invalid_config = valid_config.clone();
2809         invalid_config.fs = Some(vec![FsConfig {
2810             ..Default::default()
2811         }]);
2812         assert!(invalid_config.validate().is_err());
2813 
2814         let mut still_valid_config = valid_config.clone();
2815         still_valid_config.memory.shared = true;
2816         assert!(still_valid_config.validate().is_ok());
2817 
2818         let mut still_valid_config = valid_config.clone();
2819         still_valid_config.memory.hugepages = true;
2820         assert!(still_valid_config.validate().is_ok());
2821 
2822         let mut still_valid_config = valid_config.clone();
2823         still_valid_config.memory.hugepages = true;
2824         still_valid_config.memory.hugepage_size = Some(2 << 20);
2825         assert!(still_valid_config.validate().is_ok());
2826 
2827         let mut invalid_config = valid_config.clone();
2828         invalid_config.memory.hugepages = false;
2829         invalid_config.memory.hugepage_size = Some(2 << 20);
2830         assert!(invalid_config.validate().is_err());
2831 
2832         let mut invalid_config = valid_config;
2833         invalid_config.memory.hugepages = true;
2834         invalid_config.memory.hugepage_size = Some(3 << 20);
2835         assert!(invalid_config.validate().is_err());
2836     }
2837 }
2838