• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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