Skip to main content

starnix_kernel_runner/
mounts.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Error, anyhow, bail};
6use fidl_fuchsia_io as fio;
7use starnix_core::fs::fuchsia::{RemoteBundle, new_remotefs_in_root};
8use starnix_core::fs::tmpfs::TmpFs;
9use starnix_core::task::{CurrentTask, Kernel};
10use starnix_core::vfs::fs_args::MountParams;
11use starnix_core::vfs::{FileSystemHandle, FileSystemOptions, FsString};
12use starnix_logging::log_info;
13
14use starnix_uapi::mount_flags::{MountFlags, MountpointFlags};
15
16pub struct MountAction {
17    pub path: FsString,
18    pub fs: FileSystemHandle,
19    pub flags: MountpointFlags,
20}
21
22impl MountAction {
23    pub fn new_for_root(
24        kernel: &Kernel,
25        pkg: &fio::DirectorySynchronousProxy,
26        spec: &str,
27    ) -> Result<MountAction, Error> {
28        let (spec, options) = MountSpec::parse(spec)?;
29        assert_eq!(spec.mount_point.as_slice(), b"/");
30        let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
31
32        // We only support mounting these file systems at the root.
33        // The root file system needs to be creatable without a task because we mount the root
34        // file system before creating the initial task.
35        let fs = match spec.fs_type.as_slice() {
36            b"remote_bundle" => RemoteBundle::new_fs_in_base(kernel, pkg, options, rights)?,
37            b"remote_pkg_subdir" => new_remotefs_in_root(kernel, pkg, options, rights)?,
38            b"tmpfs" => TmpFs::new_fs_with_options(kernel, options)?,
39            _ => bail!("unsupported root file system: {}", spec.fs_type),
40        };
41
42        Ok(spec.into_action(fs))
43    }
44
45    pub fn from_spec(
46        current_task: &CurrentTask,
47        pkg: &fio::DirectorySynchronousProxy,
48        spec_str: &str,
49    ) -> Result<Option<MountAction>, Error> {
50        let (spec, options) = MountSpec::parse(spec_str)?;
51        let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
52
53        let fs = match spec.fs_type.as_slice() {
54            // The remote_bundle file system is available only via the mounts declaration in CML.
55            b"remote_bundle" => {
56                RemoteBundle::new_fs_in_base(current_task.kernel(), pkg, options, rights)
57            }
58
59            // Mounts a subdirectory of the container's `/pkg`.
60            b"remote_pkg_subdir" => {
61                new_remotefs_in_root(current_task.kernel(), pkg, options, rights)
62                    .map_err(Error::from)
63            }
64
65            _ => {
66                current_task.create_filesystem(spec.fs_type.as_ref(), options).map_err(Error::from)
67            }
68        };
69
70        let fs = match fs {
71            Ok(fs) => fs,
72            Err(err) if spec.nofail => {
73                log_info!("Skipping mount {spec_str:?} due to nofail: {err:#}");
74                return Ok(None);
75            }
76            Err(err) => return Err(err),
77        };
78
79        Ok(Some(spec.into_action(fs)))
80    }
81}
82
83struct MountSpec {
84    mount_point: FsString,
85    fs_type: FsString,
86    flags: MountFlags,
87    nofail: bool,
88}
89
90impl MountSpec {
91    fn parse(spec: &str) -> Result<(MountSpec, FileSystemOptions), Error> {
92        let mut iter = spec.splitn(4, ':');
93        let mount_point =
94            iter.next().ok_or_else(|| anyhow!("mount point is missing from {:?}", spec))?;
95        let fs_type = iter.next().ok_or_else(|| anyhow!("fs type is missing from {:?}", spec))?;
96        let fs_src = match iter.next() {
97            Some(src) if !src.is_empty() => src,
98            _ => ".",
99        };
100
101        let mut params = MountParams::parse(iter.next().unwrap_or_default().into())?;
102        let flags = params.remove_mount_flags();
103        let nofail = params.remove(b"nofail").is_some();
104
105        Ok((
106            MountSpec { fs_type: fs_type.into(), mount_point: mount_point.into(), flags, nofail },
107            FileSystemOptions {
108                source: fs_src.into(),
109                flags: flags.file_system_flags().into(),
110                params,
111            },
112        ))
113    }
114
115    fn into_action(self, fs: FileSystemHandle) -> MountAction {
116        MountAction { path: self.mount_point, fs, flags: self.flags.mountpoint_flags() }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[::fuchsia::test]
125    fn test_parse_mount_spec_nofail() {
126        let (spec, options) =
127            MountSpec::parse("/product:remote_bundle:data/product:nodev,relatime,nofail").unwrap();
128        assert_eq!(spec.mount_point, "/product");
129        assert_eq!(spec.fs_type, "remote_bundle");
130        assert_eq!(spec.flags, MountFlags::NODEV | MountFlags::RELATIME);
131        assert!(spec.nofail);
132        assert_eq!(options.source, "data/product");
133        assert!(options.params.is_empty());
134
135        let (spec, options) =
136            MountSpec::parse("/vendor:remote_bundle:data/vendor:nodev,relatime").unwrap();
137        assert_eq!(spec.mount_point, "/vendor");
138        assert_eq!(spec.fs_type, "remote_bundle");
139        assert_eq!(spec.flags, MountFlags::NODEV | MountFlags::RELATIME);
140        assert!(!spec.nofail);
141        assert_eq!(options.source, "data/vendor");
142        assert!(options.params.is_empty());
143    }
144}