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