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