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