1 use crate::{Error, NixPath, Result}; 2 use crate::errno::Errno; 3 use crate::fcntl::{self, OFlag}; 4 use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; 5 use std::ptr; 6 use std::ffi; 7 use crate::sys; 8 9 #[cfg(target_os = "linux")] 10 use libc::{dirent64 as dirent, readdir64_r as readdir_r}; 11 12 #[cfg(not(target_os = "linux"))] 13 use libc::{dirent, readdir_r}; 14 15 /// An open directory. 16 /// 17 /// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: 18 /// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing 19 /// if the path represents a file or directory). 20 /// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. 21 /// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` 22 /// after the `Dir` is dropped. 23 /// * can be iterated through multiple times without closing and reopening the file 24 /// descriptor. Each iteration rewinds when finished. 25 /// * returns entries for `.` (current directory) and `..` (parent directory). 26 /// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc 27 /// does). 28 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 29 pub struct Dir( 30 ptr::NonNull<libc::DIR> 31 ); 32 33 impl Dir { 34 /// Opens the given path as with `fcntl::open`. open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result<Self>35 pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, 36 mode: sys::stat::Mode) -> Result<Self> { 37 let fd = fcntl::open(path, oflag, mode)?; 38 Dir::from_fd(fd) 39 } 40 41 /// Opens the given path as with `fcntl::openat`. openat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result<Self>42 pub fn openat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P, oflag: OFlag, 43 mode: sys::stat::Mode) -> Result<Self> { 44 let fd = fcntl::openat(dirfd, path, oflag, mode)?; 45 Dir::from_fd(fd) 46 } 47 48 /// Converts from a descriptor-based object, closing the descriptor on success or failure. 49 #[inline] from<F: IntoRawFd>(fd: F) -> Result<Self>50 pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> { 51 Dir::from_fd(fd.into_raw_fd()) 52 } 53 54 /// Converts from a file descriptor, closing it on success or failure. from_fd(fd: RawFd) -> Result<Self>55 pub fn from_fd(fd: RawFd) -> Result<Self> { 56 let d = unsafe { libc::fdopendir(fd) }; 57 if d.is_null() { 58 let e = Error::last(); 59 unsafe { libc::close(fd) }; 60 return Err(e); 61 }; 62 // Always guaranteed to be non-null by the previous check 63 Ok(Dir(ptr::NonNull::new(d).unwrap())) 64 } 65 66 /// Returns an iterator of `Result<Entry>` which rewinds when finished. iter(&mut self) -> Iter67 pub fn iter(&mut self) -> Iter { 68 Iter(self) 69 } 70 } 71 72 // `Dir` is not `Sync`. With the current implementation, it could be, but according to 73 // https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, 74 // future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to 75 // call `readdir` simultaneously from multiple threads. 76 // 77 // `Dir` is safe to pass from one thread to another, as it's not reference-counted. 78 unsafe impl Send for Dir {} 79 80 impl AsRawFd for Dir { as_raw_fd(&self) -> RawFd81 fn as_raw_fd(&self) -> RawFd { 82 unsafe { libc::dirfd(self.0.as_ptr()) } 83 } 84 } 85 86 impl Drop for Dir { drop(&mut self)87 fn drop(&mut self) { 88 unsafe { libc::closedir(self.0.as_ptr()) }; 89 } 90 } 91 92 #[derive(Debug, Eq, Hash, PartialEq)] 93 pub struct Iter<'d>(&'d mut Dir); 94 95 impl<'d> Iterator for Iter<'d> { 96 type Item = Result<Entry>; 97 next(&mut self) -> Option<Self::Item>98 fn next(&mut self) -> Option<Self::Item> { 99 unsafe { 100 // Note: POSIX specifies that portable applications should dynamically allocate a 101 // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 102 // for the NUL byte. It doesn't look like the std library does this; it just uses 103 // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). 104 // Probably fine here too then. 105 let mut ent = std::mem::MaybeUninit::<dirent>::uninit(); 106 let mut result = ptr::null_mut(); 107 if let Err(e) = Errno::result( 108 readdir_r((self.0).0.as_ptr(), ent.as_mut_ptr(), &mut result)) 109 { 110 return Some(Err(e)); 111 } 112 if result.is_null() { 113 return None; 114 } 115 assert_eq!(result, ent.as_mut_ptr()); 116 Some(Ok(Entry(ent.assume_init()))) 117 } 118 } 119 } 120 121 impl<'d> Drop for Iter<'d> { drop(&mut self)122 fn drop(&mut self) { 123 unsafe { libc::rewinddir((self.0).0.as_ptr()) } 124 } 125 } 126 127 /// A directory entry, similar to `std::fs::DirEntry`. 128 /// 129 /// Note that unlike the std version, this may represent the `.` or `..` entries. 130 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 131 #[repr(transparent)] 132 pub struct Entry(dirent); 133 134 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 135 pub enum Type { 136 Fifo, 137 CharacterDevice, 138 Directory, 139 BlockDevice, 140 File, 141 Symlink, 142 Socket, 143 } 144 145 impl Entry { 146 /// Returns the inode number (`d_ino`) of the underlying `dirent`. 147 #[cfg(any(target_os = "android", 148 target_os = "emscripten", 149 target_os = "fuchsia", 150 target_os = "haiku", 151 target_os = "ios", 152 target_os = "l4re", 153 target_os = "linux", 154 target_os = "macos", 155 target_os = "solaris"))] ino(&self) -> u64156 pub fn ino(&self) -> u64 { 157 self.0.d_ino as u64 158 } 159 160 /// Returns the inode number (`d_fileno`) of the underlying `dirent`. 161 #[cfg(not(any(target_os = "android", 162 target_os = "emscripten", 163 target_os = "fuchsia", 164 target_os = "haiku", 165 target_os = "ios", 166 target_os = "l4re", 167 target_os = "linux", 168 target_os = "macos", 169 target_os = "solaris")))] ino(&self) -> u64170 pub fn ino(&self) -> u64 { 171 u64::from(self.0.d_fileno) 172 } 173 174 /// Returns the bare file name of this directory entry without any other leading path component. file_name(&self) -> &ffi::CStr175 pub fn file_name(&self) -> &ffi::CStr { 176 unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } 177 } 178 179 /// Returns the type of this directory entry, if known. 180 /// 181 /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; 182 /// notably, some Linux filesystems don't implement this. The caller should use `stat` or 183 /// `fstat` if this returns `None`. file_type(&self) -> Option<Type>184 pub fn file_type(&self) -> Option<Type> { 185 match self.0.d_type { 186 libc::DT_FIFO => Some(Type::Fifo), 187 libc::DT_CHR => Some(Type::CharacterDevice), 188 libc::DT_DIR => Some(Type::Directory), 189 libc::DT_BLK => Some(Type::BlockDevice), 190 libc::DT_REG => Some(Type::File), 191 libc::DT_LNK => Some(Type::Symlink), 192 libc::DT_SOCK => Some(Type::Socket), 193 /* libc::DT_UNKNOWN | */ _ => None, 194 } 195 } 196 } 197