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]
81 pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
82 Self::_read_from(fd.as_fd())
83 }
84
85 #[inline]
86 #[allow(unused_mut)]
87 fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
88 let mut any_errors = false;
89
90 let flags = fcntl_getfl(fd)?;
96 let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
97 Ok(fd) => fd,
98 #[cfg(not(target_os = "wasi"))]
99 Err(io::Errno::NOENT) => {
100 any_errors = true;
104 crate::io::dup(fd)?
105 }
106 Err(err) => return Err(err),
107 };
108
109 let raw = owned_fd(fd_for_dir);
110 unsafe {
111 let libc_dir = c::fdopendir(raw);
112
113 if let Some(libc_dir) = NonNull::new(libc_dir) {
114 Ok(Self {
115 libc_dir,
116 any_errors,
117 })
118 } else {
119 let err = io::Errno::last_os_error();
120 let _ = c::close(raw);
121 Err(err)
122 }
123 }
124 }
125
126 #[inline]
128 pub fn rewind(&mut self) {
129 self.any_errors = false;
130 unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
131 }
132
133 #[cfg(target_pointer_width = "64")]
141 #[cfg_attr(docsrs, doc(cfg(target_pointer_width = "64")))]
142 #[doc(alias = "seekdir")]
143 #[inline]
144 pub fn seek(&mut self, offset: i64) -> io::Result<()> {
145 self.any_errors = false;
146 unsafe { c::seekdir(self.libc_dir.as_ptr(), offset) }
147 Ok(())
148 }
149
150 pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
152 if self.any_errors {
155 return None;
156 }
157
158 set_errno(Errno(0));
159 let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
160 if dirent_ptr.is_null() {
161 let curr_errno = errno().0;
162 if curr_errno == 0 {
163 None
165 } else {
166 self.any_errors = true;
168 Some(Err(io::Errno(curr_errno)))
169 }
170 } else {
171 unsafe {
173 let dirent = &*dirent_ptr;
174
175 #[cfg(target_os = "openbsd")]
178 check_dirent_layout(dirent);
179
180 let result = DirEntry {
181 #[cfg(not(any(
182 solarish,
183 target_os = "aix",
184 target_os = "haiku",
185 target_os = "nto",
186 target_os = "vita"
187 )))]
188 d_type: dirent.d_type,
189
190 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
191 d_ino: dirent.d_ino,
192
193 #[cfg(any(
194 linux_like,
195 solarish,
196 target_os = "fuchsia",
197 target_os = "hermit",
198 target_os = "openbsd",
199 target_os = "redox"
200 ))]
201 d_off: dirent.d_off,
202
203 #[cfg(any(freebsdlike, netbsdlike))]
204 d_fileno: dirent.d_fileno,
205
206 name: CStr::from_ptr(dirent.d_name.as_ptr().cast()).to_owned(),
207 };
208
209 Some(Ok(result))
210 }
211 }
212 }
213
214 #[cfg(not(any(target_os = "horizon", target_os = "vita")))]
216 #[inline]
217 pub fn stat(&self) -> io::Result<Stat> {
218 fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
219 }
220
221 #[cfg(not(any(
223 solarish,
224 target_os = "haiku",
225 target_os = "horizon",
226 target_os = "netbsd",
227 target_os = "nto",
228 target_os = "redox",
229 target_os = "vita",
230 target_os = "wasi",
231 )))]
232 #[inline]
233 pub fn statfs(&self) -> io::Result<StatFs> {
234 fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
235 }
236
237 #[cfg(not(any(
239 solarish,
240 target_os = "horizon",
241 target_os = "vita",
242 target_os = "wasi"
243 )))]
244 #[inline]
245 pub fn statvfs(&self) -> io::Result<StatVfs> {
246 fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
247 }
248
249 #[cfg(feature = "process")]
251 #[cfg(not(any(
252 target_os = "fuchsia",
253 target_os = "horizon",
254 target_os = "vita",
255 target_os = "wasi"
256 )))]
257 #[cfg_attr(docsrs, doc(cfg(feature = "process")))]
258 #[inline]
259 pub fn chdir(&self) -> io::Result<()> {
260 fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
261 }
262}
263
264unsafe impl Send for Dir {}
269unsafe impl Sync for Dir {}
270
271impl Drop for Dir {
272 #[inline]
273 fn drop(&mut self) {
274 unsafe { c::closedir(self.libc_dir.as_ptr()) };
275 }
276}
277
278impl Iterator for Dir {
279 type Item = io::Result<DirEntry>;
280
281 #[inline]
282 fn next(&mut self) -> Option<Self::Item> {
283 Self::read(self)
284 }
285}
286
287impl fmt::Debug for Dir {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 let mut s = f.debug_struct("Dir");
290 #[cfg(not(any(target_os = "horizon", target_os = "vita")))]
291 s.field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) });
292 s.finish()
293 }
294}
295
296#[derive(Debug)]
298pub struct DirEntry {
299 #[cfg(not(any(
300 solarish,
301 target_os = "aix",
302 target_os = "haiku",
303 target_os = "nto",
304 target_os = "vita"
305 )))]
306 d_type: u8,
307
308 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
309 d_ino: c::ino_t,
310
311 #[cfg(any(freebsdlike, netbsdlike))]
312 d_fileno: c::ino_t,
313
314 name: CString,
315
316 #[cfg(any(
317 linux_like,
318 solarish,
319 target_os = "fuchsia",
320 target_os = "hermit",
321 target_os = "openbsd",
322 target_os = "redox"
323 ))]
324 d_off: c::off_t,
325}
326
327impl DirEntry {
328 #[inline]
330 pub fn file_name(&self) -> &CStr {
331 &self.name
332 }
333
334 #[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 #[inline]
346 pub fn offset(&self) -> i64 {
347 self.d_off as i64
348 }
349
350 #[cfg(not(any(
352 solarish,
353 target_os = "aix",
354 target_os = "haiku",
355 target_os = "nto",
356 target_os = "vita"
357 )))]
358 #[inline]
359 pub fn file_type(&self) -> FileType {
360 FileType::from_dirent_d_type(self.d_type)
361 }
362
363 #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
365 #[inline]
366 pub fn ino(&self) -> u64 {
367 self.d_ino as u64
368 }
369
370 #[cfg(any(freebsdlike, netbsdlike))]
372 #[inline]
373 pub fn ino(&self) -> u64 {
374 #[allow(clippy::useless_conversion)]
375 self.d_fileno.into()
376 }
377}
378
379#[cfg(target_os = "openbsd")]
382#[repr(C)]
383#[derive(Debug)]
384struct libc_dirent {
385 d_fileno: c::ino_t,
386 d_off: c::off_t,
387 d_reclen: u16,
388 d_type: u8,
389 d_namlen: u8,
390 __d_padding: [u8; 4],
391 d_name: [c::c_char; 256],
392}
393
394#[cfg(target_os = "openbsd")]
397fn check_dirent_layout(dirent: &c::dirent) {
398 use crate::utils::as_ptr;
399
400 #[cfg(test)]
402 {
403 assert_eq_size!(libc_dirent, c::dirent);
404 assert_eq_size!(libc_dirent, c::dirent);
405 }
406
407 assert_eq!(
409 {
410 let z = libc_dirent {
411 d_fileno: 0_u64,
412 d_off: 0_i64,
413 d_reclen: 0_u16,
414 d_type: 0_u8,
415 d_namlen: 0_u8,
416 __d_padding: [0_u8; 4],
417 d_name: [0 as c::c_char; 256],
418 };
419 let base = as_ptr(&z) as usize;
420 (
421 (as_ptr(&z.d_fileno) as usize) - base,
422 (as_ptr(&z.d_off) as usize) - base,
423 (as_ptr(&z.d_reclen) as usize) - base,
424 (as_ptr(&z.d_type) as usize) - base,
425 (as_ptr(&z.d_namlen) as usize) - base,
426 (as_ptr(&z.d_name) as usize) - base,
427 )
428 },
429 {
430 let z = dirent;
431 let base = as_ptr(z) as usize;
432 (
433 (as_ptr(&z.d_fileno) as usize) - base,
434 (as_ptr(&z.d_off) as usize) - base,
435 (as_ptr(&z.d_reclen) as usize) - base,
436 (as_ptr(&z.d_type) as usize) - base,
437 (as_ptr(&z.d_namlen) as usize) - base,
438 (as_ptr(&z.d_name) as usize) - base,
439 )
440 }
441 );
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn dir_iterator_handles_io_errors() {
450 let tmp = tempfile::tempdir().unwrap();
452 let fd = crate::fs::openat(
453 crate::fs::CWD,
454 tmp.path(),
455 crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
456 crate::fs::Mode::empty(),
457 )
458 .unwrap();
459
460 let file_fd = crate::fs::openat(
461 &fd,
462 tmp.path().join("test.txt"),
463 crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
464 crate::fs::Mode::RWXU,
465 )
466 .unwrap();
467
468 let mut dir = Dir::read_from(&fd).unwrap();
469
470 unsafe {
473 let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
474 let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
475 crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
476 core::mem::forget(owned_fd);
477 }
478
479 #[cfg(any(apple, freebsdlike))]
482 {
483 dir.rewind();
484 }
485
486 assert!(matches!(dir.next(), Some(Err(_))));
487 assert!(dir.next().is_none());
488 }
489}