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 |
77 | 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 |
86 | 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 |
100 | 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 |
126 | pub fn metadata(&self) -> Result<fs::Metadata> { |
127 | self.metadata_internal() |
128 | } |
129 | |
130 | #[cfg (windows)] |
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))] |
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 |
158 | 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. |
166 | 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. |
175 | pub fn depth(&self) -> usize { |
176 | self.depth |
177 | } |
178 | |
179 | /// Returns true if and only if this entry points to a directory. |
180 | pub(crate) fn is_dir(&self) -> bool { |
181 | self.ty.is_dir() |
182 | } |
183 | |
184 | #[cfg (windows)] |
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)] |
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)))] |
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)] |
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)] |
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)))] |
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)] |
299 | 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)] |
310 | 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)))] |
321 | 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 { |
332 | 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. |
342 | 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. |
349 | fn ino(&self) -> u64 { |
350 | self.ino |
351 | } |
352 | } |
353 | |