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 fuchsia_rcu::RcuArc;
9use starnix_logging::log_trace;
10use starnix_sync::LockDepRwLock;
11use starnix_uapi::auth::CAP_SYS_CHROOT;
12use starnix_uapi::errno;
13use starnix_uapi::errors::Errno;
14use starnix_uapi::file_mode::FileMode;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicUsize, Ordering};
17
18/// The mutable state for an FsContext.
19///
20/// This state is cloned in FsContext::fork.
21#[derive(Debug, Clone)]
22struct FsContextState {
23    /// The namespace tree for this FsContext.
24    ///
25    /// This field owns the mount table for this FsContext.
26    namespace: Arc<Namespace>,
27
28    /// The root of the namespace tree for this FsContext.
29    ///
30    /// Operations on the file system are typically either relative to this
31    /// root or to the cwd().
32    root: ActiveNamespaceNode,
33
34    /// The current working directory.
35    cwd: ActiveNamespaceNode,
36
37    // See <https://man7.org/linux/man-pages/man2/umask.2.html>
38    umask: FileMode,
39}
40
41impl FsContextState {
42    fn set_namespace(
43        &mut self,
44        new_ns: Arc<Namespace>,
45        mounts_guard: &MountsWriteToken,
46    ) -> Result<(), Errno> {
47        log_trace!("updating namespace");
48        let new_root = Namespace::translate_node(self.root.to_passive(), &new_ns, mounts_guard)
49            .ok_or_else(|| errno!(EINVAL))?;
50        let new_cwd = Namespace::translate_node(self.cwd.to_passive(), &new_ns, mounts_guard)
51            .ok_or_else(|| errno!(EINVAL))?;
52
53        // Only perform a mutation if the rebased nodes both exist in the target namespace.
54        mounts_guard.defer_drop(std::mem::replace(&mut self.root, new_root.into_active()));
55        mounts_guard.defer_drop(std::mem::replace(&mut self.cwd, new_cwd.into_active()));
56        mounts_guard.defer_drop(std::mem::replace(&mut self.namespace, new_ns));
57        log_trace!("namespace update succeeded");
58        Ok(())
59    }
60}
61
62/// The file system context associated with a task.
63///
64/// File system operations, such as opening a file or mounting a directory, are
65/// performed using this context.
66#[derive(Debug)]
67pub struct FsContext {
68    state: LockDepRwLock<FsContextState, starnix_sync::FsContextStateLock>,
69
70    /// Number of running tasks that have this `FsContext` as their active filesystem context.
71    ///
72    /// Tracked separately from `Arc::strong_count` so that transient `Arc<FsContext>` references
73    /// do not cause a task to appear to share its filesystem context.
74    share_count: AtomicUsize,
75}
76
77/// Wrapper around [`RcuArc<FsContext>`] that maintains the [`FsContext`] share count.
78#[derive(Debug)]
79pub struct SharedFsContext(RcuArc<FsContext>);
80
81impl SharedFsContext {
82    /// Creates a [`SharedFsContext`] holding `fs` and increments its share count.
83    pub fn new(fs: Arc<FsContext>) -> Self {
84        fs.share_count.fetch_add(1, Ordering::Relaxed);
85        Self(RcuArc::new(Some(fs)))
86    }
87
88    /// Returns a strong reference to the underlying [`FsContext`], or `None` if cleared on exit.
89    pub fn upgrade(&self) -> Option<Arc<FsContext>> {
90        self.0.upgrade()
91    }
92
93    /// Replaces the held [`FsContext`] for `current_task` and updates the old and new share counts.
94    pub fn update(&self, current_task: &CurrentTask, fs: impl Into<Option<Arc<FsContext>>>) {
95        // Requiring `&CurrentTask` (`!Sync`) and verifying that `self` belongs to `current_task`
96        // serializes updates so `self.set()` cannot race with another writer on `self`.
97        debug_assert!(std::ptr::eq(&current_task.running_state().fs, self));
98        self.set(fs.into());
99    }
100
101    fn set(&self, fs: Option<Arc<FsContext>>) {
102        if let Some(ref fs) = fs {
103            fs.share_count.fetch_add(1, Ordering::Relaxed);
104        }
105        let old_fs = self.0.upgrade();
106        self.0.update(fs);
107        if let Some(old_fs) = old_fs {
108            let previous = old_fs.share_count.fetch_sub(1, Ordering::AcqRel);
109            debug_assert!(previous >= 1);
110        }
111    }
112}
113
114impl Drop for SharedFsContext {
115    fn drop(&mut self) {
116        self.set(None);
117    }
118}
119
120impl FsContext {
121    /// Create an FsContext for the given namespace.
122    ///
123    /// The root and cwd of the FsContext are initialized to the root of the
124    /// namespace.
125    pub fn new(namespace: Arc<Namespace>) -> Arc<FsContext> {
126        let root = namespace.root();
127        Arc::new(FsContext {
128            state: FsContextState {
129                namespace,
130                root: root.clone().into_active(),
131                cwd: root.into_active(),
132                umask: FileMode::DEFAULT_UMASK,
133            }
134            .into(),
135            share_count: AtomicUsize::new(0),
136        })
137    }
138
139    pub fn fork(&self) -> Arc<FsContext> {
140        // A child process created via fork(2) inherits its parent's umask.
141        // The umask is left unchanged by execve(2).
142        //
143        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
144
145        Arc::new(FsContext {
146            state: self.state.read().clone().into(),
147            share_count: AtomicUsize::new(0),
148        })
149    }
150
151    /// Returns whether more than one running task shares this filesystem context.
152    pub fn is_shared(&self) -> bool {
153        self.share_count.load(Ordering::Acquire) > 1
154    }
155
156    /// Returns a reference to the current working directory.
157    pub fn cwd(&self) -> NamespaceNode {
158        let state = self.state.read();
159        state.cwd.to_passive()
160    }
161
162    /// Returns the root.
163    pub fn root(&self) -> NamespaceNode {
164        let state = self.state.read();
165        state.root.to_passive()
166    }
167
168    /// Change the current working directory.
169    pub fn chdir(&self, current_task: &CurrentTask, name: NamespaceNode) -> Result<(), Errno> {
170        name.check_access(current_task, AccessCheck::for_chdir())?;
171        let mut state = self.state.write();
172        state.cwd = name.into_active();
173        Ok(())
174    }
175
176    /// Change the root.
177    pub fn chroot(&self, current_task: &CurrentTask, name: NamespaceNode) -> Result<(), Errno> {
178        name.check_access(current_task, AccessCheck::for_chroot()).map_err(|_| errno!(EACCES))?;
179        security::check_task_capable(current_task, CAP_SYS_CHROOT)?;
180
181        let mut state = self.state.write();
182        state.root = name.into_active();
183        Ok(())
184    }
185
186    pub fn umask(&self) -> FileMode {
187        self.state.read().umask
188    }
189
190    pub fn apply_umask(&self, mode: FileMode) -> FileMode {
191        let umask = self.state.read().umask;
192        mode & !umask
193    }
194
195    pub fn set_umask(&self, umask: FileMode) -> FileMode {
196        let mut state = self.state.write();
197        let old_umask = state.umask;
198
199        // umask() sets the calling process's file mode creation mask
200        // (umask) to mask & 0o777 (i.e., only the file permission bits of
201        // mask are used), and returns the previous value of the mask.
202        //
203        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
204        state.umask = umask & FileMode::from_bits(0o777);
205
206        old_umask
207    }
208
209    pub fn set_namespace(&self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
210        let mut state = self.state.write();
211        let kernel = state.namespace.kernel();
212        let mounts_guard = kernel.mounts_lock();
213        state.set_namespace(new_ns, &mounts_guard)?;
214        Ok(())
215    }
216
217    /// Replaces the bootstrap `Namespace` backing this `FsContext`, resetting both `root` and
218    /// `cwd` to the root of `new_ns`.
219    ///
220    /// # Panics
221    ///
222    /// Panics if the outgoing bootstrap `Namespace`, its root mount, or its underlying filesystem
223    /// still have any other references or created entries.
224    pub fn replace_bootstrap_namespace(&self, current_task: &CurrentTask, new_ns: Arc<Namespace>) {
225        let mut state = self.state.write();
226        let root = new_ns.root();
227        let old_root = std::mem::replace(&mut state.root, root.clone().into_active());
228        let old_cwd = std::mem::replace(&mut state.cwd, root.into_active());
229        let old_ns = std::mem::replace(&mut state.namespace, new_ns);
230        drop(state);
231        drop((old_root, old_cwd));
232        old_ns.destroy_bootstrap();
233        current_task.trigger_delayed_releaser();
234    }
235
236    pub fn unshare_namespace(&self) {
237        let mut state = self.state.write();
238        let kernel = state.namespace.kernel();
239        let mounts_guard = kernel.mounts_lock();
240
241        let cloned = state.namespace.clone_namespace(&mounts_guard);
242        state
243            .set_namespace(cloned, &mounts_guard)
244            .expect("nodes should exist in the cloned namespace");
245    }
246
247    pub fn namespace(&self) -> Arc<Namespace> {
248        Arc::clone(&self.state.read().namespace)
249    }
250
251    pub fn update_root_after_pivot(&self, old_root: &NamespaceNode, new_root: &NamespaceNode) {
252        let mut state = self.state.write();
253        if state.root.to_passive() == *old_root {
254            state.root = new_root.clone().into_active();
255        }
256        if state.cwd.to_passive() == *old_root {
257            state.cwd = new_root.clone().into_active();
258        }
259    }
260}
261
262#[cfg(test)]
263mod test {
264    use crate::fs::tmpfs::TmpFs;
265    use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs};
266    use crate::vfs::{FsContext, Namespace};
267    use starnix_uapi::file_mode::FileMode;
268    use starnix_uapi::open_flags::OpenFlags;
269
270    #[::fuchsia::test]
271    async fn test_umask() {
272        spawn_kernel_and_run(async |current_task| {
273            let kernel = current_task.kernel();
274            let fs = FsContext::new(Namespace::new(TmpFs::new_fs(&kernel)));
275
276            assert_eq!(FileMode::from_bits(0o22), fs.set_umask(FileMode::from_bits(0o3020)));
277            assert_eq!(FileMode::from_bits(0o646), fs.apply_umask(FileMode::from_bits(0o666)));
278            assert_eq!(FileMode::from_bits(0o3646), fs.apply_umask(FileMode::from_bits(0o3666)));
279            assert_eq!(FileMode::from_bits(0o20), fs.set_umask(FileMode::from_bits(0o11)));
280        })
281        .await;
282    }
283
284    #[::fuchsia::test]
285    async fn test_chdir() {
286        spawn_kernel_and_run_with_pkgfs(async |current_task| {
287            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
288
289            let bin = current_task
290                .open_file("bin".into(), OpenFlags::RDONLY)
291                .expect("missing bin directory");
292            current_task.fs().chdir(&current_task, bin.name.to_passive()).expect("Failed to chdir");
293            assert_eq!("/bin", current_task.fs().cwd().path_escaping_chroot());
294
295            // Now that we have changed directories to bin, we're opening a file
296            // relative to that directory, which doesn't exist.
297            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_err());
298
299            // However, bin still exists in the root directory.
300            assert!(current_task.open_file("/bin".into(), OpenFlags::RDONLY).is_ok());
301
302            let previous_directory = current_task
303                .open_file("..".into(), OpenFlags::RDONLY)
304                .expect("failed to open ..")
305                .name
306                .to_passive();
307            current_task.fs().chdir(&current_task, previous_directory).expect("Failed to chdir");
308            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
309
310            // Now bin exists again because we've gone back to the root.
311            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_ok());
312
313            // Repeating the .. doesn't do anything because we're already at the root.
314            let previous_directory = current_task
315                .open_file("..".into(), OpenFlags::RDONLY)
316                .expect("failed to open ..")
317                .name
318                .to_passive();
319            current_task.fs().chdir(&current_task, previous_directory).expect("Failed to chdir");
320            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
321            assert!(current_task.open_file("bin".into(), OpenFlags::RDONLY).is_ok());
322        })
323        .await;
324    }
325}