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, DirectoryEntryType, DirentSink, FileObject, FileOps, FileSystemHandle, FsNode,
8    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};
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        self.directory.entries.lock().insert(name, node);
36    }
37
38    pub fn entry(&self, name: &str, ops: impl Into<Box<dyn FsNodeOps>>, mode: FileMode) {
39        let name: FsString = name.into();
40        let node =
41            self.fs.create_node_and_allocate_node_id(ops, FsNodeInfo::new(mode, FsCred::root()));
42        self.node(name, node);
43    }
44
45    pub fn entry_etc(
46        &self,
47        name: FsString,
48        ops: impl Into<Box<dyn FsNodeOps>>,
49        mode: FileMode,
50        dev: DeviceId,
51        creds: FsCred,
52    ) {
53        let mut info = FsNodeInfo::new(mode, creds);
54        info.rdev = dev;
55        let node = self.fs.create_node_and_allocate_node_id(ops, info);
56        self.node(name, node);
57    }
58
59    pub fn symlink(&self, name: &FsStr, target: &FsStr) {
60        let (ops, info) = SymlinkNode::new(target, FsCred::root());
61        let node = self.fs.create_node_and_allocate_node_id(ops, info);
62        self.node(name.into(), node);
63    }
64
65    pub fn subdir(&self, name: &str, mode: u32, build_subdir: impl FnOnce(&Self)) {
66        let name: &FsStr = name.into();
67        self.subdir2(name, mode, build_subdir);
68    }
69
70    // TODO: Figure out a better way to overload this function for &str and &FsStr.
71    pub fn subdir2(&self, name: &FsStr, mode: u32, build_subdir: impl FnOnce(&Self)) {
72        let dir = self.directory.subdir(&self.fs, name, mode);
73        let mutator = SimpleDirectoryMutator::new(self.fs.clone(), dir);
74        build_subdir(&mutator);
75    }
76
77    pub fn remove(&self, name: &FsStr) {
78        self.directory.remove(name);
79    }
80}
81
82/// Common implementation of a simple read-only directory `FsNodeOps`.
83///
84/// `SimpleDirectoryMutator` is used to populate the directory with child `FsNode`s allocated
85/// in the desired (usually kernel-internal, e.g. "sysfs", "proc", etc) filesystem.
86pub struct SimpleDirectory {
87    entries: LockDepMutex<BTreeMap<FsString, FsNodeHandle>, SimpleDirectoryEntriesLock>,
88    not_found_handler:
89        Box<dyn Fn(&FsStr, &BTreeMap<FsString, FsNodeHandle>) -> Errno + Send + Sync + 'static>,
90}
91
92impl SimpleDirectory {
93    /// Returns a new instance with a default handler that returns `ENOENT` and logs context
94    /// when a child is not found.
95    pub fn new() -> Arc<Self> {
96        Self::new_with_handler(|name, locked_entries| {
97            errno!(
98                ENOENT,
99                format!(
100                    "looking for {name} in {:?}",
101                    locked_entries.keys().map(|e| e.to_string()).collect::<Vec<_>>()
102                )
103            )
104        })
105    }
106
107    /// Returns a new instance configured to call the supplied `not_found_handler` whenever
108    /// `FsNodeOps::lookup()` is called for an unknown child path.
109    ///
110    /// The handler is invoked with the `name` of the requested child and a reference to
111    /// the current directory `entries`.
112    pub fn new_with_handler(
113        not_found_handler: impl Fn(&FsStr, &BTreeMap<FsString, FsNodeHandle>) -> Errno
114        + Send
115        + Sync
116        + 'static,
117    ) -> Arc<Self> {
118        let not_found_handler = Box::new(not_found_handler);
119        Arc::new(SimpleDirectory { entries: Default::default(), not_found_handler })
120    }
121
122    pub fn remove(&self, name: &FsStr) {
123        self.entries.lock().remove(name);
124    }
125
126    fn walk<'a>(self: &Arc<Self>, path: &'a FsStr) -> Option<(Arc<Self>, &'a FsStr)> {
127        fn check_component(component: &FsStr) {
128            assert!(!component.is_empty());
129
130            let dot: &FsStr = b".".into();
131            assert_ne!(component, dot);
132
133            let dotdot: &FsStr = b"..".into();
134            assert_ne!(component, dotdot);
135        }
136
137        let mut components = path.split(|c| *c == b'/');
138        let basename = components.next_back()?;
139        let basename: &FsStr = basename.into();
140        check_component(basename);
141        let mut parent = self.clone();
142        while let Some(component) = components.next() {
143            let component: &FsStr = component.into();
144            check_component(component);
145            let Some(next) = parent.get_dir(component) else {
146                return None;
147            };
148            parent = next;
149        }
150        Some((parent, basename))
151    }
152
153    pub fn edit(
154        self: &Arc<Self>,
155        fs: &FileSystemHandle,
156        callback: impl FnOnce(&SimpleDirectoryMutator),
157    ) {
158        let mutator = SimpleDirectoryMutator::new(fs.clone(), self.clone());
159        callback(&mutator);
160    }
161
162    pub fn subdir(&self, fs: &FileSystemHandle, name: &FsStr, mode: u32) -> Arc<SimpleDirectory> {
163        let mut entries = self.entries.lock();
164        if let Some(node) = entries.get(name) {
165            assert!(node.info().mode == mode!(IFDIR, mode));
166            let dir =
167                node.downcast_ops::<Arc<SimpleDirectory>>().expect("subdir is a SimpleDirectory");
168            dir.clone()
169        } else {
170            let dir = SimpleDirectory::new();
171            let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
172            let node = fs.create_node_and_allocate_node_id(dir.clone(), info);
173            entries.insert(name.into(), node);
174            dir
175        }
176    }
177
178    fn get(&self, name: &FsStr) -> Option<FsNodeHandle> {
179        let entries = self.entries.lock();
180        entries.get(name).cloned()
181    }
182
183    fn get_dir(&self, name: &FsStr) -> Option<Arc<SimpleDirectory>> {
184        let entries = self.entries.lock();
185        entries
186            .get(name)
187            .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
188            .map(Arc::clone)
189    }
190
191    pub fn lookup(self: &Arc<Self>, path: &FsStr) -> Option<FsNodeHandle> {
192        let (parent, basename) = self.walk(path)?;
193        parent.get(basename)
194    }
195
196    pub fn into_node(self: Arc<Self>, fs: &FileSystemHandle, mode: u32) -> FsNodeHandle {
197        let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
198        fs.create_node_and_allocate_node_id(self, info)
199    }
200}
201
202impl FsNodeOps for Arc<SimpleDirectory> {
203    fs_node_impl_dir_readonly!();
204
205    fn create_file_ops(
206        &self,
207        _node: &FsNode,
208        _current_task: &CurrentTask,
209        _flags: OpenFlags,
210    ) -> Result<Box<dyn FileOps>, Errno> {
211        Ok(Box::new(self.clone()))
212    }
213
214    fn lookup(
215        &self,
216        _node: &FsNode,
217        _current_task: &CurrentTask,
218        name: &FsStr,
219    ) -> Result<FsNodeHandle, Errno> {
220        let entries = self.entries.lock();
221        entries.get(name).cloned().ok_or_else(|| (self.not_found_handler)(name, &entries))
222    }
223}
224
225/// `SimpleDirectory` doesn't implement the `close` method.
226impl CloseFreeSafe for SimpleDirectory {}
227impl FileOps for SimpleDirectory {
228    fileops_impl_directory!();
229    fileops_impl_noop_sync!();
230    fileops_impl_unbounded_seek!();
231
232    fn readdir(
233        &self,
234        file: &FileObject,
235        _current_task: &CurrentTask,
236        sink: &mut dyn DirentSink,
237    ) -> Result<(), Errno> {
238        emit_dotdot(file, sink)?;
239
240        // Skip through the entries until the current offset is reached.
241        // Subtract 2 from the offset to account for `.` and `..`.
242        let entries = self.entries.lock();
243        for (name, node) in entries.iter().skip(sink.offset() as usize - 2) {
244            sink.add(
245                node.ino,
246                sink.offset() + 1,
247                DirectoryEntryType::from_mode(node.info().mode),
248                name.as_ref(),
249            )?;
250        }
251        Ok(())
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::testing::spawn_kernel_and_run;
259    use crate::vfs::FsNodeOps;
260    use starnix_uapi::errno;
261
262    #[fuchsia::test]
263    async fn test_default_not_found_handler() {
264        spawn_kernel_and_run(async |current_task| {
265            let dir = SimpleDirectory::new();
266            let node = dir.clone().into_node(&current_task.fs().root().entry.node.fs(), 0o777);
267            let result = FsNodeOps::lookup(&dir, &node, &current_task, "nonexistent".into());
268            assert_eq!(result.unwrap_err(), errno!(ENOENT));
269        })
270        .await;
271    }
272
273    #[fuchsia::test]
274    async fn test_custom_not_found_handler() {
275        spawn_kernel_and_run(async |current_task| {
276            let dir = SimpleDirectory::new_with_handler(|name, _entries| {
277                if name == "special" { errno!(EACCES) } else { errno!(ENOENT) }
278            });
279            let node = dir.clone().into_node(&current_task.fs().root().entry.node.fs(), 0o777);
280
281            let result_special = FsNodeOps::lookup(&dir, &node, &current_task, "special".into());
282            assert_eq!(result_special.unwrap_err(), errno!(EACCES));
283
284            let result_other = FsNodeOps::lookup(&dir, &node, &current_task, "other".into());
285            assert_eq!(result_other.unwrap_err(), errno!(ENOENT));
286        })
287        .await;
288    }
289
290    #[fuchsia::test]
291    async fn test_simple_directory_lookups() {
292        spawn_kernel_and_run(async |current_task| {
293            let fs = current_task.fs().root().entry.node.fs();
294            let dir = SimpleDirectory::new();
295            let mutator = SimpleDirectoryMutator::new(fs.clone(), dir.clone());
296
297            // Add a symlink
298            mutator.symlink("link".into(), "target".into());
299
300            // Add a subdir
301            mutator.subdir("subdir", 0o755, |sub_mutator| {
302                sub_mutator.symlink("sublink".into(), "subtarget".into());
303            });
304
305            let node = dir.clone().into_node(&fs, 0o777);
306
307            // Verify that lookup returns the same FsNodeHandle for multiple calls.
308            let node1 =
309                FsNodeOps::lookup(&dir, &node, &current_task, "link".into()).expect("lookup link");
310            let node2 = FsNodeOps::lookup(&dir, &node, &current_task, "link".into())
311                .expect("lookup link again");
312
313            assert!(Arc::ptr_eq(&node1, &node2));
314            assert!(node1.info().mode.is_lnk());
315
316            // Verify that lookup returns the same FsNodeHandle for subdirectories.
317            let subdir1 = FsNodeOps::lookup(&dir, &node, &current_task, "subdir".into())
318                .expect("lookup subdir");
319            let subdir2 = FsNodeOps::lookup(&dir, &node, &current_task, "subdir".into())
320                .expect("lookup subdir again");
321
322            assert!(Arc::ptr_eq(&subdir1, &subdir2));
323            assert!(subdir1.info().mode.is_dir());
324
325            // Verify that the SimpleDirectory::lookup helper works for nested paths.
326            let sublink = dir.lookup("subdir/sublink".into()).expect("lookup subdir/sublink");
327            assert!(sublink.info().mode.is_lnk());
328
329            // Verify that removing an entry works.
330            mutator.remove("link".into());
331            let result = FsNodeOps::lookup(&dir, &node, &current_task, "link".into());
332            assert_eq!(result.unwrap_err(), errno!(ENOENT));
333        })
334        .await;
335    }
336}