1use 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
23pub struct SimpleDirectoryMutator {
25 fs: FileSystemHandle,
26 pub directory: Arc<SimpleDirectory>,
27}
28
29impl SimpleDirectoryMutator {
30 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 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 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
93pub(crate) type NotFoundHandler = fn(&DirEntry, &FsStr) -> Errno;
102
103struct SimpleDirectoryState {
104 entries: BTreeMap<FsString, FsNodeHandle>,
105 not_found_handler: Option<NotFoundHandler>,
106}
107
108fn default_not_found_handler(entry: &DirEntry, name: &FsStr) -> Errno {
111 errno!(ENOENT, format!("Looking for {name} in {entry:?}"))
112}
113
114pub struct SimpleDirectory {
119 state: LockDepMutex<SimpleDirectoryState, SimpleDirectoryEntriesLock>,
120}
121
122impl SimpleDirectory {
123 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 pub(crate) fn set_not_found_handler(&self, not_found_handler: NotFoundHandler) {
137 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 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 let _token = allow_subclass();
238 subdir.subdir(fs, child_name, child_mode)
239 }
240
241 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 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 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 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
342impl 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 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(¤t_task.fs().root().entry.node.fs(), 0o777);
385 let entry = DirEntry::new_unrooted(node);
386 let result = FsNodeOps::lookup(&dir, &entry, ¤t_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(¤t_task, &root, "", errno!(ENOENT));
433 for path in ["existing", "existing/nested", "future", "attached", "attached/nested"] {
434 check_lookup(¤t_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 mutator.symlink("link".into(), "target".into());
449
450 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 let node1 =
460 FsNodeOps::lookup(&dir, &entry, ¤t_task, "link".into()).expect("lookup link");
461 let node2 = FsNodeOps::lookup(&dir, &entry, ¤t_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 let subdir1 = FsNodeOps::lookup(&dir, &entry, ¤t_task, "subdir".into())
469 .expect("lookup subdir");
470 let subdir2 = FsNodeOps::lookup(&dir, &entry, ¤t_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 let sublink = dir.lookup("subdir/sublink".into()).expect("lookup subdir/sublink");
478 assert!(sublink.info().mode.is_lnk());
479
480 let mount = MountInfo::detached();
482 let cached_link = entry
483 .component_lookup(¤t_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(¤t_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, ¤t_task, "link".into());
493 assert_eq!(result.unwrap_err(), errno!(ENOENT));
494 assert_eq!(
495 entry.component_lookup(¤t_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(¤t_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(¤t_task, &mount, "subdir".into())
506 .expect("component_lookup subdir again");
507 assert!(Arc::ptr_eq(&cached_subdir, &cached_subdir_again));
508
509 mutator.remove("subdir".into());
513 mutator.subdir("subdir", 0o755, |_| {});
514 let replaced_subdir = entry
515 .component_lookup(¤t_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 mutator.remove("subdir".into());
523 assert_eq!(
524 entry.component_lookup(¤t_task, &mount, "subdir".into()).unwrap_err(),
525 errno!(ENOENT)
526 );
527 assert!(replaced_subdir.is_dead());
528 })
529 .await;
530 }
531}