| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{ |
| task::CurrentTask, |
| vfs::{ |
| default_seek, fileops_impl_directory, DirectoryEntryType, DirentSink, FileObject, FileOps, |
| FsString, SeekTarget, |
| }, |
| }; |
| use starnix_sync::Mutex; |
| use starnix_uapi::{error, errors::Errno, off_t}; |
| use std::ops::Bound; |
| |
| pub struct MemoryDirectoryFile { |
| /// The current position for readdir. |
| /// |
| /// When readdir is called multiple times, we need to return subsequent |
| /// directory entries. This field records where the previous readdir |
| /// stopped. |
| /// |
| /// The state is actually recorded twice: once in the offset for this |
| /// FileObject and again here. Recovering the state from the offset is slow |
| /// because we would need to iterate through the keys of the BTree. Having |
| /// the FsString cached lets us search the keys of the BTree faster. |
| /// |
| /// The initial "." and ".." entries are not recorded here. They are |
| /// represented only in the offset field in the FileObject. |
| readdir_position: Mutex<Bound<FsString>>, |
| } |
| |
| impl MemoryDirectoryFile { |
| pub fn new() -> MemoryDirectoryFile { |
| MemoryDirectoryFile { readdir_position: Mutex::new(Bound::Unbounded) } |
| } |
| } |
| |
| /// If the offset is less than 2, emits . and .. entries for the specified file. |
| /// |
| /// The offset will always be at least 2 after this function returns successfully. It's often |
| /// necessary to subtract 2 from the offset in subsequent logic. |
| pub fn emit_dotdot(file: &FileObject, sink: &mut dyn DirentSink) -> Result<(), Errno> { |
| if sink.offset() == 0 { |
| sink.add(file.node().node_id, 1, DirectoryEntryType::DIR, ".".into())?; |
| } |
| if sink.offset() == 1 { |
| sink.add( |
| file.name.entry.parent_or_self().node.node_id, |
| 2, |
| DirectoryEntryType::DIR, |
| "..".into(), |
| )?; |
| } |
| Ok(()) |
| } |
| |
| impl FileOps for MemoryDirectoryFile { |
| fileops_impl_directory!(); |
| |
| fn seek( |
| &self, |
| file: &FileObject, |
| _current_task: &CurrentTask, |
| current_offset: off_t, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno> { |
| let new_offset = default_seek(current_offset, target, |_| error!(EINVAL))?; |
| // Nothing to do. |
| if current_offset == new_offset { |
| return Ok(new_offset); |
| } |
| |
| let mut readdir_position = self.readdir_position.lock(); |
| |
| // We use 0 and 1 for "." and ".." |
| if new_offset <= 2 { |
| *readdir_position = Bound::Unbounded; |
| } else { |
| file.name.entry.get_children(|children| { |
| let count = (new_offset - 2) as usize; |
| *readdir_position = children |
| .iter() |
| .take(count) |
| .last() |
| .map_or(Bound::Unbounded, |(name, _)| Bound::Excluded(name.clone())); |
| }); |
| } |
| |
| Ok(new_offset) |
| } |
| |
| fn readdir( |
| &self, |
| file: &FileObject, |
| _current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| emit_dotdot(file, sink)?; |
| |
| let mut readdir_position = self.readdir_position.lock(); |
| file.name.entry.get_children(|children| { |
| for (name, maybe_entry) in children.range((readdir_position.clone(), Bound::Unbounded)) |
| { |
| if let Some(entry) = maybe_entry.upgrade() { |
| let mode = entry.node.info().mode; |
| sink.add( |
| entry.node.node_id, |
| sink.offset() + 1, |
| DirectoryEntryType::from_mode(mode), |
| name.as_ref(), |
| )?; |
| *readdir_position = Bound::Excluded(name.clone()); |
| } |
| } |
| Ok(()) |
| }) |
| } |
| } |