use crate::{Error, NixPath, Result}; use crate::errno::Errno; use crate::fcntl::{self, OFlag}; use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; use std::ptr; use std::ffi; use crate::sys; #[cfg(target_os = "linux")] use libc::{dirent64 as dirent, readdir64_r as readdir_r}; #[cfg(not(target_os = "linux"))] use libc::{dirent, readdir_r}; /// An open directory. /// /// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: /// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing /// if the path represents a file or directory). /// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. /// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` /// after the `Dir` is dropped. /// * can be iterated through multiple times without closing and reopening the file /// descriptor. Each iteration rewinds when finished. /// * returns entries for `.` (current directory) and `..` (parent directory). /// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc /// does). #[derive(Debug, Eq, Hash, PartialEq)] pub struct Dir( ptr::NonNull ); impl Dir { /// Opens the given path as with `fcntl::open`. pub fn open(path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result { let fd = fcntl::open(path, oflag, mode)?; Dir::from_fd(fd) } /// Opens the given path as with `fcntl::openat`. pub fn openat(dirfd: RawFd, path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result { let fd = fcntl::openat(dirfd, path, oflag, mode)?; Dir::from_fd(fd) } /// Converts from a descriptor-based object, closing the descriptor on success or failure. #[inline] pub fn from(fd: F) -> Result { Dir::from_fd(fd.into_raw_fd()) } /// Converts from a file descriptor, closing it on success or failure. pub fn from_fd(fd: RawFd) -> Result { let d = unsafe { libc::fdopendir(fd) }; if d.is_null() { let e = Error::last(); unsafe { libc::close(fd) }; return Err(e); }; // Always guaranteed to be non-null by the previous check Ok(Dir(ptr::NonNull::new(d).unwrap())) } /// Returns an iterator of `Result` which rewinds when finished. pub fn iter(&mut self) -> Iter { Iter(self) } } // `Dir` is not `Sync`. With the current implementation, it could be, but according to // https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, // future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to // call `readdir` simultaneously from multiple threads. // // `Dir` is safe to pass from one thread to another, as it's not reference-counted. unsafe impl Send for Dir {} impl AsRawFd for Dir { fn as_raw_fd(&self) -> RawFd { unsafe { libc::dirfd(self.0.as_ptr()) } } } impl Drop for Dir { fn drop(&mut self) { let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) }); if !std::thread::panicking() && e == Err(Error::Sys(Errno::EBADF)) { panic!("Closing an invalid file descriptor!"); }; } } #[derive(Debug, Eq, Hash, PartialEq)] pub struct Iter<'d>(&'d mut Dir); impl<'d> Iterator for Iter<'d> { type Item = Result; fn next(&mut self) -> Option { unsafe { // Note: POSIX specifies that portable applications should dynamically allocate a // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 // for the NUL byte. It doesn't look like the std library does this; it just uses // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). // Probably fine here too then. let mut ent = std::mem::MaybeUninit::::uninit(); let mut result = ptr::null_mut(); if let Err(e) = Errno::result( readdir_r((self.0).0.as_ptr(), ent.as_mut_ptr(), &mut result)) { return Some(Err(e)); } if result.is_null() { return None; } assert_eq!(result, ent.as_mut_ptr()); Some(Ok(Entry(ent.assume_init()))) } } } impl<'d> Drop for Iter<'d> { fn drop(&mut self) { unsafe { libc::rewinddir((self.0).0.as_ptr()) } } } /// A directory entry, similar to `std::fs::DirEntry`. /// /// Note that unlike the std version, this may represent the `.` or `..` entries. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] #[repr(transparent)] pub struct Entry(dirent); #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum Type { Fifo, CharacterDevice, Directory, BlockDevice, File, Symlink, Socket, } impl Entry { /// Returns the inode number (`d_ino`) of the underlying `dirent`. #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "haiku", target_os = "ios", target_os = "l4re", target_os = "linux", target_os = "macos", target_os = "solaris"))] pub fn ino(&self) -> u64 { self.0.d_ino as u64 } /// Returns the inode number (`d_fileno`) of the underlying `dirent`. #[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "haiku", target_os = "ios", target_os = "l4re", target_os = "linux", target_os = "macos", target_os = "solaris")))] pub fn ino(&self) -> u64 { u64::from(self.0.d_fileno) } /// Returns the bare file name of this directory entry without any other leading path component. pub fn file_name(&self) -> &ffi::CStr { unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } } /// Returns the type of this directory entry, if known. /// /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; /// notably, some Linux filesystems don't implement this. The caller should use `stat` or /// `fstat` if this returns `None`. pub fn file_type(&self) -> Option { match self.0.d_type { libc::DT_FIFO => Some(Type::Fifo), libc::DT_CHR => Some(Type::CharacterDevice), libc::DT_DIR => Some(Type::Directory), libc::DT_BLK => Some(Type::BlockDevice), libc::DT_REG => Some(Type::File), libc::DT_LNK => Some(Type::Symlink), libc::DT_SOCK => Some(Type::Socket), /* libc::DT_UNKNOWN | */ _ => None, } } }