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