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. is_dir(&self) -> bool180 pub(crate) fn is_dir(&self) -> bool { 181 self.ty.is_dir() 182 } 183 184 #[cfg(windows)] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>185 pub(crate) fn from_entry( 186 depth: usize, 187 ent: &fs::DirEntry, 188 ) -> Result<DirEntry> { 189 let path = ent.path(); 190 let ty = ent 191 .file_type() 192 .map_err(|err| Error::from_path(depth, path.clone(), err))?; 193 let md = ent 194 .metadata() 195 .map_err(|err| Error::from_path(depth, path.clone(), err))?; 196 Ok(DirEntry { path, ty, follow_link: false, depth, metadata: md }) 197 } 198 199 #[cfg(unix)] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>200 pub(crate) fn from_entry( 201 depth: usize, 202 ent: &fs::DirEntry, 203 ) -> Result<DirEntry> { 204 use std::os::unix::fs::DirEntryExt; 205 206 let ty = ent 207 .file_type() 208 .map_err(|err| Error::from_path(depth, ent.path(), err))?; 209 Ok(DirEntry { 210 path: ent.path(), 211 ty, 212 follow_link: false, 213 depth, 214 ino: ent.ino(), 215 }) 216 } 217 218 #[cfg(not(any(unix, windows)))] from_entry( depth: usize, ent: &fs::DirEntry, ) -> Result<DirEntry>219 pub(crate) fn from_entry( 220 depth: usize, 221 ent: &fs::DirEntry, 222 ) -> Result<DirEntry> { 223 let ty = ent 224 .file_type() 225 .map_err(|err| Error::from_path(depth, ent.path(), err))?; 226 Ok(DirEntry { path: ent.path(), ty, follow_link: false, depth }) 227 } 228 229 #[cfg(windows)] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>230 pub(crate) fn from_path( 231 depth: usize, 232 pb: PathBuf, 233 follow: bool, 234 ) -> Result<DirEntry> { 235 let md = if follow { 236 fs::metadata(&pb) 237 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 238 } else { 239 fs::symlink_metadata(&pb) 240 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 241 }; 242 Ok(DirEntry { 243 path: pb, 244 ty: md.file_type(), 245 follow_link: follow, 246 depth, 247 metadata: md, 248 }) 249 } 250 251 #[cfg(unix)] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>252 pub(crate) fn from_path( 253 depth: usize, 254 pb: PathBuf, 255 follow: bool, 256 ) -> Result<DirEntry> { 257 use std::os::unix::fs::MetadataExt; 258 259 let md = if follow { 260 fs::metadata(&pb) 261 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 262 } else { 263 fs::symlink_metadata(&pb) 264 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 265 }; 266 Ok(DirEntry { 267 path: pb, 268 ty: md.file_type(), 269 follow_link: follow, 270 depth, 271 ino: md.ino(), 272 }) 273 } 274 275 #[cfg(not(any(unix, windows)))] from_path( depth: usize, pb: PathBuf, follow: bool, ) -> Result<DirEntry>276 pub(crate) fn from_path( 277 depth: usize, 278 pb: PathBuf, 279 follow: bool, 280 ) -> Result<DirEntry> { 281 let md = if follow { 282 fs::metadata(&pb) 283 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 284 } else { 285 fs::symlink_metadata(&pb) 286 .map_err(|err| Error::from_path(depth, pb.clone(), err))? 287 }; 288 Ok(DirEntry { 289 path: pb, 290 ty: md.file_type(), 291 follow_link: follow, 292 depth, 293 }) 294 } 295 } 296 297 impl Clone for DirEntry { 298 #[cfg(windows)] clone(&self) -> DirEntry299 fn clone(&self) -> DirEntry { 300 DirEntry { 301 path: self.path.clone(), 302 ty: self.ty, 303 follow_link: self.follow_link, 304 depth: self.depth, 305 metadata: self.metadata.clone(), 306 } 307 } 308 309 #[cfg(unix)] clone(&self) -> DirEntry310 fn clone(&self) -> DirEntry { 311 DirEntry { 312 path: self.path.clone(), 313 ty: self.ty, 314 follow_link: self.follow_link, 315 depth: self.depth, 316 ino: self.ino, 317 } 318 } 319 320 #[cfg(not(any(unix, windows)))] clone(&self) -> DirEntry321 fn clone(&self) -> DirEntry { 322 DirEntry { 323 path: self.path.clone(), 324 ty: self.ty, 325 follow_link: self.follow_link, 326 depth: self.depth, 327 } 328 } 329 } 330 331 impl fmt::Debug for DirEntry { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 333 write!(f, "DirEntry({:?})", self.path) 334 } 335 } 336 337 /// Unix-specific extension methods for `walkdir::DirEntry` 338 #[cfg(unix)] 339 pub trait DirEntryExt { 340 /// Returns the underlying `d_ino` field in the contained `dirent` 341 /// structure. ino(&self) -> u64342 fn ino(&self) -> u64; 343 } 344 345 #[cfg(unix)] 346 impl DirEntryExt for DirEntry { 347 /// Returns the underlying `d_ino` field in the contained `dirent` 348 /// structure. ino(&self) -> u64349 fn ino(&self) -> u64 { 350 self.ino 351 } 352 } 353