1use crate::task::CurrentTask;
6use crate::vfs::{
7 CloseFreeSafe, DirEntry, DirectoryEntryType, DirentSink, FileObject, FileOps, FileSystemHandle,
8 FsNode, 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, allow_subclass};
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 let mut state = self.directory.state.lock();
36 let child_dir_and_handler = state.not_found_handler.and_then(|handler| {
37 node.downcast_ops::<Arc<SimpleDirectory>>().cloned().map(|dir| (dir, handler))
38 });
39 state.entries.insert(name, node);
40 std::mem::drop(state);
43 if let Some((child_dir, handler)) = child_dir_and_handler {
44 child_dir.set_not_found_handler(handler);
45 }
46 }
47
48 pub fn entry(&self, name: &str, ops: impl Into<Box<dyn FsNodeOps>>, mode: FileMode) {
49 let name: FsString = name.into();
50 let node =
51 self.fs.create_node_and_allocate_node_id(ops, FsNodeInfo::new(mode, FsCred::root()));
52 self.node(name, node);
53 }
54
55 pub fn entry_etc(
56 &self,
57 name: FsString,
58 ops: impl Into<Box<dyn FsNodeOps>>,
59 mode: FileMode,
60 dev: DeviceId,
61 creds: FsCred,
62 ) {
63 let mut info = FsNodeInfo::new(mode, creds);
64 info.rdev = dev;
65 let node = self.fs.create_node_and_allocate_node_id(ops, info);
66 self.node(name, node);
67 }
68
69 pub fn symlink(&self, name: &FsStr, target: &FsStr) {
70 let (ops, info) = SymlinkNode::new(target, FsCred::root());
71 let node = self.fs.create_node_and_allocate_node_id(ops, info);
72 self.node(name.into(), node);
73 }
74
75 pub fn subdir(&self, name: &str, mode: u32, build_subdir: impl FnOnce(&Self)) {
76 let name: &FsStr = name.into();
77 self.subdir2(name, mode, build_subdir);
78 }
79
80 pub fn subdir2(&self, name: &FsStr, mode: u32, build_subdir: impl FnOnce(&Self)) {
82 let dir = self.directory.subdir(&self.fs, name, mode);
83 let mutator = SimpleDirectoryMutator::new(self.fs.clone(), dir);
84 build_subdir(&mutator);
85 }
86
87 pub fn remove(&self, name: &FsStr) {
88 self.directory.remove(name);
89 }
90}
91
92pub(crate) type NotFoundHandler = fn(&DirEntry, &FsStr) -> Errno;
101
102struct SimpleDirectoryState {
103 entries: BTreeMap<FsString, FsNodeHandle>,
104 not_found_handler: Option<NotFoundHandler>,
105}
106
107fn default_not_found_handler(entry: &DirEntry, name: &FsStr) -> Errno {
110 errno!(ENOENT, format!("Looking for {name} in {entry:?}"))
111}
112
113pub struct SimpleDirectory {
118 state: LockDepMutex<SimpleDirectoryState, SimpleDirectoryEntriesLock>,
119}
120
121impl SimpleDirectory {
122 pub fn new() -> Arc<Self> {
125 Arc::new(SimpleDirectory {
126 state: LockDepMutex::new(SimpleDirectoryState {
127 entries: Default::default(),
128 not_found_handler: None,
129 }),
130 })
131 }
132
133 pub(crate) fn set_not_found_handler(&self, not_found_handler: NotFoundHandler) {
136 let child_dirs: Vec<Arc<SimpleDirectory>> = {
139 let mut state = self.state.lock();
140 state.not_found_handler = Some(not_found_handler);
141 state
142 .entries
143 .values()
144 .filter_map(|node| node.downcast_ops::<Arc<SimpleDirectory>>().cloned())
145 .collect()
146 };
147 for child_dir in child_dirs {
148 child_dir.set_not_found_handler(not_found_handler);
149 }
150 }
151
152 pub fn remove(&self, name: &FsStr) {
153 self.state.lock().entries.remove(name);
154 }
155
156 fn walk<'a>(self: &Arc<Self>, path: &'a FsStr) -> Option<(Arc<Self>, &'a FsStr)> {
157 fn check_component(component: &FsStr) {
158 assert!(!component.is_empty());
159
160 let dot: &FsStr = b".".into();
161 assert_ne!(component, dot);
162
163 let dotdot: &FsStr = b"..".into();
164 assert_ne!(component, dotdot);
165 }
166
167 let mut components = path.split(|c| *c == b'/');
168 let basename = components.next_back()?;
169 let basename: &FsStr = basename.into();
170 check_component(basename);
171 let mut parent = self.clone();
172 while let Some(component) = components.next() {
173 let component: &FsStr = component.into();
174 check_component(component);
175 let Some(next) = parent.get_dir(component) else {
176 return None;
177 };
178 parent = next;
179 }
180 Some((parent, basename))
181 }
182
183 pub fn edit(
184 self: &Arc<Self>,
185 fs: &FileSystemHandle,
186 callback: impl FnOnce(&SimpleDirectoryMutator),
187 ) {
188 let mutator = SimpleDirectoryMutator::new(fs.clone(), self.clone());
189 callback(&mutator);
190 }
191
192 fn get_or_create_subdir_locked(
193 state: &mut SimpleDirectoryState,
194 fs: &FileSystemHandle,
195 name: &FsStr,
196 mode: u32,
197 ) -> Arc<SimpleDirectory> {
198 if let Some(node) = state.entries.get(name) {
199 assert!(node.info().mode == mode!(IFDIR, mode));
200 let dir =
201 node.downcast_ops::<Arc<SimpleDirectory>>().expect("subdir is a SimpleDirectory");
202 dir.clone()
203 } else {
204 let dir = Arc::new(SimpleDirectory {
205 state: LockDepMutex::new(SimpleDirectoryState {
206 entries: Default::default(),
207 not_found_handler: state.not_found_handler,
208 }),
209 });
210 let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
211 let node = fs.create_node_and_allocate_node_id(dir.clone(), info);
212 state.entries.insert(name.into(), node);
213 dir
214 }
215 }
216
217 pub fn subdir(&self, fs: &FileSystemHandle, name: &FsStr, mode: u32) -> Arc<SimpleDirectory> {
218 let mut state = self.state.lock();
219 Self::get_or_create_subdir_locked(&mut state, fs, name, mode)
220 }
221
222 pub fn nested_subdir(
226 &self,
227 fs: &FileSystemHandle,
228 subdir_name: &FsStr,
229 subdir_mode: u32,
230 child_name: &FsStr,
231 child_mode: u32,
232 ) -> Arc<SimpleDirectory> {
233 let mut state = self.state.lock();
234 let subdir = Self::get_or_create_subdir_locked(&mut state, fs, subdir_name, subdir_mode);
235 let _token = allow_subclass();
237 subdir.subdir(fs, child_name, child_mode)
238 }
239
240 pub fn remove_from_subdir_if_empty(&self, subdir_name: &FsStr, child_name: &FsStr) {
243 let mut state = self.state.lock();
244 let Some(subdir) = state
245 .entries
246 .get(subdir_name)
247 .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
248 .map(Arc::clone)
249 else {
250 return;
251 };
252 let is_empty = {
253 let _token = allow_subclass();
255 let mut subdir_state = subdir.state.lock();
256 subdir_state.entries.remove(child_name);
257 subdir_state.entries.is_empty()
258 };
259 if is_empty {
260 state.entries.remove(subdir_name);
261 }
262 }
263
264 fn get(&self, name: &FsStr) -> Option<FsNodeHandle> {
265 let state = self.state.lock();
266 state.entries.get(name).cloned()
267 }
268
269 pub fn get_dir(&self, name: &FsStr) -> Option<Arc<SimpleDirectory>> {
270 let state = self.state.lock();
271 state
272 .entries
273 .get(name)
274 .and_then(|node| node.downcast_ops::<Arc<SimpleDirectory>>())
275 .map(Arc::clone)
276 }
277
278 pub fn lookup(self: &Arc<Self>, path: &FsStr) -> Option<FsNodeHandle> {
279 let (parent, basename) = self.walk(path)?;
280 parent.get(basename)
281 }
282
283 pub fn into_node(self: Arc<Self>, fs: &FileSystemHandle, mode: u32) -> FsNodeHandle {
284 let info = FsNodeInfo::new(mode!(IFDIR, mode), FsCred::root());
285 fs.create_node_and_allocate_node_id(self, info)
286 }
287}
288
289impl FsNodeOps for Arc<SimpleDirectory> {
290 fs_node_impl_dir_readonly!();
291
292 fn create_file_ops(
293 &self,
294 _node: &FsNode,
295 _current_task: &CurrentTask,
296 _flags: OpenFlags,
297 ) -> Result<Box<dyn FileOps>, Errno> {
298 Ok(Box::new(self.clone()))
299 }
300
301 fn lookup(
302 &self,
303 entry: &DirEntry,
304 _current_task: &CurrentTask,
305 name: &FsStr,
306 ) -> Result<FsNodeHandle, Errno> {
307 let not_found_handler = {
308 let state = self.state.lock();
309 if let Some(node) = state.entries.get(name) {
310 return Ok(node.clone());
311 }
312 state.not_found_handler.unwrap_or(default_not_found_handler)
313 };
314 Err(not_found_handler(entry, name))
317 }
318}
319
320impl CloseFreeSafe for SimpleDirectory {}
322impl FileOps for SimpleDirectory {
323 fileops_impl_directory!();
324 fileops_impl_noop_sync!();
325 fileops_impl_unbounded_seek!();
326
327 fn readdir(
328 &self,
329 file: &FileObject,
330 _current_task: &CurrentTask,
331 sink: &mut dyn DirentSink,
332 ) -> Result<(), Errno> {
333 emit_dotdot(file, sink)?;
334
335 let state = self.state.lock();
338 for (name, node) in state.entries.iter().skip(sink.offset() as usize - 2) {
339 let mode = node.info().mode;
340 sink.add(
341 node.ino,
342 sink.offset() + 1,
343 DirectoryEntryType::from_mode(mode),
344 name.as_ref(),
345 )?;
346 }
347 Ok(())
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use crate::testing::spawn_kernel_and_run;
355 use crate::vfs::{DirEntryHandle, FsNodeOps, MountInfo};
356 use starnix_uapi::errno;
357
358 #[fuchsia::test]
359 async fn test_default_not_found_handler() {
360 spawn_kernel_and_run(async |current_task| {
361 let dir = SimpleDirectory::new();
362 let node = dir.clone().into_node(¤t_task.fs().root().entry.node.fs(), 0o777);
363 let entry = DirEntry::new_unrooted(node);
364 let result = FsNodeOps::lookup(&dir, &entry, ¤t_task, "nonexistent".into());
365 assert_eq!(result.unwrap_err(), errno!(ENOENT));
366 })
367 .await;
368 }
369
370 #[fuchsia::test]
371 async fn test_set_not_found_handler() {
372 #[track_caller]
373 fn check_lookup(
374 current_task: &CurrentTask,
375 root: &DirEntryHandle,
376 path: &str,
377 expected_error: Errno,
378 ) {
379 let mount = MountInfo::detached();
380 let mut dir = root.clone();
381 for component in path.split('/').filter(|s| !s.is_empty()) {
382 dir = dir.component_lookup(current_task, &mount, component.into()).unwrap();
383 }
384 let res = dir.component_lookup(current_task, &mount, "expected_error".into());
385 assert_eq!(res.unwrap_err(), expected_error);
386 }
387
388 spawn_kernel_and_run(async |current_task| {
389 let fs = current_task.fs().root().entry.node.fs();
390 let root_dir = SimpleDirectory::new();
391 root_dir.subdir(&fs, "existing".into(), 0o755).subdir(&fs, "nested".into(), 0o755);
392
393 root_dir.set_not_found_handler(|entry, name| {
394 if name == "expected_error" && entry.parent().is_some() {
395 errno!(EACCES)
396 } else {
397 errno!(ENOENT)
398 }
399 });
400
401 root_dir.subdir(&fs, "future".into(), 0o755);
402
403 let attached = SimpleDirectory::new();
404 attached.subdir(&fs, "nested".into(), 0o755);
405 root_dir.edit(&fs, |dir| {
406 dir.entry("attached", attached, mode!(IFDIR, 0o755));
407 });
408
409 let root = DirEntry::new_unrooted(root_dir.into_node(&fs, 0o755));
410 check_lookup(¤t_task, &root, "", errno!(ENOENT));
411 for path in ["existing", "existing/nested", "future", "attached", "attached/nested"] {
412 check_lookup(¤t_task, &root, path, errno!(EACCES));
413 }
414 })
415 .await;
416 }
417
418 #[fuchsia::test]
419 async fn test_simple_directory_lookups() {
420 spawn_kernel_and_run(async |current_task| {
421 let fs = current_task.fs().root().entry.node.fs();
422 let dir = SimpleDirectory::new();
423 let mutator = SimpleDirectoryMutator::new(fs.clone(), dir.clone());
424
425 mutator.symlink("link".into(), "target".into());
427
428 mutator.subdir("subdir", 0o755, |sub_mutator| {
430 sub_mutator.symlink("sublink".into(), "subtarget".into());
431 });
432
433 let node = dir.clone().into_node(&fs, 0o777);
434 let entry = DirEntry::new_unrooted(node);
435
436 let node1 =
438 FsNodeOps::lookup(&dir, &entry, ¤t_task, "link".into()).expect("lookup link");
439 let node2 = FsNodeOps::lookup(&dir, &entry, ¤t_task, "link".into())
440 .expect("lookup link again");
441
442 assert!(Arc::ptr_eq(&node1, &node2));
443 assert!(node1.info().mode.is_lnk());
444
445 let subdir1 = FsNodeOps::lookup(&dir, &entry, ¤t_task, "subdir".into())
447 .expect("lookup subdir");
448 let subdir2 = FsNodeOps::lookup(&dir, &entry, ¤t_task, "subdir".into())
449 .expect("lookup subdir again");
450
451 assert!(Arc::ptr_eq(&subdir1, &subdir2));
452 assert!(subdir1.info().mode.is_dir());
453
454 let sublink = dir.lookup("subdir/sublink".into()).expect("lookup subdir/sublink");
456 assert!(sublink.info().mode.is_lnk());
457
458 mutator.remove("link".into());
460 let result = FsNodeOps::lookup(&dir, &entry, ¤t_task, "link".into());
461 assert_eq!(result.unwrap_err(), errno!(ENOENT));
462 })
463 .await;
464 }
465}