1use std::path::PathBuf;
6use std::str::FromStr;
7
8use anyhow::anyhow;
9use anyhow::bail;
10use anyhow::Context;
11use devices::IommuDevType;
12use devices::PciAddress;
13use devices::SerialParameters;
14use libc::getegid;
15use libc::geteuid;
16use serde::Deserialize;
17use serde::Serialize;
18use serde_keyvalue::from_key_values;
19use serde_keyvalue::FromKeyValues;
20
21use crate::crosvm::config::Config;
22
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
24#[serde(deny_unknown_fields, rename_all = "kebab-case")]
25pub enum HypervisorKind {
26 Kvm {
27 device: Option<PathBuf>,
28 },
29 #[cfg(target_arch = "aarch64")]
30 #[cfg(feature = "halla")]
31 Halla {
32 device: Option<PathBuf>,
33 },
34 #[cfg(all(target_arch = "aarch64", feature = "geniezone"))]
35 Geniezone {
36 device: Option<PathBuf>,
37 },
38 #[cfg(all(target_arch = "aarch64", feature = "gunyah"))]
39 Gunyah {
40 device: Option<PathBuf>,
41 qcom_trusted_vm_id: Option<u16>,
42 qcom_trusted_vm_pas_id: Option<u32>,
43 },
44}
45
46pub fn check_serial_params(_serial_params: &SerialParameters) -> Result<(), String> {
48 Ok(())
49}
50
51pub fn validate_config(_cfg: &mut Config) -> std::result::Result<(), String> {
52 Ok(())
53}
54
55#[derive(Serialize, Deserialize, FromKeyValues)]
57#[serde(deny_unknown_fields, rename_all = "kebab-case")]
58pub struct VfioOption {
59 pub path: PathBuf,
61
62 #[serde(default)]
64 pub iommu: IommuDevType,
65
66 pub guest_address: Option<PciAddress>,
69
70 pub dt_symbol: Option<String>,
73}
74
75#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
76pub enum SharedDirKind {
77 FS,
78 #[default]
79 P9,
80}
81
82impl FromStr for SharedDirKind {
83 type Err = anyhow::Error;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 use SharedDirKind::*;
87 match s {
88 "fs" | "FS" => Ok(FS),
89 "9p" | "9P" | "p9" | "P9" => Ok(P9),
90 _ => {
91 bail!("invalid file system type");
92 }
93 }
94 }
95}
96
97pub struct SharedDir {
98 pub src: PathBuf,
99 pub tag: String,
100 pub kind: SharedDirKind,
101 pub ugid: (Option<u32>, Option<u32>),
102 pub uid_map: String,
103 pub gid_map: String,
104 pub fs_cfg: devices::virtio::fs::Config,
105 pub p9_cfg: p9::Config,
106}
107
108impl Default for SharedDir {
109 fn default() -> SharedDir {
110 SharedDir {
111 src: Default::default(),
112 tag: Default::default(),
113 kind: Default::default(),
114 ugid: (None, None),
115 uid_map: format!("0 {} 1", unsafe { geteuid() }),
117 gid_map: format!("0 {} 1", unsafe { getegid() }),
119 fs_cfg: Default::default(),
120 p9_cfg: Default::default(),
121 }
122 }
123}
124
125struct UgidConfig {
126 uid: Option<u32>,
127 gid: Option<u32>,
128 uid_map: String,
129 gid_map: String,
130}
131
132impl Default for UgidConfig {
133 fn default() -> Self {
134 Self {
135 uid: None,
136 gid: None,
137 uid_map: format!("0 {} 1", unsafe { geteuid() }),
139 gid_map: format!("0 {} 1", unsafe { getegid() }),
141 }
142 }
143}
144
145impl UgidConfig {
146 fn parse_ugid_config(&mut self, kind: &str, value: &str) -> anyhow::Result<bool> {
149 match kind {
150 "uid" => {
151 self.uid = Some(value.parse().context("`uid` must be an integer")?);
152 }
153 "gid" => {
154 self.gid = Some(value.parse().context("`gid` must be an integer")?);
155 }
156 "uidmap" => self.uid_map = value.into(),
157 "gidmap" => self.gid_map = value.into(),
158 _ => {
159 return Ok(false);
160 }
161 }
162 Ok(true)
163 }
164}
165
166impl FromStr for SharedDir {
167 type Err = anyhow::Error;
168
169 fn from_str(param: &str) -> Result<Self, Self::Err> {
170 let mut components = param.split(':');
204 let src = PathBuf::from(
205 components
206 .next()
207 .context("missing source path for `shared-dir`")?,
208 );
209 let tag = components
210 .next()
211 .context("missing tag for `shared-dir`")?
212 .to_owned();
213
214 if !src.is_dir() {
215 bail!("source path for `shared-dir` must be a directory");
216 }
217
218 let mut shared_dir = SharedDir {
219 src,
220 tag,
221 ..Default::default()
222 };
223 let mut type_opts = vec![];
224 let mut ugid_cfg = UgidConfig::default();
225 for opt in components {
226 let mut o = opt.splitn(2, '=');
227 let kind = o.next().context("`shared-dir` options must not be empty")?;
228 let value = o
229 .next()
230 .context("`shared-dir` options must be of the form `kind=value`")?;
231
232 if !ugid_cfg
233 .parse_ugid_config(kind, value)
234 .context("failed to parse ugid config")?
235 {
236 match kind {
237 "type" => {
238 shared_dir.kind = value.parse().with_context(|| {
239 anyhow!("`type` must be one of `fs` or `9p` but {value}")
240 })?
241 }
242 _ => type_opts.push(opt),
243 }
244 }
245 }
246 shared_dir.ugid = (ugid_cfg.uid, ugid_cfg.gid);
247 shared_dir.uid_map = ugid_cfg.uid_map;
248 shared_dir.gid_map = ugid_cfg.gid_map;
249
250 match shared_dir.kind {
251 SharedDirKind::FS => {
252 shared_dir.fs_cfg = from_key_values(&type_opts.join(","))
253 .map_err(|e| anyhow!("failed to parse fs config '{:?}': {e}", type_opts))?;
254
255 if shared_dir.fs_cfg.ascii_casefold && !shared_dir.fs_cfg.negative_timeout.is_zero()
256 {
257 bail!("'negative_timeout' cannot be used with 'ascii_casefold'");
265 }
266 }
267 SharedDirKind::P9 => {
268 shared_dir.p9_cfg = type_opts
269 .join(":")
270 .parse()
271 .map_err(|e| anyhow!("failed to parse 9p config '{:?}': {e}", type_opts))?;
272 }
273 }
274 Ok(shared_dir)
275 }
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
279#[serde(deny_unknown_fields)]
280pub struct PmemExt2Option {
281 pub path: PathBuf,
282 pub blocks_per_group: u32,
283 pub inodes_per_group: u32,
284 pub size: u32,
285 pub ugid: (Option<u32>, Option<u32>),
286 pub uid_map: String,
287 pub gid_map: String,
288}
289
290impl Default for PmemExt2Option {
291 fn default() -> Self {
292 let blocks_per_group = 4096;
293 let inodes_per_group = 1024;
294 let size = ext2::BLOCK_SIZE as u32 * blocks_per_group; let ugid_cfg = UgidConfig::default();
296 Self {
297 path: Default::default(),
298 blocks_per_group,
299 inodes_per_group,
300 size,
301 ugid: (ugid_cfg.uid, ugid_cfg.gid),
302 uid_map: ugid_cfg.uid_map,
303 gid_map: ugid_cfg.gid_map,
304 }
305 }
306}
307
308pub fn parse_pmem_ext2_option(param: &str) -> Result<PmemExt2Option, String> {
309 let mut opt = PmemExt2Option::default();
310 let mut components = param.split(':');
311 opt.path = PathBuf::from(
312 components
313 .next()
314 .ok_or("missing source path for `pmem-ext2`")?,
315 );
316
317 let mut ugid_cfg = UgidConfig::default();
318 for c in components {
319 let mut o = c.splitn(2, '=');
320 let kind = o.next().ok_or("`pmem-ext2` options must not be empty")?;
321 let value = o
322 .next()
323 .ok_or("`pmem-ext2` options must be of the form `kind=value`")?;
324
325 if !ugid_cfg
326 .parse_ugid_config(kind, value)
327 .map_err(|e| format!("failed to parse ugid config for pmem-ext2: {e:#}"))?
328 {
329 match kind {
330 "blocks_per_group" => {
331 opt.blocks_per_group = value.parse().map_err(|e| {
332 format!("failed to parse blocks_per_groups '{value}': {e:#}")
333 })?
334 }
335 "inodes_per_group" => {
336 opt.inodes_per_group = value.parse().map_err(|e| {
337 format!("failed to parse inodes_per_groups '{value}': {e:#}")
338 })?
339 }
340 "size" => {
341 opt.size = value
342 .parse()
343 .map_err(|e| format!("failed to parse memory size '{value}': {e:#}"))?
344 }
345 _ => return Err(format!("invalid `pmem-ext2` option: {kind}")),
346 }
347 }
348 }
349 opt.ugid = (ugid_cfg.uid, ugid_cfg.gid);
350 opt.uid_map = ugid_cfg.uid_map;
351 opt.gid_map = ugid_cfg.gid_map;
352
353 Ok(opt)
354}
355
356#[cfg(test)]
357mod tests {
358 use std::path::Path;
359 use std::path::PathBuf;
360 use std::time::Duration;
361
362 use argh::FromArgs;
363 use devices::virtio::fs::CachePolicy;
364
365 use super::*;
366 use crate::crosvm::config::from_key_values;
367
368 #[test]
369 fn parse_coiommu_options() {
370 use std::time::Duration;
371
372 use devices::CoIommuParameters;
373 use devices::CoIommuUnpinPolicy;
374
375 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=off").unwrap();
377 assert_eq!(
378 coiommu_params,
379 CoIommuParameters {
380 unpin_policy: CoIommuUnpinPolicy::Off,
381 ..Default::default()
382 }
383 );
384 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=lru").unwrap();
385 assert_eq!(
386 coiommu_params,
387 CoIommuParameters {
388 unpin_policy: CoIommuUnpinPolicy::Lru,
389 ..Default::default()
390 }
391 );
392 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=foo");
393 assert!(coiommu_params.is_err());
394
395 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=42").unwrap();
397 assert_eq!(
398 coiommu_params,
399 CoIommuParameters {
400 unpin_interval: Duration::from_secs(42),
401 ..Default::default()
402 }
403 );
404 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=foo");
405 assert!(coiommu_params.is_err());
406
407 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=256").unwrap();
409 assert_eq!(
410 coiommu_params,
411 CoIommuParameters {
412 unpin_limit: Some(256),
413 ..Default::default()
414 }
415 );
416 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=0");
417 assert!(coiommu_params.is_err());
418 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=foo");
419 assert!(coiommu_params.is_err());
420
421 let coiommu_params =
423 from_key_values::<CoIommuParameters>("unpin_gen_threshold=32").unwrap();
424 assert_eq!(
425 coiommu_params,
426 CoIommuParameters {
427 unpin_gen_threshold: 32,
428 ..Default::default()
429 }
430 );
431 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_gen_threshold=foo");
432 assert!(coiommu_params.is_err());
433
434 let coiommu_params = from_key_values::<CoIommuParameters>(
436 "unpin_policy=lru,unpin_interval=90,unpin_limit=8,unpin_gen_threshold=64",
437 )
438 .unwrap();
439 assert_eq!(
440 coiommu_params,
441 CoIommuParameters {
442 unpin_policy: CoIommuUnpinPolicy::Lru,
443 unpin_interval: Duration::from_secs(90),
444 unpin_limit: Some(8),
445 unpin_gen_threshold: 64,
446 }
447 );
448
449 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_invalid_param=0");
451 assert!(coiommu_params.is_err());
452 }
453
454 #[test]
455 fn vfio_pci_path() {
456 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
457 &[],
458 &["--vfio", "/path/to/dev", "/dev/null"],
459 )
460 .unwrap()
461 .try_into()
462 .unwrap();
463
464 let vfio = config.vfio.first().unwrap();
465
466 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
467 assert_eq!(vfio.iommu, IommuDevType::NoIommu);
468 assert_eq!(vfio.guest_address, None);
469 }
470
471 #[test]
472 fn vfio_pci_path_coiommu() {
473 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
474 &[],
475 &["--vfio", "/path/to/dev,iommu=coiommu", "/dev/null"],
476 )
477 .unwrap()
478 .try_into()
479 .unwrap();
480
481 let vfio = config.vfio.first().unwrap();
482
483 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
484 assert_eq!(vfio.iommu, IommuDevType::CoIommu);
485 assert_eq!(vfio.guest_address, None);
486 }
487
488 #[test]
489 fn vfio_pci_path_viommu_guest_address() {
490 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
491 &[],
492 &[
493 "--vfio",
494 "/path/to/dev,iommu=viommu,guest-address=42:15.4",
495 "/dev/null",
496 ],
497 )
498 .unwrap()
499 .try_into()
500 .unwrap();
501
502 let vfio = config.vfio.first().unwrap();
503
504 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
505 assert_eq!(vfio.iommu, IommuDevType::VirtioIommu);
506 assert_eq!(
507 vfio.guest_address,
508 Some(PciAddress::new(0, 0x42, 0x15, 4).unwrap())
509 );
510 }
511
512 #[test]
513 fn vfio_platform() {
514 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
515 &[],
516 &["--vfio-platform", "/path/to/dev", "/dev/null"],
517 )
518 .unwrap()
519 .try_into()
520 .unwrap();
521
522 let vfio = config.vfio.first().unwrap();
523
524 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
525 }
526
527 #[test]
528 fn hypervisor_default() {
529 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(&[], &["/dev/null"])
530 .unwrap()
531 .try_into()
532 .unwrap();
533
534 assert_eq!(config.hypervisor, None);
535 }
536
537 #[test]
538 fn hypervisor_kvm() {
539 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
540 &[],
541 &["--hypervisor", "kvm", "/dev/null"],
542 )
543 .unwrap()
544 .try_into()
545 .unwrap();
546
547 assert_eq!(
548 config.hypervisor,
549 Some(HypervisorKind::Kvm { device: None })
550 );
551 }
552
553 #[test]
554 fn hypervisor_kvm_device() {
555 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
556 &[],
557 &["--hypervisor", "kvm[device=/not/default]", "/dev/null"],
558 )
559 .unwrap()
560 .try_into()
561 .unwrap();
562
563 assert_eq!(
564 config.hypervisor,
565 Some(HypervisorKind::Kvm {
566 device: Some(PathBuf::from("/not/default"))
567 })
568 );
569 }
570
571 #[test]
572 #[cfg(all(target_arch = "aarch64", feature = "halla"))]
573 fn hypervisor_halla() {
574 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
575 &[],
576 &["--hypervisor", "halla", "/dev/null"],
577 )
578 .unwrap()
579 .try_into()
580 .unwrap();
581
582 assert_eq!(
583 config.hypervisor,
584 Some(HypervisorKind::Halla { device: None })
585 );
586 }
587
588 #[test]
589 #[cfg(all(target_arch = "aarch64", feature = "halla"))]
590 fn hypervisor_halla_device() {
591 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
592 &[],
593 &["--hypervisor", "halla[device=/not/default]", "/dev/null"],
594 )
595 .unwrap()
596 .try_into()
597 .unwrap();
598
599 assert_eq!(
600 config.hypervisor,
601 Some(HypervisorKind::Halla {
602 device: Some(PathBuf::from("/not/default"))
603 })
604 );
605 }
606
607 #[test]
608 #[cfg(all(target_arch = "aarch64", feature = "geniezone"))]
609 fn hypervisor_geniezone() {
610 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
611 &[],
612 &["--hypervisor", "geniezone", "/dev/null"],
613 )
614 .unwrap()
615 .try_into()
616 .unwrap();
617
618 assert_eq!(
619 config.hypervisor,
620 Some(HypervisorKind::Geniezone { device: None })
621 );
622 }
623
624 #[test]
625 #[cfg(all(target_arch = "aarch64", feature = "geniezone"))]
626 fn hypervisor_geniezone_device() {
627 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
628 &[],
629 &[
630 "--hypervisor",
631 "geniezone[device=/not/default]",
632 "/dev/null",
633 ],
634 )
635 .unwrap()
636 .try_into()
637 .unwrap();
638
639 assert_eq!(
640 config.hypervisor,
641 Some(HypervisorKind::Geniezone {
642 device: Some(PathBuf::from("/not/default"))
643 })
644 );
645 }
646
647 #[test]
648 #[cfg(all(target_arch = "aarch64", feature = "gunyah"))]
649 fn hypervisor_gunyah() {
650 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
651 &[],
652 &["--hypervisor", "gunyah", "/dev/null"],
653 )
654 .unwrap()
655 .try_into()
656 .unwrap();
657
658 assert_eq!(
659 config.hypervisor,
660 Some(HypervisorKind::Gunyah {
661 device: None,
662 qcom_trusted_vm_id: None,
663 qcom_trusted_vm_pas_id: None,
664 })
665 );
666 }
667
668 #[test]
669 #[cfg(all(target_arch = "aarch64", feature = "gunyah"))]
670 fn hypervisor_gunyah_device() {
671 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
672 &[],
673 &["--hypervisor", "gunyah[device=/not/default]", "/dev/null"],
674 )
675 .unwrap()
676 .try_into()
677 .unwrap();
678
679 assert_eq!(
680 config.hypervisor,
681 Some(HypervisorKind::Gunyah {
682 device: Some(PathBuf::from("/not/default")),
683 qcom_trusted_vm_id: None,
684 qcom_trusted_vm_pas_id: None,
685 })
686 );
687 }
688
689 #[test]
690 #[cfg(all(target_arch = "aarch64", feature = "gunyah"))]
691 fn hypervisor_gunyah_device_with_qtvm_ids() {
692 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
693 &[],
694 &[
695 "--hypervisor",
696 "gunyah[device=/not/default,qcom_trusted_vm_id=0,qcom_trusted_vm_pas_id=0]",
697 "/dev/null",
698 ],
699 )
700 .unwrap()
701 .try_into()
702 .unwrap();
703
704 assert_eq!(
705 config.hypervisor,
706 Some(HypervisorKind::Gunyah {
707 device: Some(PathBuf::from("/not/default")),
708 qcom_trusted_vm_id: Some(0),
709 qcom_trusted_vm_pas_id: Some(0),
710 })
711 );
712 }
713
714 #[test]
715 fn parse_shared_dir() {
716 let s = "/:usr_local_bin:type=fs:cache=always:uidmap=0 655360 5000,5000 600 50,5050 660410 1994950:gidmap=0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950:timeout=3600:rewrite-security-xattrs=true:writeback=true";
719
720 let shared_dir: SharedDir = s.parse().unwrap();
721 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
722 assert_eq!(shared_dir.tag, "usr_local_bin");
723 assert!(shared_dir.kind == SharedDirKind::FS);
724 assert_eq!(
725 shared_dir.uid_map,
726 "0 655360 5000,5000 600 50,5050 660410 1994950"
727 );
728 assert_eq!(
729 shared_dir.gid_map,
730 "0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950"
731 );
732 assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
733 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
734 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
735 assert_eq!(shared_dir.fs_cfg.writeback, true);
736 assert_eq!(
737 shared_dir.fs_cfg.cache_policy,
738 devices::virtio::fs::CachePolicy::Always
739 );
740 assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
741 assert_eq!(shared_dir.fs_cfg.use_dax, false);
742 assert_eq!(shared_dir.fs_cfg.posix_acl, true);
743 assert_eq!(shared_dir.ugid, (None, None));
744 }
745
746 #[test]
747 fn parse_shared_dir_parses_ascii_casefold_and_posix_acl() {
748 let s = "/:usr_local_bin:type=fs:ascii_casefold=true:posix_acl=false";
751
752 let shared_dir: SharedDir = s.parse().unwrap();
753 assert_eq!(shared_dir.fs_cfg.ascii_casefold, true);
754 assert_eq!(shared_dir.fs_cfg.posix_acl, false);
755 }
756
757 #[test]
758 fn parse_shared_dir_negative_timeout() {
759 let s = "/:usr_local_bin:type=fs:cache=always:timeout=3600:negative_timeout=60";
762
763 let shared_dir: SharedDir = s.parse().unwrap();
764 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
765 assert_eq!(shared_dir.tag, "usr_local_bin");
766 assert!(shared_dir.kind == SharedDirKind::FS);
767 assert_eq!(
768 shared_dir.fs_cfg.cache_policy,
769 devices::virtio::fs::CachePolicy::Always
770 );
771 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
772 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::from_secs(60));
773 }
774
775 #[test]
776 fn parse_shared_dir_oem() {
777 let shared_dir: SharedDir = "/:oem_etc:type=fs:cache=always:uidmap=0 299 1, 5000 600 50:gidmap=0 300 1, 5000 600 50:timeout=3600:rewrite-security-xattrs=true".parse().unwrap();
778 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
779 assert_eq!(shared_dir.tag, "oem_etc");
780 assert!(shared_dir.kind == SharedDirKind::FS);
781 assert_eq!(shared_dir.uid_map, "0 299 1, 5000 600 50");
782 assert_eq!(shared_dir.gid_map, "0 300 1, 5000 600 50");
783 assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
784 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
785 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
786 assert_eq!(shared_dir.fs_cfg.writeback, false);
787 assert_eq!(
788 shared_dir.fs_cfg.cache_policy,
789 devices::virtio::fs::CachePolicy::Always
790 );
791 assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
792 assert_eq!(shared_dir.fs_cfg.use_dax, false);
793 assert_eq!(shared_dir.fs_cfg.posix_acl, true);
794 assert_eq!(shared_dir.ugid, (None, None));
795 }
796
797 #[test]
798 #[cfg(feature = "arc_quota")]
799 fn parse_shared_dir_arcvm_data() {
800 let arcvm_arg = "/:_data:type=fs:cache=always:uidmap=0 655360 5000,5000 600 50,5050 660410 1994950:gidmap=0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950:timeout=3600:rewrite-security-xattrs=true:writeback=true:privileged_quota_uids=0";
802 assert_eq!(
803 arcvm_arg.parse::<SharedDir>().unwrap().fs_cfg,
804 devices::virtio::fs::Config {
805 cache_policy: CachePolicy::Always,
806 timeout: Duration::from_secs(3600),
807 rewrite_security_xattrs: true,
808 writeback: true,
809 privileged_quota_uids: vec![0],
810 ..Default::default()
811 }
812 );
813 }
814
815 #[test]
816 fn parse_shared_dir_ugid_set() {
817 let shared_dir: SharedDir =
818 "/:hostRoot:type=fs:uidmap=40417 40417 1:gidmap=5000 5000 1:uid=40417:gid=5000"
819 .parse()
820 .unwrap();
821 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
822 assert_eq!(shared_dir.tag, "hostRoot");
823 assert!(shared_dir.kind == SharedDirKind::FS);
824 assert_eq!(shared_dir.uid_map, "40417 40417 1");
825 assert_eq!(shared_dir.gid_map, "5000 5000 1");
826 assert_eq!(shared_dir.ugid, (Some(40417), Some(5000)));
827 }
828
829 #[test]
830 fn parse_shared_dir_vm_fio() {
831 let shared_dir: SharedDir =
835 "/:root:type=fs:cache=always:timeout=5:writeback=false:dax=false:ascii_casefold=false"
836 .parse()
837 .unwrap();
838 assert_eq!(
839 shared_dir.fs_cfg,
840 devices::virtio::fs::Config {
841 cache_policy: CachePolicy::Always,
842 timeout: Duration::from_secs(5),
843 writeback: false,
844 use_dax: false,
845 ascii_casefold: false,
846 ..Default::default()
847 }
848 );
849
850 let shared_dir: SharedDir =
852 "/:shared:type=fs:cache=auto:timeout=1:writeback=true:dax=true:ascii_casefold=false"
853 .parse()
854 .unwrap();
855 assert_eq!(
856 shared_dir.fs_cfg,
857 devices::virtio::fs::Config {
858 cache_policy: CachePolicy::Auto,
859 timeout: Duration::from_secs(1),
860 writeback: true,
861 use_dax: true,
862 ascii_casefold: false,
863 ..Default::default()
864 }
865 );
866 }
867
868 #[test]
869 fn parse_cache_policy() {
870 assert_eq!(
872 "/:_data:type=fs"
873 .parse::<SharedDir>()
874 .unwrap()
875 .fs_cfg
876 .cache_policy,
877 CachePolicy::Auto
878 );
879 assert_eq!(
880 "/:_data:type=fs:cache=always"
881 .parse::<SharedDir>()
882 .unwrap()
883 .fs_cfg
884 .cache_policy,
885 CachePolicy::Always
886 );
887 assert_eq!(
888 "/:_data:type=fs:cache=auto"
889 .parse::<SharedDir>()
890 .unwrap()
891 .fs_cfg
892 .cache_policy,
893 CachePolicy::Auto
894 );
895 assert_eq!(
896 "/:_data:type=fs:cache=never"
897 .parse::<SharedDir>()
898 .unwrap()
899 .fs_cfg
900 .cache_policy,
901 CachePolicy::Never
902 );
903
904 assert!("/:_data:type=fs:cache=Always".parse::<SharedDir>().is_err());
906 assert!("/:_data:type=fs:cache=ALWAYS".parse::<SharedDir>().is_err());
907 assert!("/:_data:type=fs:cache=Auto".parse::<SharedDir>().is_err());
908 assert!("/:_data:type=fs:cache=AUTO".parse::<SharedDir>().is_err());
909 assert!("/:_data:type=fs:cache=Never".parse::<SharedDir>().is_err());
910 assert!("/:_data:type=fs:cache=NEVER".parse::<SharedDir>().is_err());
911
912 assert!("/:_data:type=fs:cache=foobar".parse::<SharedDir>().is_err());
914 }
915
916 #[cfg(feature = "arc_quota")]
917 #[test]
918 fn parse_privileged_quota_uids() {
919 assert_eq!(
920 "/:_data:type=fs:privileged_quota_uids=0"
921 .parse::<SharedDir>()
922 .unwrap()
923 .fs_cfg
924 .privileged_quota_uids,
925 vec![0]
926 );
927 assert_eq!(
928 "/:_data:type=fs:privileged_quota_uids=0 1 2 3 4"
929 .parse::<SharedDir>()
930 .unwrap()
931 .fs_cfg
932 .privileged_quota_uids,
933 vec![0, 1, 2, 3, 4]
934 );
935 }
936
937 #[test]
938 fn parse_dax() {
939 assert!(
941 !"/:_data:type=fs"
942 .parse::<SharedDir>()
943 .unwrap()
944 .fs_cfg
945 .use_dax
946 );
947 assert!(
948 "/:_data:type=fs:dax=true"
949 .parse::<SharedDir>()
950 .unwrap()
951 .fs_cfg
952 .use_dax
953 );
954 assert!(
955 !"/:_data:type=fs:dax=false"
956 .parse::<SharedDir>()
957 .unwrap()
958 .fs_cfg
959 .use_dax
960 );
961 }
962
963 #[test]
964 fn parse_pmem_ext2() {
965 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
966 &[],
967 &["--pmem-ext2", "/path/to/dir", "/dev/null"],
968 )
969 .unwrap()
970 .try_into()
971 .unwrap();
972
973 let opt = config.pmem_ext2.first().unwrap();
974
975 assert_eq!(opt.path, PathBuf::from("/path/to/dir"));
976 }
977
978 #[test]
979 fn parse_pmem_ext2_size() {
980 let blocks_per_group = 2048;
981 let inodes_per_group = 1024;
982 let size = 4096 * blocks_per_group;
983
984 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
985 &[],
986 &[
987 "--pmem-ext2",
988 &format!("/path/to/dir:blocks_per_group={blocks_per_group}:inodes_per_group={inodes_per_group}:size={size}"),
989 "/dev/null",
990 ],
991 )
992 .unwrap()
993 .try_into()
994 .unwrap();
995
996 let opt = config.pmem_ext2.first().unwrap();
997
998 assert_eq!(opt.path, PathBuf::from("/path/to/dir"));
999 assert_eq!(opt.blocks_per_group, blocks_per_group);
1000 assert_eq!(opt.inodes_per_group, inodes_per_group);
1001 assert_eq!(opt.size, size);
1002 }
1003}