use crate::Ext4;
use crate::block_index::FsBlockIndex;
use crate::checksum::Checksum;
use crate::dir_block::DirBlock;
use crate::dir_entry::DirEntry;
use crate::error::{CorruptKind, Ext4Error};
use crate::inode::{Inode, InodeFlags, InodeIndex};
use crate::iters::file_blocks::FileBlocks;
use crate::path::PathBuf;
use alloc::rc::Rc;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::{self, Debug, Formatter};
pub struct ReadDir {
fs: Ext4,
path: Rc<PathBuf>,
file_blocks: FileBlocks,
block_index: Option<FsBlockIndex>,
is_first_block: bool,
block: Vec<u8>,
offset_within_block: usize,
is_done: bool,
has_htree: bool,
checksum_base: Checksum,
inode: InodeIndex,
}
impl ReadDir {
pub(crate) fn new(
fs: Ext4,
inode: &Inode,
path: PathBuf,
) -> Result<Self, Ext4Error> {
let has_htree = inode.flags.contains(InodeFlags::DIRECTORY_HTREE);
if inode.flags.contains(InodeFlags::DIRECTORY_ENCRYPTED) {
return Err(Ext4Error::Encrypted);
}
Ok(Self {
fs: fs.clone(),
path: Rc::new(path),
file_blocks: FileBlocks::new(fs.clone(), inode)?,
block_index: None,
is_first_block: true,
block: vec![0; fs.0.superblock.block_size.to_usize()],
offset_within_block: 0,
is_done: false,
has_htree,
checksum_base: inode.checksum_base.clone(),
inode: inode.index,
})
}
fn next_impl(&mut self) -> Result<Option<DirEntry>, Ext4Error> {
let block_index = if let Some(block_index) = self.block_index {
block_index
} else {
match self.file_blocks.next() {
Some(Ok(block_index)) => {
self.block_index = Some(block_index);
self.offset_within_block = 0;
block_index
}
Some(Err(err)) => return Err(err),
None => {
self.is_done = true;
return Ok(None);
}
}
};
let block_size = self.fs.0.superblock.block_size;
if self.offset_within_block >= block_size {
self.is_first_block = false;
self.block_index = None;
return Ok(None);
}
if self.offset_within_block == 0 {
DirBlock {
fs: &self.fs,
dir_inode: self.inode,
block_index,
is_first: self.is_first_block,
has_htree: self.has_htree,
checksum_base: self.checksum_base.clone(),
}
.read(&mut self.block)?;
}
let (entry, entry_size) = DirEntry::from_bytes(
self.fs.clone(),
&self.block[self.offset_within_block..],
self.inode,
self.path.clone(),
)?;
self.offset_within_block = self
.offset_within_block
.checked_add(entry_size)
.ok_or(CorruptKind::DirEntry(self.inode))?;
Ok(entry)
}
}
impl Debug for ReadDir {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, r#"ReadDir("{:?}")"#, self.path)
}
}
impl_result_iter!(ReadDir, DirEntry);
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::load_test_disk1;
#[test]
fn test_read_dir() {
let fs = load_test_disk1();
let root_inode = fs.read_root_inode().unwrap();
let root_path = crate::PathBuf::new("/");
let entries: Vec<_> = ReadDir::new(fs, &root_inode, root_path)
.unwrap()
.map(|e| e.unwrap())
.collect();
assert!(entries.iter().any(|e| e.file_name() == "."));
assert!(entries.iter().any(|e| e.file_name() == ".."));
assert!(entries.iter().any(|e| e.file_name() == "empty_file"));
assert!(entries.iter().any(|e| e.file_name() == "empty_dir"));
assert!(!entries.iter().any(|e| e.file_name() == "does_not_exist"));
}
}