1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::path::PathBuf;
6 use std::str::FromStr;
7
8 use anyhow::anyhow;
9 use anyhow::bail;
10 use anyhow::Context;
11 use devices::IommuDevType;
12 use devices::PciAddress;
13 use devices::SerialParameters;
14 use libc::getegid;
15 use libc::geteuid;
16 use serde::Deserialize;
17 use serde::Serialize;
18 use serde_keyvalue::from_key_values;
19 use serde_keyvalue::FromKeyValues;
20
21 use crate::crosvm::config::Config;
22
23 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
24 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
25 pub enum HypervisorKind {
26 Kvm {
27 device: Option<PathBuf>,
28 },
29 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
30 #[cfg(feature = "geniezone")]
31 Geniezone {
32 device: Option<PathBuf>,
33 },
34 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
35 Gunyah {
36 device: Option<PathBuf>,
37 qcom_trusted_vm_id: Option<u16>,
38 qcom_trusted_vm_pas_id: Option<u32>,
39 },
40 }
41
42 // Doesn't do anything on unix.
check_serial_params(_serial_params: &SerialParameters) -> Result<(), String>43 pub fn check_serial_params(_serial_params: &SerialParameters) -> Result<(), String> {
44 Ok(())
45 }
46
validate_config(_cfg: &mut Config) -> std::result::Result<(), String>47 pub fn validate_config(_cfg: &mut Config) -> std::result::Result<(), String> {
48 Ok(())
49 }
50
51 /// VFIO device structure for creating a new instance based on command line options.
52 #[derive(Serialize, Deserialize, FromKeyValues)]
53 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
54 pub struct VfioOption {
55 /// Path to the VFIO device.
56 pub path: PathBuf,
57
58 /// IOMMU type to use for this VFIO device.
59 #[serde(default)]
60 pub iommu: IommuDevType,
61
62 /// PCI address to use for the VFIO device in the guest.
63 /// If not specified, defaults to mirroring the host PCI address.
64 pub guest_address: Option<PciAddress>,
65
66 /// The symbol that labels the overlay device tree node which corresponds to this
67 /// VFIO device.
68 pub dt_symbol: Option<String>,
69 }
70
71 #[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
72 pub enum SharedDirKind {
73 FS,
74 #[default]
75 P9,
76 }
77
78 impl FromStr for SharedDirKind {
79 type Err = anyhow::Error;
80
from_str(s: &str) -> Result<Self, Self::Err>81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 use SharedDirKind::*;
83 match s {
84 "fs" | "FS" => Ok(FS),
85 "9p" | "9P" | "p9" | "P9" => Ok(P9),
86 _ => {
87 bail!("invalid file system type");
88 }
89 }
90 }
91 }
92
93 pub struct SharedDir {
94 pub src: PathBuf,
95 pub tag: String,
96 pub kind: SharedDirKind,
97 pub ugid: (Option<u32>, Option<u32>),
98 pub uid_map: String,
99 pub gid_map: String,
100 pub fs_cfg: devices::virtio::fs::Config,
101 pub p9_cfg: p9::Config,
102 }
103
104 impl Default for SharedDir {
default() -> SharedDir105 fn default() -> SharedDir {
106 SharedDir {
107 src: Default::default(),
108 tag: Default::default(),
109 kind: Default::default(),
110 ugid: (None, None),
111 // SAFETY: trivially safe
112 uid_map: format!("0 {} 1", unsafe { geteuid() }),
113 // SAFETY: trivially safe
114 gid_map: format!("0 {} 1", unsafe { getegid() }),
115 fs_cfg: Default::default(),
116 p9_cfg: Default::default(),
117 }
118 }
119 }
120
121 struct UgidConfig {
122 uid: Option<u32>,
123 gid: Option<u32>,
124 uid_map: String,
125 gid_map: String,
126 }
127
128 impl Default for UgidConfig {
default() -> Self129 fn default() -> Self {
130 Self {
131 uid: None,
132 gid: None,
133 // SAFETY: geteuid never fails.
134 uid_map: format!("0 {} 1", unsafe { geteuid() }),
135 // SAFETY: getegid never fails.
136 gid_map: format!("0 {} 1", unsafe { getegid() }),
137 }
138 }
139 }
140
141 impl UgidConfig {
142 /// Parse a key-value pair of ugid config to update `UgidConfig`.
143 /// Returns whether `self` was updated or not.
parse_ugid_config(&mut self, kind: &str, value: &str) -> anyhow::Result<bool>144 fn parse_ugid_config(&mut self, kind: &str, value: &str) -> anyhow::Result<bool> {
145 match kind {
146 "uid" => {
147 self.uid = Some(value.parse().context("`uid` must be an integer")?);
148 }
149 "gid" => {
150 self.gid = Some(value.parse().context("`gid` must be an integer")?);
151 }
152 "uidmap" => self.uid_map = value.into(),
153 "gidmap" => self.gid_map = value.into(),
154 _ => {
155 return Ok(false);
156 }
157 }
158 Ok(true)
159 }
160 }
161
162 impl FromStr for SharedDir {
163 type Err = anyhow::Error;
164
from_str(param: &str) -> Result<Self, Self::Err>165 fn from_str(param: &str) -> Result<Self, Self::Err> {
166 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
167 // fixed (src:tag). The rest may appear in any order:
168 //
169 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
170 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
171 // (default: "0 <current euid> 1")
172 // * gidmap=GIDMAP - a gid map in the same format as uidmap (default: "0 <current egid> 1")
173 // * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
174 // performing quota-related operations, these UIDs are treated as if they have CAP_FOWNER.
175 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes and
176 // directory contents should be considered valid (default: 5)
177 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
178 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
179 // * uid=UID - uid of the device process in the user namespace created by minijail.
180 // (default: 0)
181 // * gid=GID - gid of the device process in the user namespace created by minijail.
182 // (default: 0)
183 // * max_dynamic_perm=uint - number of maximum number of dynamic permissions paths (default:
184 // 0) This feature is arc_quota specific feature.
185 // * max_dynamic_xattr=uint - number of maximum number of dynamic xattr paths (default: 0).
186 // This feature is arc_quota specific feature.
187 // * security_ctx=BOOL - indicates whether use FUSE_SECURITY_CONTEXT feature or not.
188 //
189 // These two options (uid/gid) are useful when the crosvm process has no
190 // CAP_SETGID/CAP_SETUID but an identity mapping of the current user/group
191 // between the VM and the host is required.
192 // Say the current user and the crosvm process has uid 5000, a user can use
193 // "uid=5000" and "uidmap=5000 5000 1" such that files owned by user 5000
194 // still appear to be owned by user 5000 in the VM. These 2 options are
195 // useful only when there is 1 user in the VM accessing shared files.
196 // If multiple users want to access the shared file, gid/uid options are
197 // useless. It'd be better to create a new user namespace and give
198 // CAP_SETUID/CAP_SETGID to the crosvm.
199 let mut components = param.split(':');
200 let src = PathBuf::from(
201 components
202 .next()
203 .context("missing source path for `shared-dir`")?,
204 );
205 let tag = components
206 .next()
207 .context("missing tag for `shared-dir`")?
208 .to_owned();
209
210 if !src.is_dir() {
211 bail!("source path for `shared-dir` must be a directory");
212 }
213
214 let mut shared_dir = SharedDir {
215 src,
216 tag,
217 ..Default::default()
218 };
219 let mut type_opts = vec![];
220 let mut ugid_cfg = UgidConfig::default();
221 for opt in components {
222 let mut o = opt.splitn(2, '=');
223 let kind = o.next().context("`shared-dir` options must not be empty")?;
224 let value = o
225 .next()
226 .context("`shared-dir` options must be of the form `kind=value`")?;
227
228 if !ugid_cfg
229 .parse_ugid_config(kind, value)
230 .context("failed to parse ugid config")?
231 {
232 match kind {
233 "type" => {
234 shared_dir.kind = value.parse().with_context(|| {
235 anyhow!("`type` must be one of `fs` or `9p` but {value}")
236 })?
237 }
238 _ => type_opts.push(opt),
239 }
240 }
241 }
242 shared_dir.ugid = (ugid_cfg.uid, ugid_cfg.gid);
243 shared_dir.uid_map = ugid_cfg.uid_map;
244 shared_dir.gid_map = ugid_cfg.gid_map;
245
246 match shared_dir.kind {
247 SharedDirKind::FS => {
248 shared_dir.fs_cfg = from_key_values(&type_opts.join(","))
249 .map_err(|e| anyhow!("failed to parse fs config '{:?}': {e}", type_opts))?;
250
251 if shared_dir.fs_cfg.ascii_casefold && !shared_dir.fs_cfg.negative_timeout.is_zero()
252 {
253 // Disallow the combination of `ascii_casefold` and `negative_timeout` because
254 // negative dentry caches doesn't wort well in scenarios like the following:
255 // 1. Lookup "foo", an non-existing file. Negative dentry is cached on the
256 // guest.
257 // 2. Create "FOO".
258 // 3. Lookup "foo". This needs to be successful on the casefold directory, but
259 // the lookup can fail due the negative cache created at 1.
260 bail!("'negative_timeout' cannot be used with 'ascii_casefold'");
261 }
262 }
263 SharedDirKind::P9 => {
264 shared_dir.p9_cfg = type_opts
265 .join(":")
266 .parse()
267 .map_err(|e| anyhow!("failed to parse 9p config '{:?}': {e}", type_opts))?;
268 }
269 }
270 Ok(shared_dir)
271 }
272 }
273
274 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
275 #[serde(deny_unknown_fields)]
276 pub struct PmemExt2Option {
277 pub path: PathBuf,
278 pub blocks_per_group: u32,
279 pub inodes_per_group: u32,
280 pub size: u32,
281 pub ugid: (Option<u32>, Option<u32>),
282 pub uid_map: String,
283 pub gid_map: String,
284 }
285
286 impl Default for PmemExt2Option {
default() -> Self287 fn default() -> Self {
288 let blocks_per_group = 4096;
289 let inodes_per_group = 1024;
290 let size = ext2::BLOCK_SIZE as u32 * blocks_per_group; // only one block group
291 let ugid_cfg = UgidConfig::default();
292 Self {
293 path: Default::default(),
294 blocks_per_group,
295 inodes_per_group,
296 size,
297 ugid: (ugid_cfg.uid, ugid_cfg.gid),
298 uid_map: ugid_cfg.uid_map,
299 gid_map: ugid_cfg.gid_map,
300 }
301 }
302 }
303
parse_pmem_ext2_option(param: &str) -> Result<PmemExt2Option, String>304 pub fn parse_pmem_ext2_option(param: &str) -> Result<PmemExt2Option, String> {
305 let mut opt = PmemExt2Option::default();
306 let mut components = param.split(':');
307 opt.path = PathBuf::from(
308 components
309 .next()
310 .ok_or("missing source path for `pmem-ext2`")?,
311 );
312
313 let mut ugid_cfg = UgidConfig::default();
314 for c in components {
315 let mut o = c.splitn(2, '=');
316 let kind = o.next().ok_or("`pmem-ext2` options must not be empty")?;
317 let value = o
318 .next()
319 .ok_or("`pmem-ext2` options must be of the form `kind=value`")?;
320
321 if !ugid_cfg
322 .parse_ugid_config(kind, value)
323 .map_err(|e| format!("failed to parse ugid config for pmem-ext2: {:#}", e))?
324 {
325 match kind {
326 "blocks_per_group" => {
327 opt.blocks_per_group = value.parse().map_err(|e| {
328 format!("failed to parse blocks_per_groups '{value}': {:#}", e)
329 })?
330 }
331 "inodes_per_group" => {
332 opt.inodes_per_group = value.parse().map_err(|e| {
333 format!("failed to parse inodes_per_groups '{value}': {:#}", e)
334 })?
335 }
336 "size" => {
337 opt.size = value
338 .parse()
339 .map_err(|e| format!("failed to parse memory size '{value}': {:#}", e))?
340 }
341 _ => return Err(format!("invalid `pmem-ext2` option: {}", kind)),
342 }
343 }
344 }
345 opt.ugid = (ugid_cfg.uid, ugid_cfg.gid);
346 opt.uid_map = ugid_cfg.uid_map;
347 opt.gid_map = ugid_cfg.gid_map;
348
349 Ok(opt)
350 }
351
352 #[cfg(test)]
353 mod tests {
354 use std::path::Path;
355 use std::path::PathBuf;
356 use std::time::Duration;
357
358 use argh::FromArgs;
359 use devices::virtio::fs::CachePolicy;
360
361 use super::*;
362 use crate::crosvm::config::from_key_values;
363
364 #[test]
parse_coiommu_options()365 fn parse_coiommu_options() {
366 use std::time::Duration;
367
368 use devices::CoIommuParameters;
369 use devices::CoIommuUnpinPolicy;
370
371 // unpin_policy
372 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=off").unwrap();
373 assert_eq!(
374 coiommu_params,
375 CoIommuParameters {
376 unpin_policy: CoIommuUnpinPolicy::Off,
377 ..Default::default()
378 }
379 );
380 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=lru").unwrap();
381 assert_eq!(
382 coiommu_params,
383 CoIommuParameters {
384 unpin_policy: CoIommuUnpinPolicy::Lru,
385 ..Default::default()
386 }
387 );
388 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=foo");
389 assert!(coiommu_params.is_err());
390
391 // unpin_interval
392 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=42").unwrap();
393 assert_eq!(
394 coiommu_params,
395 CoIommuParameters {
396 unpin_interval: Duration::from_secs(42),
397 ..Default::default()
398 }
399 );
400 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=foo");
401 assert!(coiommu_params.is_err());
402
403 // unpin_limit
404 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=256").unwrap();
405 assert_eq!(
406 coiommu_params,
407 CoIommuParameters {
408 unpin_limit: Some(256),
409 ..Default::default()
410 }
411 );
412 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=0");
413 assert!(coiommu_params.is_err());
414 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=foo");
415 assert!(coiommu_params.is_err());
416
417 // unpin_gen_threshold
418 let coiommu_params =
419 from_key_values::<CoIommuParameters>("unpin_gen_threshold=32").unwrap();
420 assert_eq!(
421 coiommu_params,
422 CoIommuParameters {
423 unpin_gen_threshold: 32,
424 ..Default::default()
425 }
426 );
427 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_gen_threshold=foo");
428 assert!(coiommu_params.is_err());
429
430 // All together
431 let coiommu_params = from_key_values::<CoIommuParameters>(
432 "unpin_policy=lru,unpin_interval=90,unpin_limit=8,unpin_gen_threshold=64",
433 )
434 .unwrap();
435 assert_eq!(
436 coiommu_params,
437 CoIommuParameters {
438 unpin_policy: CoIommuUnpinPolicy::Lru,
439 unpin_interval: Duration::from_secs(90),
440 unpin_limit: Some(8),
441 unpin_gen_threshold: 64,
442 }
443 );
444
445 // invalid parameter
446 let coiommu_params = from_key_values::<CoIommuParameters>("unpin_invalid_param=0");
447 assert!(coiommu_params.is_err());
448 }
449
450 #[test]
vfio_pci_path()451 fn vfio_pci_path() {
452 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
453 &[],
454 &["--vfio", "/path/to/dev", "/dev/null"],
455 )
456 .unwrap()
457 .try_into()
458 .unwrap();
459
460 let vfio = config.vfio.first().unwrap();
461
462 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
463 assert_eq!(vfio.iommu, IommuDevType::NoIommu);
464 assert_eq!(vfio.guest_address, None);
465 }
466
467 #[test]
vfio_pci_path_coiommu()468 fn vfio_pci_path_coiommu() {
469 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
470 &[],
471 &["--vfio", "/path/to/dev,iommu=coiommu", "/dev/null"],
472 )
473 .unwrap()
474 .try_into()
475 .unwrap();
476
477 let vfio = config.vfio.first().unwrap();
478
479 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
480 assert_eq!(vfio.iommu, IommuDevType::CoIommu);
481 assert_eq!(vfio.guest_address, None);
482 }
483
484 #[test]
vfio_pci_path_viommu_guest_address()485 fn vfio_pci_path_viommu_guest_address() {
486 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
487 &[],
488 &[
489 "--vfio",
490 "/path/to/dev,iommu=viommu,guest-address=42:15.4",
491 "/dev/null",
492 ],
493 )
494 .unwrap()
495 .try_into()
496 .unwrap();
497
498 let vfio = config.vfio.first().unwrap();
499
500 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
501 assert_eq!(vfio.iommu, IommuDevType::VirtioIommu);
502 assert_eq!(
503 vfio.guest_address,
504 Some(PciAddress::new(0, 0x42, 0x15, 4).unwrap())
505 );
506 }
507
508 #[test]
vfio_platform()509 fn vfio_platform() {
510 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
511 &[],
512 &["--vfio-platform", "/path/to/dev", "/dev/null"],
513 )
514 .unwrap()
515 .try_into()
516 .unwrap();
517
518 let vfio = config.vfio.first().unwrap();
519
520 assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
521 }
522
523 #[test]
hypervisor_default()524 fn hypervisor_default() {
525 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(&[], &["/dev/null"])
526 .unwrap()
527 .try_into()
528 .unwrap();
529
530 assert_eq!(config.hypervisor, None);
531 }
532
533 #[test]
hypervisor_kvm()534 fn hypervisor_kvm() {
535 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
536 &[],
537 &["--hypervisor", "kvm", "/dev/null"],
538 )
539 .unwrap()
540 .try_into()
541 .unwrap();
542
543 assert_eq!(
544 config.hypervisor,
545 Some(HypervisorKind::Kvm { device: None })
546 );
547 }
548
549 #[test]
hypervisor_kvm_device()550 fn hypervisor_kvm_device() {
551 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
552 &[],
553 &["--hypervisor", "kvm[device=/not/default]", "/dev/null"],
554 )
555 .unwrap()
556 .try_into()
557 .unwrap();
558
559 assert_eq!(
560 config.hypervisor,
561 Some(HypervisorKind::Kvm {
562 device: Some(PathBuf::from("/not/default"))
563 })
564 );
565 }
566
567 #[test]
568 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
569 #[cfg(feature = "geniezone")]
hypervisor_geniezone()570 fn hypervisor_geniezone() {
571 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
572 &[],
573 &["--hypervisor", "geniezone", "/dev/null"],
574 )
575 .unwrap()
576 .try_into()
577 .unwrap();
578
579 assert_eq!(
580 config.hypervisor,
581 Some(HypervisorKind::Geniezone { device: None })
582 );
583 }
584
585 #[test]
586 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
587 #[cfg(feature = "geniezone")]
hypervisor_geniezone_device()588 fn hypervisor_geniezone_device() {
589 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
590 &[],
591 &[
592 "--hypervisor",
593 "geniezone[device=/not/default]",
594 "/dev/null",
595 ],
596 )
597 .unwrap()
598 .try_into()
599 .unwrap();
600
601 assert_eq!(
602 config.hypervisor,
603 Some(HypervisorKind::Geniezone {
604 device: Some(PathBuf::from("/not/default"))
605 })
606 );
607 }
608
609 #[test]
610 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
hypervisor_gunyah()611 fn hypervisor_gunyah() {
612 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
613 &[],
614 &["--hypervisor", "gunyah", "/dev/null"],
615 )
616 .unwrap()
617 .try_into()
618 .unwrap();
619
620 assert_eq!(
621 config.hypervisor,
622 Some(HypervisorKind::Gunyah {
623 device: None,
624 qcom_trusted_vm_id: None,
625 qcom_trusted_vm_pas_id: None,
626 })
627 );
628 }
629
630 #[test]
631 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
hypervisor_gunyah_device()632 fn hypervisor_gunyah_device() {
633 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
634 &[],
635 &["--hypervisor", "gunyah[device=/not/default]", "/dev/null"],
636 )
637 .unwrap()
638 .try_into()
639 .unwrap();
640
641 assert_eq!(
642 config.hypervisor,
643 Some(HypervisorKind::Gunyah {
644 device: Some(PathBuf::from("/not/default")),
645 qcom_trusted_vm_id: None,
646 qcom_trusted_vm_pas_id: None,
647 })
648 );
649 }
650
651 #[test]
652 #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
hypervisor_gunyah_device_with_qtvm_ids()653 fn hypervisor_gunyah_device_with_qtvm_ids() {
654 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
655 &[],
656 &["--hypervisor", "gunyah[device=/not/default,qcom_trusted_vm_id=0,qcom_trusted_vm_pas_id=0]", "/dev/null"],
657 )
658 .unwrap()
659 .try_into()
660 .unwrap();
661
662 assert_eq!(
663 config.hypervisor,
664 Some(HypervisorKind::Gunyah {
665 device: Some(PathBuf::from("/not/default")),
666 qcom_trusted_vm_id: Some(0),
667 qcom_trusted_vm_pas_id: Some(0),
668 })
669 );
670 }
671
672 #[test]
parse_shared_dir()673 fn parse_shared_dir() {
674 // Although I want to test /usr/local/bin, Use / instead of
675 // /usr/local/bin, as /usr/local/bin doesn't always exist.
676 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";
677
678 let shared_dir: SharedDir = s.parse().unwrap();
679 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
680 assert_eq!(shared_dir.tag, "usr_local_bin");
681 assert!(shared_dir.kind == SharedDirKind::FS);
682 assert_eq!(
683 shared_dir.uid_map,
684 "0 655360 5000,5000 600 50,5050 660410 1994950"
685 );
686 assert_eq!(
687 shared_dir.gid_map,
688 "0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950"
689 );
690 assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
691 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
692 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
693 assert_eq!(shared_dir.fs_cfg.writeback, true);
694 assert_eq!(
695 shared_dir.fs_cfg.cache_policy,
696 devices::virtio::fs::CachePolicy::Always
697 );
698 assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
699 assert_eq!(shared_dir.fs_cfg.use_dax, false);
700 assert_eq!(shared_dir.fs_cfg.posix_acl, true);
701 assert_eq!(shared_dir.ugid, (None, None));
702 }
703
704 #[test]
parse_shared_dir_parses_ascii_casefold_and_posix_acl()705 fn parse_shared_dir_parses_ascii_casefold_and_posix_acl() {
706 // Although I want to test /usr/local/bin, Use / instead of
707 // /usr/local/bin, as /usr/local/bin doesn't always exist.
708 let s = "/:usr_local_bin:type=fs:ascii_casefold=true:posix_acl=false";
709
710 let shared_dir: SharedDir = s.parse().unwrap();
711 assert_eq!(shared_dir.fs_cfg.ascii_casefold, true);
712 assert_eq!(shared_dir.fs_cfg.posix_acl, false);
713 }
714
715 #[test]
parse_shared_dir_negative_timeout()716 fn parse_shared_dir_negative_timeout() {
717 // Although I want to test /usr/local/bin, Use / instead of
718 // /usr/local/bin, as /usr/local/bin doesn't always exist.
719 let s = "/:usr_local_bin:type=fs:cache=always:timeout=3600:negative_timeout=60";
720
721 let shared_dir: SharedDir = s.parse().unwrap();
722 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
723 assert_eq!(shared_dir.tag, "usr_local_bin");
724 assert!(shared_dir.kind == SharedDirKind::FS);
725 assert_eq!(
726 shared_dir.fs_cfg.cache_policy,
727 devices::virtio::fs::CachePolicy::Always
728 );
729 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
730 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::from_secs(60));
731 }
732
733 #[test]
parse_shared_dir_oem()734 fn parse_shared_dir_oem() {
735 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();
736 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
737 assert_eq!(shared_dir.tag, "oem_etc");
738 assert!(shared_dir.kind == SharedDirKind::FS);
739 assert_eq!(shared_dir.uid_map, "0 299 1, 5000 600 50");
740 assert_eq!(shared_dir.gid_map, "0 300 1, 5000 600 50");
741 assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
742 assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
743 assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
744 assert_eq!(shared_dir.fs_cfg.writeback, false);
745 assert_eq!(
746 shared_dir.fs_cfg.cache_policy,
747 devices::virtio::fs::CachePolicy::Always
748 );
749 assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
750 assert_eq!(shared_dir.fs_cfg.use_dax, false);
751 assert_eq!(shared_dir.fs_cfg.posix_acl, true);
752 assert_eq!(shared_dir.ugid, (None, None));
753 }
754
755 #[test]
756 #[cfg(feature = "arc_quota")]
parse_shared_dir_arcvm_data()757 fn parse_shared_dir_arcvm_data() {
758 // Test an actual ARCVM argument for /data/, where the path is replaced with `/`.
759 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";
760 assert_eq!(
761 arcvm_arg.parse::<SharedDir>().unwrap().fs_cfg,
762 devices::virtio::fs::Config {
763 cache_policy: CachePolicy::Always,
764 timeout: Duration::from_secs(3600),
765 rewrite_security_xattrs: true,
766 writeback: true,
767 privileged_quota_uids: vec![0],
768 ..Default::default()
769 }
770 );
771 }
772
773 #[test]
parse_shared_dir_ugid_set()774 fn parse_shared_dir_ugid_set() {
775 let shared_dir: SharedDir =
776 "/:hostRoot:type=fs:uidmap=40417 40417 1:gidmap=5000 5000 1:uid=40417:gid=5000"
777 .parse()
778 .unwrap();
779 assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
780 assert_eq!(shared_dir.tag, "hostRoot");
781 assert!(shared_dir.kind == SharedDirKind::FS);
782 assert_eq!(shared_dir.uid_map, "40417 40417 1");
783 assert_eq!(shared_dir.gid_map, "5000 5000 1");
784 assert_eq!(shared_dir.ugid, (Some(40417), Some(5000)));
785 }
786
787 #[test]
parse_shared_dir_vm_fio()788 fn parse_shared_dir_vm_fio() {
789 // Tests shared-dir argurments used in ChromeOS's vm.Fio tast tests.
790
791 // --shared-dir for rootfs
792 let shared_dir: SharedDir =
793 "/:root:type=fs:cache=always:timeout=5:writeback=false:dax=false:ascii_casefold=false"
794 .parse()
795 .unwrap();
796 assert_eq!(
797 shared_dir.fs_cfg,
798 devices::virtio::fs::Config {
799 cache_policy: CachePolicy::Always,
800 timeout: Duration::from_secs(5),
801 writeback: false,
802 use_dax: false,
803 ascii_casefold: false,
804 ..Default::default()
805 }
806 );
807
808 // --shared-dir for vm.Fio.virtiofs_dax_*
809 let shared_dir: SharedDir =
810 "/:shared:type=fs:cache=auto:timeout=1:writeback=true:dax=true:ascii_casefold=false"
811 .parse()
812 .unwrap();
813 assert_eq!(
814 shared_dir.fs_cfg,
815 devices::virtio::fs::Config {
816 cache_policy: CachePolicy::Auto,
817 timeout: Duration::from_secs(1),
818 writeback: true,
819 use_dax: true,
820 ascii_casefold: false,
821 ..Default::default()
822 }
823 );
824 }
825
826 #[test]
parse_cache_policy()827 fn parse_cache_policy() {
828 // The default policy is `auto`.
829 assert_eq!(
830 "/:_data:type=fs"
831 .parse::<SharedDir>()
832 .unwrap()
833 .fs_cfg
834 .cache_policy,
835 CachePolicy::Auto
836 );
837 assert_eq!(
838 "/:_data:type=fs:cache=always"
839 .parse::<SharedDir>()
840 .unwrap()
841 .fs_cfg
842 .cache_policy,
843 CachePolicy::Always
844 );
845 assert_eq!(
846 "/:_data:type=fs:cache=auto"
847 .parse::<SharedDir>()
848 .unwrap()
849 .fs_cfg
850 .cache_policy,
851 CachePolicy::Auto
852 );
853 assert_eq!(
854 "/:_data:type=fs:cache=never"
855 .parse::<SharedDir>()
856 .unwrap()
857 .fs_cfg
858 .cache_policy,
859 CachePolicy::Never
860 );
861
862 // cache policy is case-sensitive
863 assert!("/:_data:type=fs:cache=Always".parse::<SharedDir>().is_err());
864 assert!("/:_data:type=fs:cache=ALWAYS".parse::<SharedDir>().is_err());
865 assert!("/:_data:type=fs:cache=Auto".parse::<SharedDir>().is_err());
866 assert!("/:_data:type=fs:cache=AUTO".parse::<SharedDir>().is_err());
867 assert!("/:_data:type=fs:cache=Never".parse::<SharedDir>().is_err());
868 assert!("/:_data:type=fs:cache=NEVER".parse::<SharedDir>().is_err());
869
870 // we don't accept unknown policy
871 assert!("/:_data:type=fs:cache=foobar".parse::<SharedDir>().is_err());
872 }
873
874 #[cfg(feature = "arc_quota")]
875 #[test]
parse_privileged_quota_uids()876 fn parse_privileged_quota_uids() {
877 assert_eq!(
878 "/:_data:type=fs:privileged_quota_uids=0"
879 .parse::<SharedDir>()
880 .unwrap()
881 .fs_cfg
882 .privileged_quota_uids,
883 vec![0]
884 );
885 assert_eq!(
886 "/:_data:type=fs:privileged_quota_uids=0 1 2 3 4"
887 .parse::<SharedDir>()
888 .unwrap()
889 .fs_cfg
890 .privileged_quota_uids,
891 vec![0, 1, 2, 3, 4]
892 );
893 }
894
895 #[test]
parse_dax()896 fn parse_dax() {
897 // DAX is disabled by default
898 assert!(
899 !"/:_data:type=fs"
900 .parse::<SharedDir>()
901 .unwrap()
902 .fs_cfg
903 .use_dax
904 );
905 assert!(
906 "/:_data:type=fs:dax=true"
907 .parse::<SharedDir>()
908 .unwrap()
909 .fs_cfg
910 .use_dax
911 );
912 assert!(
913 !"/:_data:type=fs:dax=false"
914 .parse::<SharedDir>()
915 .unwrap()
916 .fs_cfg
917 .use_dax
918 );
919 }
920
921 #[test]
parse_pmem_ext2()922 fn parse_pmem_ext2() {
923 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
924 &[],
925 &["--pmem-ext2", "/path/to/dir", "/dev/null"],
926 )
927 .unwrap()
928 .try_into()
929 .unwrap();
930
931 let opt = config.pmem_ext2.first().unwrap();
932
933 assert_eq!(opt.path, PathBuf::from("/path/to/dir"));
934 }
935
936 #[test]
parse_pmem_ext2_size()937 fn parse_pmem_ext2_size() {
938 let blocks_per_group = 2048;
939 let inodes_per_group = 1024;
940 let size = 4096 * blocks_per_group;
941
942 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
943 &[],
944 &[
945 "--pmem-ext2",
946 &format!("/path/to/dir:blocks_per_group={blocks_per_group}:inodes_per_group={inodes_per_group}:size={size}"),
947 "/dev/null",
948 ],
949 )
950 .unwrap()
951 .try_into()
952 .unwrap();
953
954 let opt = config.pmem_ext2.first().unwrap();
955
956 assert_eq!(opt.path, PathBuf::from("/path/to/dir"));
957 assert_eq!(opt.blocks_per_group, blocks_per_group);
958 assert_eq!(opt.inodes_per_group, inodes_per_group);
959 assert_eq!(opt.size, size);
960 }
961 }
962