Skip to main content

starnix_modules_layeredfs/
lib.rs

1// Copyright 2022 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
5#![recursion_limit = "512"]
6
7use starnix_core::task::{CurrentTask, Kernel};
8use starnix_core::vfs::{
9    CacheMode, DirectoryEntryType, DirentSink, FileHandle, FileObject, FileOps, FileSystem,
10    FileSystemHandle, FileSystemOps, FsNode, FsNodeHandle, FsNodeOps, FsStr, FsString, MountInfo,
11    SeekTarget, ValueOrSize, WhatToMount, XattrOp, fileops_impl_directory, fileops_impl_noop_sync,
12    fs_node_impl_dir_readonly, unbounded_seek,
13};
14
15use starnix_uapi::errors::Errno;
16use starnix_uapi::mount_flags::{FileSystemFlags, MountpointFlags};
17use starnix_uapi::open_flags::OpenFlags;
18use starnix_uapi::{errno, ino_t, off_t, statfs};
19use std::collections::BTreeMap;
20use std::sync::Arc;
21use std::sync::atomic::Ordering;
22
23struct LayeredMountAction {
24    path: FsString,
25    fs: FileSystemHandle,
26}
27
28/// A callback used to complete the initialization of a `LayeredFs`.
29///
30/// After the `FileSystem` has been created by [`LayeredFsBuilder::build`], this closure
31/// must be invoked to create the sub-mounts that layer the additional filesystems
32/// at their specified paths.
33pub type LayeredFsMounts = Box<dyn FnOnce(&CurrentTask) -> Result<(), Errno>>;
34
35/// `FileSystem` builder that allows a set of auxiliary `FileSystem`s to be mounted at specified
36/// paths relative to the base filesystem, regardless of whether the base filesystem has directories
37/// at those paths, that may be mounted-onto.
38///
39/// Auxiliary `FileSystem`s and their mount paths are provided via calls to `add()`, and the layered
40/// filesystem created using `build()`.
41pub struct LayeredFsBuilder {
42    fs: FileSystemHandle,
43    subdirs: BTreeMap<FsString, LayeredFsBuilder>,
44}
45
46fn split_path(path: &FsStr) -> Vec<&FsStr> {
47    path.split(|c| *c == b'/').map(<&FsStr>::from).collect()
48}
49
50impl LayeredFsBuilder {
51    /// Returns a `LayeredFsBuilder` with `root_fs` as the underlying base filesystem.
52    pub fn new(root_fs: FileSystemHandle) -> Self {
53        Self { fs: root_fs, subdirs: Default::default() }
54    }
55
56    /// Specifies that filesystem `fs` should be mounted at the specified `path` relative to the
57    /// base filesystem.
58    ///
59    /// `path` must specify an absolute path under the base filesystem (i.e. starting with "/").
60    /// If `path` has multiple components then intermediate components must already have been
61    /// added to the builder.
62    pub fn add(&mut self, path: &str, fs: FileSystemHandle) {
63        let path = FsStr::new(path);
64        assert_eq!(path[0], b'/');
65        let parts = split_path(&path[1..]);
66        assert!(!parts.is_empty());
67        let final_part = parts.len() - 1;
68
69        let mut parent = self;
70        for i in 0..final_part {
71            parent = parent.subdirs.get_mut(parts[i]).unwrap();
72        }
73
74        parent.subdirs.insert(parts[parts.len() - 1].into(), Self::new(fs));
75    }
76
77    /// Returns the new `FileSystem` handle, and a finalization callback that must be invoked to
78    /// set up the subordinate mount points.
79    ///
80    /// The underlying base `FileSystem` will be returned directly if no sub-mounts were specified
81    /// via `add()`. Otherwise a `LayeredFs` instance will be returned, to provide stub directory
82    /// entries for the sub-mounts to be mounted onto.
83    pub fn build(self, kernel: &Kernel) -> (FileSystemHandle, LayeredFsMounts) {
84        let (fs, actions) = self.build_internal(kernel, Default::default());
85        let cb = Box::new(move |current_task: &CurrentTask| {
86            for action in actions {
87                let mount_point =
88                    current_task.lookup_path_from_root(action.path.as_ref()).map_err(|e| {
89                        Errno::with_context(
90                            e.code,
91                            format!("lookup path from root: {}", action.path),
92                        )
93                    })?;
94                mount_point.mount(WhatToMount::Fs(action.fs), MountpointFlags::empty()).map_err(
95                    |e| {
96                        Errno::with_context(e.code, format!("mount layered fs at: {}", action.path))
97                    },
98                )?;
99            }
100            Ok(())
101        });
102        (fs, cb)
103    }
104
105    fn build_internal(
106        self,
107        kernel: &Kernel,
108        prefix: &FsStr,
109    ) -> (FileSystemHandle, Vec<LayeredMountAction>) {
110        if self.subdirs.is_empty() {
111            return (self.fs, Vec::new());
112        }
113
114        let names =
115            self.subdirs.iter().map(|(name, entry)| (name.clone(), entry.fs.clone())).collect();
116        let fs = LayeredFs::new_fs(kernel, self.fs, names);
117
118        let mut mount_actions = Vec::new();
119        for (subpath, builder) in self.subdirs {
120            let path = FsString::from(format!("{}/{}", prefix, subpath));
121            let (fs, subdir_actions) = builder.build_internal(kernel, path.as_ref());
122            mount_actions.push(LayeredMountAction { path, fs });
123            mount_actions.extend(subdir_actions.into_iter());
124        }
125
126        (fs, mount_actions)
127    }
128}
129
130/// A filesystem that will delegate most operation to a base one, but have a number of top level
131/// directory that points to other filesystems.
132struct LayeredFs {
133    base_fs: FileSystemHandle,
134    mappings: BTreeMap<FsString, FileSystemHandle>,
135}
136
137impl LayeredFs {
138    /// Build a new filesystem.
139    ///
140    /// `base_fs`: The base file system that this file system will delegate to.
141    /// `mappings`: The map of top level directory to filesystems that will be layered on top of
142    /// `base_fs`.
143    fn new_fs(
144        kernel: &Kernel,
145        base_fs: FileSystemHandle,
146        mappings: BTreeMap<FsString, FileSystemHandle>,
147    ) -> FileSystemHandle {
148        let options = base_fs.options.clone();
149        let layered_fs = Arc::new(LayeredFs { base_fs, mappings });
150        let fs = FileSystem::new(
151            kernel,
152            CacheMode::Uncached,
153            LayeredFileSystemOps { fs: layered_fs.clone() },
154            options,
155        )
156        .expect("layeredfs constructed with valid options");
157        let root_ino = fs.allocate_ino();
158        fs.create_root(root_ino, LayeredNodeOps { fs: layered_fs });
159        fs
160    }
161}
162
163struct LayeredFileSystemOps {
164    fs: Arc<LayeredFs>,
165}
166
167impl FileSystemOps for LayeredFileSystemOps {
168    fn statfs(&self, _fs: &FileSystem, current_task: &CurrentTask) -> Result<statfs, Errno> {
169        self.fs.base_fs.statfs(current_task)
170    }
171    fn name(&self) -> &'static FsStr {
172        self.fs.base_fs.name()
173    }
174    fn update_flags(
175        &self,
176        fs: &FileSystem,
177        current_task: &CurrentTask,
178        new_flags: FileSystemFlags,
179    ) -> Result<(), Errno> {
180        self.fs.base_fs.update_flags(current_task, new_flags)?;
181        let flags = self.fs.base_fs.options.flags.load(Ordering::Relaxed);
182        fs.options.flags.store(flags, Ordering::Relaxed);
183        Ok(())
184    }
185}
186
187struct LayeredNodeOps {
188    fs: Arc<LayeredFs>,
189}
190
191impl FsNodeOps for LayeredNodeOps {
192    fs_node_impl_dir_readonly!();
193
194    fn create_file_ops(
195        &self,
196        _node: &FsNode,
197        current_task: &CurrentTask,
198        flags: OpenFlags,
199    ) -> Result<Box<dyn FileOps>, Errno> {
200        Ok(Box::new(LayeredFileOps {
201            fs: self.fs.clone(),
202            root_file: self.fs.base_fs.root().open_anonymous(current_task, flags)?,
203        }))
204    }
205
206    fn lookup(
207        &self,
208        _node: &FsNode,
209        current_task: &CurrentTask,
210        name: &FsStr,
211    ) -> Result<FsNodeHandle, Errno> {
212        if let Some(fs) = self.fs.mappings.get(name) {
213            Ok(fs.root().node.clone())
214        } else {
215            self.fs.base_fs.root().node.lookup(current_task, &MountInfo::detached(), name)
216        }
217    }
218
219    fn get_xattr(
220        &self,
221        _node: &FsNode,
222        current_task: &CurrentTask,
223        name: &FsStr,
224        max_size: usize,
225    ) -> Result<ValueOrSize<FsString>, Errno> {
226        self.fs.base_fs.root().node.ops().get_xattr(
227            &*self.fs.base_fs.root().node,
228            current_task,
229            name,
230            max_size,
231        )
232    }
233
234    /// Set an extended attribute on the node.
235    fn set_xattr(
236        &self,
237        _node: &FsNode,
238        current_task: &CurrentTask,
239        name: &FsStr,
240        value: &FsStr,
241        op: XattrOp,
242    ) -> Result<(), Errno> {
243        self.fs.base_fs.root().node.set_xattr(current_task, &MountInfo::detached(), name, value, op)
244    }
245
246    fn remove_xattr(
247        &self,
248        _node: &FsNode,
249        current_task: &CurrentTask,
250        name: &FsStr,
251    ) -> Result<(), Errno> {
252        self.fs.base_fs.root().node.remove_xattr(current_task, &MountInfo::detached(), name)
253    }
254
255    fn list_xattrs(
256        &self,
257        _node: &FsNode,
258        current_task: &CurrentTask,
259        max_size: usize,
260    ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
261        self.fs.base_fs.root().node.list_xattrs(current_task, max_size)
262    }
263}
264
265struct LayeredFileOps {
266    fs: Arc<LayeredFs>,
267    root_file: FileHandle,
268}
269
270impl FileOps for LayeredFileOps {
271    fileops_impl_directory!();
272    fileops_impl_noop_sync!();
273
274    fn seek(
275        &self,
276        _file: &FileObject,
277        current_task: &CurrentTask,
278        current_offset: off_t,
279        target: SeekTarget,
280    ) -> Result<off_t, Errno> {
281        let mut new_offset = unbounded_seek(current_offset, target)?;
282        if new_offset >= self.fs.mappings.len() as off_t {
283            new_offset = self
284                .root_file
285                .seek(current_task, SeekTarget::Set(new_offset - self.fs.mappings.len() as off_t))?
286                .checked_add(self.fs.mappings.len() as off_t)
287                .ok_or_else(|| errno!(EINVAL))?;
288        }
289        Ok(new_offset)
290    }
291
292    fn readdir(
293        &self,
294        _file: &FileObject,
295        current_task: &CurrentTask,
296        sink: &mut dyn DirentSink,
297    ) -> Result<(), Errno> {
298        for (key, fs) in self.fs.mappings.iter().skip(sink.offset() as usize) {
299            sink.add(fs.root().node.ino, sink.offset() + 1, DirectoryEntryType::DIR, key.as_ref())?;
300        }
301
302        struct DirentSinkWrapper<'a> {
303            sink: &'a mut dyn DirentSink,
304            mappings: &'a BTreeMap<FsString, FileSystemHandle>,
305            offset: &'a mut off_t,
306        }
307
308        impl<'a> DirentSink for DirentSinkWrapper<'a> {
309            fn add(
310                &mut self,
311                inode_num: ino_t,
312                offset: off_t,
313                entry_type: DirectoryEntryType,
314                name: &FsStr,
315            ) -> Result<(), Errno> {
316                if !self.mappings.contains_key(name) {
317                    self.sink.add(
318                        inode_num,
319                        offset + (self.mappings.len() as off_t),
320                        entry_type,
321                        name,
322                    )?;
323                }
324                *self.offset = offset;
325                Ok(())
326            }
327            fn offset(&self) -> off_t {
328                *self.offset
329            }
330        }
331
332        // Allow subclassing for FileObjectOffset because the lock on the
333        // inner file's offset is acquired while holding the lock on the
334        // outer (layered) file's offset.
335        // This is safe because the locks are on different file instances
336        // and follow a strict outer-to-inner hierarchy, preventing cycles.
337        let _token = starnix_sync::allow_subclass();
338        let mut root_file_offset = self.root_file.offset.copy();
339        let mut wrapper =
340            DirentSinkWrapper { sink, mappings: &self.fs.mappings, offset: &mut *root_file_offset };
341
342        self.root_file.readdir(current_task, &mut wrapper)?;
343        root_file_offset.update();
344        Ok(())
345    }
346}
347
348#[cfg(test)]
349mod test {
350    use super::*;
351    use starnix_core::fs::tmpfs::TmpFs;
352    use starnix_core::testing::*;
353
354    fn get_root_entry_names(current_task: &CurrentTask, fs: &FileSystem) -> Vec<Vec<u8>> {
355        struct DirentNameCapturer {
356            pub names: Vec<Vec<u8>>,
357            offset: off_t,
358        }
359        impl DirentSink for DirentNameCapturer {
360            fn add(
361                &mut self,
362                _inode_num: ino_t,
363                offset: off_t,
364                _entry_type: DirectoryEntryType,
365                name: &FsStr,
366            ) -> Result<(), Errno> {
367                self.names.push(name.to_vec());
368                self.offset = offset;
369                Ok(())
370            }
371            fn offset(&self) -> off_t {
372                self.offset
373            }
374        }
375        let mut sink = DirentNameCapturer { names: vec![], offset: 0 };
376        fs.root()
377            .open_anonymous(current_task, OpenFlags::RDONLY)
378            .expect("open")
379            .readdir(current_task, &mut sink)
380            .expect("readdir");
381        std::mem::take(&mut sink.names)
382    }
383
384    #[::fuchsia::test]
385    async fn test_remove_duplicates() {
386        spawn_kernel_and_run(async move |current_task| {
387            let kernel = current_task.kernel();
388            let base = TmpFs::new_fs(kernel);
389            base.root().create_dir_for_testing(current_task, "d1".into()).expect("create_dir");
390            base.root().create_dir_for_testing(current_task, "d2".into()).expect("create_dir");
391            let base_entries = get_root_entry_names(current_task, &base);
392            assert_eq!(base_entries.len(), 4);
393            assert!(base_entries.contains(&b".".to_vec()));
394            assert!(base_entries.contains(&b"..".to_vec()));
395            assert!(base_entries.contains(&b"d1".to_vec()));
396            assert!(base_entries.contains(&b"d2".to_vec()));
397
398            let tmpfs1 = TmpFs::new_fs(kernel);
399            let tmpfs2 = TmpFs::new_fs(kernel);
400            let layered_fs = LayeredFs::new_fs(
401                kernel,
402                base,
403                BTreeMap::from([("d1".into(), tmpfs1), ("d3".into(), tmpfs2)]),
404            );
405            let layered_fs_entries = get_root_entry_names(current_task, &layered_fs);
406            assert_eq!(layered_fs_entries.len(), 5);
407            assert!(layered_fs_entries.contains(&b".".to_vec()));
408            assert!(layered_fs_entries.contains(&b"..".to_vec()));
409            assert!(layered_fs_entries.contains(&b"d1".to_vec()));
410            assert!(layered_fs_entries.contains(&b"d2".to_vec()));
411            assert!(layered_fs_entries.contains(&b"d3".to_vec()));
412        })
413        .await;
414    }
415}