Skip to main content

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