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