Skip to main content

starnix_core/vfs/
memory_directory.rs

1// Copyright 2021 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    DirectoryEntryType, DirentSink, FileObject, FileOps, FsString, SeekTarget, default_seek,
8    fileops_impl_directory, fileops_impl_noop_sync,
9};
10use starnix_sync::{LockDepMutex, MemoryDirectoryReaddirPositionLock};
11use starnix_uapi::errors::Errno;
12use starnix_uapi::{error, off_t};
13use std::ops::Bound;
14
15pub struct MemoryDirectoryFile {
16    /// The current position for readdir.
17    ///
18    /// When readdir is called multiple times, we need to return subsequent
19    /// directory entries. This field records where the previous readdir
20    /// stopped.
21    ///
22    /// The state is actually recorded twice: once in the offset for this
23    /// FileObject and again here. Recovering the state from the offset is slow
24    /// because we would need to iterate through the keys of the BTree. Having
25    /// the FsString cached lets us search the keys of the BTree faster.
26    ///
27    /// The initial "." and ".." entries are not recorded here. They are
28    /// represented only in the offset field in the FileObject.
29    readdir_position: LockDepMutex<Bound<FsString>, MemoryDirectoryReaddirPositionLock>,
30}
31
32impl MemoryDirectoryFile {
33    pub fn new() -> MemoryDirectoryFile {
34        MemoryDirectoryFile { readdir_position: Bound::Unbounded.into() }
35    }
36}
37
38/// If the offset is less than 2, emits . and .. entries for the specified file.
39///
40/// The offset will always be at least 2 after this function returns successfully. It's often
41/// necessary to subtract 2 from the offset in subsequent logic.
42pub fn emit_dotdot(file: &FileObject, sink: &mut dyn DirentSink) -> Result<(), Errno> {
43    if sink.offset() == 0 {
44        sink.add(file.node().ino, 1, DirectoryEntryType::DIR, ".".into())?;
45    }
46    if sink.offset() == 1 {
47        sink.add(
48            file.name.entry.parent_or_self().node.ino,
49            2,
50            DirectoryEntryType::DIR,
51            "..".into(),
52        )?;
53    }
54    Ok(())
55}
56
57impl FileOps for MemoryDirectoryFile {
58    fileops_impl_directory!();
59    fileops_impl_noop_sync!();
60
61    fn seek(
62        &self,
63        file: &FileObject,
64        _current_task: &CurrentTask,
65        current_offset: off_t,
66        target: SeekTarget,
67    ) -> Result<off_t, Errno> {
68        let new_offset = default_seek(current_offset, target, || error!(EINVAL))?;
69        // Nothing to do.
70        if current_offset == new_offset {
71            return Ok(new_offset);
72        }
73
74        let mut readdir_position = self.readdir_position.lock();
75
76        // We use 0 and 1 for "." and ".."
77        if new_offset <= 2 {
78            *readdir_position = Bound::Unbounded;
79        } else {
80            file.name.entry.get_children(|children| {
81                let count = (new_offset - 2) as usize;
82                *readdir_position = children
83                    .iter()
84                    .take(count)
85                    .next_back()
86                    .map_or(Bound::Unbounded, |(name, _)| Bound::Excluded(name.clone()));
87            });
88        }
89
90        Ok(new_offset)
91    }
92
93    fn readdir(
94        &self,
95        file: &FileObject,
96        _current_task: &CurrentTask,
97        sink: &mut dyn DirentSink,
98    ) -> Result<(), Errno> {
99        emit_dotdot(file, sink)?;
100
101        let mut readdir_position = self.readdir_position.lock();
102        file.name.entry.get_children(|children| {
103            for (name, maybe_entry) in children.range((readdir_position.clone(), Bound::Unbounded))
104            {
105                if let Some(entry) = maybe_entry.upgrade() {
106                    sink.add(
107                        entry.node.ino,
108                        sink.offset() + 1,
109                        DirectoryEntryType::from_mode(entry.node.info().mode),
110                        name.as_ref(),
111                    )?;
112                    *readdir_position = Bound::Excluded(name.clone());
113                }
114            }
115            Ok(())
116        })
117    }
118}