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};
14use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
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 =
34    Box<dyn FnOnce(&mut Locked<Unlocked>, &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<L>(
85        self,
86        locked: &mut Locked<L>,
87        kernel: &Kernel,
88    ) -> (FileSystemHandle, LayeredFsMounts)
89    where
90        L: LockEqualOrBefore<FileOpsCore>,
91    {
92        let (fs, actions) = self.build_internal(locked, kernel, Default::default());
93        let cb = Box::new(move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| {
94            for action in actions {
95                let mount_point = current_task
96                    .lookup_path_from_root(locked, action.path.as_ref())
97                    .map_err(|e| {
98                        Errno::with_context(
99                            e.code,
100                            format!("lookup path from root: {}", action.path),
101                        )
102                    })?;
103                mount_point.mount(WhatToMount::Fs(action.fs), MountpointFlags::empty()).map_err(
104                    |e| {
105                        Errno::with_context(e.code, format!("mount layered fs at: {}", action.path))
106                    },
107                )?;
108            }
109            Ok(())
110        });
111        (fs, cb)
112    }
113
114    fn build_internal<L>(
115        self,
116        locked: &mut Locked<L>,
117        kernel: &Kernel,
118        prefix: &FsStr,
119    ) -> (FileSystemHandle, Vec<LayeredMountAction>)
120    where
121        L: LockEqualOrBefore<FileOpsCore>,
122    {
123        if self.subdirs.is_empty() {
124            return (self.fs, Vec::new());
125        }
126
127        let names =
128            self.subdirs.iter().map(|(name, entry)| (name.clone(), entry.fs.clone())).collect();
129        let fs = LayeredFs::new_fs(locked, kernel, self.fs, names);
130
131        let mut mount_actions = Vec::new();
132        for (subpath, builder) in self.subdirs {
133            let path = FsString::from(format!("{}/{}", prefix, subpath));
134            let (fs, subdir_actions) = builder.build_internal(locked, kernel, path.as_ref());
135            mount_actions.push(LayeredMountAction { path, fs });
136            mount_actions.extend(subdir_actions.into_iter());
137        }
138
139        (fs, mount_actions)
140    }
141}
142
143/// A filesystem that will delegate most operation to a base one, but have a number of top level
144/// directory that points to other filesystems.
145struct LayeredFs {
146    base_fs: FileSystemHandle,
147    mappings: BTreeMap<FsString, FileSystemHandle>,
148}
149
150impl LayeredFs {
151    /// Build a new filesystem.
152    ///
153    /// `base_fs`: The base file system that this file system will delegate to.
154    /// `mappings`: The map of top level directory to filesystems that will be layered on top of
155    /// `base_fs`.
156    fn new_fs<L>(
157        locked: &mut Locked<L>,
158        kernel: &Kernel,
159        base_fs: FileSystemHandle,
160        mappings: BTreeMap<FsString, FileSystemHandle>,
161    ) -> FileSystemHandle
162    where
163        L: LockEqualOrBefore<FileOpsCore>,
164    {
165        let options = base_fs.options.clone();
166        let layered_fs = Arc::new(LayeredFs { base_fs, mappings });
167        let fs = FileSystem::new(
168            locked,
169            kernel,
170            CacheMode::Uncached,
171            LayeredFileSystemOps { fs: layered_fs.clone() },
172            options,
173        )
174        .expect("layeredfs constructed with valid options");
175        let root_ino = fs.allocate_ino();
176        fs.create_root(root_ino, LayeredNodeOps { fs: layered_fs });
177        fs
178    }
179}
180
181struct LayeredFileSystemOps {
182    fs: Arc<LayeredFs>,
183}
184
185impl FileSystemOps for LayeredFileSystemOps {
186    fn statfs(
187        &self,
188        locked: &mut Locked<FileOpsCore>,
189        _fs: &FileSystem,
190        current_task: &CurrentTask,
191    ) -> Result<statfs, Errno> {
192        self.fs.base_fs.statfs(locked, current_task)
193    }
194    fn name(&self) -> &'static FsStr {
195        self.fs.base_fs.name()
196    }
197    fn update_flags(
198        &self,
199        fs: &FileSystem,
200        current_task: &CurrentTask,
201        new_flags: FileSystemFlags,
202    ) -> Result<(), Errno> {
203        self.fs.base_fs.update_flags(current_task, new_flags)?;
204        let flags = self.fs.base_fs.options.flags.load(Ordering::Relaxed);
205        fs.options.flags.store(flags, Ordering::Relaxed);
206        Ok(())
207    }
208}
209
210struct LayeredNodeOps {
211    fs: Arc<LayeredFs>,
212}
213
214impl FsNodeOps for LayeredNodeOps {
215    fs_node_impl_dir_readonly!();
216
217    fn create_file_ops(
218        &self,
219        locked: &mut Locked<FileOpsCore>,
220        _node: &FsNode,
221        current_task: &CurrentTask,
222        flags: OpenFlags,
223    ) -> Result<Box<dyn FileOps>, Errno> {
224        Ok(Box::new(LayeredFileOps {
225            fs: self.fs.clone(),
226            root_file: self.fs.base_fs.root().open_anonymous(locked, current_task, flags)?,
227        }))
228    }
229
230    fn lookup(
231        &self,
232        locked: &mut Locked<FileOpsCore>,
233        _node: &FsNode,
234        current_task: &CurrentTask,
235        name: &FsStr,
236    ) -> Result<FsNodeHandle, Errno> {
237        if let Some(fs) = self.fs.mappings.get(name) {
238            Ok(fs.root().node.clone())
239        } else {
240            self.fs.base_fs.root().node.lookup(locked, current_task, &MountInfo::detached(), name)
241        }
242    }
243
244    fn get_xattr(
245        &self,
246        locked: &mut Locked<FileOpsCore>,
247        _node: &FsNode,
248        current_task: &CurrentTask,
249        name: &FsStr,
250        max_size: usize,
251    ) -> Result<ValueOrSize<FsString>, Errno> {
252        self.fs.base_fs.root().node.ops().get_xattr(
253            locked,
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        locked: &mut Locked<FileOpsCore>,
265        _node: &FsNode,
266        current_task: &CurrentTask,
267        name: &FsStr,
268        value: &FsStr,
269        op: XattrOp,
270    ) -> Result<(), Errno> {
271        self.fs.base_fs.root().node.set_xattr(
272            locked,
273            current_task,
274            &MountInfo::detached(),
275            name,
276            value,
277            op,
278        )
279    }
280
281    fn remove_xattr(
282        &self,
283        locked: &mut Locked<FileOpsCore>,
284        _node: &FsNode,
285        current_task: &CurrentTask,
286        name: &FsStr,
287    ) -> Result<(), Errno> {
288        self.fs.base_fs.root().node.remove_xattr(locked, current_task, &MountInfo::detached(), name)
289    }
290
291    fn list_xattrs(
292        &self,
293        locked: &mut Locked<FileOpsCore>,
294        _node: &FsNode,
295        current_task: &CurrentTask,
296        max_size: usize,
297    ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
298        self.fs.base_fs.root().node.list_xattrs(locked, current_task, max_size)
299    }
300}
301
302struct LayeredFileOps {
303    fs: Arc<LayeredFs>,
304    root_file: FileHandle,
305}
306
307impl FileOps for LayeredFileOps {
308    fileops_impl_directory!();
309    fileops_impl_noop_sync!();
310
311    fn seek(
312        &self,
313        locked: &mut Locked<FileOpsCore>,
314        _file: &FileObject,
315        current_task: &CurrentTask,
316        current_offset: off_t,
317        target: SeekTarget,
318    ) -> Result<off_t, Errno> {
319        let mut new_offset = unbounded_seek(current_offset, target)?;
320        if new_offset >= self.fs.mappings.len() as off_t {
321            new_offset = self
322                .root_file
323                .seek(
324                    locked,
325                    current_task,
326                    SeekTarget::Set(new_offset - self.fs.mappings.len() as off_t),
327                )?
328                .checked_add(self.fs.mappings.len() as off_t)
329                .ok_or_else(|| errno!(EINVAL))?;
330        }
331        Ok(new_offset)
332    }
333
334    fn readdir(
335        &self,
336        locked: &mut Locked<FileOpsCore>,
337        _file: &FileObject,
338        current_task: &CurrentTask,
339        sink: &mut dyn DirentSink,
340    ) -> Result<(), Errno> {
341        for (key, fs) in self.fs.mappings.iter().skip(sink.offset() as usize) {
342            sink.add(fs.root().node.ino, sink.offset() + 1, DirectoryEntryType::DIR, key.as_ref())?;
343        }
344
345        struct DirentSinkWrapper<'a> {
346            sink: &'a mut dyn DirentSink,
347            mappings: &'a BTreeMap<FsString, FileSystemHandle>,
348            offset: &'a mut off_t,
349        }
350
351        impl<'a> DirentSink for DirentSinkWrapper<'a> {
352            fn add(
353                &mut self,
354                inode_num: ino_t,
355                offset: off_t,
356                entry_type: DirectoryEntryType,
357                name: &FsStr,
358            ) -> Result<(), Errno> {
359                if !self.mappings.contains_key(name) {
360                    self.sink.add(
361                        inode_num,
362                        offset + (self.mappings.len() as off_t),
363                        entry_type,
364                        name,
365                    )?;
366                }
367                *self.offset = offset;
368                Ok(())
369            }
370            fn offset(&self) -> off_t {
371                *self.offset
372            }
373        }
374
375        // Allow subclassing for FileObjectOffset because the lock on the
376        // inner file's offset is acquired while holding the lock on the
377        // outer (layered) file's offset.
378        // This is safe because the locks are on different file instances
379        // and follow a strict outer-to-inner hierarchy, preventing cycles.
380        let _token = starnix_sync::allow_subclass();
381        let mut root_file_offset = self.root_file.offset.copy();
382        let mut wrapper =
383            DirentSinkWrapper { sink, mappings: &self.fs.mappings, offset: &mut *root_file_offset };
384
385        self.root_file.readdir(locked, current_task, &mut wrapper)?;
386        root_file_offset.update();
387        Ok(())
388    }
389}
390
391#[cfg(test)]
392mod test {
393    use super::*;
394    use starnix_core::fs::tmpfs::TmpFs;
395    use starnix_core::testing::*;
396    use starnix_sync::Unlocked;
397
398    fn get_root_entry_names(
399        locked: &mut Locked<Unlocked>,
400        current_task: &CurrentTask,
401        fs: &FileSystem,
402    ) -> Vec<Vec<u8>> {
403        struct DirentNameCapturer {
404            pub names: Vec<Vec<u8>>,
405            offset: off_t,
406        }
407        impl DirentSink for DirentNameCapturer {
408            fn add(
409                &mut self,
410                _inode_num: ino_t,
411                offset: off_t,
412                _entry_type: DirectoryEntryType,
413                name: &FsStr,
414            ) -> Result<(), Errno> {
415                self.names.push(name.to_vec());
416                self.offset = offset;
417                Ok(())
418            }
419            fn offset(&self) -> off_t {
420                self.offset
421            }
422        }
423        let mut sink = DirentNameCapturer { names: vec![], offset: 0 };
424        fs.root()
425            .open_anonymous(locked, current_task, OpenFlags::RDONLY)
426            .expect("open")
427            .readdir(locked, current_task, &mut sink)
428            .expect("readdir");
429        std::mem::take(&mut sink.names)
430    }
431
432    #[::fuchsia::test]
433    async fn test_remove_duplicates() {
434        #[allow(deprecated, reason = "pre-existing usage")]
435        let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
436        let base = TmpFs::new_fs(locked, &kernel);
437        base.root().create_dir_for_testing(locked, &current_task, "d1".into()).expect("create_dir");
438        base.root().create_dir_for_testing(locked, &current_task, "d2".into()).expect("create_dir");
439        let base_entries = get_root_entry_names(locked, &current_task, &base);
440        assert_eq!(base_entries.len(), 4);
441        assert!(base_entries.contains(&b".".to_vec()));
442        assert!(base_entries.contains(&b"..".to_vec()));
443        assert!(base_entries.contains(&b"d1".to_vec()));
444        assert!(base_entries.contains(&b"d2".to_vec()));
445
446        let tmpfs1 = TmpFs::new_fs(locked, &kernel);
447        let tmpfs2 = TmpFs::new_fs(locked, &kernel);
448        let layered_fs = LayeredFs::new_fs(
449            locked,
450            &kernel,
451            base,
452            BTreeMap::from([("d1".into(), tmpfs1), ("d3".into(), tmpfs2)]),
453        );
454        let layered_fs_entries = get_root_entry_names(locked, &current_task, &layered_fs);
455        assert_eq!(layered_fs_entries.len(), 5);
456        assert!(layered_fs_entries.contains(&b".".to_vec()));
457        assert!(layered_fs_entries.contains(&b"..".to_vec()));
458        assert!(layered_fs_entries.contains(&b"d1".to_vec()));
459        assert!(layered_fs_entries.contains(&b"d2".to_vec()));
460        assert!(layered_fs_entries.contains(&b"d3".to_vec()));
461    }
462}