1 #[cfg(not(any(solarish, target_os = "haiku", target_os = "nto", target_os = "vita")))]
2 use super::types::FileType;
3 use crate::backend::c;
4 use crate::backend::conv::owned_fd;
5 use crate::fd::{AsFd, BorrowedFd, OwnedFd};
6 use crate::ffi::{CStr, CString};
7 use crate::fs::{fcntl_getfl, openat, Mode, OFlags};
8 #[cfg(not(target_os = "vita"))]
9 use crate::fs::{fstat, Stat};
10 #[cfg(not(any(
11 solarish,
12 target_os = "haiku",
13 target_os = "netbsd",
14 target_os = "nto",
15 target_os = "redox",
16 target_os = "vita",
17 target_os = "wasi",
18 )))]
19 use crate::fs::{fstatfs, StatFs};
20 #[cfg(not(any(
21 solarish,
22 target_os = "haiku",
23 target_os = "redox",
24 target_os = "vita",
25 target_os = "wasi"
26 )))]
27 use crate::fs::{fstatvfs, StatVfs};
28 use crate::io;
29 #[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
30 #[cfg(feature = "process")]
31 use crate::process::fchdir;
32 use alloc::borrow::ToOwned;
33 #[cfg(not(any(linux_like, target_os = "hurd")))]
34 use c::readdir as libc_readdir;
35 #[cfg(any(linux_like, target_os = "hurd"))]
36 use c::readdir64 as libc_readdir;
37 use core::fmt;
38 use core::ptr::NonNull;
39 use libc_errno::{errno, set_errno, Errno};
40
41 /// `DIR*`
42 pub struct Dir {
43 /// The `libc` `DIR` pointer.
44 libc_dir: NonNull<c::DIR>,
45
46 /// Have we seen any errors in this iteration?
47 any_errors: bool,
48 }
49
50 impl Dir {
51 /// Take ownership of `fd` and construct a `Dir` that reads entries from
52 /// the given directory file descriptor.
53 #[inline]
new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self>54 pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
55 Self::_new(fd.into())
56 }
57
58 #[inline]
_new(fd: OwnedFd) -> io::Result<Self>59 fn _new(fd: OwnedFd) -> io::Result<Self> {
60 let raw = owned_fd(fd);
61 unsafe {
62 let libc_dir = c::fdopendir(raw);
63
64 if let Some(libc_dir) = NonNull::new(libc_dir) {
65 Ok(Self {
66 libc_dir,
67 any_errors: false,
68 })
69 } else {
70 let err = io::Errno::last_os_error();
71 let _ = c::close(raw);
72 Err(err)
73 }
74 }
75 }
76
77 /// Borrow `fd` and construct a `Dir` that reads entries from the given
78 /// directory file descriptor.
79 #[inline]
read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self>80 pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
81 Self::_read_from(fd.as_fd())
82 }
83
84 #[inline]
85 #[allow(unused_mut)]
_read_from(fd: BorrowedFd<'_>) -> io::Result<Self>86 fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
87 let mut any_errors = false;
88
89 // Given an arbitrary `OwnedFd`, it's impossible to know whether the
90 // user holds a `dup`'d copy which could continue to modify the
91 // file description state, which would cause Undefined Behavior after
92 // our call to `fdopendir`. To prevent this, we obtain an independent
93 // `OwnedFd`.
94 let flags = fcntl_getfl(fd)?;
95 let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
96 Ok(fd) => fd,
97 #[cfg(not(target_os = "wasi"))]
98 Err(io::Errno::NOENT) => {
99 // If "." doesn't exist, it means the directory was removed.
100 // We treat that as iterating through a directory with no
101 // entries.
102 any_errors = true;
103 crate::io::dup(fd)?
104 }
105 Err(err) => return Err(err),
106 };
107
108 let raw = owned_fd(fd_for_dir);
109 unsafe {
110 let libc_dir = c::fdopendir(raw);
111
112 if let Some(libc_dir) = NonNull::new(libc_dir) {
113 Ok(Self {
114 libc_dir,
115 any_errors,
116 })
117 } else {
118 let err = io::Errno::last_os_error();
119 let _ = c::close(raw);
120 Err(err)
121 }
122 }
123 }
124
125 /// `rewinddir(self)`
126 #[inline]
rewind(&mut self)127 pub fn rewind(&mut self) {
128 self.any_errors = false;
129 unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
130 }
131
132 /// `readdir(self)`, where `None` means the end of the directory.
read(&mut self) -> Option<io::Result<DirEntry>>133 pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
134 // If we've seen errors, don't continue to try to read anything further.
135 if self.any_errors {
136 return None;
137 }
138
139 set_errno(Errno(0));
140 let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
141 if dirent_ptr.is_null() {
142 let curr_errno = errno().0;
143 if curr_errno == 0 {
144 // We successfully reached the end of the stream.
145 None
146 } else {
147 // `errno` is unknown or non-zero, so an error occurred.
148 self.any_errors = true;
149 Some(Err(io::Errno(curr_errno)))
150 }
151 } else {
152 // We successfully read an entry.
153 unsafe {
154 let dirent = &*dirent_ptr;
155
156 // We have our own copy of OpenBSD's dirent; check that the
157 // layout minimally matches libc's.
158 #[cfg(target_os = "openbsd")]
159 check_dirent_layout(dirent);
160
161 let result = DirEntry {
162 #[cfg(not(any(
163 solarish,
164 target_os = "aix",
165 target_os = "haiku",
166 target_os = "nto",
167 target_os = "vita"
168 )))]
169 d_type: dirent.d_type,
170
171 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
172 d_ino: dirent.d_ino,
173
174 #[cfg(any(freebsdlike, netbsdlike))]
175 d_fileno: dirent.d_fileno,
176
177 name: CStr::from_ptr(dirent.d_name.as_ptr()).to_owned(),
178 };
179
180 Some(Ok(result))
181 }
182 }
183 }
184
185 /// `fstat(self)`
186 #[cfg(not(target_os = "vita"))]
187 #[inline]
stat(&self) -> io::Result<Stat>188 pub fn stat(&self) -> io::Result<Stat> {
189 fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
190 }
191
192 /// `fstatfs(self)`
193 #[cfg(not(any(
194 solarish,
195 target_os = "haiku",
196 target_os = "netbsd",
197 target_os = "nto",
198 target_os = "redox",
199 target_os = "vita",
200 target_os = "wasi",
201 )))]
202 #[inline]
statfs(&self) -> io::Result<StatFs>203 pub fn statfs(&self) -> io::Result<StatFs> {
204 fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
205 }
206
207 /// `fstatvfs(self)`
208 #[cfg(not(any(
209 solarish,
210 target_os = "haiku",
211 target_os = "redox",
212 target_os = "vita",
213 target_os = "wasi"
214 )))]
215 #[inline]
statvfs(&self) -> io::Result<StatVfs>216 pub fn statvfs(&self) -> io::Result<StatVfs> {
217 fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
218 }
219
220 /// `fchdir(self)`
221 #[cfg(feature = "process")]
222 #[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
223 #[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
224 #[inline]
chdir(&self) -> io::Result<()>225 pub fn chdir(&self) -> io::Result<()> {
226 fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
227 }
228 }
229
230 /// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
231 /// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
232 /// need `Sync`, which is effectively what'd need to do to implement `Sync`
233 /// ourselves.
234 unsafe impl Send for Dir {}
235
236 impl Drop for Dir {
237 #[inline]
drop(&mut self)238 fn drop(&mut self) {
239 unsafe { c::closedir(self.libc_dir.as_ptr()) };
240 }
241 }
242
243 impl Iterator for Dir {
244 type Item = io::Result<DirEntry>;
245
246 #[inline]
next(&mut self) -> Option<Self::Item>247 fn next(&mut self) -> Option<Self::Item> {
248 Self::read(self)
249 }
250 }
251
252 impl fmt::Debug for Dir {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 let mut s = f.debug_struct("Dir");
255 #[cfg(not(target_os = "vita"))]
256 s.field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) });
257 s.finish()
258 }
259 }
260
261 /// `struct dirent`
262 #[derive(Debug)]
263 pub struct DirEntry {
264 #[cfg(not(any(
265 solarish,
266 target_os = "aix",
267 target_os = "haiku",
268 target_os = "nto",
269 target_os = "vita"
270 )))]
271 d_type: u8,
272
273 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
274 d_ino: c::ino_t,
275
276 #[cfg(any(freebsdlike, netbsdlike))]
277 d_fileno: c::ino_t,
278
279 name: CString,
280 }
281
282 impl DirEntry {
283 /// Returns the file name of this directory entry.
284 #[inline]
file_name(&self) -> &CStr285 pub fn file_name(&self) -> &CStr {
286 &self.name
287 }
288
289 /// Returns the type of this directory entry.
290 #[cfg(not(any(
291 solarish,
292 target_os = "aix",
293 target_os = "haiku",
294 target_os = "nto",
295 target_os = "vita"
296 )))]
297 #[inline]
file_type(&self) -> FileType298 pub fn file_type(&self) -> FileType {
299 FileType::from_dirent_d_type(self.d_type)
300 }
301
302 /// Return the inode number of this directory entry.
303 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
304 #[inline]
ino(&self) -> u64305 pub fn ino(&self) -> u64 {
306 self.d_ino as u64
307 }
308
309 /// Return the inode number of this directory entry.
310 #[cfg(any(freebsdlike, netbsdlike))]
311 #[inline]
ino(&self) -> u64312 pub fn ino(&self) -> u64 {
313 #[allow(clippy::useless_conversion)]
314 self.d_fileno.into()
315 }
316 }
317
318 /// libc's OpenBSD `dirent` has a private field so we can't construct it
319 /// directly, so we declare it ourselves to make all fields accessible.
320 #[cfg(target_os = "openbsd")]
321 #[repr(C)]
322 #[derive(Debug)]
323 struct libc_dirent {
324 d_fileno: c::ino_t,
325 d_off: c::off_t,
326 d_reclen: u16,
327 d_type: u8,
328 d_namlen: u8,
329 __d_padding: [u8; 4],
330 d_name: [c::c_char; 256],
331 }
332
333 /// We have our own copy of OpenBSD's dirent; check that the layout
334 /// minimally matches libc's.
335 #[cfg(target_os = "openbsd")]
check_dirent_layout(dirent: &c::dirent)336 fn check_dirent_layout(dirent: &c::dirent) {
337 use crate::utils::as_ptr;
338
339 // Check that the basic layouts match.
340 #[cfg(test)]
341 {
342 assert_eq_size!(libc_dirent, c::dirent);
343 assert_eq_size!(libc_dirent, c::dirent);
344 }
345
346 // Check that the field offsets match.
347 assert_eq!(
348 {
349 let z = libc_dirent {
350 d_fileno: 0_u64,
351 d_off: 0_i64,
352 d_reclen: 0_u16,
353 d_type: 0_u8,
354 d_namlen: 0_u8,
355 __d_padding: [0_u8; 4],
356 d_name: [0 as c::c_char; 256],
357 };
358 let base = as_ptr(&z) as usize;
359 (
360 (as_ptr(&z.d_fileno) as usize) - base,
361 (as_ptr(&z.d_off) as usize) - base,
362 (as_ptr(&z.d_reclen) as usize) - base,
363 (as_ptr(&z.d_type) as usize) - base,
364 (as_ptr(&z.d_namlen) as usize) - base,
365 (as_ptr(&z.d_name) as usize) - base,
366 )
367 },
368 {
369 let z = dirent;
370 let base = as_ptr(z) as usize;
371 (
372 (as_ptr(&z.d_fileno) as usize) - base,
373 (as_ptr(&z.d_off) as usize) - base,
374 (as_ptr(&z.d_reclen) as usize) - base,
375 (as_ptr(&z.d_type) as usize) - base,
376 (as_ptr(&z.d_namlen) as usize) - base,
377 (as_ptr(&z.d_name) as usize) - base,
378 )
379 }
380 );
381 }
382
383 #[test]
dir_iterator_handles_io_errors()384 fn dir_iterator_handles_io_errors() {
385 // create a dir, keep the FD, then delete the dir
386 let tmp = tempfile::tempdir().unwrap();
387 let fd = crate::fs::openat(
388 crate::fs::CWD,
389 tmp.path(),
390 crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
391 crate::fs::Mode::empty(),
392 )
393 .unwrap();
394
395 let file_fd = crate::fs::openat(
396 &fd,
397 tmp.path().join("test.txt"),
398 crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
399 crate::fs::Mode::RWXU,
400 )
401 .unwrap();
402
403 let mut dir = Dir::read_from(&fd).unwrap();
404
405 // Reach inside the `Dir` and replace its directory with a file, which
406 // will cause the subsequent `readdir` to fail.
407 unsafe {
408 let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
409 let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
410 crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
411 core::mem::forget(owned_fd);
412 }
413
414 // FreeBSD and macOS seem to read some directory entries before we call
415 // `.next()`.
416 #[cfg(any(apple, freebsdlike))]
417 {
418 dir.rewind();
419 }
420
421 assert!(matches!(dir.next(), Some(Err(_))));
422 assert!(dir.next().is_none());
423 }
424