Skip to main content

starnix_core/vfs/pseudo/
simple_directory.rs

1// Copyright 2025 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
5use crate::task::CurrentTask;
6use crate::vfs::{
7    CloseFreeSafe, DirEntry, DirectoryEntryType, DirentSink, FileObject, FileOps, FileSystemHandle,
8    FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, SymlinkNode, emit_dotdot,
9    fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_unbounded_seek,
10    fs_node_impl_dir_readonly,
11};
12use starnix_sync::{LockDepMutex, SimpleDirectoryEntriesLock, allow_subclass};
13use starnix_uapi::auth::FsCred;
14use starnix_uapi::device_id::DeviceId;
15use starnix_uapi::errno;
16use starnix_uapi::errors::Errno;
17use starnix_uapi::file_mode::{FileMode, mode};
18use starnix_uapi::open_flags::OpenFlags;
19use std::collections::BTreeMap;
20use std::sync::Arc;
21
22/// Helper used to populate a `SimpleDirectory` with nodes for a specific `FileSystem`.
23pub struct SimpleDirectoryMutator {
24    fs: FileSystemHandle,
25    pub directory: Arc<SimpleDirectory>,
26}
27
28impl SimpleDirectoryMutator {
29    /// Creates a mutator that will allocate nodes in `fs` and insert them into `directory`.
30    pub fn new(fs: FileSystemHandle, directory: Arc<SimpleDirectory>) -> Self {
31        Self { fs, directory }
32    }
33
34    pub fn node(&self, name: FsString, node: FsNodeHandle) {
35        let mut state = self.directory.state.lock();
36        let child_dir_and_handler = state.not_found_handler.and_then(|handler| {
37            node.downcast_ops::<Arc<SimpleDirectory>>().cloned().map(|dir| (dir, handler))
38        });
39        state.entries.insert(name, node);
40        // Propagate the parent's handler after releasing `self.directory.state.lock()`, so that
41        // at most one `SimpleDirectoryEntriesLock` is held at a time.
42        std::mem::drop(state);
43        if let Some((child_dir, handler)) = child_dir_and_handler {
44            child_dir.set_not_found_handler(handler);
45        }
46    }
47
48    pub fn entry(&self, name: &str, ops: impl Into<Box<dyn FsNodeOps>>, mode: FileMode) {
49        let name: FsString = name.into();
50        let node =
51            self.fs.create_node_and_allocate_node_id(ops, FsNodeInfo::new(mode, FsCred::root()));
52        self.node(name, node);
53    }
54
55    pub fn entry_etc(
56        &self,
57        name: FsString,
58        ops: impl Into<Box<dyn FsNodeOps>>,
59        mode: FileMode,
60        dev: DeviceId,
61        creds: FsCred,
62    ) {
63        let mut info = FsNodeInfo::new(mode, creds);
64        info.rdev = dev;
65        let node = self.fs.create_node_and_allocate_node_id(ops, info);
66        self.node(name, node);
67    }
68
69    pub fn symlink(&self, name: &FsStr, target: &FsStr) {
70        let (ops, info) = SymlinkNode::new(target, FsCred::root());
71        let node = self.fs.create_node_and_allocate_node_id(ops, info);
72        self.node(name.into(), node);
73    }
74
75    pub fn subdir(&self, name: &str, mode: u32, build_subdir: impl FnOnce(&Self)) {
76        let name: &FsStr = name.into();
77        self.subdir2(name, mode, build_subdir);
78    }
79
80    // TODO: Figure out a better way to overload this function for &str and &FsStr.
81    pub fn subdir2(&self, name: &FsStr, mode: u32, build_subdir: impl FnOnce(&Self)) {
82        let dir = self.directory.subdir(&self.fs, name, mode);
83        let mutator = SimpleDirectoryMutator::new(self.fs.clone(), dir);
84        build_subdir(&mutator);
85    }
86
87    pub fn remove(&self, name: &FsStr) {
88        self.directory.remove(name);
89    }
90}
91
92/// Handler invoked by [`FsNodeOps::lookup`] when the requested child of a [`SimpleDirectory`] is
93/// not present.
94///
95/// The handler receives the [`DirEntry`] of the directory that was searched, and the `name` of
96/// the requested child, and returns the [`Errno`] to report to the caller.
97///
98/// Child [`SimpleDirectory`] nodes created via [`SimpleDirectory::subdir`] or attached via
99/// [`SimpleDirectoryMutator`] inherit their parent directory's handler.
100pub(crate) type NotFoundHandler = fn(&DirEntry, &FsStr) -> Errno;
101
102struct SimpleDirectoryState {
103    entries: BTreeMap<FsString, FsNodeHandle>,
104    not_found_handler: Option<NotFoundHandler>,
105}
106
107/// Returns `ENOENT`, with context identifying the directory (via its [`DirEntry`] `Debug` form)
108/// and the `name` that was looked up in it.
109fn default_not_found_handler(entry: &DirEntry, name: &FsStr) -> Errno {
110    errno!(ENOENT, format!("Looking for {name} in {entry:?}"))
111}
112
113/// Common implementation of a simple read-only directory `FsNodeOps`.
114///
115/// `SimpleDirectoryMutator` is used to populate the directory with child `FsNode`s allocated
116/// in the desired (usually kernel-internal, e.g. "sysfs", "proc", etc) filesystem.
117pub struct SimpleDirectory {
118    state: LockDepMutex<SimpleDirectoryState, SimpleDirectoryEntriesLock>,
119}
120
121impl SimpleDirectory {
122    /// Returns a new instance with a default handler that returns `ENOENT` and logs context
123    /// when a child is not found.
124    pub fn new() -> Arc<Self> {
125        Arc::new(SimpleDirectory {
126            state: LockDepMutex::new(SimpleDirectoryState {
127                entries: Default::default(),
128                not_found_handler: None,
129            }),
130        })
131    }
132
133    /// Installs `not_found_handler` on this directory and recursively across all existing
134    /// and future [`SimpleDirectory`] descendants.
135    pub(crate) fn set_not_found_handler(&self, not_found_handler: NotFoundHandler) {
136        // Collect the child directories and release this directory's lock before recursing, so
137        // that at most one `SimpleDirectoryEntriesLock` is held at a time.
138        let child_dirs: Vec<Arc<SimpleDirectory>> = {
139            let mut state = self.state.lock();
140            state.not_found_handler = Some(not_found_handler);
141            state
142                .entries
143                .values()
144                .filter_map(|node| node.downcast_ops::<Arc<SimpleDirectory>>().cloned())
145                .collect()
146        };
147        for child_dir in child_dirs {
148            child_dir.set_not_found_handler(not_found_handler);
149        }
150    }
151
152    pub fn remove(&self, name: &FsStr) {
153        self.state.lock().entries.remove(name);
154    }
155
156    fn walk<'a>(self: &Arc<Self>, path: &'a FsStr) -> Option<(Arc<Self>, &'a FsStr)> {
157        fn check_component(component: &FsStr) {
158            assert!(!component.is_empty());
159
160            let dot: &FsStr = b".".into();
161            assert_ne!(component, dot);
162
163            let dotdot: &FsStr = b"..".into();
164            assert_ne!(component, dotdot);
165        }
166
167        let mut components = path.split(|c| *c == b'/');
168        let basename = components.next_back()?;
169        let basename: &FsStr = basename.into();
170        check_component(basename);
171        let mut parent = self.clone();
172        while let Some(component) = components.next() {
173            let component: &FsStr = component.into();
174            check_component(component);
175            let Some(next) = parent.get_dir(component) else {
176                return None;
177            };
178            parent = next;
179        }
180        Some((parent, basename))
181    }
182
183    pub fn edit(
184        self: &Arc<Self>,
185        fs: &FileSystemHandle,
186        callback: impl FnOnce(&SimpleDirectoryMutator),
187    ) {
188        let mutator = SimpleDirectoryMutator::new(fs.clone(), self.clone());
189        callback(&mutator);
190    }
191
192    fn get_or_create_subdir_locked(
193        state: &mut SimpleDirectoryState,
194        fs: &FileSystemHandle,
195        name: &FsStr,
196        mode: u32,
197    ) -> Arc<SimpleDirectory> {
198        if let Some(node) = state.entries.get(name) {
199            assert!(node.info().mode == mode!(IFDIR, mode));
200            let dir =
201                node.downcast_ops::<Arc<SimpleDirectory>>().expect("subdir is a SimpleDirectory");
202            dir.clone()
203        } else {
204            let dir = Arc::new(SimpleDirectory {
205                state: LockDepMutex::new(SimpleDirectoryState {
206                    entries: Default::default(),
207                    not_found_handler: state.not_found_handler,
208                }),
209            });
210            let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
211            let node = fs.create_node_and_allocate_node_id(dir.clone(), info);
212            state.entries.insert(name.into(), node);
213            dir
214        }
215    }
216
217    pub fn subdir(&self, fs: &FileSystemHandle, name: &FsStr, mode: u32) -> Arc<SimpleDirectory> {
218        let mut state = self.state.lock();
219        Self::get_or_create_subdir_locked(&mut state, fs, name, mode)
220    }
221
222    /// Creates or looks up `subdir_name`, and creates or looks up `child_name` within it while
223    /// holding this directory's lock so the intermediate subdirectory cannot be removed
224    /// concurrently by [`Self::remove_from_subdir_if_empty`].
225    pub fn nested_subdir(
226        &self,
227        fs: &FileSystemHandle,
228        subdir_name: &FsStr,
229        subdir_mode: u32,
230        child_name: &FsStr,
231        child_mode: u32,
232    ) -> Arc<SimpleDirectory> {
233        let mut state = self.state.lock();
234        let subdir = Self::get_or_create_subdir_locked(&mut state, fs, subdir_name, subdir_mode);
235        // Safe because locking parent then child strictly follows the directory tree hierarchy.
236        let _token = allow_subclass();
237        subdir.subdir(fs, child_name, child_mode)
238    }
239
240    /// Removes `child_name` from `subdir_name`, and removes `subdir_name` itself if it becomes
241    /// empty, atomically with respect to [`Self::nested_subdir`].
242    pub fn remove_from_subdir_if_empty(&self, subdir_name: &FsStr, child_name: &FsStr) {
243        let mut state = self.state.lock();
244        let Some(subdir) = state
245            .entries
246            .get(subdir_name)
247            .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
248            .map(Arc::clone)
249        else {
250            return;
251        };
252        let is_empty = {
253            // Safe because locking parent then child strictly follows the directory tree hierarchy.
254            let _token = allow_subclass();
255            let mut subdir_state = subdir.state.lock();
256            subdir_state.entries.remove(child_name);
257            subdir_state.entries.is_empty()
258        };
259        if is_empty {
260            state.entries.remove(subdir_name);
261        }
262    }
263
264    fn get(&self, name: &FsStr) -> Option<FsNodeHandle> {
265        let state = self.state.lock();
266        state.entries.get(name).cloned()
267    }
268
269    pub fn get_dir(&self, name: &FsStr) -> Option<Arc<SimpleDirectory>> {
270        let state = self.state.lock();
271        state
272            .entries
273            .get(name)
274            .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
275            .map(Arc::clone)
276    }
277
278    pub fn lookup(self: &Arc<Self>, path: &FsStr) -> Option<FsNodeHandle> {
279        let (parent, basename) = self.walk(path)?;
280        parent.get(basename)
281    }
282
283    pub fn into_node(self: Arc<Self>, fs: &FileSystemHandle, mode: u32) -> FsNodeHandle {
284        let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
285        fs.create_node_and_allocate_node_id(self, info)
286    }
287}
288
289impl FsNodeOps for Arc<SimpleDirectory> {
290    fs_node_impl_dir_readonly!();
291
292    fn create_file_ops(
293        &self,
294        _node: &FsNode,
295        _current_task: &CurrentTask,
296        _flags: OpenFlags,
297    ) -> Result<Box<dyn FileOps>, Errno> {
298        Ok(Box::new(self.clone()))
299    }
300
301    fn lookup(
302        &self,
303        entry: &DirEntry,
304        _current_task: &CurrentTask,
305        name: &FsStr,
306    ) -> Result<FsNodeHandle, Errno> {
307        let not_found_handler = {
308            let state = self.state.lock();
309            if let Some(node) = state.entries.get(name) {
310                return Ok(node.clone());
311            }
312            state.not_found_handler.unwrap_or(default_not_found_handler)
313        };
314        // Invoke the handler after releasing the directory lock, so that handlers which format
315        // paths or log diagnostics do not extend the critical section.
316        Err(not_found_handler(entry, name))
317    }
318}
319
320/// `SimpleDirectory` doesn't implement the `close` method.
321impl CloseFreeSafe for SimpleDirectory {}
322impl FileOps for SimpleDirectory {
323    fileops_impl_directory!();
324    fileops_impl_noop_sync!();
325    fileops_impl_unbounded_seek!();
326
327    fn readdir(
328        &self,
329        file: &FileObject,
330        _current_task: &CurrentTask,
331        sink: &mut dyn DirentSink,
332    ) -> Result<(), Errno> {
333        emit_dotdot(file, sink)?;
334
335        // Skip through the entries until the current offset is reached.
336        // Subtract 2 from the offset to account for `.` and `..`.
337        let state = self.state.lock();
338        for (name, node) in state.entries.iter().skip(sink.offset() as usize - 2) {
339            let mode = node.info().mode;
340            sink.add(
341                node.ino,
342                sink.offset() + 1,
343                DirectoryEntryType::from_mode(mode),
344                name.as_ref(),
345            )?;
346        }
347        Ok(())
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354    use crate::testing::spawn_kernel_and_run;
355    use crate::vfs::{DirEntryHandle, FsNodeOps, MountInfo};
356    use starnix_uapi::errno;
357
358    #[fuchsia::test]
359    async fn test_default_not_found_handler() {
360        spawn_kernel_and_run(async |current_task| {
361            let dir = SimpleDirectory::new();
362            let node = dir.clone().into_node(&current_task.fs().root().entry.node.fs(), 0o777);
363            let entry = DirEntry::new_unrooted(node);
364            let result = FsNodeOps::lookup(&dir, &entry, &current_task, "nonexistent".into());
365            assert_eq!(result.unwrap_err(), errno!(ENOENT));
366        })
367        .await;
368    }
369
370    #[fuchsia::test]
371    async fn test_set_not_found_handler() {
372        #[track_caller]
373        fn check_lookup(
374            current_task: &CurrentTask,
375            root: &DirEntryHandle,
376            path: &str,
377            expected_error: Errno,
378        ) {
379            let mount = MountInfo::detached();
380            let mut dir = root.clone();
381            for component in path.split('/').filter(|s| !s.is_empty()) {
382                dir = dir.component_lookup(current_task, &mount, component.into()).unwrap();
383            }
384            let res = dir.component_lookup(current_task, &mount, "expected_error".into());
385            assert_eq!(res.unwrap_err(), expected_error);
386        }
387
388        spawn_kernel_and_run(async |current_task| {
389            let fs = current_task.fs().root().entry.node.fs();
390            let root_dir = SimpleDirectory::new();
391            root_dir.subdir(&fs, "existing".into(), 0o755).subdir(&fs, "nested".into(), 0o755);
392
393            root_dir.set_not_found_handler(|entry, name| {
394                if name == "expected_error" && entry.parent().is_some() {
395                    errno!(EACCES)
396                } else {
397                    errno!(ENOENT)
398                }
399            });
400
401            root_dir.subdir(&fs, "future".into(), 0o755);
402
403            let attached = SimpleDirectory::new();
404            attached.subdir(&fs, "nested".into(), 0o755);
405            root_dir.edit(&fs, |dir| {
406                dir.entry("attached", attached, mode!(IFDIR, 0o755));
407            });
408
409            let root = DirEntry::new_unrooted(root_dir.into_node(&fs, 0o755));
410            check_lookup(&current_task, &root, "", errno!(ENOENT));
411            for path in ["existing", "existing/nested", "future", "attached", "attached/nested"] {
412                check_lookup(&current_task, &root, path, errno!(EACCES));
413            }
414        })
415        .await;
416    }
417
418    #[fuchsia::test]
419    async fn test_simple_directory_lookups() {
420        spawn_kernel_and_run(async |current_task| {
421            let fs = current_task.fs().root().entry.node.fs();
422            let dir = SimpleDirectory::new();
423            let mutator = SimpleDirectoryMutator::new(fs.clone(), dir.clone());
424
425            // Add a symlink
426            mutator.symlink("link".into(), "target".into());
427
428            // Add a subdir
429            mutator.subdir("subdir", 0o755, |sub_mutator| {
430                sub_mutator.symlink("sublink".into(), "subtarget".into());
431            });
432
433            let node = dir.clone().into_node(&fs, 0o777);
434            let entry = DirEntry::new_unrooted(node);
435
436            // Verify that lookup returns the same FsNodeHandle for multiple calls.
437            let node1 =
438                FsNodeOps::lookup(&dir, &entry, &current_task, "link".into()).expect("lookup link");
439            let node2 = FsNodeOps::lookup(&dir, &entry, &current_task, "link".into())
440                .expect("lookup link again");
441
442            assert!(Arc::ptr_eq(&node1, &node2));
443            assert!(node1.info().mode.is_lnk());
444
445            // Verify that lookup returns the same FsNodeHandle for subdirectories.
446            let subdir1 = FsNodeOps::lookup(&dir, &entry, &current_task, "subdir".into())
447                .expect("lookup subdir");
448            let subdir2 = FsNodeOps::lookup(&dir, &entry, &current_task, "subdir".into())
449                .expect("lookup subdir again");
450
451            assert!(Arc::ptr_eq(&subdir1, &subdir2));
452            assert!(subdir1.info().mode.is_dir());
453
454            // Verify that the SimpleDirectory::lookup helper works for nested paths.
455            let sublink = dir.lookup("subdir/sublink".into()).expect("lookup subdir/sublink");
456            assert!(sublink.info().mode.is_lnk());
457
458            // Verify that removing an entry works.
459            mutator.remove("link".into());
460            let result = FsNodeOps::lookup(&dir, &entry, &current_task, "link".into());
461            assert_eq!(result.unwrap_err(), errno!(ENOENT));
462        })
463        .await;
464    }
465}