1//! List directory contents 2 3use crate::errno::Errno; 4use crate::fcntl::{self, OFlag}; 5use crate::sys; 6use crate::{Error, NixPath, Result}; 7use cfg_if::cfg_if; 8use std::ffi; 9use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; 10use std::ptr; 11 12#[cfg(target_os = "linux")] 13use libc::{dirent64 as dirent, readdir64_r as readdir_r}; 14 15#[cfg(not(target_os = "linux"))] 16use libc::{dirent, readdir_r}; 17 18/// An open directory. 19/// 20/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: 21/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing 22/// if the path represents a file or directory). 23/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. 24/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` 25/// after the `Dir` is dropped. 26/// * can be iterated through multiple times without closing and reopening the file 27/// descriptor. Each iteration rewinds when finished. 28/// * returns entries for `.` (current directory) and `..` (parent directory). 29/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc 30/// does). 31#[derive(Debug, Eq, Hash, PartialEq)] 32pub struct Dir(ptr::NonNull<libc::DIR>); 33 34impl Dir { 35 /// Opens the given path as with `fcntl::open`. 36 pub fn open<P: ?Sized + NixPath>( 37 path: &P, 38 oflag: OFlag, 39 mode: sys::stat::Mode, 40 ) -> Result<Self> { 41 let fd = fcntl::open(path, oflag, mode)?; 42 Dir::from_fd(fd) 43 } 44 45 /// Opens the given path as with `fcntl::openat`. 46 pub fn openat<P: ?Sized + NixPath>( 47 dirfd: RawFd, 48 path: &P, 49 oflag: OFlag, 50 mode: sys::stat::Mode, 51 ) -> Result<Self> { 52 let fd = fcntl::openat(dirfd, path, oflag, mode)?; 53 Dir::from_fd(fd) 54 } 55 56 /// Converts from a descriptor-based object, closing the descriptor on success or failure. 57 #[inline] 58 pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> { 59 Dir::from_fd(fd.into_raw_fd()) 60 } 61 62 /// Converts from a file descriptor, closing it on success or failure. 63 #[doc(alias("fdopendir"))] 64 pub fn from_fd(fd: RawFd) -> Result<Self> { 65 let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else( 66 || { 67 let e = Error::last(); 68 unsafe { libc::close(fd) }; 69 e 70 }, 71 )?; 72 Ok(Dir(d)) 73 } 74 75 /// Returns an iterator of `Result<Entry>` which rewinds when finished. 76 pub fn iter(&mut self) -> Iter { 77 Iter(self) 78 } 79} 80 81// `Dir` is not `Sync`. With the current implementation, it could be, but according to 82// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, 83// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to 84// call `readdir` simultaneously from multiple threads. 85// 86// `Dir` is safe to pass from one thread to another, as it's not reference-counted. 87unsafe impl Send for Dir {} 88 89impl AsRawFd for Dir { 90 fn as_raw_fd(&self) -> RawFd { 91 unsafe { libc::dirfd(self.0.as_ptr()) } 92 } 93} 94 95impl Drop for Dir { 96 fn drop(&mut self) { 97 let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) }); 98 if !std::thread::panicking() && e == Err(Errno::EBADF) { 99 panic!("Closing an invalid file descriptor!"); 100 }; 101 } 102} 103 104fn next(dir: &mut Dir) -> Option<Result<Entry>> { 105 unsafe { 106 // Note: POSIX specifies that portable applications should dynamically allocate a 107 // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 108 // for the NUL byte. It doesn't look like the std library does this; it just uses 109 // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). 110 // Probably fine here too then. 111 let mut ent = std::mem::MaybeUninit::<dirent>::uninit(); 112 let mut result = ptr::null_mut(); 113 if let Err(e) = Errno::result(readdir_r( 114 dir.0.as_ptr(), 115 ent.as_mut_ptr(), 116 &mut result, 117 )) { 118 return Some(Err(e)); 119 } 120 if result.is_null() { 121 return None; 122 } 123 assert_eq!(result, ent.as_mut_ptr()); 124 Some(Ok(Entry(ent.assume_init()))) 125 } 126} 127 128/// Return type of [`Dir::iter`]. 129#[derive(Debug, Eq, Hash, PartialEq)] 130pub struct Iter<'d>(&'d mut Dir); 131 132impl<'d> Iterator for Iter<'d> { 133 type Item = Result<Entry>; 134 135 fn next(&mut self) -> Option<Self::Item> { 136 next(self.0) 137 } 138} 139 140impl<'d> Drop for Iter<'d> { 141 fn drop(&mut self) { 142 unsafe { libc::rewinddir((self.0).0.as_ptr()) } 143 } 144} 145 146/// The return type of [Dir::into_iter] 147#[derive(Debug, Eq, Hash, PartialEq)] 148pub struct OwningIter(Dir); 149 150impl Iterator for OwningIter { 151 type Item = Result<Entry>; 152 153 fn next(&mut self) -> Option<Self::Item> { 154 next(&mut self.0) 155 } 156} 157 158/// The file descriptor continues to be owned by the `OwningIter`, 159/// so callers must not keep a `RawFd` after the `OwningIter` is dropped. 160impl AsRawFd for OwningIter { 161 fn as_raw_fd(&self) -> RawFd { 162 self.0.as_raw_fd() 163 } 164} 165 166impl IntoIterator for Dir { 167 type Item = Result<Entry>; 168 type IntoIter = OwningIter; 169 170 /// Creates a owning iterator, that is, one that takes ownership of the 171 /// `Dir`. The `Dir` cannot be used after calling this. This can be useful 172 /// when you have a function that both creates a `Dir` instance and returns 173 /// an `Iterator`. 174 /// 175 /// Example: 176 /// 177 /// ``` 178 /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode}; 179 /// use std::{iter::Iterator, string::String}; 180 /// 181 /// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> { 182 /// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap(); 183 /// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase()) 184 /// } 185 /// ``` 186 fn into_iter(self) -> Self::IntoIter { 187 OwningIter(self) 188 } 189} 190 191/// A directory entry, similar to `std::fs::DirEntry`. 192/// 193/// Note that unlike the std version, this may represent the `.` or `..` entries. 194#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 195#[repr(transparent)] 196pub struct Entry(dirent); 197 198/// Type of file referenced by a directory entry 199#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 200pub enum Type { 201 /// FIFO (Named pipe) 202 Fifo, 203 /// Character device 204 CharacterDevice, 205 /// Directory 206 Directory, 207 /// Block device 208 BlockDevice, 209 /// Regular file 210 File, 211 /// Symbolic link 212 Symlink, 213 /// Unix-domain socket 214 Socket, 215} 216 217impl Entry { 218 /// Returns the inode number (`d_ino`) of the underlying `dirent`. 219 #[allow(clippy::useless_conversion)] // Not useless on all OSes 220 // The cast is not unnecessary on all platforms. 221 #[allow(clippy::unnecessary_cast)] 222 pub fn ino(&self) -> u64 { 223 cfg_if! { 224 if #[cfg(any(target_os = "android", 225 target_os = "emscripten", 226 target_os = "fuchsia", 227 target_os = "haiku", 228 target_os = "illumos", 229 target_os = "ios", 230 target_os = "l4re", 231 target_os = "linux", 232 target_os = "macos", 233 target_os = "solaris"))] { 234 self.0.d_ino as u64 235 } else { 236 u64::from(self.0.d_fileno) 237 } 238 } 239 } 240 241 /// Returns the bare file name of this directory entry without any other leading path component. 242 pub fn file_name(&self) -> &ffi::CStr { 243 unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } 244 } 245 246 /// Returns the type of this directory entry, if known. 247 /// 248 /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; 249 /// notably, some Linux filesystems don't implement this. The caller should use `stat` or 250 /// `fstat` if this returns `None`. 251 pub fn file_type(&self) -> Option<Type> { 252 #[cfg(not(any( 253 target_os = "illumos", 254 target_os = "solaris", 255 target_os = "haiku" 256 )))] 257 match self.0.d_type { 258 libc::DT_FIFO => Some(Type::Fifo), 259 libc::DT_CHR => Some(Type::CharacterDevice), 260 libc::DT_DIR => Some(Type::Directory), 261 libc::DT_BLK => Some(Type::BlockDevice), 262 libc::DT_REG => Some(Type::File), 263 libc::DT_LNK => Some(Type::Symlink), 264 libc::DT_SOCK => Some(Type::Socket), 265 /* libc::DT_UNKNOWN | */ _ => None, 266 } 267 268 // illumos, Solaris, and Haiku systems do not have the d_type member at all: 269 #[cfg(any( 270 target_os = "illumos", 271 target_os = "solaris", 272 target_os = "haiku" 273 ))] 274 None 275 } 276} 277