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