standard_lib/fs/dir/dir_entry.rs
1use std::ffi::OsStr;
2use std::io::RawOsError;
3use std::mem::MaybeUninit;
4use std::num::NonZero;
5use std::os::unix::ffi::OsStrExt;
6use std::ptr::NonNull;
7use std::slice;
8
9use crate::collections::contiguous::Array;
10use crate::fs::dir::Directory;
11use crate::fs::error::RemovedDirectoryError;
12use crate::fs::file::ReadWrite;
13use crate::fs::panic::{BadFdPanic, BadStackAddrPanic, NotADirPanic, Panic, UnexpectedErrorPanic};
14use crate::fs::{File, FileType, OwnedPath, Rel};
15use crate::util;
16
17#[derive(Debug)]
18#[repr(C)]
19pub(crate) struct DirEntrySized {
20 pub d_ino: u64,
21 pub d_off: i64,
22 pub d_reclen: u8,
23 pub d_type: u8,
24}
25
26// TODO: Add wrapped methods on DirEntry for the ..at syscalls, e.g. fstatat.
27
28/// An entry in a [`Directory`]'s records, taking the form of a reference to the parent `Directory`
29/// and the relative path to this entry, along with a few other pieces of information.
30///
31/// Among these other pieces of information, is the entry's inode number and a hint about the
32/// [`FileType`] of the entry. Unfortunately, this field is often not present, and therefore
33/// considered only a hint. If the `FileType` is present and has some value, it can be trusted
34/// (subject to TOCTOU restrictions), but the possibility of it not existing should always be
35/// considered or even expected.
36#[derive(Debug)]
37pub struct DirEntry<'a> {
38 pub parent: &'a Directory,
39 pub path: OwnedPath<Rel>,
40 pub file_type_hint: Option<FileType>,
41 pub inode_num: NonZero<u64>,
42 // Neither of these have any relevance to users.
43 // pub d_off: i64,
44 // pub d_reclen: u8,
45}
46
47impl<'a> DirEntry<'a> {
48 pub fn name(&self) -> &OsStr {
49 self.path.as_os_str_no_lead()
50 }
51
52 pub fn open_file(&self) -> Result<File<ReadWrite>, RawOsError> {
53 File::options().open_dir_entry(self)
54 }
55
56 // pub fn open_dir(&self) -> Result<Directory, _>; // openat
57
58 // TODO: forward to path methods
59}
60
61pub(crate) const BUFFER_SIZE: usize = 1024;
62
63/// An iterator over a [`Directory`]'s contained entries. Obtainable via
64/// [`Directory::read_entries`], this buffered iterator produces [`DirEntry`]s for the referenced
65/// directory.
66///
67/// When reading from the file system, the redundant "." and ".." entries are skipped, along with
68/// any deleted entries.
69// TODO: Heaps of TOCTOU notes.
70pub struct DirEntries<'a> {
71 pub(crate) dir: &'a Directory,
72 pub(crate) buf: Array<MaybeUninit<u8>>,
73 pub(crate) head: NonNull<MaybeUninit<u8>>,
74 pub(crate) rem: usize,
75}
76
77// TODO: impl other Iterator traits
78
79impl<'a> Iterator for DirEntries<'a> {
80 type Item = Result<DirEntry<'a>, RemovedDirectoryError>;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 if self.rem == 0 {
84 loop {
85 match unsafe { util::fs::getdents(
86 *self.dir.fd,
87 self.buf.as_ptr().cast_mut().cast(),
88 self.buf.size()
89 ) } {
90 -1 => match util::fs::err_no() {
91 libc::EBADF => BadFdPanic.panic(),
92 libc::EFAULT => BadStackAddrPanic.panic(),
93 // TODO: Handle (array) overflows etc here? Smart size selection?
94 // If the buffer isn't large enough, double it. Panics in the event of an
95 // overflow.
96 // This is currently the only way that the buffer size can change.
97 libc::EINVAL => {
98 self.buf = Array::new_uninit(self.buf.size * 2);
99 continue;
100 },
101 // TODO: Do I really have to return this? Result from an Iterator is gross.
102 libc::ENOENT => return Some(Err(RemovedDirectoryError)),
103 libc::ENOTDIR => NotADirPanic.panic(),
104 e => UnexpectedErrorPanic(e).panic(),
105 },
106 0 => None?,
107 count => self.rem = count as usize,
108 }
109 break;
110 }
111 }
112 let sized = unsafe { self.head.cast::<DirEntrySized>().as_ref() };
113
114 let entry_size = sized.d_reclen as usize;
115
116 let entry = if let Ok(inode_num) = NonZero::try_from(sized.d_ino) {
117 let char_head = unsafe { self.head.add(19).cast() };
118 let mut char_tail = char_head;
119 while unsafe { char_tail.read() } != 0 {
120 char_tail = unsafe { char_tail.add(1) };
121 }
122
123 let name = unsafe { OwnedPath::<Rel>::from_os_str_sanitized(OsStr::from_bytes(
124 slice::from_ptr_range(char_head.as_ptr()..char_tail.as_ptr())
125 )) };
126
127 if name.as_os_str() == OsStr::new("/") || name.as_os_str() == OsStr::new("/..") {
128 // Skip redundant "." and ".." entries. "." will be normalized to "/", which we
129 // don't want either.
130 None
131 } else {
132 Some(DirEntry {
133 parent: self.dir,
134 inode_num,
135 file_type_hint: FileType::from_dirent_type(sized.d_type),
136 path: name,
137 })
138 }
139 } else {
140 // Skip entries with inode number zero, these are considered deleted on some file
141 // systems and generally invalid on others.
142 None
143 };
144
145 // head + entry_size - 1: first nul terminator for string.
146 // head + entry_size: start of next dirent.
147 self.head = unsafe { self.head.add(entry_size) };
148 // If head is now outside the bounds of the buffer, there should be no remaining elements
149 // and head will be reset next iteration anyway.
150 // It turns out this is also was glibc does with readdir.
151 self.rem -= entry_size;
152
153 match entry {
154 Some(e) => Some(Ok(e)),
155 // If a None has made it to this point without propagating immediately, we are skipping
156 // a value and need to iterate again.
157 None => self.next(),
158 }
159 }
160}