kernel/fs/
mapping.rs

1use alloc::{
2    boxed::Box,
3    collections::{btree_map, BTreeMap},
4    sync::{Arc, Weak},
5};
6use tracing::info;
7
8use crate::{
9    io::NoDebug,
10    sync::{once::OnceLock, spin::rwlock::RwLock},
11};
12
13use super::{
14    path::{Component, Path, PathBuf},
15    EmptyFileSystem, FileSystem, FileSystemError,
16};
17
18static FILESYSTEM_MAPPING: OnceLock<FileSystemMapping> = OnceLock::new();
19
20/// Retrieves the mapping for the given path.
21///
22/// This function traverses the filesystem mapping tree to find the appropriate mapping node for the given path.
23/// It returns a tuple containing the mapping path, the remaining path after the mapping, and the corresponding mapping node.
24///
25/// # Parameters
26///
27/// * `path`: A reference to the path for which the mapping needs to be retrieved.
28///
29/// # Returns
30///
31/// * `Ok`: If the mapping is found successfully, it returns a tuple containing the mapping path, the remaining path, and the mapping node.
32/// * `Err`: If the mapping is not found or an error occurs, it returns a `FileSystemError`.
33pub fn get_mapping(path: &Path) -> Result<(PathBuf, &Path, Arc<MappingNode>), FileSystemError> {
34    FILESYSTEM_MAPPING.get().get_mapping(path)
35}
36
37/// Mounts a given filesystem at the specified path.
38///
39/// This function allows mounting a new filesystem at a specific path within the virtual filesystem.
40/// If the path is "/", the provided filesystem becomes the root filesystem (if not already mounted).
41/// If the path is not "/", the provided filesystem is mounted as a child of the filesystem at the specified path.
42///
43/// # Parameters
44///
45/// * `arg`: A reference to a string representing the path where the filesystem should be mounted.
46///   The path must be absolute and not contain any ".." or "." components.
47///
48/// * `filesystem`: An `Arc` smart pointer to a trait object implementing the `FileSystem` trait.
49///   This trait defines the behavior of the filesystem to be mounted.
50///
51/// # Returns
52///
53/// * `Ok(())`: If the filesystem is successfully mounted at the specified path.
54///
55/// * `Err(MappingError)`: If an error occurs during the mounting process.
56///   The specific error can be one of the following:
57///   - `MappingError::MustBeAbsolute`: If the provided path is not absolute.
58///   - `MappingError::InvalidPath`: If the provided path contains ".." or "." components.
59///   - `MappingError::PartOfParentNotMounted`: If the parent path of the provided path is not mounted.
60///   - `MappingError::AlreadyMounted`: If the provided path is already mounted.
61pub fn mount(arg: &str, filesystem: Arc<dyn FileSystem>) -> Result<(), MappingError> {
62    let mapping = FILESYSTEM_MAPPING.get_or_init(FileSystemMapping::empty_root);
63
64    if arg == "/" {
65        mapping.set_root(filesystem)
66    } else {
67        mapping.mount(arg, filesystem)
68    }
69}
70
71/// Unmounts all filesystems from the virtual filesystem.
72/// This function removes all mounted filesystems from the virtual filesystem, effectively clearing
73/// the filesystem mapping tree.
74pub fn unmount_all() {
75    // The `Drop` will call `unmount` for each filesystem
76    FILESYSTEM_MAPPING.get().root.unmount_all(Path::new("/"));
77}
78
79/// Traverses the filesystem mapping tree and applies a handler function to all matching mappings.
80///
81/// This function iterates through the filesystem mapping tree, starting from the root, and applies a handler function
82/// to all nodes whose paths match the provided input path. The matching is performed by comparing the components
83/// of the input path with the components of the mapping paths.
84///
85/// # Parameters
86///
87/// * `inp_path`: A reference to the input path for which matching mappings need to be found.
88///   The input path must be absolute and not contain any ".." or "." components.
89///
90/// * `handler`: A closure or function that takes a reference to a path and an `Arc` smart pointer to a trait object
91///   implementing the `FileSystem` trait. This closure or function will be applied to each matching mapping.
92///
93/// # Returns
94///
95/// * `Ok(())`: If the traversal and application of the handler function are successful.
96///
97/// * `Err(FileSystemError)`: If an error occurs during the traversal or application of the handler function.
98///   The specific error can be one of the following:
99///   - `FileSystemError::MustBeAbsolute`: If the provided input path is not absolute.
100///   - `FileSystemError::InvalidPath`: If the provided input path contains ".." or "." components.
101pub fn on_all_matching_mappings(
102    inp_path: &Path,
103    handler: impl FnMut(&Path, Arc<dyn FileSystem>),
104) -> Result<(), FileSystemError> {
105    FILESYSTEM_MAPPING
106        .get()
107        .on_all_matching_mappings(inp_path, handler)
108}
109
110#[derive(Debug)]
111pub enum MappingError {
112    MustBeAbsolute,
113    InvalidPath,
114    PartOfParentNotMounted,
115    AlreadyMounted,
116}
117
118impl From<MappingError> for FileSystemError {
119    fn from(value: MappingError) -> Self {
120        Self::MappingError(value)
121    }
122}
123
124#[derive(Debug)]
125#[allow(dead_code)]
126pub struct MappingNode {
127    filesystem: NoDebug<RwLock<Arc<dyn FileSystem>>>,
128    parent: Weak<MappingNode>,
129    children: RwLock<BTreeMap<Box<str>, Arc<MappingNode>>>,
130}
131
132impl MappingNode {
133    fn check_and_traverse(
134        &self,
135        target: &Path,
136        this_component: Component<'_>,
137        handler: &mut dyn FnMut(&Path, Arc<dyn FileSystem>),
138    ) -> Result<(), FileSystemError> {
139        let mut components = target.components();
140
141        // doesn't match anything from here, stop
142        if components.next() != Some(this_component) {
143            return Ok(());
144        }
145
146        if components.peek().is_none() {
147            for (name, node) in self.children.read().iter() {
148                node.traverse(name.into(), handler);
149            }
150        } else {
151            for (name, node) in self.children.read().iter() {
152                node.check_and_traverse(
153                    components.as_path(),
154                    Component::Normal(name.as_ref()),
155                    handler,
156                )?;
157            }
158        }
159
160        Ok(())
161    }
162
163    fn traverse(&self, current_path: PathBuf, handler: &mut dyn FnMut(&Path, Arc<dyn FileSystem>)) {
164        handler(&current_path, self.filesystem());
165
166        for (name, node) in self.children.read().iter() {
167            node.traverse(current_path.join(name.as_ref()), handler);
168        }
169    }
170
171    pub fn try_find_child(&self, component_name: &str) -> Option<Arc<MappingNode>> {
172        self.children.read().get(component_name).cloned()
173    }
174
175    pub fn filesystem(&self) -> Arc<dyn FileSystem> {
176        self.filesystem.0.read().clone()
177    }
178
179    pub fn parent(&self) -> Option<Arc<MappingNode>> {
180        self.parent.upgrade()
181    }
182
183    fn unmount_all(&self, this_name: &Path) {
184        let mut children = self.children.write();
185        while let Some((name, node)) = children.pop_first() {
186            node.unmount_all(&this_name.join(name.as_ref()));
187        }
188
189        info!("Unmounting {}", this_name.display());
190        let fs = core::mem::replace(&mut *self.filesystem.0.write(), Arc::new(EmptyFileSystem));
191        assert_eq!(
192            Arc::strong_count(&fs),
193            1, // this one
194            "Filesystem still in use"
195        );
196        fs.unmount();
197    }
198}
199
200#[derive(Debug)]
201struct FileSystemMapping {
202    root: Arc<MappingNode>,
203}
204
205impl FileSystemMapping {
206    fn empty_root() -> Self {
207        Self {
208            root: Arc::new(MappingNode {
209                filesystem: NoDebug(RwLock::new(Arc::new(EmptyFileSystem))),
210                parent: Weak::new(),
211                children: RwLock::new(BTreeMap::new()),
212            }),
213        }
214    }
215
216    fn set_root(&self, filesystem: Arc<dyn FileSystem>) -> Result<(), MappingError> {
217        // Only `EmptyFileSystem` does this
218        if let Err(FileSystemError::FileNotFound) = self.root.filesystem().open_root() {
219            // FIXME: very bad. Not sure if there is race condition here, seems very suspicious
220
221            *self.root.filesystem.0.write() = filesystem;
222            Ok(())
223        } else {
224            Err(MappingError::AlreadyMounted)
225        }
226    }
227
228    fn get_mapping<'p>(
229        &self,
230        path: &'p Path,
231    ) -> Result<(PathBuf, &'p Path, Arc<MappingNode>), FileSystemError> {
232        let mut current = self.root.clone();
233        // must start with `/`
234        let mut mapping_path = PathBuf::from("/");
235
236        let mut components = path.components();
237
238        if components.next() != Some(Component::RootDir) {
239            return Err(FileSystemError::MustBeAbsolute);
240        }
241
242        while let Some(component) = components.peek() {
243            match component {
244                Component::Normal(name) => {
245                    if let Some(child) = current.try_find_child(name) {
246                        mapping_path.push(name);
247                        current = child;
248                    } else {
249                        break;
250                    }
251                }
252                _ => {
253                    break;
254                }
255            }
256
257            // consume
258            components.next();
259        }
260
261        Ok((mapping_path, components.as_path(), current))
262    }
263
264    fn on_all_matching_mappings(
265        &self,
266        path: &Path,
267        mut handler: impl FnMut(&Path, Arc<dyn FileSystem>),
268    ) -> Result<(), FileSystemError> {
269        self.root
270            .check_and_traverse(path, Component::RootDir, &mut handler)
271    }
272
273    fn mount<P: AsRef<Path>>(
274        &self,
275        arg: P,
276        filesystem: Arc<dyn FileSystem>,
277    ) -> Result<(), MappingError> {
278        let mut components: super::path::Components = arg.as_ref().components();
279
280        if components.next() != Some(Component::RootDir) {
281            return Err(MappingError::MustBeAbsolute);
282        }
283
284        {
285            // no `..` or `.` in the path
286            if components
287                .clone()
288                .any(|c| !matches!(c, Component::Normal(_)))
289            {
290                return Err(MappingError::InvalidPath);
291            }
292        }
293
294        let mut current_element = self.root.clone();
295
296        let size = components.clone().count();
297
298        for (i, component) in components.enumerate() {
299            let Component::Normal(component_path) = component else {
300                unreachable!("Already checked all the components")
301            };
302            let is_last = i == size - 1;
303
304            match current_element
305                .clone()
306                .children
307                .write()
308                .entry(component_path.into())
309            {
310                btree_map::Entry::Vacant(entry) => {
311                    if is_last {
312                        entry.insert(Arc::new(MappingNode {
313                            filesystem: NoDebug(RwLock::new(filesystem)),
314                            parent: Arc::downgrade(&current_element),
315                            children: RwLock::new(BTreeMap::new()),
316                        }));
317                        return Ok(());
318                    } else {
319                        return Err(MappingError::PartOfParentNotMounted);
320                    }
321                }
322                btree_map::Entry::Occupied(entry) => {
323                    if is_last {
324                        return Err(MappingError::AlreadyMounted);
325                    } else {
326                        current_element = entry.get().clone();
327                    }
328                }
329            }
330        }
331
332        unreachable!("For some reason, it wasn't mounted")
333    }
334}