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