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