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::{FileOpsCore, Locked, Mutex};
13use starnix_uapi::auth::FsCred;
14use starnix_uapi::device_type::DeviceType;
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
22pub struct SimpleDirectoryMutator {
23    fs: FileSystemHandle,
24    pub directory: Arc<SimpleDirectory>,
25}
26
27impl SimpleDirectoryMutator {
28    pub fn new(fs: FileSystemHandle, directory: Arc<SimpleDirectory>) -> Self {
29        Self { fs, directory }
30    }
31
32    pub fn node(&self, name: FsString, node: FsNodeHandle) {
33        self.directory.entries.lock().insert(name, node);
34    }
35
36    pub fn entry(&self, name: &str, ops: impl Into<Box<dyn FsNodeOps>>, mode: FileMode) {
37        let name: FsString = name.into();
38        let node =
39            self.fs.create_node_and_allocate_node_id(ops, FsNodeInfo::new(mode, FsCred::root()));
40        self.node(name, node);
41    }
42
43    pub fn entry_etc(
44        &self,
45        name: FsString,
46        ops: impl Into<Box<dyn FsNodeOps>>,
47        mode: FileMode,
48        dev: DeviceType,
49        creds: FsCred,
50    ) {
51        let mut info = FsNodeInfo::new(mode, creds);
52        info.rdev = dev;
53        let node = self.fs.create_node_and_allocate_node_id(ops, info);
54        self.node(name, node);
55    }
56
57    pub fn symlink(&self, name: &FsStr, target: &FsStr) {
58        let (ops, info) = SymlinkNode::new(target, FsCred::root());
59        let node = self.fs.create_node_and_allocate_node_id(ops, info);
60        self.node(name.into(), node);
61    }
62
63    pub fn subdir(&self, name: &str, mode: u32, build_subdir: impl FnOnce(&Self)) {
64        let name: &FsStr = name.into();
65        self.subdir2(name, mode, build_subdir);
66    }
67
68    // TODO: Figure out a better way to overload this function for &str and &FsStr.
69    pub fn subdir2(&self, name: &FsStr, mode: u32, build_subdir: impl FnOnce(&Self)) {
70        let dir = self.directory.subdir(&self.fs, name, mode);
71        let mutator = SimpleDirectoryMutator::new(self.fs.clone(), dir);
72        build_subdir(&mutator);
73    }
74
75    pub fn remove(&self, name: &FsStr) {
76        self.directory.remove(name);
77    }
78}
79
80pub struct SimpleDirectory {
81    entries: Mutex<BTreeMap<FsString, FsNodeHandle>>,
82}
83
84impl SimpleDirectory {
85    pub fn new() -> Arc<Self> {
86        Arc::new(SimpleDirectory { entries: Default::default() })
87    }
88
89    pub fn remove(&self, name: &FsStr) {
90        self.entries.lock().remove(name);
91    }
92
93    fn walk<'a>(self: &Arc<Self>, path: &'a FsStr) -> Option<(Arc<Self>, &'a FsStr)> {
94        fn check_component(component: &FsStr) {
95            assert!(!component.is_empty());
96
97            let dot: &FsStr = b".".into();
98            assert_ne!(component, dot);
99
100            let dotdot: &FsStr = b"..".into();
101            assert_ne!(component, dotdot);
102        }
103
104        let mut components = path.split(|c| *c == b'/');
105        let basename = components.next_back()?;
106        let basename: &FsStr = basename.into();
107        check_component(basename);
108        let mut parent = self.clone();
109        while let Some(component) = components.next() {
110            let component: &FsStr = component.into();
111            check_component(component);
112            let Some(next) = parent.get_dir(component) else {
113                return None;
114            };
115            parent = next;
116        }
117        Some((parent, basename))
118    }
119
120    pub fn edit(
121        self: &Arc<Self>,
122        fs: &FileSystemHandle,
123        callback: impl FnOnce(&SimpleDirectoryMutator),
124    ) {
125        let mutator = SimpleDirectoryMutator::new(fs.clone(), self.clone());
126        callback(&mutator);
127    }
128
129    pub fn subdir(&self, fs: &FileSystemHandle, name: &FsStr, mode: u32) -> Arc<SimpleDirectory> {
130        let mut entries = self.entries.lock();
131        if let Some(node) = entries.get(name) {
132            assert!(node.info().mode == mode!(IFDIR, mode));
133            let dir =
134                node.downcast_ops::<Arc<SimpleDirectory>>().expect("subdir is a SimpleDirectory");
135            dir.clone()
136        } else {
137            let dir = SimpleDirectory::new();
138            let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
139            let node = fs.create_node_and_allocate_node_id(dir.clone(), info);
140            entries.insert(name.into(), node);
141            dir
142        }
143    }
144
145    fn get(&self, name: &FsStr) -> Option<FsNodeHandle> {
146        let entries = self.entries.lock();
147        entries.get(name).cloned()
148    }
149
150    fn get_dir(&self, name: &FsStr) -> Option<Arc<SimpleDirectory>> {
151        let entries = self.entries.lock();
152        entries
153            .get(name)
154            .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
155            .map(Arc::clone)
156    }
157
158    pub fn lookup(self: &Arc<Self>, path: &FsStr) -> Option<FsNodeHandle> {
159        let (parent, basename) = self.walk(path)?;
160        parent.get(basename)
161    }
162
163    pub fn into_node(self: Arc<Self>, fs: &FileSystemHandle, mode: u32) -> FsNodeHandle {
164        let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
165        fs.create_node_and_allocate_node_id(self, info)
166    }
167}
168
169impl FsNodeOps for Arc<SimpleDirectory> {
170    fs_node_impl_dir_readonly!();
171
172    fn create_file_ops(
173        &self,
174        _locked: &mut Locked<FileOpsCore>,
175        _node: &FsNode,
176        _current_task: &CurrentTask,
177        _flags: OpenFlags,
178    ) -> Result<Box<dyn FileOps>, Errno> {
179        Ok(Box::new(self.clone()))
180    }
181
182    fn lookup(
183        &self,
184        _locked: &mut Locked<FileOpsCore>,
185        _node: &FsNode,
186        _current_task: &CurrentTask,
187        name: &FsStr,
188    ) -> Result<FsNodeHandle, Errno> {
189        let entries = self.entries.lock();
190        entries.get(name).cloned().ok_or_else(|| {
191            errno!(
192                ENOENT,
193                format!(
194                    "looking for {name} in {:?}",
195                    entries.keys().map(|e| e.to_string()).collect::<Vec<_>>()
196                )
197            )
198        })
199    }
200}
201
202/// `SimpleDirectory` doesn't implement the `close` method.
203impl CloseFreeSafe for SimpleDirectory {}
204impl FileOps for SimpleDirectory {
205    fileops_impl_directory!();
206    fileops_impl_noop_sync!();
207    fileops_impl_unbounded_seek!();
208
209    fn readdir(
210        &self,
211        _locked: &mut Locked<FileOpsCore>,
212        file: &FileObject,
213        _current_task: &CurrentTask,
214        sink: &mut dyn DirentSink,
215    ) -> Result<(), Errno> {
216        emit_dotdot(file, sink)?;
217
218        // Skip through the entries until the current offset is reached.
219        // Subtract 2 from the offset to account for `.` and `..`.
220        let entries = self.entries.lock();
221        for (name, node) in entries.iter().skip(sink.offset() as usize - 2) {
222            sink.add(
223                node.ino,
224                sink.offset() + 1,
225                DirectoryEntryType::from_mode(node.info().mode),
226                name.as_ref(),
227            )?;
228        }
229        Ok(())
230    }
231}