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