1#[cfg(not(any(
2 solarish,
3 target_os = "aix",
4 target_os = "haiku",
5 target_os = "nto",
6 target_os = "vita"
7)))]
8use super::types::FileType;
9use crate::backend::c;
10use crate::backend::conv::owned_fd;
11use crate::fd::{AsFd, BorrowedFd, OwnedFd};
12use crate::ffi::{CStr, CString};
13use crate::fs::{fcntl_getfl, openat, Mode, OFlags};
14#[cfg(not(target_os = "vita"))]
15use crate::fs::{fstat, Stat};
16#[cfg(not(any(
17 solarish,
18 target_os = "haiku",
19 target_os = "horizon",
20 target_os = "netbsd",
21 target_os = "nto",
22 target_os = "redox",
23 target_os = "vita",
24 target_os = "wasi",
25)))]
26use crate::fs::{fstatfs, StatFs};
27#[cfg(not(any(
28 solarish,
29 target_os = "haiku",
30 target_os = "redox",
31 target_os = "vita",
32 target_os = "wasi"
33)))]
34use crate::fs::{fstatvfs, StatVfs};
35use crate::io;
36#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
37#[cfg(feature = "process")]
38use crate::process::fchdir;
39use alloc::borrow::ToOwned as _;
40#[cfg(not(any(linux_like, target_os = "hurd")))]
41use c::readdir as libc_readdir;
42#[cfg(any(linux_like, target_os = "hurd"))]
43use c::readdir64 as libc_readdir;
44use core::fmt;
45use core::ptr::NonNull;
46use libc_errno::{errno, set_errno, Errno};
47
48pub struct Dir {
50 libc_dir: NonNull<c::DIR>,
52
53 any_errors: bool,
55}
56
57impl Dir {
58 #[inline]
61 pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
62 Self::_new(fd.into())
63 }
64
65 #[inline]
66 fn _new(fd: OwnedFd) -> io::Result<Self> {
67 let raw = owned_fd(fd);
68 unsafe {
69 let libc_dir = c::fdopendir(raw);
70
71 if let Some(libc_dir) = NonNull::new(libc_dir) {
72 Ok(Self {
73 libc_dir,
74 any_errors: false,
75 })
76 } else {
77 let err = io::Errno::last_os_error();
78 let _ = c::close(raw);
79 Err(err)
80 }
81 }
82 }
83
84 #[inline]
87 pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
88 Self::_read_from(fd.as_fd())
89 }
90
91 #[inline]
92 #[allow(unused_mut)]
93 fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
94 let mut any_errors = false;
95
96 let flags = fcntl_getfl(fd)?;
102 let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
103 Ok(fd) => fd,
104 #[cfg(not(target_os = "wasi"))]
105 Err(io::Errno::NOENT) => {
106 any_errors = true;
110 crate::io::dup(fd)?
111 }
112 Err(err) => return Err(err),
113 };
114
115 let raw = owned_fd(fd_for_dir);
116 unsafe {
117 let libc_dir = c::fdopendir(raw);
118
119 if let Some(libc_dir) = NonNull::new(libc_dir) {
120 Ok(Self {
121 libc_dir,
122 any_errors,
123 })
124 } else {
125 let err = io::Errno::last_os_error();
126 let _ = c::close(raw);
127 Err(err)
128 }
129 }
130 }
131
132 #[inline]
134 pub fn rewind(&mut self) {
135 self.any_errors = false;
136 unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
137 }
138
139 #[cfg(target_pointer_width = "64")]
147 #[cfg_attr(docsrs, doc(cfg(target_pointer_width = "64")))]
148 #[doc(alias = "seekdir")]
149 #[inline]
150 pub fn seek(&mut self, offset: i64) -> io::Result<()> {
151 self.any_errors = false;
152 unsafe { c::seekdir(self.libc_dir.as_ptr(), offset) }
153 Ok(())
154 }
155
156 pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
158 if self.any_errors {
161 return None;
162 }
163
164 set_errno(Errno(0));
165 let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
166 if dirent_ptr.is_null() {
167 let curr_errno = errno().0;
168 if curr_errno == 0 {
169 None
171 } else {
172 self.any_errors = true;
174 Some(Err(io::Errno(curr_errno)))
175 }
176 } else {
177 unsafe {
179 let dirent = &*dirent_ptr;
180
181 #[cfg(target_os = "openbsd")]
184 check_dirent_layout(dirent);
185
186 let result = DirEntry {
187 #[cfg(not(any(
188 solarish,
189 target_os = "aix",
190 target_os = "haiku",
191 target_os = "nto",
192 target_os = "vita"
193 )))]
194 d_type: dirent.d_type,
195
196 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
197 d_ino: dirent.d_ino,
198
199 #[cfg(any(
200 linux_like,
201 solarish,
202 target_os = "fuchsia",
203 target_os = "hermit",
204 target_os = "openbsd",
205 target_os = "redox"
206 ))]
207 d_off: dirent.d_off,
208
209 #[cfg(any(freebsdlike, netbsdlike))]
210 d_fileno: dirent.d_fileno,
211
212 name: CStr::from_ptr(dirent.d_name.as_ptr().cast()).to_owned(),
213 };
214
215 Some(Ok(result))
216 }
217 }
218 }
219
220 #[cfg(not(any(target_os = "horizon", target_os = "vita")))]
222 #[inline]
223 pub fn stat(&self) -> io::Result<Stat> {
224 fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
225 }
226
227 #[cfg(not(any(
229 solarish,
230 target_os = "haiku",
231 target_os = "horizon",
232 target_os = "netbsd",
233 target_os = "nto",
234 target_os = "redox",
235 target_os = "vita",
236 target_os = "wasi",
237 )))]
238 #[inline]
239 pub fn statfs(&self) -> io::Result<StatFs> {
240 fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
241 }
242
243 #[cfg(not(any(
245 solarish,
246 target_os = "haiku",
247 target_os = "horizon",
248 target_os = "redox",
249 target_os = "vita",
250 target_os = "wasi"
251 )))]
252 #[inline]
253 pub fn statvfs(&self) -> io::Result<StatVfs> {
254 fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
255 }
256
257 #[cfg(feature = "process")]
259 #[cfg(not(any(
260 target_os = "fuchsia",
261 target_os = "horizon",
262 target_os = "vita",
263 target_os = "wasi"
264 )))]
265 #[cfg_attr(docsrs, doc(cfg(feature = "process")))]
266 #[inline]
267 pub fn chdir(&self) -> io::Result<()> {
268 fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
269 }
270}
271
272unsafe impl Send for Dir {}
277unsafe impl Sync for Dir {}
278
279impl Drop for Dir {
280 #[inline]
281 fn drop(&mut self) {
282 unsafe { c::closedir(self.libc_dir.as_ptr()) };
283 }
284}
285
286impl Iterator for Dir {
287 type Item = io::Result<DirEntry>;
288
289 #[inline]
290 fn next(&mut self) -> Option<Self::Item> {
291 Self::read(self)
292 }
293}
294
295impl fmt::Debug for Dir {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 let mut s = f.debug_struct("Dir");
298 #[cfg(not(any(target_os = "horizon", target_os = "vita")))]
299 s.field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) });
300 s.finish()
301 }
302}
303
304#[derive(Debug)]
306pub struct DirEntry {
307 #[cfg(not(any(
308 solarish,
309 target_os = "aix",
310 target_os = "haiku",
311 target_os = "nto",
312 target_os = "vita"
313 )))]
314 d_type: u8,
315
316 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
317 d_ino: c::ino_t,
318
319 #[cfg(any(freebsdlike, netbsdlike))]
320 d_fileno: c::ino_t,
321
322 name: CString,
323
324 #[cfg(any(
325 linux_like,
326 solarish,
327 target_os = "fuchsia",
328 target_os = "hermit",
329 target_os = "openbsd",
330 target_os = "redox"
331 ))]
332 d_off: c::off_t,
333}
334
335impl DirEntry {
336 #[inline]
338 pub fn file_name(&self) -> &CStr {
339 &self.name
340 }
341
342 #[cfg(any(
346 linux_like,
347 solarish,
348 target_os = "fuchsia",
349 target_os = "hermit",
350 target_os = "openbsd",
351 target_os = "redox"
352 ))]
353 #[inline]
354 pub fn offset(&self) -> i64 {
355 self.d_off as i64
356 }
357
358 #[cfg(not(any(
360 solarish,
361 target_os = "aix",
362 target_os = "haiku",
363 target_os = "nto",
364 target_os = "vita"
365 )))]
366 #[inline]
367 pub fn file_type(&self) -> FileType {
368 FileType::from_dirent_d_type(self.d_type)
369 }
370
371 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
373 #[inline]
374 pub fn ino(&self) -> u64 {
375 self.d_ino as u64
376 }
377
378 #[cfg(any(freebsdlike, netbsdlike))]
380 #[inline]
381 pub fn ino(&self) -> u64 {
382 #[allow(clippy::useless_conversion)]
383 self.d_fileno.into()
384 }
385}
386
387#[cfg(target_os = "openbsd")]
390#[repr(C)]
391#[derive(Debug)]
392struct libc_dirent {
393 d_fileno: c::ino_t,
394 d_off: c::off_t,
395 d_reclen: u16,
396 d_type: u8,
397 d_namlen: u8,
398 __d_padding: [u8; 4],
399 d_name: [c::c_char; 256],
400}
401
402#[cfg(target_os = "openbsd")]
405fn check_dirent_layout(dirent: &c::dirent) {
406 use crate::utils::as_ptr;
407
408 #[cfg(test)]
410 {
411 assert_eq_size!(libc_dirent, c::dirent);
412 assert_eq_size!(libc_dirent, c::dirent);
413 }
414
415 assert_eq!(
417 {
418 let z = libc_dirent {
419 d_fileno: 0_u64,
420 d_off: 0_i64,
421 d_reclen: 0_u16,
422 d_type: 0_u8,
423 d_namlen: 0_u8,
424 __d_padding: [0_u8; 4],
425 d_name: [0 as c::c_char; 256],
426 };
427 let base = as_ptr(&z) as usize;
428 (
429 (as_ptr(&z.d_fileno) as usize) - base,
430 (as_ptr(&z.d_off) as usize) - base,
431 (as_ptr(&z.d_reclen) as usize) - base,
432 (as_ptr(&z.d_type) as usize) - base,
433 (as_ptr(&z.d_namlen) as usize) - base,
434 (as_ptr(&z.d_name) as usize) - base,
435 )
436 },
437 {
438 let z = dirent;
439 let base = as_ptr(z) as usize;
440 (
441 (as_ptr(&z.d_fileno) as usize) - base,
442 (as_ptr(&z.d_off) as usize) - base,
443 (as_ptr(&z.d_reclen) as usize) - base,
444 (as_ptr(&z.d_type) as usize) - base,
445 (as_ptr(&z.d_namlen) as usize) - base,
446 (as_ptr(&z.d_name) as usize) - base,
447 )
448 }
449 );
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn dir_iterator_handles_io_errors() {
458 let tmp = tempfile::tempdir().unwrap();
460 let fd = crate::fs::openat(
461 crate::fs::CWD,
462 tmp.path(),
463 crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
464 crate::fs::Mode::empty(),
465 )
466 .unwrap();
467
468 let file_fd = crate::fs::openat(
469 &fd,
470 tmp.path().join("test.txt"),
471 crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
472 crate::fs::Mode::RWXU,
473 )
474 .unwrap();
475
476 let mut dir = Dir::read_from(&fd).unwrap();
477
478 unsafe {
481 let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
482 let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
483 crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
484 core::mem::forget(owned_fd);
485 }
486
487 #[cfg(any(apple, freebsdlike))]
490 {
491 dir.rewind();
492 }
493
494 assert!(matches!(dir.next(), Some(Err(_))));
495 assert!(dir.next().is_none());
496 }
497}