1 use super::super::c;
2 use super::super::conv::owned_fd;
3 #[cfg(not(any(target_os = "haiku", target_os = "illumos", target_os = "solaris")))]
4 use super::types::FileType;
5 use crate::fd::{AsFd, BorrowedFd};
6 use crate::ffi::CStr;
7 #[cfg(target_os = "wasi")]
8 use crate::ffi::CString;
9 use crate::fs::{fcntl_getfl, fstat, openat, Mode, OFlags, Stat};
10 #[cfg(not(any(
11 target_os = "haiku",
12 target_os = "illumos",
13 target_os = "netbsd",
14 target_os = "redox",
15 target_os = "solaris",
16 target_os = "wasi",
17 )))]
18 use crate::fs::{fstatfs, StatFs};
19 #[cfg(not(any(
20 target_os = "haiku",
21 target_os = "illumos",
22 target_os = "redox",
23 target_os = "solaris",
24 target_os = "wasi",
25 )))]
26 use crate::fs::{fstatvfs, StatVfs};
27 use crate::io;
28 #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
29 use crate::process::fchdir;
30 #[cfg(target_os = "wasi")]
31 use alloc::borrow::ToOwned;
32 #[cfg(not(any(
33 target_os = "android",
34 target_os = "emscripten",
35 target_os = "l4re",
36 target_os = "linux",
37 target_os = "openbsd",
38 )))]
39 use c::dirent as libc_dirent;
40 #[cfg(not(any(
41 target_os = "android",
42 target_os = "emscripten",
43 target_os = "l4re",
44 target_os = "linux",
45 )))]
46 use c::readdir as libc_readdir;
47 #[cfg(any(
48 target_os = "android",
49 target_os = "emscripten",
50 target_os = "l4re",
51 target_os = "linux",
52 ))]
53 use c::{dirent64 as libc_dirent, readdir64 as libc_readdir};
54 use core::fmt;
55 use core::mem::zeroed;
56 use core::ptr::NonNull;
57 use libc_errno::{errno, set_errno, Errno};
58
59 /// `DIR*`
60 pub struct Dir {
61 /// The `libc` `DIR` pointer.
62 libc_dir: NonNull<c::DIR>,
63
64 /// Have we seen any errors in this iteration?
65 any_errors: bool,
66 }
67
68 impl Dir {
69 /// Construct a `Dir` that reads entries from the given directory
70 /// file descriptor.
71 #[inline]
read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self>72 pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
73 Self::_read_from(fd.as_fd())
74 }
75
76 #[inline]
77 #[allow(unused_mut)]
_read_from(fd: BorrowedFd<'_>) -> io::Result<Self>78 fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
79 let mut any_errors = false;
80
81 // Given an arbitrary `OwnedFd`, it's impossible to know whether the
82 // user holds a `dup`'d copy which could continue to modify the
83 // file description state, which would cause Undefined Behavior after
84 // our call to `fdopendir`. To prevent this, we obtain an independent
85 // `OwnedFd`.
86 let flags = fcntl_getfl(fd)?;
87 let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
88 Ok(fd) => fd,
89 #[cfg(not(target_os = "wasi"))]
90 Err(io::Errno::NOENT) => {
91 // If "." doesn't exist, it means the directory was removed.
92 // We treat that as iterating through a directory with no
93 // entries.
94 any_errors = true;
95 crate::io::dup(fd)?
96 }
97 Err(err) => return Err(err),
98 };
99
100 let raw = owned_fd(fd_for_dir);
101 unsafe {
102 let libc_dir = c::fdopendir(raw);
103
104 if let Some(libc_dir) = NonNull::new(libc_dir) {
105 Ok(Self {
106 libc_dir,
107 any_errors,
108 })
109 } else {
110 let err = io::Errno::last_os_error();
111 let _ = c::close(raw);
112 Err(err)
113 }
114 }
115 }
116
117 /// `rewinddir(self)`
118 #[inline]
rewind(&mut self)119 pub fn rewind(&mut self) {
120 self.any_errors = false;
121 unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
122 }
123
124 /// `readdir(self)`, where `None` means the end of the directory.
read(&mut self) -> Option<io::Result<DirEntry>>125 pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
126 // If we've seen errors, don't continue to try to read anyting further.
127 if self.any_errors {
128 return None;
129 }
130
131 set_errno(Errno(0));
132 let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
133 if dirent_ptr.is_null() {
134 let curr_errno = errno().0;
135 if curr_errno == 0 {
136 // We successfully reached the end of the stream.
137 None
138 } else {
139 // `errno` is unknown or non-zero, so an error occurred.
140 self.any_errors = true;
141 Some(Err(io::Errno(curr_errno)))
142 }
143 } else {
144 // We successfully read an entry.
145 unsafe {
146 // We have our own copy of OpenBSD's dirent; check that the
147 // layout minimally matches libc's.
148 #[cfg(target_os = "openbsd")]
149 check_dirent_layout(&*dirent_ptr);
150
151 let result = DirEntry {
152 dirent: read_dirent(&*dirent_ptr.cast()),
153
154 #[cfg(target_os = "wasi")]
155 name: CStr::from_ptr((*dirent_ptr).d_name.as_ptr()).to_owned(),
156 };
157
158 Some(Ok(result))
159 }
160 }
161 }
162
163 /// `fstat(self)`
164 #[inline]
stat(&self) -> io::Result<Stat>165 pub fn stat(&self) -> io::Result<Stat> {
166 fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
167 }
168
169 /// `fstatfs(self)`
170 #[cfg(not(any(
171 target_os = "haiku",
172 target_os = "illumos",
173 target_os = "netbsd",
174 target_os = "redox",
175 target_os = "solaris",
176 target_os = "wasi",
177 )))]
178 #[inline]
statfs(&self) -> io::Result<StatFs>179 pub fn statfs(&self) -> io::Result<StatFs> {
180 fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
181 }
182
183 /// `fstatvfs(self)`
184 #[cfg(not(any(
185 target_os = "haiku",
186 target_os = "illumos",
187 target_os = "redox",
188 target_os = "solaris",
189 target_os = "wasi",
190 )))]
191 #[inline]
statvfs(&self) -> io::Result<StatVfs>192 pub fn statvfs(&self) -> io::Result<StatVfs> {
193 fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
194 }
195
196 /// `fchdir(self)`
197 #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
198 #[inline]
chdir(&self) -> io::Result<()>199 pub fn chdir(&self) -> io::Result<()> {
200 fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
201 }
202 }
203
204 // A `dirent` pointer returned from `readdir` may not point to a full `dirent`
205 // struct, as the name is NUL-terminated and memory may not be allocated for
206 // the full extent of the struct. Copy the fields one at a time.
read_dirent(input: &libc_dirent) -> libc_dirent207 unsafe fn read_dirent(input: &libc_dirent) -> libc_dirent {
208 #[cfg(not(any(
209 target_os = "aix",
210 target_os = "haiku",
211 target_os = "illumos",
212 target_os = "solaris"
213 )))]
214 let d_type = input.d_type;
215
216 #[cfg(not(any(
217 target_os = "aix",
218 target_os = "dragonfly",
219 target_os = "freebsd",
220 target_os = "haiku",
221 target_os = "ios",
222 target_os = "macos",
223 target_os = "netbsd",
224 target_os = "wasi",
225 )))]
226 let d_off = input.d_off;
227
228 #[cfg(target_os = "aix")]
229 let d_offset = input.d_offset;
230
231 #[cfg(not(any(
232 target_os = "dragonfly",
233 target_os = "freebsd",
234 target_os = "netbsd",
235 target_os = "openbsd",
236 )))]
237 let d_ino = input.d_ino;
238
239 #[cfg(any(
240 target_os = "dragonfly",
241 target_os = "freebsd",
242 target_os = "netbsd",
243 target_os = "openbsd"
244 ))]
245 let d_fileno = input.d_fileno;
246
247 #[cfg(not(any(target_os = "dragonfly", target_os = "wasi")))]
248 let d_reclen = input.d_reclen;
249
250 #[cfg(any(
251 target_os = "dragonfly",
252 target_os = "freebsd",
253 target_os = "netbsd",
254 target_os = "openbsd",
255 target_os = "ios",
256 target_os = "macos",
257 ))]
258 let d_namlen = input.d_namlen;
259
260 #[cfg(any(target_os = "ios", target_os = "macos"))]
261 let d_seekoff = input.d_seekoff;
262
263 #[cfg(target_os = "haiku")]
264 let d_dev = input.d_dev;
265 #[cfg(target_os = "haiku")]
266 let d_pdev = input.d_pdev;
267 #[cfg(target_os = "haiku")]
268 let d_pino = input.d_pino;
269
270 // Construct the input. Rust will give us an error if any OS has a input
271 // with a field that we missed here. And we can avoid blindly copying the
272 // whole `d_name` field, which may not be entirely allocated.
273 #[cfg_attr(target_os = "wasi", allow(unused_mut))]
274 #[cfg(not(any(target_os = "freebsd", target_os = "dragonfly")))]
275 let mut dirent = libc_dirent {
276 #[cfg(not(any(
277 target_os = "aix",
278 target_os = "haiku",
279 target_os = "illumos",
280 target_os = "solaris"
281 )))]
282 d_type,
283 #[cfg(not(any(
284 target_os = "aix",
285 target_os = "freebsd", // Until FreeBSD 12
286 target_os = "haiku",
287 target_os = "ios",
288 target_os = "macos",
289 target_os = "netbsd",
290 target_os = "wasi",
291 )))]
292 d_off,
293 #[cfg(target_os = "aix")]
294 d_offset,
295 #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))]
296 d_ino,
297 #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
298 d_fileno,
299 #[cfg(not(target_os = "wasi"))]
300 d_reclen,
301 #[cfg(any(
302 target_os = "aix",
303 target_os = "freebsd",
304 target_os = "ios",
305 target_os = "macos",
306 target_os = "netbsd",
307 target_os = "openbsd",
308 ))]
309 d_namlen,
310 #[cfg(any(target_os = "ios", target_os = "macos"))]
311 d_seekoff,
312 // The `d_name` field is NUL-terminated, and we need to be careful not
313 // to read bytes past the NUL, even though they're within the nominal
314 // extent of the `struct dirent`, because they may not be allocated. So
315 // don't read it from `dirent_ptr`.
316 //
317 // In theory this could use `MaybeUninit::uninit().assume_init()`, but
318 // that [invokes undefined behavior].
319 //
320 // [invokes undefined behavior]: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#initialization-invariant
321 d_name: zeroed(),
322 #[cfg(target_os = "openbsd")]
323 __d_padding: zeroed(),
324 #[cfg(target_os = "haiku")]
325 d_dev,
326 #[cfg(target_os = "haiku")]
327 d_pdev,
328 #[cfg(target_os = "haiku")]
329 d_pino,
330 };
331 /*
332 pub d_ino: ino_t,
333 pub d_pino: i64,
334 pub d_reclen: ::c_ushort,
335 pub d_name: [::c_char; 1024], // Max length is _POSIX_PATH_MAX
336 // */
337
338 // On dragonfly and FreeBSD 12, `dirent` has some non-public padding fields
339 // so we can't directly initialize it.
340 #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
341 let mut dirent = {
342 let mut dirent: libc_dirent = zeroed();
343 dirent.d_fileno = d_fileno;
344 dirent.d_namlen = d_namlen;
345 dirent.d_type = d_type;
346 #[cfg(target_os = "freebsd")]
347 {
348 dirent.d_reclen = d_reclen;
349 }
350 dirent
351 };
352
353 // Copy from d_name, reading up to and including the first NUL.
354 #[cfg(not(target_os = "wasi"))]
355 {
356 let name_len = CStr::from_ptr(input.d_name.as_ptr())
357 .to_bytes_with_nul()
358 .len();
359 dirent.d_name[..name_len].copy_from_slice(&input.d_name[..name_len]);
360 }
361
362 dirent
363 }
364
365 /// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
366 /// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
367 /// need `Sync`, which is effectively what'd need to do to implement `Sync`
368 /// ourselves.
369 unsafe impl Send for Dir {}
370
371 impl Drop for Dir {
372 #[inline]
drop(&mut self)373 fn drop(&mut self) {
374 unsafe { c::closedir(self.libc_dir.as_ptr()) };
375 }
376 }
377
378 impl Iterator for Dir {
379 type Item = io::Result<DirEntry>;
380
381 #[inline]
next(&mut self) -> Option<Self::Item>382 fn next(&mut self) -> Option<Self::Item> {
383 Self::read(self)
384 }
385 }
386
387 impl fmt::Debug for Dir {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 f.debug_struct("Dir")
390 .field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) })
391 .finish()
392 }
393 }
394
395 /// `struct dirent`
396 #[derive(Debug)]
397 pub struct DirEntry {
398 dirent: libc_dirent,
399
400 #[cfg(target_os = "wasi")]
401 name: CString,
402 }
403
404 impl DirEntry {
405 /// Returns the file name of this directory entry.
406 #[inline]
file_name(&self) -> &CStr407 pub fn file_name(&self) -> &CStr {
408 #[cfg(not(target_os = "wasi"))]
409 unsafe {
410 CStr::from_ptr(self.dirent.d_name.as_ptr())
411 }
412
413 #[cfg(target_os = "wasi")]
414 &self.name
415 }
416
417 /// Returns the type of this directory entry.
418 #[cfg(not(any(
419 target_os = "aix",
420 target_os = "haiku",
421 target_os = "illumos",
422 target_os = "solaris"
423 )))]
424 #[inline]
file_type(&self) -> FileType425 pub fn file_type(&self) -> FileType {
426 FileType::from_dirent_d_type(self.dirent.d_type)
427 }
428
429 /// Return the inode number of this directory entry.
430 #[cfg(not(any(
431 target_os = "dragonfly",
432 target_os = "freebsd",
433 target_os = "netbsd",
434 target_os = "openbsd",
435 )))]
436 #[inline]
ino(&self) -> u64437 pub fn ino(&self) -> u64 {
438 self.dirent.d_ino as u64
439 }
440
441 /// Return the inode number of this directory entry.
442 #[cfg(any(
443 target_os = "dragonfly",
444 target_os = "freebsd",
445 target_os = "netbsd",
446 target_os = "openbsd",
447 ))]
448 #[inline]
ino(&self) -> u64449 pub fn ino(&self) -> u64 {
450 #[allow(clippy::useless_conversion)]
451 self.dirent.d_fileno.into()
452 }
453 }
454
455 /// libc's OpenBSD `dirent` has a private field so we can't construct it
456 /// directly, so we declare it ourselves to make all fields accessible.
457 #[cfg(target_os = "openbsd")]
458 #[repr(C)]
459 #[derive(Debug)]
460 struct libc_dirent {
461 d_fileno: c::ino_t,
462 d_off: c::off_t,
463 d_reclen: u16,
464 d_type: u8,
465 d_namlen: u8,
466 __d_padding: [u8; 4],
467 d_name: [c::c_char; 256],
468 }
469
470 /// We have our own copy of OpenBSD's dirent; check that the layout
471 /// minimally matches libc's.
472 #[cfg(target_os = "openbsd")]
check_dirent_layout(dirent: &c::dirent)473 fn check_dirent_layout(dirent: &c::dirent) {
474 use crate::utils::as_ptr;
475 use core::mem::{align_of, size_of};
476
477 // Check that the basic layouts match.
478 assert_eq!(size_of::<libc_dirent>(), size_of::<c::dirent>());
479 assert_eq!(align_of::<libc_dirent>(), align_of::<c::dirent>());
480
481 // Check that the field offsets match.
482 assert_eq!(
483 {
484 let z = libc_dirent {
485 d_fileno: 0_u64,
486 d_off: 0_i64,
487 d_reclen: 0_u16,
488 d_type: 0_u8,
489 d_namlen: 0_u8,
490 __d_padding: [0_u8; 4],
491 d_name: [0 as c::c_char; 256],
492 };
493 let base = as_ptr(&z) as usize;
494 (
495 (as_ptr(&z.d_fileno) as usize) - base,
496 (as_ptr(&z.d_off) as usize) - base,
497 (as_ptr(&z.d_reclen) as usize) - base,
498 (as_ptr(&z.d_type) as usize) - base,
499 (as_ptr(&z.d_namlen) as usize) - base,
500 (as_ptr(&z.d_name) as usize) - base,
501 )
502 },
503 {
504 let z = dirent;
505 let base = as_ptr(z) as usize;
506 (
507 (as_ptr(&z.d_fileno) as usize) - base,
508 (as_ptr(&z.d_off) as usize) - base,
509 (as_ptr(&z.d_reclen) as usize) - base,
510 (as_ptr(&z.d_type) as usize) - base,
511 (as_ptr(&z.d_namlen) as usize) - base,
512 (as_ptr(&z.d_name) as usize) - base,
513 )
514 }
515 );
516 }
517
518 #[test]
dir_iterator_handles_io_errors()519 fn dir_iterator_handles_io_errors() {
520 // create a dir, keep the FD, then delete the dir
521 let tmp = tempfile::tempdir().unwrap();
522 let fd = crate::fs::openat(
523 crate::fs::cwd(),
524 tmp.path(),
525 crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
526 crate::fs::Mode::empty(),
527 )
528 .unwrap();
529
530 let file_fd = crate::fs::openat(
531 &fd,
532 tmp.path().join("test.txt"),
533 crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
534 crate::fs::Mode::RWXU,
535 )
536 .unwrap();
537
538 let mut dir = Dir::read_from(&fd).unwrap();
539
540 // Reach inside the `Dir` and replace its directory with a file, which
541 // will cause the subsequent `readdir` to fail.
542 unsafe {
543 let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
544 let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
545 crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
546 core::mem::forget(owned_fd);
547 }
548
549 // FreeBSD and macOS seem to read some directory entries before we call
550 // `.next()`.
551 #[cfg(any(
552 target_os = "macos",
553 target_os = "ios",
554 target_os = "watchos",
555 target_os = "tvos",
556 target_os = "freebsd",
557 target_os = "dragonflybsd"
558 ))]
559 {
560 dir.rewind();
561 }
562
563 assert!(matches!(dir.next(), Some(Err(_))));
564 assert!(matches!(dir.next(), None));
565 }
566