• 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         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