1use crate::fd::{AsFd, BorrowedFd, OwnedFd};
2use crate::ffi::{CStr, CString};
3use crate::fs::{
4 fcntl_getfl, fstat, fstatfs, fstatvfs, openat, FileType, Mode, OFlags, Stat, StatFs, StatVfs,
5};
6use crate::io;
7use crate::process::fchdir;
8use crate::utils::as_ptr;
9use alloc::borrow::ToOwned;
10use alloc::vec::Vec;
11use core::fmt;
12use core::mem::size_of;
13use linux_raw_sys::general::{linux_dirent64, SEEK_SET};
14
15/// `DIR*`
16pub struct Dir {
17 /// The `OwnedFd` that we read directory entries from.
18 fd: OwnedFd,
19
20 /// Have we seen any errors in this iteration?
21 any_errors: bool,
22
23 /// Should we rewind the stream on the next iteration?
24 rewind: bool,
25
26 /// The buffer for `linux_dirent64` entries.
27 buf: Vec<u8>,
28
29 /// Where we are in the buffer.
30 pos: usize,
31}
32
33impl Dir {
34 /// Construct a `Dir` that reads entries from the given directory
35 /// file descriptor.
36 #[inline]
37 pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
38 Self::_read_from(fd.as_fd())
39 }
40
41 #[inline]
42 fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
43 let flags = fcntl_getfl(fd)?;
44 let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
45
46 Ok(Self {
47 fd: fd_for_dir,
48 any_errors: false,
49 rewind: false,
50 buf: Vec::new(),
51 pos: 0,
52 })
53 }
54
55 /// `rewinddir(self)`
56 #[inline]
57 pub fn rewind(&mut self) {
58 self.any_errors = false;
59 self.rewind = true;
60 self.pos = self.buf.len();
61 }
62
63 /// `readdir(self)`, where `None` means the end of the directory.
64 pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
65 // If we've seen errors, don't continue to try to read anyting further.
66 if self.any_errors {
67 return None;
68 }
69
70 // If a rewind was requested, seek to the beginning.
71 if self.rewind {
72 self.rewind = false;
73 match io::retry_on_intr(|| {
74 crate::backend::fs::syscalls::_seek(self.fd.as_fd(), 0, SEEK_SET)
75 }) {
76 Ok(_) => (),
77 Err(err) => {
78 self.any_errors = true;
79 return Some(Err(err));
80 }
81 }
82 }
83
84 // Compute linux_dirent64 field offsets.
85 let z = linux_dirent64 {
86 d_ino: 0_u64,
87 d_off: 0_i64,
88 d_type: 0_u8,
89 d_reclen: 0_u16,
90 d_name: Default::default(),
91 };
92 let base = as_ptr(&z) as usize;
93 let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base;
94 let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base;
95 let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base;
96 let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base;
97
98 // Test if we need more entries, and if so, read more.
99 if self.buf.len() - self.pos < size_of::<linux_dirent64>() {
100 match self.read_more()? {
101 Ok(()) => (),
102 Err(err) => return Some(Err(err)),
103 }
104 }
105
106 // We successfully read an entry. Extract the fields.
107 let pos = self.pos;
108
109 // Do an unaligned u16 load.
110 let d_reclen = u16::from_ne_bytes([
111 self.buf[pos + offsetof_d_reclen],
112 self.buf[pos + offsetof_d_reclen + 1],
113 ]);
114 assert!(self.buf.len() - pos >= d_reclen as usize);
115 self.pos += d_reclen as usize;
116
117 // Read the NUL-terminated name from the `d_name` field. Without
118 // `unsafe`, we need to scan for the NUL twice: once to obtain a size
119 // for the slice, and then once within `CStr::from_bytes_with_nul`.
120 let name_start = pos + offsetof_d_name;
121 let name_len = self.buf[name_start..]
122 .iter()
123 .position(|x| *x == b'\0')
124 .unwrap();
125 let name = CStr::from_bytes_with_nul(&self.buf[name_start..][..name_len + 1]).unwrap();
126 let name = name.to_owned();
127 assert!(name.as_bytes().len() <= self.buf.len() - name_start);
128
129 // Do an unaligned u64 load.
130 let d_ino = u64::from_ne_bytes([
131 self.buf[pos + offsetof_d_ino],
132 self.buf[pos + offsetof_d_ino + 1],
133 self.buf[pos + offsetof_d_ino + 2],
134 self.buf[pos + offsetof_d_ino + 3],
135 self.buf[pos + offsetof_d_ino + 4],
136 self.buf[pos + offsetof_d_ino + 5],
137 self.buf[pos + offsetof_d_ino + 6],
138 self.buf[pos + offsetof_d_ino + 7],
139 ]);
140
141 let d_type = self.buf[pos + offsetof_d_type];
142
143 // Check that our types correspond to the `linux_dirent64` types.
144 let _ = linux_dirent64 {
145 d_ino,
146 d_off: 0,
147 d_type,
148 d_reclen,
149 d_name: Default::default(),
150 };
151
152 Some(Ok(DirEntry {
153 d_ino,
154 d_type,
155 name,
156 }))
157 }
158
159 fn read_more(&mut self) -> Option<io::Result<()>> {
160 // The first few times we're called, we allocate a relatively small
161 // buffer, because many directories are small. If we're called more,
162 // use progressively larger allocations, up to a fixed maximum.
163 //
164 // The specific sizes and policy here have not been tuned in detail yet
165 // and may need to be adjusted. In doing so, we should be careful to
166 // avoid unbounded buffer growth. This buffer only exists to share the
167 // cost of a `getdents` call over many entries, so if it gets too big,
168 // cache and heap usage will outweigh the benefit. And ultimately,
169 // directories can contain more entries than we can allocate contiguous
170 // memory for, so we'll always need to cap the size at some point.
171 if self.buf.len() < 1024 * size_of::<linux_dirent64>() {
172 self.buf.reserve(32 * size_of::<linux_dirent64>());
173 }
174 self.buf.resize(self.buf.capacity(), 0);
175 let nread = match io::retry_on_intr(|| {
176 crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf)
177 }) {
178 Ok(nread) => nread,
179 Err(io::Errno::NOENT) => {
180 self.any_errors = true;
181 return None;
182 }
183 Err(err) => {
184 self.any_errors = true;
185 return Some(Err(err));
186 }
187 };
188 self.buf.resize(nread, 0);
189 self.pos = 0;
190 if nread == 0 {
191 None
192 } else {
193 Some(Ok(()))
194 }
195 }
196
197 /// `fstat(self)`
198 #[inline]
199 pub fn stat(&self) -> io::Result<Stat> {
200 fstat(&self.fd)
201 }
202
203 /// `fstatfs(self)`
204 #[inline]
205 pub fn statfs(&self) -> io::Result<StatFs> {
206 fstatfs(&self.fd)
207 }
208
209 /// `fstatvfs(self)`
210 #[inline]
211 pub fn statvfs(&self) -> io::Result<StatVfs> {
212 fstatvfs(&self.fd)
213 }
214
215 /// `fchdir(self)`
216 #[inline]
217 pub fn chdir(&self) -> io::Result<()> {
218 fchdir(&self.fd)
219 }
220}
221
222impl Iterator for Dir {
223 type Item = io::Result<DirEntry>;
224
225 #[inline]
226 fn next(&mut self) -> Option<Self::Item> {
227 Self::read(self)
228 }
229}
230
231impl fmt::Debug for Dir {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.debug_struct("Dir").field(name:"fd", &self.fd).finish()
234 }
235}
236
237/// `struct dirent`
238#[derive(Debug)]
239pub struct DirEntry {
240 d_ino: u64,
241 d_type: u8,
242 name: CString,
243}
244
245impl DirEntry {
246 /// Returns the file name of this directory entry.
247 #[inline]
248 pub fn file_name(&self) -> &CStr {
249 &self.name
250 }
251
252 /// Returns the type of this directory entry.
253 #[inline]
254 pub fn file_type(&self) -> FileType {
255 FileType::from_dirent_d_type(self.d_type)
256 }
257
258 /// Return the inode number of this directory entry.
259 #[inline]
260 pub fn ino(&self) -> u64 {
261 self.d_ino
262 }
263}
264
265#[test]
266fn dir_iterator_handles_io_errors() {
267 // create a dir, keep the FD, then delete the dir
268 let tmp = tempfile::tempdir().unwrap();
269 let fd = crate::fs::openat(
270 crate::fs::cwd(),
271 tmp.path(),
272 crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
273 crate::fs::Mode::empty(),
274 )
275 .unwrap();
276
277 let file_fd = crate::fs::openat(
278 &fd,
279 tmp.path().join("test.txt"),
280 crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
281 crate::fs::Mode::RWXU,
282 )
283 .unwrap();
284
285 let mut dir = Dir::read_from(&fd).unwrap();
286
287 // Reach inside the `Dir` and replace its directory with a file, which
288 // will cause the subsequent `getdents64` to fail.
289 crate::io::dup2(&file_fd, &mut dir.fd).unwrap();
290
291 assert!(matches!(dir.next(), Some(Err(_))));
292 assert!(matches!(dir.next(), None));
293}
294