• 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 #[repr(transparent)]
61 pub struct Dir(NonNull<c::DIR>);
62 
63 impl Dir {
64     /// Construct a `Dir` that reads entries from the given directory
65     /// file descriptor.
66     #[inline]
read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self>67     pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
68         Self::_read_from(fd.as_fd())
69     }
70 
71     #[inline]
_read_from(fd: BorrowedFd<'_>) -> io::Result<Self>72     fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
73         // Given an arbitrary `OwnedFd`, it's impossible to know whether the
74         // user holds a `dup`'d copy which could continue to modify the
75         // file description state, which would cause Undefined Behavior after
76         // our call to `fdopendir`. To prevent this, we obtain an independent
77         // `OwnedFd`.
78         let flags = fcntl_getfl(fd)?;
79         let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
80 
81         let raw = owned_fd(fd_for_dir);
82         unsafe {
83             let libc_dir = c::fdopendir(raw);
84 
85             if let Some(libc_dir) = NonNull::new(libc_dir) {
86                 Ok(Self(libc_dir))
87             } else {
88                 let err = io::Errno::last_os_error();
89                 let _ = c::close(raw);
90                 Err(err)
91             }
92         }
93     }
94 
95     /// `rewinddir(self)`
96     #[inline]
rewind(&mut self)97     pub fn rewind(&mut self) {
98         unsafe { c::rewinddir(self.0.as_ptr()) }
99     }
100 
101     /// `readdir(self)`, where `None` means the end of the directory.
read(&mut self) -> Option<io::Result<DirEntry>>102     pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
103         set_errno(Errno(0));
104         let dirent_ptr = unsafe { libc_readdir(self.0.as_ptr()) };
105         if dirent_ptr.is_null() {
106             let curr_errno = errno().0;
107             if curr_errno == 0 {
108                 // We successfully reached the end of the stream.
109                 None
110             } else {
111                 // `errno` is unknown or non-zero, so an error occurred.
112                 Some(Err(io::Errno(curr_errno)))
113             }
114         } else {
115             // We successfully read an entry.
116             unsafe {
117                 // We have our own copy of OpenBSD's dirent; check that the
118                 // layout minimally matches libc's.
119                 #[cfg(target_os = "openbsd")]
120                 check_dirent_layout(&*dirent_ptr);
121 
122                 let result = DirEntry {
123                     dirent: read_dirent(&*dirent_ptr.cast()),
124 
125                     #[cfg(target_os = "wasi")]
126                     name: CStr::from_ptr((*dirent_ptr).d_name.as_ptr()).to_owned(),
127                 };
128 
129                 Some(Ok(result))
130             }
131         }
132     }
133 
134     /// `fstat(self)`
135     #[inline]
stat(&self) -> io::Result<Stat>136     pub fn stat(&self) -> io::Result<Stat> {
137         fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
138     }
139 
140     /// `fstatfs(self)`
141     #[cfg(not(any(
142         target_os = "haiku",
143         target_os = "illumos",
144         target_os = "netbsd",
145         target_os = "redox",
146         target_os = "solaris",
147         target_os = "wasi",
148     )))]
149     #[inline]
statfs(&self) -> io::Result<StatFs>150     pub fn statfs(&self) -> io::Result<StatFs> {
151         fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
152     }
153 
154     /// `fstatvfs(self)`
155     #[cfg(not(any(
156         target_os = "haiku",
157         target_os = "illumos",
158         target_os = "redox",
159         target_os = "solaris",
160         target_os = "wasi",
161     )))]
162     #[inline]
statvfs(&self) -> io::Result<StatVfs>163     pub fn statvfs(&self) -> io::Result<StatVfs> {
164         fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
165     }
166 
167     /// `fchdir(self)`
168     #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
169     #[inline]
chdir(&self) -> io::Result<()>170     pub fn chdir(&self) -> io::Result<()> {
171         fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
172     }
173 }
174 
175 // A `dirent` pointer returned from `readdir` may not point to a full `dirent`
176 // struct, as the name is NUL-terminated and memory may not be allocated for
177 // the full extent of the struct. Copy the fields one at a time.
read_dirent(input: &libc_dirent) -> libc_dirent178 unsafe fn read_dirent(input: &libc_dirent) -> libc_dirent {
179     #[cfg(not(any(
180         target_os = "aix",
181         target_os = "haiku",
182         target_os = "illumos",
183         target_os = "solaris"
184     )))]
185     let d_type = input.d_type;
186 
187     #[cfg(not(any(
188         target_os = "aix",
189         target_os = "dragonfly",
190         target_os = "freebsd",
191         target_os = "haiku",
192         target_os = "ios",
193         target_os = "macos",
194         target_os = "netbsd",
195         target_os = "wasi",
196     )))]
197     let d_off = input.d_off;
198 
199     #[cfg(target_os = "aix")]
200     let d_offset = input.d_offset;
201 
202     #[cfg(not(any(
203         target_os = "dragonfly",
204         target_os = "freebsd",
205         target_os = "netbsd",
206         target_os = "openbsd",
207     )))]
208     let d_ino = input.d_ino;
209 
210     #[cfg(any(
211         target_os = "dragonfly",
212         target_os = "freebsd",
213         target_os = "netbsd",
214         target_os = "openbsd"
215     ))]
216     let d_fileno = input.d_fileno;
217 
218     #[cfg(not(any(target_os = "dragonfly", target_os = "wasi")))]
219     let d_reclen = input.d_reclen;
220 
221     #[cfg(any(
222         target_os = "dragonfly",
223         target_os = "freebsd",
224         target_os = "netbsd",
225         target_os = "openbsd",
226         target_os = "ios",
227         target_os = "macos",
228     ))]
229     let d_namlen = input.d_namlen;
230 
231     #[cfg(any(target_os = "ios", target_os = "macos"))]
232     let d_seekoff = input.d_seekoff;
233 
234     #[cfg(target_os = "haiku")]
235     let d_dev = input.d_dev;
236     #[cfg(target_os = "haiku")]
237     let d_pdev = input.d_pdev;
238     #[cfg(target_os = "haiku")]
239     let d_pino = input.d_pino;
240 
241     // Construct the input. Rust will give us an error if any OS has a input
242     // with a field that we missed here. And we can avoid blindly copying the
243     // whole `d_name` field, which may not be entirely allocated.
244     #[cfg_attr(target_os = "wasi", allow(unused_mut))]
245     #[cfg(not(any(target_os = "freebsd", target_os = "dragonfly")))]
246     let mut dirent = libc_dirent {
247         #[cfg(not(any(
248             target_os = "aix",
249             target_os = "haiku",
250             target_os = "illumos",
251             target_os = "solaris"
252         )))]
253         d_type,
254         #[cfg(not(any(
255             target_os = "aix",
256             target_os = "freebsd",  // Until FreeBSD 12
257             target_os = "haiku",
258             target_os = "ios",
259             target_os = "macos",
260             target_os = "netbsd",
261             target_os = "wasi",
262         )))]
263         d_off,
264         #[cfg(target_os = "aix")]
265         d_offset,
266         #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))]
267         d_ino,
268         #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
269         d_fileno,
270         #[cfg(not(target_os = "wasi"))]
271         d_reclen,
272         #[cfg(any(
273             target_os = "aix",
274             target_os = "freebsd",
275             target_os = "ios",
276             target_os = "macos",
277             target_os = "netbsd",
278             target_os = "openbsd",
279         ))]
280         d_namlen,
281         #[cfg(any(target_os = "ios", target_os = "macos"))]
282         d_seekoff,
283         // The `d_name` field is NUL-terminated, and we need to be careful not
284         // to read bytes past the NUL, even though they're within the nominal
285         // extent of the `struct dirent`, because they may not be allocated. So
286         // don't read it from `dirent_ptr`.
287         //
288         // In theory this could use `MaybeUninit::uninit().assume_init()`, but
289         // that [invokes undefined behavior].
290         //
291         // [invokes undefined behavior]: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#initialization-invariant
292         d_name: zeroed(),
293         #[cfg(target_os = "openbsd")]
294         __d_padding: zeroed(),
295         #[cfg(target_os = "haiku")]
296         d_dev,
297         #[cfg(target_os = "haiku")]
298         d_pdev,
299         #[cfg(target_os = "haiku")]
300         d_pino,
301     };
302     /*
303     pub d_ino: ino_t,
304     pub d_pino: i64,
305     pub d_reclen: ::c_ushort,
306     pub d_name: [::c_char; 1024], // Max length is _POSIX_PATH_MAX
307                                   // */
308 
309     // On dragonfly and FreeBSD 12, `dirent` has some non-public padding fields
310     // so we can't directly initialize it.
311     #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
312     let mut dirent = {
313         let mut dirent: libc_dirent = zeroed();
314         dirent.d_fileno = d_fileno;
315         dirent.d_namlen = d_namlen;
316         dirent.d_type = d_type;
317         #[cfg(target_os = "freebsd")]
318         {
319             dirent.d_reclen = d_reclen;
320         }
321         dirent
322     };
323 
324     // Copy from d_name, reading up to and including the first NUL.
325     #[cfg(not(target_os = "wasi"))]
326     {
327         let name_len = CStr::from_ptr(input.d_name.as_ptr())
328             .to_bytes_with_nul()
329             .len();
330         dirent.d_name[..name_len].copy_from_slice(&input.d_name[..name_len]);
331     }
332 
333     dirent
334 }
335 
336 /// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
337 /// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
338 /// need `Sync`, which is effectively what'd need to do to implement `Sync`
339 /// ourselves.
340 unsafe impl Send for Dir {}
341 
342 impl Drop for Dir {
343     #[inline]
drop(&mut self)344     fn drop(&mut self) {
345         unsafe { c::closedir(self.0.as_ptr()) };
346     }
347 }
348 
349 impl Iterator for Dir {
350     type Item = io::Result<DirEntry>;
351 
352     #[inline]
next(&mut self) -> Option<Self::Item>353     fn next(&mut self) -> Option<Self::Item> {
354         Self::read(self)
355     }
356 }
357 
358 impl fmt::Debug for Dir {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result359     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360         f.debug_struct("Dir")
361             .field("fd", unsafe { &c::dirfd(self.0.as_ptr()) })
362             .finish()
363     }
364 }
365 
366 /// `struct dirent`
367 #[derive(Debug)]
368 pub struct DirEntry {
369     dirent: libc_dirent,
370 
371     #[cfg(target_os = "wasi")]
372     name: CString,
373 }
374 
375 impl DirEntry {
376     /// Returns the file name of this directory entry.
377     #[inline]
file_name(&self) -> &CStr378     pub fn file_name(&self) -> &CStr {
379         #[cfg(not(target_os = "wasi"))]
380         unsafe {
381             CStr::from_ptr(self.dirent.d_name.as_ptr())
382         }
383 
384         #[cfg(target_os = "wasi")]
385         &self.name
386     }
387 
388     /// Returns the type of this directory entry.
389     #[cfg(not(any(
390         target_os = "aix",
391         target_os = "haiku",
392         target_os = "illumos",
393         target_os = "solaris"
394     )))]
395     #[inline]
file_type(&self) -> FileType396     pub fn file_type(&self) -> FileType {
397         FileType::from_dirent_d_type(self.dirent.d_type)
398     }
399 
400     /// Return the inode number of this directory entry.
401     #[cfg(not(any(
402         target_os = "dragonfly",
403         target_os = "freebsd",
404         target_os = "netbsd",
405         target_os = "openbsd",
406     )))]
407     #[inline]
ino(&self) -> u64408     pub fn ino(&self) -> u64 {
409         self.dirent.d_ino as u64
410     }
411 
412     /// Return the inode number of this directory entry.
413     #[cfg(any(
414         target_os = "dragonfly",
415         target_os = "freebsd",
416         target_os = "netbsd",
417         target_os = "openbsd",
418     ))]
419     #[inline]
ino(&self) -> u64420     pub fn ino(&self) -> u64 {
421         #[allow(clippy::useless_conversion)]
422         self.dirent.d_fileno.into()
423     }
424 }
425 
426 /// libc's OpenBSD `dirent` has a private field so we can't construct it
427 /// directly, so we declare it ourselves to make all fields accessible.
428 #[cfg(target_os = "openbsd")]
429 #[repr(C)]
430 #[derive(Debug)]
431 struct libc_dirent {
432     d_fileno: c::ino_t,
433     d_off: c::off_t,
434     d_reclen: u16,
435     d_type: u8,
436     d_namlen: u8,
437     __d_padding: [u8; 4],
438     d_name: [c::c_char; 256],
439 }
440 
441 /// We have our own copy of OpenBSD's dirent; check that the layout
442 /// minimally matches libc's.
443 #[cfg(target_os = "openbsd")]
check_dirent_layout(dirent: &c::dirent)444 fn check_dirent_layout(dirent: &c::dirent) {
445     use crate::utils::as_ptr;
446     use core::mem::{align_of, size_of};
447 
448     // Check that the basic layouts match.
449     assert_eq!(size_of::<libc_dirent>(), size_of::<c::dirent>());
450     assert_eq!(align_of::<libc_dirent>(), align_of::<c::dirent>());
451 
452     // Check that the field offsets match.
453     assert_eq!(
454         {
455             let z = libc_dirent {
456                 d_fileno: 0_u64,
457                 d_off: 0_i64,
458                 d_reclen: 0_u16,
459                 d_type: 0_u8,
460                 d_namlen: 0_u8,
461                 __d_padding: [0_u8; 4],
462                 d_name: [0 as c::c_char; 256],
463             };
464             let base = as_ptr(&z) as usize;
465             (
466                 (as_ptr(&z.d_fileno) as usize) - base,
467                 (as_ptr(&z.d_off) as usize) - base,
468                 (as_ptr(&z.d_reclen) as usize) - base,
469                 (as_ptr(&z.d_type) as usize) - base,
470                 (as_ptr(&z.d_namlen) as usize) - base,
471                 (as_ptr(&z.d_name) as usize) - base,
472             )
473         },
474         {
475             let z = dirent;
476             let base = as_ptr(z) as usize;
477             (
478                 (as_ptr(&z.d_fileno) as usize) - base,
479                 (as_ptr(&z.d_off) as usize) - base,
480                 (as_ptr(&z.d_reclen) as usize) - base,
481                 (as_ptr(&z.d_type) as usize) - base,
482                 (as_ptr(&z.d_namlen) as usize) - base,
483                 (as_ptr(&z.d_name) as usize) - base,
484             )
485         }
486     );
487 }
488