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