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