• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 use crate::process::fchdir;
8 use crate::utils::as_ptr;
9 use alloc::borrow::ToOwned;
10 use alloc::vec::Vec;
11 use core::fmt;
12 use core::mem::size_of;
13 use linux_raw_sys::general::{linux_dirent64, SEEK_SET};
14 
15 /// `DIR*`
16 pub 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 
33 impl Dir {
34     /// Construct a `Dir` that reads entries from the given directory
35     /// file descriptor.
36     #[inline]
read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self>37     pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
38         Self::_read_from(fd.as_fd())
39     }
40 
41     #[inline]
_read_from(fd: BorrowedFd<'_>) -> io::Result<Self>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]
rewind(&mut self)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.
read(&mut self) -> Option<io::Result<DirEntry>>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 =
126             CStr::from_bytes_with_nul(&self.buf[name_start..name_start + name_len + 1]).unwrap();
127         let name = name.to_owned();
128         assert!(name.as_bytes().len() <= self.buf.len() - name_start);
129 
130         // Do an unaligned u64 load.
131         let d_ino = u64::from_ne_bytes([
132             self.buf[pos + offsetof_d_ino],
133             self.buf[pos + offsetof_d_ino + 1],
134             self.buf[pos + offsetof_d_ino + 2],
135             self.buf[pos + offsetof_d_ino + 3],
136             self.buf[pos + offsetof_d_ino + 4],
137             self.buf[pos + offsetof_d_ino + 5],
138             self.buf[pos + offsetof_d_ino + 6],
139             self.buf[pos + offsetof_d_ino + 7],
140         ]);
141 
142         let d_type = self.buf[pos + offsetof_d_type];
143 
144         // Check that our types correspond to the `linux_dirent64` types.
145         let _ = linux_dirent64 {
146             d_ino,
147             d_off: 0,
148             d_type,
149             d_reclen,
150             d_name: Default::default(),
151         };
152 
153         Some(Ok(DirEntry {
154             d_ino,
155             d_type,
156             name,
157         }))
158     }
159 
read_more(&mut self) -> Option<io::Result<()>>160     fn read_more(&mut self) -> Option<io::Result<()>> {
161         // The first few times we're called, we allocate a relatively small
162         // buffer, because many directories are small. If we're called more,
163         // use progressively larger allocations, up to a fixed maximum.
164         //
165         // The specific sizes and policy here have not been tuned in detail yet
166         // and may need to be adjusted. In doing so, we should be careful to
167         // avoid unbounded buffer growth. This buffer only exists to share the
168         // cost of a `getdents` call over many entries, so if it gets too big,
169         // cache and heap usage will outweigh the benefit. And ultimately,
170         // directories can contain more entries than we can allocate contiguous
171         // memory for, so we'll always need to cap the size at some point.
172         if self.buf.len() < 1024 * size_of::<linux_dirent64>() {
173             self.buf.reserve(32 * size_of::<linux_dirent64>());
174         }
175         self.buf.resize(self.buf.capacity(), 0);
176         let nread = match io::retry_on_intr(|| {
177             crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf)
178         }) {
179             Ok(nread) => nread,
180             Err(io::Errno::NOENT) => {
181                 self.any_errors = true;
182                 return None;
183             }
184             Err(err) => {
185                 self.any_errors = true;
186                 return Some(Err(err));
187             }
188         };
189         self.buf.resize(nread, 0);
190         self.pos = 0;
191         if nread == 0 {
192             None
193         } else {
194             Some(Ok(()))
195         }
196     }
197 
198     /// `fstat(self)`
199     #[inline]
stat(&self) -> io::Result<Stat>200     pub fn stat(&self) -> io::Result<Stat> {
201         fstat(&self.fd)
202     }
203 
204     /// `fstatfs(self)`
205     #[inline]
statfs(&self) -> io::Result<StatFs>206     pub fn statfs(&self) -> io::Result<StatFs> {
207         fstatfs(&self.fd)
208     }
209 
210     /// `fstatvfs(self)`
211     #[inline]
statvfs(&self) -> io::Result<StatVfs>212     pub fn statvfs(&self) -> io::Result<StatVfs> {
213         fstatvfs(&self.fd)
214     }
215 
216     /// `fchdir(self)`
217     #[inline]
chdir(&self) -> io::Result<()>218     pub fn chdir(&self) -> io::Result<()> {
219         fchdir(&self.fd)
220     }
221 }
222 
223 impl Iterator for Dir {
224     type Item = io::Result<DirEntry>;
225 
226     #[inline]
next(&mut self) -> Option<Self::Item>227     fn next(&mut self) -> Option<Self::Item> {
228         Self::read(self)
229     }
230 }
231 
232 impl fmt::Debug for Dir {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result233     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234         f.debug_struct("Dir").field("fd", &self.fd).finish()
235     }
236 }
237 
238 /// `struct dirent`
239 #[derive(Debug)]
240 pub struct DirEntry {
241     d_ino: u64,
242     d_type: u8,
243     name: CString,
244 }
245 
246 impl DirEntry {
247     /// Returns the file name of this directory entry.
248     #[inline]
file_name(&self) -> &CStr249     pub fn file_name(&self) -> &CStr {
250         &self.name
251     }
252 
253     /// Returns the type of this directory entry.
254     #[inline]
file_type(&self) -> FileType255     pub fn file_type(&self) -> FileType {
256         FileType::from_dirent_d_type(self.d_type)
257     }
258 
259     /// Return the inode number of this directory entry.
260     #[inline]
ino(&self) -> u64261     pub fn ino(&self) -> u64 {
262         self.d_ino
263     }
264 }
265 
266 #[test]
dir_iterator_handles_io_errors()267 fn dir_iterator_handles_io_errors() {
268     // create a dir, keep the FD, then delete the dir
269     let tmp = tempfile::tempdir().unwrap();
270     let fd = crate::fs::openat(
271         crate::fs::cwd(),
272         tmp.path(),
273         crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
274         crate::fs::Mode::empty(),
275     )
276     .unwrap();
277 
278     let file_fd = crate::fs::openat(
279         &fd,
280         tmp.path().join("test.txt"),
281         crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
282         crate::fs::Mode::RWXU,
283     )
284     .unwrap();
285 
286     let mut dir = Dir::read_from(&fd).unwrap();
287 
288     // Reach inside the `Dir` and replace its directory with a file, which
289     // will cause the subsequent `getdents64` to fail.
290     crate::io::dup2(&file_fd, &mut dir.fd).unwrap();
291 
292     assert!(matches!(dir.next(), Some(Err(_))));
293     assert!(matches!(dir.next(), None));
294 }
295