crosvm/crosvm/sys/linux/
config.rs

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