1use 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_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
22pub struct SimpleDirectoryMutator {
24 fs: FileSystemHandle,
25 pub directory: Arc<SimpleDirectory>,
26}
27
28impl SimpleDirectoryMutator {
29 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 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
82pub struct SimpleDirectory {
87 entries: Mutex<BTreeMap<FsString, FsNodeHandle>>,
88 not_found_handler:
89 Box<dyn Fn(&FsStr, &BTreeMap<FsString, FsNodeHandle>) -> Errno + Send + Sync + 'static>,
90}
91
92impl SimpleDirectory {
93 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 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 _locked: &mut Locked<FileOpsCore>,
208 _node: &FsNode,
209 _current_task: &CurrentTask,
210 _flags: OpenFlags,
211 ) -> Result<Box<dyn FileOps>, Errno> {
212 Ok(Box::new(self.clone()))
213 }
214
215 fn lookup(
216 &self,
217 _locked: &mut Locked<FileOpsCore>,
218 _node: &FsNode,
219 _current_task: &CurrentTask,
220 name: &FsStr,
221 ) -> Result<FsNodeHandle, Errno> {
222 let entries = self.entries.lock();
223 entries.get(name).cloned().ok_or_else(|| (self.not_found_handler)(name, &entries))
224 }
225}
226
227impl CloseFreeSafe for SimpleDirectory {}
229impl FileOps for SimpleDirectory {
230 fileops_impl_directory!();
231 fileops_impl_noop_sync!();
232 fileops_impl_unbounded_seek!();
233
234 fn readdir(
235 &self,
236 _locked: &mut Locked<FileOpsCore>,
237 file: &FileObject,
238 _current_task: &CurrentTask,
239 sink: &mut dyn DirentSink,
240 ) -> Result<(), Errno> {
241 emit_dotdot(file, sink)?;
242
243 let entries = self.entries.lock();
246 for (name, node) in entries.iter().skip(sink.offset() as usize - 2) {
247 sink.add(
248 node.ino,
249 sink.offset() + 1,
250 DirectoryEntryType::from_mode(node.info().mode),
251 name.as_ref(),
252 )?;
253 }
254 Ok(())
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::testing::spawn_kernel_and_run;
262 use crate::vfs::FsNodeOps;
263 use starnix_uapi::errno;
264
265 #[fuchsia::test]
266 async fn test_default_not_found_handler() {
267 spawn_kernel_and_run(async |locked, current_task| {
268 let dir = SimpleDirectory::new();
269 let node = dir.clone().into_node(¤t_task.fs().root().entry.node.fs(), 0o777);
270 let mut locked = locked.cast_locked();
271 let result =
272 FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "nonexistent".into());
273 assert_eq!(result.unwrap_err(), errno!(ENOENT));
274 })
275 .await;
276 }
277
278 #[fuchsia::test]
279 async fn test_custom_not_found_handler() {
280 spawn_kernel_and_run(async |locked, current_task| {
281 let dir = SimpleDirectory::new_with_handler(|name, _entries| {
282 if name == "special" { errno!(EACCES) } else { errno!(ENOENT) }
283 });
284 let node = dir.clone().into_node(¤t_task.fs().root().entry.node.fs(), 0o777);
285
286 let mut locked = locked.cast_locked::<FileOpsCore>();
287 let result_special =
288 FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "special".into());
289 assert_eq!(result_special.unwrap_err(), errno!(EACCES));
290
291 let result_other =
292 FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "other".into());
293 assert_eq!(result_other.unwrap_err(), errno!(ENOENT));
294 })
295 .await;
296 }
297
298 #[fuchsia::test]
299 async fn test_simple_directory_lookups() {
300 spawn_kernel_and_run(async |locked, current_task| {
301 let fs = current_task.fs().root().entry.node.fs();
302 let dir = SimpleDirectory::new();
303 let mutator = SimpleDirectoryMutator::new(fs.clone(), dir.clone());
304
305 mutator.symlink("link".into(), "target".into());
307
308 mutator.subdir("subdir", 0o755, |sub_mutator| {
310 sub_mutator.symlink("sublink".into(), "subtarget".into());
311 });
312
313 let node = dir.clone().into_node(&fs, 0o777);
314 let mut locked = locked.cast_locked::<FileOpsCore>();
315
316 let node1 = FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "link".into())
318 .expect("lookup link");
319 let node2 = FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "link".into())
320 .expect("lookup link again");
321
322 assert!(Arc::ptr_eq(&node1, &node2));
323 assert!(node1.info().mode.is_lnk());
324
325 let subdir1 =
327 FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "subdir".into())
328 .expect("lookup subdir");
329 let subdir2 =
330 FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "subdir".into())
331 .expect("lookup subdir again");
332
333 assert!(Arc::ptr_eq(&subdir1, &subdir2));
334 assert!(subdir1.info().mode.is_dir());
335
336 let sublink = dir.lookup("subdir/sublink".into()).expect("lookup subdir/sublink");
338 assert!(sublink.info().mode.is_lnk());
339
340 mutator.remove("link".into());
342 let result = FsNodeOps::lookup(&dir, &mut locked, &node, ¤t_task, "link".into());
343 assert_eq!(result.unwrap_err(), errno!(ENOENT));
344 })
345 .await;
346 }
347}