1 use std::ffi::OsStr; 2 use std::fmt; 3 use std::fs::{self, FileType}; 4 use std::path::{Path, PathBuf}; 5 6 use crate::error::Error; 7 use crate::Result; 8 9 /// A directory entry. 10 /// 11 /// This is the type of value that is yielded from the iterators defined in 12 /// this crate. 13 /// 14 /// On Unix systems, this type implements the [`DirEntryExt`] trait, which 15 /// provides efficient access to the inode number of the directory entry. 16 /// 17 /// # Differences with `std::fs::DirEntry` 18 /// 19 /// This type mostly mirrors the type by the same name in [`std::fs`]. There 20 /// are some differences however: 21 /// 22 /// * All recursive directory iterators must inspect the entry's type. 23 /// Therefore, the value is stored and its access is guaranteed to be cheap and 24 /// successful. 25 /// * [`path`] and [`file_name`] return borrowed variants. 26 /// * If [`follow_links`] was enabled on the originating iterator, then all 27 /// operations except for [`path`] operate on the link target. Otherwise, all 28 /// operations operate on the symbolic link. 29 /// 30 /// [`std::fs`]: https://doc.rust-lang.org/stable/std/fs/index.html 31 /// [`path`]: #method.path 32 /// [`file_name`]: #method.file_name 33 /// [`follow_links`]: struct.WalkDir.html#method.follow_links 34 /// [`DirEntryExt`]: trait.DirEntryExt.html 35 pub struct DirEntry { 36 /// The path as reported by the [`fs::ReadDir`] iterator (even if it's a 37 /// symbolic link). 38 /// 39 /// [`fs::ReadDir`]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html 40 path: PathBuf, 41 /// The file type. Necessary for recursive iteration, so store it. 42 ty: FileType, 43 /// Is set when this entry was created from a symbolic link and the user 44 /// expects the iterator to follow symbolic links. 45 follow_link: bool, 46 /// The depth at which this entry was generated relative to the root. 47 depth: usize, 48 /// The underlying inode number (Unix only). 49 #[cfg(unix)] 50 ino: u64, 51 /// The underlying metadata (Windows only). We store this on Windows 52 /// because this comes for free while reading a directory. 53 /// 54 /// We use this to determine whether an entry is a directory or not, which 55 /// works around a bug in Rust's standard library: 56 /// https://github.com/rust-lang/rust/issues/46484 57 #[cfg(windows)] 58 metadata: fs::Metadata, 59 } 60 61 impl DirEntry { 62 /// The full path that this entry represents. 63 /// 64 /// The full path is created by joining the parents of this entry up to the 65 /// root initially given to [`WalkDir::new`] with the file name of this 66 /// entry. 67 /// 68 /// Note that this *always* returns the path reported by the underlying 69 /// directory entry, even when symbolic links are followed. To get the 70 /// target path, use [`path_is_symlink`] to (cheaply) check if this entry 71 /// corresponds to a symbolic link, and [`std::fs::read_link`] to resolve 72 /// the target. 73 /// 74 /// [`WalkDir::new`]: struct.WalkDir.html#method.new 75 /// [`path_is_symlink`]: struct.DirEntry.html#method.path_is_symlink 76 /// [`std::fs::read_link`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html path(&self) -> &Path77 pub fn path(&self) -> &Path { 78 &self.path 79 } 80 81 /// The full path that this entry represents. 82 /// 83 /// Analogous to [`path`], but moves ownership of the path. 84 /// 85 /// [`path`]: struct.DirEntry.html#method.path into_path(self) -> PathBuf86 pub fn into_path(self) -> PathBuf { 87 self.path 88 } 89 90 /// Returns `true` if and only if this entry was created from a symbolic 91 /// link. This is unaffected by the [`follow_links`] setting. 92 /// 93 /// When `true`, the value returned by the [`path`] method is a 94 /// symbolic link name. To get the full target path, you must call 95 /// [`std::fs::read_link(entry.path())`]. 96 /// 97 /// [`path`]: struct.DirEntry.html#method.path 98 /// [`follow_links`]: struct.WalkDir.html#method.follow_links 99 /// [`std::fs::read_link(entry.path())`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html path_is_symlink(&self) -> bool100 pub fn path_is_symlink(&self) -> bool { 101 self.ty.is_symlink() || self.follow_link 102 } 103 104 /// Return the metadata for the file that this entry points to. 105 /// 106 /// This will follow symbolic links if and only if the [`WalkDir`] value 107 /// has [`follow_links`] enabled. 108 /// 109 /// # Platform behavior 110 /// 111 /// This always calls [`std::fs::symlink_metadata`]. 112 /// 113 /// If this entry is a symbolic link and [`follow_links`] is enabled, then 114 /// [`std::fs::metadata`] is called instead. 115 /// 116 /// # Errors 117 /// 118 /// Similar to [`std::fs::metadata`], returns errors for path values that 119 /// the program does not have permissions to access or if the path does not 120 /// exist. 121 /// 122 /// [`WalkDir`]: struct.WalkDir.html 123 /// [`follow_links`]: struct.WalkDir.html#method.follow_links 124 /// [`std::fs::metadata`]: https://doc.rust-lang.org/std/fs/fn.metadata.html 125 /// [`std::fs::symlink_metadata`]: https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html metadata(&self) -> Result<fs::Metadata>126 pub fn metadata(&self) -> Result<fs::Metadata> { 127 self.metadata_internal() 128 } 129 130 #[cfg(windows)] metadata_internal(&self) -> Result<fs::Metadata>131 fn metadata_internal(&self) -> Result<fs::Metadata> { 132 if self.follow_link { 133 fs::metadata(&self.path) 134 } else { 135 Ok(self.metadata.clone()) 136 } 137 .map_err(|err| Error::from_entry(self, err)) 138 } 139 140 #[cfg(not(windows))] metadata_internal(&self) -> Result<fs::Metadata>141 fn metadata_internal(&self) -> Result<fs::Metadata> { 142 if self.follow_link { 143 fs::metadata(&self.path) 144 } else { 145 fs::symlink_metadata(&self.path) 146 } 147 .map_err(|err| Error::from_entry(self, err)) 148 } 149 150 /// Return the file type for the file that this entry points to. 151 /// 152 /// If this is a symbolic link and [`follow_links`] is `true`, then this 153 /// returns the type of the target. 154 /// 155 /// This never makes any system calls. 156 /// 157 /// [`follow_links`]: struct.WalkDir.html#method.follow_links file_type(&self) -> fs::FileType158 pub fn file_type(&self) -> fs::FileType { 159 self.ty 160 } 161 162 /// Return the file name of this entry. 163 /// 164 /// If this entry has no file name (e.g., `/`), then the full path is 165 /// returned. file_name(&self) -> &OsStr166 pub fn file_name(&self) -> &OsStr { 167 self.path.file_name().unwrap_or_else(|| self.path.as_os_str()) 168 } 169 170 /// Returns the depth at which this entry was created relative to the root. 171 /// 172 /// The smallest depth is `0` and always corresponds to the path given 173 /// to the `new` function on `WalkDir`. Its direct descendents have depth 174 /// `1`, and their descendents have depth `2`, and so on. depth(&self) -> usize175 pub fn depth(&self) -> usize { 176 self.depth 177 } 178 179 /// Returns true if and only if this entry points to a directory. 180 /// 181 /// This works around a bug in Rust's standard library: 182 /// https://github.com/rust-lang/rust/issues/46484 183 #[cfg(windows)] is_dir(&self) -> bool184 pub(crate) fn is_dir(&self) -> bool { 185 use std::os::windows::fs::MetadataExt; 186 use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; 187 self.metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 188 } 189 190 /// Returns true if and only if this entry points to a directory. 191 #[cfg(not(windows))] is_dir(&self) -> bool192 pub(crate) fn is_dir(&self) -> bool { 193 self.ty.is_dir() 194 } 195 196 #[cfg(windows)] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>197 pub(crate) fn from_entry( 198 depth: usize, 199 ent: &fs::DirEntry, 200 ) -> Result<DirEntry> { 201 let path = ent.path(); 202 let ty = ent 203 .file_type() 204 .map_err(|err| Error::from_path(depth, path.clone(), err))?; 205 let md = ent 206 .metadata() 207 .map_err(|err| Error::from_path(depth, path.clone(), err))?; 208 Ok(DirEntry { 209 path: path, 210 ty: ty, 211 follow_link: false, 212 depth: depth, 213 metadata: md, 214 }) 215 } 216 217 #[cfg(unix)] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>218 pub(crate) fn from_entry( 219 depth: usize, 220 ent: &fs::DirEntry, 221 ) -> Result<DirEntry> { 222 use std::os::unix::fs::DirEntryExt; 223 224 let ty = ent 225 .file_type() 226 .map_err(|err| Error::from_path(depth, ent.path(), err))?; 227 Ok(DirEntry { 228 path: ent.path(), 229 ty: ty, 230 follow_link: false, 231 depth: depth, 232 ino: ent.ino(), 233 }) 234 } 235 236 #[cfg(not(any(unix, windows)))] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>237 pub(crate) fn from_entry( 238 depth: usize, 239 ent: &fs::DirEntry, 240 ) -> Result<DirEntry> { 241 let ty = ent 242 .file_type() 243 .map_err(|err| Error::from_path(depth, ent.path(), err))?; 244 Ok(DirEntry { 245 path: ent.path(), 246 ty: ty, 247 follow_link: false, 248 depth: depth, 249 }) 250 } 251 252 #[cfg(windows)] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>253 pub(crate) fn from_path( 254 depth: usize, 255 pb: PathBuf, 256 follow: bool, 257 ) -> Result<DirEntry> { 258 let md = if follow { 259 fs::metadata(&pb) 260 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 261 } else { 262 fs::symlink_metadata(&pb) 263 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 264 }; 265 Ok(DirEntry { 266 path: pb, 267 ty: md.file_type(), 268 follow_link: follow, 269 depth: depth, 270 metadata: md, 271 }) 272 } 273 274 #[cfg(unix)] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>275 pub(crate) fn from_path( 276 depth: usize, 277 pb: PathBuf, 278 follow: bool, 279 ) -> Result<DirEntry> { 280 use std::os::unix::fs::MetadataExt; 281 282 let md = if follow { 283 fs::metadata(&pb) 284 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 285 } else { 286 fs::symlink_metadata(&pb) 287 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 288 }; 289 Ok(DirEntry { 290 path: pb, 291 ty: md.file_type(), 292 follow_link: follow, 293 depth: depth, 294 ino: md.ino(), 295 }) 296 } 297 298 #[cfg(not(any(unix, windows)))] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>299 pub(crate) fn from_path( 300 depth: usize, 301 pb: PathBuf, 302 follow: bool, 303 ) -> Result<DirEntry> { 304 let md = if follow { 305 fs::metadata(&pb) 306 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 307 } else { 308 fs::symlink_metadata(&pb) 309 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 310 }; 311 Ok(DirEntry { 312 path: pb, 313 ty: md.file_type(), 314 follow_link: follow, 315 depth: depth, 316 }) 317 } 318 } 319 320 impl Clone for DirEntry { 321 #[cfg(windows)] clone(&self) -> DirEntry322 fn clone(&self) -> DirEntry { 323 DirEntry { 324 path: self.path.clone(), 325 ty: self.ty, 326 follow_link: self.follow_link, 327 depth: self.depth, 328 metadata: self.metadata.clone(), 329 } 330 } 331 332 #[cfg(unix)] clone(&self) -> DirEntry333 fn clone(&self) -> DirEntry { 334 DirEntry { 335 path: self.path.clone(), 336 ty: self.ty, 337 follow_link: self.follow_link, 338 depth: self.depth, 339 ino: self.ino, 340 } 341 } 342 343 #[cfg(not(any(unix, windows)))] clone(&self) -> DirEntry344 fn clone(&self) -> DirEntry { 345 DirEntry { 346 path: self.path.clone(), 347 ty: self.ty, 348 follow_link: self.follow_link, 349 depth: self.depth, 350 } 351 } 352 } 353 354 impl fmt::Debug for DirEntry { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 356 write!(f, "DirEntry({:?})", self.path) 357 } 358 } 359 360 /// Unix-specific extension methods for `walkdir::DirEntry` 361 #[cfg(unix)] 362 pub trait DirEntryExt { 363 /// Returns the underlying `d_ino` field in the contained `dirent` 364 /// structure. ino(&self) -> u64365 fn ino(&self) -> u64; 366 } 367 368 #[cfg(unix)] 369 impl DirEntryExt for DirEntry { 370 /// Returns the underlying `d_ino` field in the contained `dirent` 371 /// structure. ino(&self) -> u64372 fn ino(&self) -> u64 { 373 self.ino 374 } 375 } 376