Skip to main content

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}