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