isideload_walkdir/dent.rs
1use isideload_vfs::fs::{self, FileType};
2use std::ffi::OsStr;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6use crate::error::Error;
7use 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
35pub 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}
52
53impl DirEntry {
54 /// The full path that this entry represents.
55 ///
56 /// The full path is created by joining the parents of this entry up to the
57 /// root initially given to [`WalkDir::new`] with the file name of this
58 /// entry.
59 ///
60 /// Note that this *always* returns the path reported by the underlying
61 /// directory entry, even when symbolic links are followed. To get the
62 /// target path, use [`path_is_symlink`] to (cheaply) check if this entry
63 /// corresponds to a symbolic link, and [`std::fs::read_link`] to resolve
64 /// the target.
65 ///
66 /// [`WalkDir::new`]: struct.WalkDir.html#method.new
67 /// [`path_is_symlink`]: struct.DirEntry.html#method.path_is_symlink
68 /// [`std::fs::read_link`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html
69 pub fn path(&self) -> &Path {
70 &self.path
71 }
72
73 /// The full path that this entry represents.
74 ///
75 /// Analogous to [`path`], but moves ownership of the path.
76 ///
77 /// [`path`]: struct.DirEntry.html#method.path
78 pub fn into_path(self) -> PathBuf {
79 self.path
80 }
81
82 /// Returns `true` if and only if this entry was created from a symbolic
83 /// link. This is unaffected by the [`follow_links`] setting.
84 ///
85 /// When `true`, the value returned by the [`path`] method is a
86 /// symbolic link name. To get the full target path, you must call
87 /// [`std::fs::read_link(entry.path())`].
88 ///
89 /// [`path`]: struct.DirEntry.html#method.path
90 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
91 /// [`std::fs::read_link(entry.path())`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html
92 pub fn path_is_symlink(&self) -> bool {
93 self.ty.is_symlink() || self.follow_link
94 }
95
96 /// Return the metadata for the file that this entry points to.
97 ///
98 /// This will follow symbolic links if and only if the [`WalkDir`] value
99 /// has [`follow_links`] enabled.
100 ///
101 /// # Platform behavior
102 ///
103 /// This always calls [`std::fs::symlink_metadata`].
104 ///
105 /// If this entry is a symbolic link and [`follow_links`] is enabled, then
106 /// [`std::fs::metadata`] is called instead.
107 ///
108 /// # Errors
109 ///
110 /// Similar to [`std::fs::metadata`], returns errors for path values that
111 /// the program does not have permissions to access or if the path does not
112 /// exist.
113 ///
114 /// [`WalkDir`]: struct.WalkDir.html
115 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
116 /// [`std::fs::metadata`]: https://doc.rust-lang.org/std/fs/fn.metadata.html
117 /// [`std::fs::symlink_metadata`]: https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html
118 pub fn metadata(&self) -> Result<fs::Metadata> {
119 self.metadata_internal()
120 }
121
122 fn metadata_internal(&self) -> Result<fs::Metadata> {
123 if self.follow_link {
124 fs::metadata(&self.path)
125 } else {
126 fs::symlink_metadata(&self.path)
127 }
128 .map_err(|err| Error::from_entry(self, err))
129 }
130
131 /// Return the file type for the file that this entry points to.
132 ///
133 /// If this is a symbolic link and [`follow_links`] is `true`, then this
134 /// returns the type of the target.
135 ///
136 /// This never makes any system calls.
137 ///
138 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
139 pub fn file_type(&self) -> fs::FileType {
140 self.ty
141 }
142
143 /// Return the file name of this entry.
144 ///
145 /// If this entry has no file name (e.g., `/`), then the full path is
146 /// returned.
147 pub fn file_name(&self) -> &OsStr {
148 self.path
149 .file_name()
150 .unwrap_or_else(|| self.path.as_os_str())
151 }
152
153 /// Returns the depth at which this entry was created relative to the root.
154 ///
155 /// The smallest depth is `0` and always corresponds to the path given
156 /// to the `new` function on `WalkDir`. Its direct descendents have depth
157 /// `1`, and their descendents have depth `2`, and so on.
158 pub fn depth(&self) -> usize {
159 self.depth
160 }
161
162 /// Returns true if and only if this entry points to a directory.
163 pub(crate) fn is_dir(&self) -> bool {
164 self.ty.is_dir()
165 }
166
167 #[cfg(windows)]
168 pub(crate) fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntry> {
169 let path = ent.path();
170 let ty = ent
171 .file_type()
172 .map_err(|err| Error::from_path(depth, path.clone(), err))?;
173 Ok(DirEntry {
174 path,
175 ty,
176 follow_link: false,
177 depth,
178 })
179 }
180
181 #[cfg(unix)]
182 pub(crate) fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntry> {
183 use isideload_vfs::fs::DirEntryExt;
184
185 let ty = ent
186 .file_type()
187 .map_err(|err| Error::from_path(depth, ent.path(), err))?;
188 Ok(DirEntry {
189 path: ent.path(),
190 ty,
191 follow_link: false,
192 depth,
193 ino: ent.ino(),
194 })
195 }
196
197 #[cfg(not(any(unix, windows)))]
198 pub(crate) fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntry> {
199 let ty = ent
200 .file_type()
201 .map_err(|err| Error::from_path(depth, ent.path(), err))?;
202 Ok(DirEntry {
203 path: ent.path(),
204 ty,
205 follow_link: false,
206 depth,
207 })
208 }
209
210 #[cfg(windows)]
211 pub(crate) fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> {
212 let md = if follow {
213 fs::metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
214 } else {
215 fs::symlink_metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
216 };
217 Ok(DirEntry {
218 path: pb,
219 ty: md.file_type(),
220 follow_link: follow,
221 depth,
222 })
223 }
224
225 #[cfg(unix)]
226 pub(crate) fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> {
227 use isideload_vfs::fs::MetadataExt;
228
229 let md = if follow {
230 fs::metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
231 } else {
232 fs::symlink_metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
233 };
234 Ok(DirEntry {
235 path: pb,
236 ty: md.file_type(),
237 follow_link: follow,
238 depth,
239 ino: md.ino(),
240 })
241 }
242
243 #[cfg(not(any(unix, windows)))]
244 pub(crate) fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> {
245 let md = if follow {
246 fs::metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
247 } else {
248 fs::symlink_metadata(&pb).map_err(|err| Error::from_path(depth, pb.clone(), err))?
249 };
250 Ok(DirEntry {
251 path: pb,
252 ty: md.file_type(),
253 follow_link: follow,
254 depth,
255 })
256 }
257}
258
259impl Clone for DirEntry {
260 #[cfg(windows)]
261 fn clone(&self) -> DirEntry {
262 DirEntry {
263 path: self.path.clone(),
264 ty: self.ty,
265 follow_link: self.follow_link,
266 depth: self.depth,
267 }
268 }
269
270 #[cfg(unix)]
271 fn clone(&self) -> DirEntry {
272 DirEntry {
273 path: self.path.clone(),
274 ty: self.ty,
275 follow_link: self.follow_link,
276 depth: self.depth,
277 ino: self.ino,
278 }
279 }
280
281 #[cfg(not(any(unix, windows)))]
282 fn clone(&self) -> DirEntry {
283 DirEntry {
284 path: self.path.clone(),
285 ty: self.ty,
286 follow_link: self.follow_link,
287 depth: self.depth,
288 }
289 }
290}
291
292impl fmt::Debug for DirEntry {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 write!(f, "DirEntry({:?})", self.path)
295 }
296}
297
298/// Unix-specific extension methods for `walkdir::DirEntry`
299#[cfg(unix)]
300pub trait DirEntryExt {
301 /// Returns the underlying `d_ino` field in the contained `dirent`
302 /// structure.
303 fn ino(&self) -> u64;
304}
305
306#[cfg(unix)]
307impl DirEntryExt for DirEntry {
308 /// Returns the underlying `d_ino` field in the contained `dirent`
309 /// structure.
310 fn ino(&self) -> u64 {
311 self.ino
312 }
313}