use crate::fs::{ open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks, Metadata, OpenOptions, ReadDir, }; use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; use io_lifetimes::AsFd; use rustix::fd::OwnedFd; use rustix::fs::Dir; use std::ffi::OsStr; use std::mem::ManuallyDrop; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; use std::path::{Component, Path}; use std::sync::{Arc, Mutex}; use std::{fmt, fs, io}; pub(crate) struct ReadDirInner { raw_fd: RawFd, // `Dir` doesn't implement `AsFd`, because libc `fdopendir` has UB if the // file descriptor is used in almost any way, so we hold a separate // `OwnedFd` that we can do `as_fd()` on. rustix: Arc>, } impl ReadDirInner { pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { let fd = open_dir_for_reading(start, path, follow)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, OwnedFd::from(fd)))), }) } pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { // Open ".", to obtain a new independent file descriptor. Don't use // `dup` since in that case the resulting file descriptor would share // a current position with the original, and `read_dir` calls after // the first `read_dir` call wouldn't start from the beginning. let fd = open_dir_for_reading_unchecked(start, Component::CurDir.as_ref(), FollowSymlinks::No)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, fd.into()))), }) } pub(crate) fn new_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { let fd = open_dir_for_reading_unchecked(start, path, follow)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, fd.into()))), }) } pub(super) fn open(&self, file_name: &OsStr, options: &OpenOptions) -> io::Result { open_entry_impl(&self.as_file_view(), file_name, options) } pub(super) fn metadata(&self, file_name: &OsStr) -> io::Result { stat_unchecked(&self.as_file_view(), file_name.as_ref(), FollowSymlinks::No) } pub(super) fn remove_file(&self, file_name: &OsStr) -> io::Result<()> { remove_file_unchecked(&self.as_file_view(), file_name.as_ref()) } pub(super) fn remove_dir(&self, file_name: &OsStr) -> io::Result<()> { remove_dir_unchecked(&self.as_file_view(), file_name.as_ref()) } pub(super) fn self_metadata(&self) -> io::Result { Metadata::from_file(&self.as_file_view()) } pub(super) fn read_dir( &self, file_name: &OsStr, follow: FollowSymlinks, ) -> io::Result { read_dir_unchecked(&self.as_file_view(), file_name.as_ref(), follow) } #[allow(unsafe_code)] fn as_file_view(&self) -> ManuallyDrop { // Safety: `self.rustix` owns the file descriptor. We just hold a // copy outside so that we can read it without taking a lock. ManuallyDrop::new(unsafe { fs::File::from_raw_fd(self.raw_fd) }) } } impl Iterator for ReadDirInner { type Item = io::Result; fn next(&mut self) -> Option { loop { let entry = self.rustix.lock().unwrap().0.read()?; let entry = match entry { Ok(entry) => entry, Err(e) => return Some(Err(e.into())), }; let file_name = entry.file_name().to_bytes(); if file_name != Component::CurDir.as_os_str().as_bytes() && file_name != Component::ParentDir.as_os_str().as_bytes() { let clone = Arc::clone(&self.rustix); return Some(Ok(DirEntryInner { rustix: entry, read_dir: Self { raw_fd: self.raw_fd, rustix: clone, }, })); } } } } impl fmt::Debug for ReadDirInner { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("ReadDir"); b.field("raw_fd", &self.raw_fd); b.finish() } }