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::{ActiveNamespaceNode, CheckAccessReason, 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::{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(
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, Access::EXEC, CheckAccessReason::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, Access::EXEC, CheckAccessReason::Chroot)
119            .map_err(|_| errno!(EACCES))?;
120        security::check_task_capable(current_task, CAP_SYS_CHROOT)?;
121
122        let mut state = self.state.write();
123        state.root = name.into_active();
124        Ok(())
125    }
126
127    pub fn umask(&self) -> FileMode {
128        self.state.read().umask
129    }
130
131    pub fn apply_umask(&self, mode: FileMode) -> FileMode {
132        let umask = self.state.read().umask;
133        mode & !umask
134    }
135
136    pub fn set_umask(&self, umask: FileMode) -> FileMode {
137        let mut state = self.state.write();
138        let old_umask = state.umask;
139
140        // umask() sets the calling process's file mode creation mask
141        // (umask) to mask & 0o777 (i.e., only the file permission bits of
142        // mask are used), and returns the previous value of the mask.
143        //
144        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
145        state.umask = umask & FileMode::from_bits(0o777);
146
147        old_umask
148    }
149
150    pub fn set_namespace(&self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
151        let mut state = self.state.write();
152        let kernel = state.namespace.kernel();
153        let mounts_guard = kernel.mounts_lock();
154        state.set_namespace(new_ns, &mounts_guard)?;
155        Ok(())
156    }
157
158    pub fn unshare_namespace(&self) {
159        let mut state = self.state.write();
160        let kernel = state.namespace.kernel();
161        let mounts_guard = kernel.mounts_lock();
162
163        let cloned = state.namespace.clone_namespace(&mounts_guard);
164        state
165            .set_namespace(cloned, &mounts_guard)
166            .expect("nodes should exist in the cloned namespace");
167    }
168
169    pub fn namespace(&self) -> Arc<Namespace> {
170        Arc::clone(&self.state.read().namespace)
171    }
172}
173
174#[cfg(test)]
175mod test {
176    use crate::fs::tmpfs::TmpFs;
177    use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs};
178    use crate::vfs::{FsContext, Namespace};
179    use starnix_uapi::file_mode::FileMode;
180    use starnix_uapi::open_flags::OpenFlags;
181
182    #[::fuchsia::test]
183    async fn test_umask() {
184        spawn_kernel_and_run(async |current_task| {
185            let kernel = current_task.kernel();
186            let fs = FsContext::new(Namespace::new(TmpFs::new_fs(&kernel)));
187
188            assert_eq!(FileMode::from_bits(0o22), fs.set_umask(FileMode::from_bits(0o3020)));
189            assert_eq!(FileMode::from_bits(0o646), fs.apply_umask(FileMode::from_bits(0o666)));
190            assert_eq!(FileMode::from_bits(0o3646), fs.apply_umask(FileMode::from_bits(0o3666)));
191            assert_eq!(FileMode::from_bits(0o20), fs.set_umask(FileMode::from_bits(0o11)));
192        })
193        .await;
194    }
195
196    #[::fuchsia::test]
197    async fn test_chdir() {
198        spawn_kernel_and_run_with_pkgfs(async |current_task| {
199            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
200
201            let bin = current_task
202                .open_file("bin".into(), OpenFlags::RDONLY)
203                .expect("missing bin directory");
204            current_task.fs().chdir(&current_task, bin.name.to_passive()).expect("Failed to chdir");
205            assert_eq!("/bin", current_task.fs().cwd().path_escaping_chroot());
206
207            // Now that we have changed directories to bin, we're opening a file
208            // relative to that directory, which doesn't exist.
209            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_err());
210
211            // However, bin still exists in the root directory.
212            assert!(current_task.open_file("/bin".into(), OpenFlags::RDONLY).is_ok());
213
214            let previous_directory = current_task
215                .open_file("..".into(), OpenFlags::RDONLY)
216                .expect("failed to open ..")
217                .name
218                .to_passive();
219            current_task.fs().chdir(&current_task, previous_directory).expect("Failed to chdir");
220            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
221
222            // Now bin exists again because we've gone back to the root.
223            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_ok());
224
225            // Repeating the .. doesn't do anything because we're already at the root.
226            let previous_directory = current_task
227                .open_file("..".into(), OpenFlags::RDONLY)
228                .expect("failed to open ..")
229                .name
230                .to_passive();
231            current_task.fs().chdir(&current_task, previous_directory).expect("Failed to chdir");
232            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
233            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_ok());
234        })
235        .await;
236    }
237}