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