starnix_core/vfs/
fs_context.rs

1// Copyright 2021 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 crate::security;
6use crate::task::CurrentTask;
7use crate::vfs::{ActiveNamespaceNode, CheckAccessReason, Namespace, NamespaceNode};
8use starnix_logging::log_trace;
9use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, RwLock};
10use starnix_uapi::auth::CAP_SYS_CHROOT;
11use starnix_uapi::errno;
12use starnix_uapi::errors::Errno;
13use starnix_uapi::file_mode::{Access, FileMode};
14use std::sync::Arc;
15
16/// The mutable state for an FsContext.
17///
18/// This state is cloned in FsContext::fork.
19#[derive(Debug, Clone)]
20struct FsContextState {
21    /// The namespace tree for this FsContext.
22    ///
23    /// This field owns the mount table for this FsContext.
24    namespace: Arc<Namespace>,
25
26    /// The root of the namespace tree for this FsContext.
27    ///
28    /// Operations on the file system are typically either relative to this
29    /// root or to the cwd().
30    root: ActiveNamespaceNode,
31
32    /// The current working directory.
33    cwd: ActiveNamespaceNode,
34
35    // See <https://man7.org/linux/man-pages/man2/umask.2.html>
36    umask: FileMode,
37}
38
39impl FsContextState {
40    fn set_namespace(&mut self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
41        log_trace!("updating namespace");
42        let new_root = Namespace::translate_node(self.root.to_passive(), &new_ns)
43            .ok_or_else(|| errno!(EINVAL))?;
44        let new_cwd = Namespace::translate_node(self.cwd.to_passive(), &new_ns)
45            .ok_or_else(|| errno!(EINVAL))?;
46
47        // Only perform a mutation if the rebased nodes both exist in the target namespace.
48        self.root = new_root.into_active();
49        self.cwd = new_cwd.into_active();
50        self.namespace = new_ns;
51        log_trace!("namespace update succeeded");
52        Ok(())
53    }
54}
55
56/// The file system context associated with a task.
57///
58/// File system operations, such as opening a file or mounting a directory, are
59/// performed using this context.
60#[derive(Debug)]
61pub struct FsContext {
62    /// The mutable state for this FsContext.
63    state: RwLock<FsContextState>,
64}
65
66impl FsContext {
67    /// Create an FsContext for the given namespace.
68    ///
69    /// The root and cwd of the FsContext are initialized to the root of the
70    /// namespace.
71    pub fn new(namespace: Arc<Namespace>) -> Arc<FsContext> {
72        let root = namespace.root();
73        Arc::new(FsContext {
74            state: RwLock::new(FsContextState {
75                namespace,
76                root: root.clone().into_active(),
77                cwd: root.into_active(),
78                umask: FileMode::DEFAULT_UMASK,
79            }),
80        })
81    }
82
83    pub fn fork(&self) -> Arc<FsContext> {
84        // A child process created via fork(2) inherits its parent's umask.
85        // The umask is left unchanged by execve(2).
86        //
87        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
88
89        Arc::new(FsContext { state: RwLock::new(self.state.read().clone()) })
90    }
91
92    /// Returns a reference to the current working directory.
93    pub fn cwd(&self) -> NamespaceNode {
94        let state = self.state.read();
95        state.cwd.to_passive()
96    }
97
98    /// Returns the root.
99    pub fn root(&self) -> NamespaceNode {
100        let state = self.state.read();
101        state.root.to_passive()
102    }
103
104    /// Change the current working directory.
105    pub fn chdir<L>(
106        &self,
107        locked: &mut Locked<L>,
108        current_task: &CurrentTask,
109        name: NamespaceNode,
110    ) -> Result<(), Errno>
111    where
112        L: LockEqualOrBefore<FileOpsCore>,
113    {
114        name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chdir)?;
115        let mut state = self.state.write();
116        state.cwd = name.into_active();
117        Ok(())
118    }
119
120    /// Change the root.
121    pub fn chroot<L>(
122        &self,
123        locked: &mut Locked<L>,
124        current_task: &CurrentTask,
125        name: NamespaceNode,
126    ) -> Result<(), Errno>
127    where
128        L: LockEqualOrBefore<FileOpsCore>,
129    {
130        name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chroot)
131            .map_err(|_| errno!(EACCES))?;
132        security::check_task_capable(current_task, CAP_SYS_CHROOT)?;
133
134        let mut state = self.state.write();
135        state.root = name.into_active();
136        Ok(())
137    }
138
139    pub fn umask(&self) -> FileMode {
140        self.state.read().umask
141    }
142
143    pub fn apply_umask(&self, mode: FileMode) -> FileMode {
144        let umask = self.state.read().umask;
145        mode & !umask
146    }
147
148    pub fn set_umask(&self, umask: FileMode) -> FileMode {
149        let mut state = self.state.write();
150        let old_umask = state.umask;
151
152        // umask() sets the calling process's file mode creation mask
153        // (umask) to mask & 0o777 (i.e., only the file permission bits of
154        // mask are used), and returns the previous value of the mask.
155        //
156        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
157        state.umask = umask & FileMode::from_bits(0o777);
158
159        old_umask
160    }
161
162    pub fn set_namespace(&self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
163        let mut state = self.state.write();
164        state.set_namespace(new_ns)?;
165        Ok(())
166    }
167
168    pub fn unshare_namespace(&self) {
169        let mut state = self.state.write();
170        // TODO(https:://https://fxbug.dev/42080384): Implement better locking to make these failures
171        // impossible. These expects can only fail if another thread changes mounts between the
172        // clone_namespace and the translate_node calls, making the cwd or root disappear or move.
173        let cloned = state.namespace.clone_namespace();
174        state.set_namespace(cloned).expect("nodes should exist in the cloned namespace");
175    }
176
177    pub fn namespace(&self) -> Arc<Namespace> {
178        Arc::clone(&self.state.read().namespace)
179    }
180}
181
182#[cfg(test)]
183mod test {
184    use crate::fs::tmpfs::TmpFs;
185    use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs};
186    use crate::vfs::{FsContext, Namespace};
187    use starnix_uapi::file_mode::FileMode;
188    use starnix_uapi::open_flags::OpenFlags;
189
190    #[::fuchsia::test]
191    async fn test_umask() {
192        spawn_kernel_and_run(async |locked, current_task| {
193            let kernel = current_task.kernel();
194            let fs = FsContext::new(Namespace::new(TmpFs::new_fs(locked, &kernel)));
195
196            assert_eq!(FileMode::from_bits(0o22), fs.set_umask(FileMode::from_bits(0o3020)));
197            assert_eq!(FileMode::from_bits(0o646), fs.apply_umask(FileMode::from_bits(0o666)));
198            assert_eq!(FileMode::from_bits(0o3646), fs.apply_umask(FileMode::from_bits(0o3666)));
199            assert_eq!(FileMode::from_bits(0o20), fs.set_umask(FileMode::from_bits(0o11)));
200        })
201        .await;
202    }
203
204    #[::fuchsia::test]
205    async fn test_chdir() {
206        spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
207            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
208
209            let bin = current_task
210                .open_file(locked, "bin".into(), OpenFlags::RDONLY)
211                .expect("missing bin directory");
212            current_task
213                .fs()
214                .chdir(locked, &current_task, bin.name.to_passive())
215                .expect("Failed to chdir");
216            assert_eq!("/bin", current_task.fs().cwd().path_escaping_chroot());
217
218            // Now that we have changed directories to bin, we're opening a file
219            // relative to that directory, which doesn't exist.
220            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_err());
221
222            // However, bin still exists in the root directory.
223            assert!(current_task.open_file(locked, "/bin".into(), OpenFlags::RDONLY).is_ok());
224
225            let previous_directory = current_task
226                .open_file(locked, "..".into(), OpenFlags::RDONLY)
227                .expect("failed to open ..")
228                .name
229                .to_passive();
230            current_task
231                .fs()
232                .chdir(locked, &current_task, previous_directory)
233                .expect("Failed to chdir");
234            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
235
236            // Now bin exists again because we've gone back to the root.
237            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
238
239            // Repeating the .. doesn't do anything because we're already at the root.
240            let previous_directory = current_task
241                .open_file(locked, "..".into(), OpenFlags::RDONLY)
242                .expect("failed to open ..")
243                .name
244                .to_passive();
245            current_task
246                .fs()
247                .chdir(locked, &current_task, previous_directory)
248                .expect("Failed to chdir");
249            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
250            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
251        })
252        .await;
253    }
254}