• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     },
38 }
39 
40 // Doesn't do anything on unix.
check_serial_params(_serial_params: &SerialParameters) -> Result<(), String>41 pub fn check_serial_params(_serial_params: &SerialParameters) -> Result<(), String> {
42     Ok(())
43 }
44 
validate_config(_cfg: &mut Config) -> std::result::Result<(), String>45 pub fn validate_config(_cfg: &mut Config) -> std::result::Result<(), String> {
46     Ok(())
47 }
48 
49 /// VFIO device structure for creating a new instance based on command line options.
50 #[derive(Serialize, Deserialize, FromKeyValues)]
51 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
52 pub struct VfioOption {
53     /// Path to the VFIO device.
54     pub path: PathBuf,
55 
56     /// IOMMU type to use for this VFIO device.
57     #[serde(default)]
58     pub iommu: IommuDevType,
59 
60     /// PCI address to use for the VFIO device in the guest.
61     /// If not specified, defaults to mirroring the host PCI address.
62     pub guest_address: Option<PciAddress>,
63 
64     /// The symbol that labels the overlay device tree node which corresponds to this
65     /// VFIO device.
66     pub dt_symbol: Option<String>,
67 }
68 
69 #[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
70 pub enum SharedDirKind {
71     FS,
72     #[default]
73     P9,
74 }
75 
76 impl FromStr for SharedDirKind {
77     type Err = anyhow::Error;
78 
from_str(s: &str) -> Result<Self, Self::Err>79     fn from_str(s: &str) -> Result<Self, Self::Err> {
80         use SharedDirKind::*;
81         match s {
82             "fs" | "FS" => Ok(FS),
83             "9p" | "9P" | "p9" | "P9" => Ok(P9),
84             _ => {
85                 bail!("invalid file system type");
86             }
87         }
88     }
89 }
90 
91 pub struct SharedDir {
92     pub src: PathBuf,
93     pub tag: String,
94     pub kind: SharedDirKind,
95     pub ugid: (Option<u32>, Option<u32>),
96     pub uid_map: String,
97     pub gid_map: String,
98     pub fs_cfg: devices::virtio::fs::Config,
99     pub p9_cfg: p9::Config,
100 }
101 
102 impl Default for SharedDir {
default() -> SharedDir103     fn default() -> SharedDir {
104         SharedDir {
105             src: Default::default(),
106             tag: Default::default(),
107             kind: Default::default(),
108             ugid: (None, None),
109             // SAFETY: trivially safe
110             uid_map: format!("0 {} 1", unsafe { geteuid() }),
111             // SAFETY: trivially safe
112             gid_map: format!("0 {} 1", unsafe { getegid() }),
113             fs_cfg: Default::default(),
114             p9_cfg: Default::default(),
115         }
116     }
117 }
118 
119 impl FromStr for SharedDir {
120     type Err = anyhow::Error;
121 
from_str(param: &str) -> Result<Self, Self::Err>122     fn from_str(param: &str) -> Result<Self, Self::Err> {
123         // This is formatted as multiple fields, each separated by ":". The first 2 fields are
124         // fixed (src:tag).  The rest may appear in any order:
125         //
126         // * type=TYPE - must be one of "p9" or "fs" (default: p9)
127         // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
128         //   (default: "0 <current euid> 1")
129         // * gidmap=GIDMAP - a gid map in the same format as uidmap (default: "0 <current egid> 1")
130         // * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
131         //   performing quota-related operations, these UIDs are treated as if they have CAP_FOWNER.
132         // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes and
133         //   directory contents should be considered valid (default: 5)
134         // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
135         // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
136         // * uid=UID - uid of the device process in the user namespace created by minijail.
137         //   (default: 0)
138         // * gid=GID - gid of the device process in the user namespace created by minijail.
139         //   (default: 0)
140         // These two options (uid/gid) are useful when the crosvm process has no
141         // CAP_SETGID/CAP_SETUID but an identity mapping of the current user/group
142         // between the VM and the host is required.
143         // Say the current user and the crosvm process has uid 5000, a user can use
144         // "uid=5000" and "uidmap=5000 5000 1" such that files owned by user 5000
145         // still appear to be owned by user 5000 in the VM. These 2 options are
146         // useful only when there is 1 user in the VM accessing shared files.
147         // If multiple users want to access the shared file, gid/uid options are
148         // useless. It'd be better to create a new user namespace and give
149         // CAP_SETUID/CAP_SETGID to the crosvm.
150         let mut components = param.split(':');
151         let src = PathBuf::from(
152             components
153                 .next()
154                 .context("missing source path for `shared-dir`")?,
155         );
156         let tag = components
157             .next()
158             .context("missing tag for `shared-dir`")?
159             .to_owned();
160 
161         if !src.is_dir() {
162             bail!("source path for `shared-dir` must be a directory");
163         }
164 
165         let mut shared_dir = SharedDir {
166             src,
167             tag,
168             ..Default::default()
169         };
170         let mut type_opts = vec![];
171         for opt in components {
172             let mut o = opt.splitn(2, '=');
173             let kind = o.next().context("`shared-dir` options must not be empty")?;
174             let value = o
175                 .next()
176                 .context("`shared-dir` options must be of the form `kind=value`")?;
177 
178             match kind {
179                 "type" => {
180                     shared_dir.kind = value.parse().with_context(|| {
181                         anyhow!("`type` must be one of `fs` or `9p` but {value}")
182                     })?
183                 }
184                 "uidmap" => shared_dir.uid_map = value.into(),
185                 "gidmap" => shared_dir.gid_map = value.into(),
186                 "uid" => {
187                     shared_dir.ugid.0 = Some(value.parse().context("`uid` must be an integer")?);
188                 }
189                 "gid" => {
190                     shared_dir.ugid.1 = Some(value.parse().context("`gid` must be an integer")?);
191                 }
192                 _ => type_opts.push(opt),
193             }
194         }
195         match shared_dir.kind {
196             SharedDirKind::FS => {
197                 shared_dir.fs_cfg = from_key_values(&type_opts.join(","))
198                     .map_err(|e| anyhow!("failed to parse fs config '{:?}': {e}", type_opts))?;
199 
200                 if shared_dir.fs_cfg.ascii_casefold && !shared_dir.fs_cfg.negative_timeout.is_zero()
201                 {
202                     // Disallow the combination of `ascii_casefold` and `negative_timeout` because
203                     // negative dentry caches doesn't wort well in scenarios like the following:
204                     // 1. Lookup "foo", an non-existing file. Negative dentry is cached on the
205                     //    guest.
206                     // 2. Create "FOO".
207                     // 3. Lookup "foo". This needs to be successful on the casefold directory, but
208                     //    the lookup can fail due the negative cache created at 1.
209                     bail!("'negative_timeout' cannot be used with 'ascii_casefold'");
210                 }
211             }
212             SharedDirKind::P9 => {
213                 shared_dir.p9_cfg = type_opts
214                     .join(":")
215                     .parse()
216                     .map_err(|e| anyhow!("failed to parse 9p config '{:?}': {e}", type_opts))?;
217             }
218         }
219         Ok(shared_dir)
220     }
221 }
222 
223 #[cfg(test)]
224 mod tests {
225     use std::path::Path;
226     use std::path::PathBuf;
227     use std::time::Duration;
228 
229     use argh::FromArgs;
230     use devices::virtio::fs::CachePolicy;
231 
232     use super::*;
233     use crate::crosvm::config::from_key_values;
234 
235     #[test]
parse_coiommu_options()236     fn parse_coiommu_options() {
237         use std::time::Duration;
238 
239         use devices::CoIommuParameters;
240         use devices::CoIommuUnpinPolicy;
241 
242         // unpin_policy
243         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=off").unwrap();
244         assert_eq!(
245             coiommu_params,
246             CoIommuParameters {
247                 unpin_policy: CoIommuUnpinPolicy::Off,
248                 ..Default::default()
249             }
250         );
251         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=lru").unwrap();
252         assert_eq!(
253             coiommu_params,
254             CoIommuParameters {
255                 unpin_policy: CoIommuUnpinPolicy::Lru,
256                 ..Default::default()
257             }
258         );
259         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=foo");
260         assert!(coiommu_params.is_err());
261 
262         // unpin_interval
263         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=42").unwrap();
264         assert_eq!(
265             coiommu_params,
266             CoIommuParameters {
267                 unpin_interval: Duration::from_secs(42),
268                 ..Default::default()
269             }
270         );
271         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=foo");
272         assert!(coiommu_params.is_err());
273 
274         // unpin_limit
275         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=256").unwrap();
276         assert_eq!(
277             coiommu_params,
278             CoIommuParameters {
279                 unpin_limit: Some(256),
280                 ..Default::default()
281             }
282         );
283         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=0");
284         assert!(coiommu_params.is_err());
285         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=foo");
286         assert!(coiommu_params.is_err());
287 
288         // unpin_gen_threshold
289         let coiommu_params =
290             from_key_values::<CoIommuParameters>("unpin_gen_threshold=32").unwrap();
291         assert_eq!(
292             coiommu_params,
293             CoIommuParameters {
294                 unpin_gen_threshold: 32,
295                 ..Default::default()
296             }
297         );
298         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_gen_threshold=foo");
299         assert!(coiommu_params.is_err());
300 
301         // All together
302         let coiommu_params = from_key_values::<CoIommuParameters>(
303             "unpin_policy=lru,unpin_interval=90,unpin_limit=8,unpin_gen_threshold=64",
304         )
305         .unwrap();
306         assert_eq!(
307             coiommu_params,
308             CoIommuParameters {
309                 unpin_policy: CoIommuUnpinPolicy::Lru,
310                 unpin_interval: Duration::from_secs(90),
311                 unpin_limit: Some(8),
312                 unpin_gen_threshold: 64,
313             }
314         );
315 
316         // invalid parameter
317         let coiommu_params = from_key_values::<CoIommuParameters>("unpin_invalid_param=0");
318         assert!(coiommu_params.is_err());
319     }
320 
321     #[test]
vfio_pci_path()322     fn vfio_pci_path() {
323         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
324             &[],
325             &["--vfio", "/path/to/dev", "/dev/null"],
326         )
327         .unwrap()
328         .try_into()
329         .unwrap();
330 
331         let vfio = config.vfio.first().unwrap();
332 
333         assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
334         assert_eq!(vfio.iommu, IommuDevType::NoIommu);
335         assert_eq!(vfio.guest_address, None);
336     }
337 
338     #[test]
vfio_pci_path_coiommu()339     fn vfio_pci_path_coiommu() {
340         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
341             &[],
342             &["--vfio", "/path/to/dev,iommu=coiommu", "/dev/null"],
343         )
344         .unwrap()
345         .try_into()
346         .unwrap();
347 
348         let vfio = config.vfio.first().unwrap();
349 
350         assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
351         assert_eq!(vfio.iommu, IommuDevType::CoIommu);
352         assert_eq!(vfio.guest_address, None);
353     }
354 
355     #[test]
vfio_pci_path_viommu_guest_address()356     fn vfio_pci_path_viommu_guest_address() {
357         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
358             &[],
359             &[
360                 "--vfio",
361                 "/path/to/dev,iommu=viommu,guest-address=42:15.4",
362                 "/dev/null",
363             ],
364         )
365         .unwrap()
366         .try_into()
367         .unwrap();
368 
369         let vfio = config.vfio.first().unwrap();
370 
371         assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
372         assert_eq!(vfio.iommu, IommuDevType::VirtioIommu);
373         assert_eq!(
374             vfio.guest_address,
375             Some(PciAddress::new(0, 0x42, 0x15, 4).unwrap())
376         );
377     }
378 
379     #[test]
vfio_platform()380     fn vfio_platform() {
381         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
382             &[],
383             &["--vfio-platform", "/path/to/dev", "/dev/null"],
384         )
385         .unwrap()
386         .try_into()
387         .unwrap();
388 
389         let vfio = config.vfio.first().unwrap();
390 
391         assert_eq!(vfio.path, PathBuf::from("/path/to/dev"));
392     }
393 
394     #[test]
hypervisor_default()395     fn hypervisor_default() {
396         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(&[], &["/dev/null"])
397             .unwrap()
398             .try_into()
399             .unwrap();
400 
401         assert_eq!(config.hypervisor, None);
402     }
403 
404     #[test]
hypervisor_kvm()405     fn hypervisor_kvm() {
406         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
407             &[],
408             &["--hypervisor", "kvm", "/dev/null"],
409         )
410         .unwrap()
411         .try_into()
412         .unwrap();
413 
414         assert_eq!(
415             config.hypervisor,
416             Some(HypervisorKind::Kvm { device: None })
417         );
418     }
419 
420     #[test]
hypervisor_kvm_device()421     fn hypervisor_kvm_device() {
422         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
423             &[],
424             &["--hypervisor", "kvm[device=/not/default]", "/dev/null"],
425         )
426         .unwrap()
427         .try_into()
428         .unwrap();
429 
430         assert_eq!(
431             config.hypervisor,
432             Some(HypervisorKind::Kvm {
433                 device: Some(PathBuf::from("/not/default"))
434             })
435         );
436     }
437 
438     #[test]
439     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
440     #[cfg(feature = "geniezone")]
hypervisor_geniezone()441     fn hypervisor_geniezone() {
442         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
443             &[],
444             &["--hypervisor", "geniezone", "/dev/null"],
445         )
446         .unwrap()
447         .try_into()
448         .unwrap();
449 
450         assert_eq!(
451             config.hypervisor,
452             Some(HypervisorKind::Geniezone { device: None })
453         );
454     }
455 
456     #[test]
457     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
458     #[cfg(feature = "geniezone")]
hypervisor_geniezone_device()459     fn hypervisor_geniezone_device() {
460         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
461             &[],
462             &[
463                 "--hypervisor",
464                 "geniezone[device=/not/default]",
465                 "/dev/null",
466             ],
467         )
468         .unwrap()
469         .try_into()
470         .unwrap();
471 
472         assert_eq!(
473             config.hypervisor,
474             Some(HypervisorKind::Geniezone {
475                 device: Some(PathBuf::from("/not/default"))
476             })
477         );
478     }
479 
480     #[test]
481     #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
hypervisor_gunyah()482     fn hypervisor_gunyah() {
483         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
484             &[],
485             &["--hypervisor", "gunyah", "/dev/null"],
486         )
487         .unwrap()
488         .try_into()
489         .unwrap();
490 
491         assert_eq!(
492             config.hypervisor,
493             Some(HypervisorKind::Gunyah { device: None })
494         );
495     }
496 
497     #[test]
498     #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
hypervisor_gunyah_device()499     fn hypervisor_gunyah_device() {
500         let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
501             &[],
502             &["--hypervisor", "gunyah[device=/not/default]", "/dev/null"],
503         )
504         .unwrap()
505         .try_into()
506         .unwrap();
507 
508         assert_eq!(
509             config.hypervisor,
510             Some(HypervisorKind::Gunyah {
511                 device: Some(PathBuf::from("/not/default"))
512             })
513         );
514     }
515 
516     #[test]
parse_shared_dir()517     fn parse_shared_dir() {
518         // Although I want to test /usr/local/bin, Use / instead of
519         // /usr/local/bin, as /usr/local/bin doesn't always exist.
520         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";
521 
522         let shared_dir: SharedDir = s.parse().unwrap();
523         assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
524         assert_eq!(shared_dir.tag, "usr_local_bin");
525         assert!(shared_dir.kind == SharedDirKind::FS);
526         assert_eq!(
527             shared_dir.uid_map,
528             "0 655360 5000,5000 600 50,5050 660410 1994950"
529         );
530         assert_eq!(
531             shared_dir.gid_map,
532             "0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950"
533         );
534         assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
535         assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
536         assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
537         assert_eq!(shared_dir.fs_cfg.writeback, true);
538         assert_eq!(
539             shared_dir.fs_cfg.cache_policy,
540             devices::virtio::fs::CachePolicy::Always
541         );
542         assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
543         assert_eq!(shared_dir.fs_cfg.use_dax, false);
544         assert_eq!(shared_dir.fs_cfg.posix_acl, true);
545         assert_eq!(shared_dir.ugid, (None, None));
546     }
547 
548     #[test]
parse_shared_dir_parses_ascii_casefold_and_posix_acl()549     fn parse_shared_dir_parses_ascii_casefold_and_posix_acl() {
550         // Although I want to test /usr/local/bin, Use / instead of
551         // /usr/local/bin, as /usr/local/bin doesn't always exist.
552         let s = "/:usr_local_bin:type=fs:ascii_casefold=true:posix_acl=false";
553 
554         let shared_dir: SharedDir = s.parse().unwrap();
555         assert_eq!(shared_dir.fs_cfg.ascii_casefold, true);
556         assert_eq!(shared_dir.fs_cfg.posix_acl, false);
557     }
558 
559     #[test]
parse_shared_dir_negative_timeout()560     fn parse_shared_dir_negative_timeout() {
561         // Although I want to test /usr/local/bin, Use / instead of
562         // /usr/local/bin, as /usr/local/bin doesn't always exist.
563         let s = "/:usr_local_bin:type=fs:cache=always:timeout=3600:negative_timeout=60";
564 
565         let shared_dir: SharedDir = s.parse().unwrap();
566         assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
567         assert_eq!(shared_dir.tag, "usr_local_bin");
568         assert!(shared_dir.kind == SharedDirKind::FS);
569         assert_eq!(
570             shared_dir.fs_cfg.cache_policy,
571             devices::virtio::fs::CachePolicy::Always
572         );
573         assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
574         assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::from_secs(60));
575     }
576 
577     #[test]
parse_shared_dir_oem()578     fn parse_shared_dir_oem() {
579         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();
580         assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
581         assert_eq!(shared_dir.tag, "oem_etc");
582         assert!(shared_dir.kind == SharedDirKind::FS);
583         assert_eq!(shared_dir.uid_map, "0 299 1, 5000 600 50");
584         assert_eq!(shared_dir.gid_map, "0 300 1, 5000 600 50");
585         assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
586         assert_eq!(shared_dir.fs_cfg.timeout, Duration::from_secs(3600));
587         assert_eq!(shared_dir.fs_cfg.negative_timeout, Duration::ZERO);
588         assert_eq!(shared_dir.fs_cfg.writeback, false);
589         assert_eq!(
590             shared_dir.fs_cfg.cache_policy,
591             devices::virtio::fs::CachePolicy::Always
592         );
593         assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
594         assert_eq!(shared_dir.fs_cfg.use_dax, false);
595         assert_eq!(shared_dir.fs_cfg.posix_acl, true);
596         assert_eq!(shared_dir.ugid, (None, None));
597     }
598 
599     #[test]
600     #[cfg(feature = "arc_quota")]
parse_shared_dir_arcvm_data()601     fn parse_shared_dir_arcvm_data() {
602         // Test an actual ARCVM argument for /data/, where the path is replaced with `/`.
603         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";
604         assert_eq!(
605             arcvm_arg.parse::<SharedDir>().unwrap().fs_cfg,
606             devices::virtio::fs::Config {
607                 cache_policy: CachePolicy::Always,
608                 timeout: Duration::from_secs(3600),
609                 rewrite_security_xattrs: true,
610                 writeback: true,
611                 privileged_quota_uids: vec![0],
612                 ..Default::default()
613             }
614         );
615     }
616 
617     #[test]
parse_shared_dir_ugid_set()618     fn parse_shared_dir_ugid_set() {
619         let shared_dir: SharedDir =
620             "/:hostRoot:type=fs:uidmap=40417 40417 1:gidmap=5000 5000 1:uid=40417:gid=5000"
621                 .parse()
622                 .unwrap();
623         assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
624         assert_eq!(shared_dir.tag, "hostRoot");
625         assert!(shared_dir.kind == SharedDirKind::FS);
626         assert_eq!(shared_dir.uid_map, "40417 40417 1");
627         assert_eq!(shared_dir.gid_map, "5000 5000 1");
628         assert_eq!(shared_dir.ugid, (Some(40417), Some(5000)));
629     }
630 
631     #[test]
parse_shared_dir_vm_fio()632     fn parse_shared_dir_vm_fio() {
633         // Tests shared-dir argurments used in ChromeOS's vm.Fio tast tests.
634 
635         // --shared-dir for rootfs
636         let shared_dir: SharedDir =
637             "/:root:type=fs:cache=always:timeout=5:writeback=false:dax=false:ascii_casefold=false"
638                 .parse()
639                 .unwrap();
640         assert_eq!(
641             shared_dir.fs_cfg,
642             devices::virtio::fs::Config {
643                 cache_policy: CachePolicy::Always,
644                 timeout: Duration::from_secs(5),
645                 writeback: false,
646                 use_dax: false,
647                 ascii_casefold: false,
648                 ..Default::default()
649             }
650         );
651 
652         // --shared-dir for vm.Fio.virtiofs_dax_*
653         let shared_dir: SharedDir =
654             "/:shared:type=fs:cache=auto:timeout=1:writeback=true:dax=true:ascii_casefold=false"
655                 .parse()
656                 .unwrap();
657         assert_eq!(
658             shared_dir.fs_cfg,
659             devices::virtio::fs::Config {
660                 cache_policy: CachePolicy::Auto,
661                 timeout: Duration::from_secs(1),
662                 writeback: true,
663                 use_dax: true,
664                 ascii_casefold: false,
665                 ..Default::default()
666             }
667         );
668     }
669 
670     #[test]
parse_cache_policy()671     fn parse_cache_policy() {
672         // The default policy is `auto`.
673         assert_eq!(
674             "/:_data:type=fs"
675                 .parse::<SharedDir>()
676                 .unwrap()
677                 .fs_cfg
678                 .cache_policy,
679             CachePolicy::Auto
680         );
681         assert_eq!(
682             "/:_data:type=fs:cache=always"
683                 .parse::<SharedDir>()
684                 .unwrap()
685                 .fs_cfg
686                 .cache_policy,
687             CachePolicy::Always
688         );
689         assert_eq!(
690             "/:_data:type=fs:cache=auto"
691                 .parse::<SharedDir>()
692                 .unwrap()
693                 .fs_cfg
694                 .cache_policy,
695             CachePolicy::Auto
696         );
697         assert_eq!(
698             "/:_data:type=fs:cache=never"
699                 .parse::<SharedDir>()
700                 .unwrap()
701                 .fs_cfg
702                 .cache_policy,
703             CachePolicy::Never
704         );
705 
706         // cache policy is case-sensitive
707         assert!("/:_data:type=fs:cache=Always".parse::<SharedDir>().is_err());
708         assert!("/:_data:type=fs:cache=ALWAYS".parse::<SharedDir>().is_err());
709         assert!("/:_data:type=fs:cache=Auto".parse::<SharedDir>().is_err());
710         assert!("/:_data:type=fs:cache=AUTO".parse::<SharedDir>().is_err());
711         assert!("/:_data:type=fs:cache=Never".parse::<SharedDir>().is_err());
712         assert!("/:_data:type=fs:cache=NEVER".parse::<SharedDir>().is_err());
713 
714         // we don't accept unknown policy
715         assert!("/:_data:type=fs:cache=foobar".parse::<SharedDir>().is_err());
716     }
717 
718     #[cfg(feature = "arc_quota")]
719     #[test]
parse_privileged_quota_uids()720     fn parse_privileged_quota_uids() {
721         assert_eq!(
722             "/:_data:type=fs:privileged_quota_uids=0"
723                 .parse::<SharedDir>()
724                 .unwrap()
725                 .fs_cfg
726                 .privileged_quota_uids,
727             vec![0]
728         );
729         assert_eq!(
730             "/:_data:type=fs:privileged_quota_uids=0 1 2 3 4"
731                 .parse::<SharedDir>()
732                 .unwrap()
733                 .fs_cfg
734                 .privileged_quota_uids,
735             vec![0, 1, 2, 3, 4]
736         );
737     }
738 
739     #[test]
parse_dax()740     fn parse_dax() {
741         // DAX is disabled by default
742         assert!(
743             !"/:_data:type=fs"
744                 .parse::<SharedDir>()
745                 .unwrap()
746                 .fs_cfg
747                 .use_dax
748         );
749         assert!(
750             "/:_data:type=fs:dax=true"
751                 .parse::<SharedDir>()
752                 .unwrap()
753                 .fs_cfg
754                 .use_dax
755         );
756         assert!(
757             !"/:_data:type=fs:dax=false"
758                 .parse::<SharedDir>()
759                 .unwrap()
760                 .fs_cfg
761                 .use_dax
762         );
763     }
764 }
765