jwalk/core/dir_entry.rs
1use std::ffi::{OsStr, OsString};
2use std::fmt;
3use std::fs::{self, FileType};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use crate::{ClientState, Error, ReadDirSpec, Result};
8
9/// Representation of a file or directory.
10///
11/// This representation does not wrap a `std::fs::DirEntry`. Instead it copies
12/// `file_name`, `file_type`, and optionally `metadata` out of the underlying
13/// `std::fs::DirEntry`. This allows it to quickly drop the underlying file
14/// descriptor.
15pub struct DirEntry<C: ClientState> {
16 /// Depth of this entry relative to the root directory where the walk
17 /// started.
18 pub depth: usize,
19 /// File name of this entry without leading path component.
20 pub file_name: OsString,
21 /// File type for the file/directory that this entry points at.
22 pub file_type: FileType,
23 /// Field where clients can store state from within the The
24 /// [`process_read_dir`](struct.WalkDirGeneric.html#method.process_read_dir)
25 /// callback.
26 pub client_state: C::DirEntryState,
27 /// Path used by this entry's parent to read this entry.
28 pub parent_path: Arc<Path>,
29 /// Path that will be used to read child entries. This is automatically set
30 /// for directories. The
31 /// [`process_read_dir`](struct.WalkDirGeneric.html#method.process_read_dir) callback
32 /// may set this field to `None` to skip reading the contents of a
33 /// particular directory.
34 pub read_children_path: Option<Arc<Path>>,
35 /// If `read_children_path` is set and resulting `fs::read_dir` generates an error
36 /// then that error is stored here.
37 pub read_children_error: Option<Error>,
38 // True if [`follow_links`] is `true` AND was created from a symlink path.
39 follow_link: bool,
40 // Origins of symlinks followed to get to this entry.
41 follow_link_ancestors: Arc<Vec<Arc<Path>>>,
42}
43
44impl<C: ClientState> DirEntry<C> {
45 pub(crate) fn from_entry(
46 depth: usize,
47 parent_path: Arc<Path>,
48 fs_dir_entry: &fs::DirEntry,
49 follow_link_ancestors: Arc<Vec<Arc<Path>>>,
50 ) -> Result<Self> {
51 let file_type = fs_dir_entry
52 .file_type()
53 .map_err(|err| Error::from_path(depth, fs_dir_entry.path(), err))?;
54 let file_name = fs_dir_entry.file_name();
55 let read_children_path: Option<Arc<Path>> = if file_type.is_dir() {
56 Some(Arc::from(parent_path.join(&file_name)))
57 } else {
58 None
59 };
60
61 Ok(DirEntry {
62 depth,
63 file_name,
64 file_type,
65 parent_path,
66 read_children_path,
67 read_children_error: None,
68 client_state: C::DirEntryState::default(),
69 follow_link: false,
70 follow_link_ancestors,
71 })
72 }
73
74 // Only used for root and when following links.
75 pub(crate) fn from_path(
76 depth: usize,
77 path: &Path,
78 follow_link: bool,
79 follow_link_ancestors: Arc<Vec<Arc<Path>>>,
80 ) -> Result<Self> {
81 let metadata = if follow_link {
82 fs::metadata(path).map_err(|err| Error::from_path(depth, path.to_owned(), err))?
83 } else {
84 fs::symlink_metadata(path)
85 .map_err(|err| Error::from_path(depth, path.to_owned(), err))?
86 };
87
88 let root_name = path.file_name().unwrap_or(path.as_os_str());
89
90 let read_children_path: Option<Arc<Path>> = if metadata.file_type().is_dir() {
91 Some(Arc::from(path))
92 } else {
93 None
94 };
95
96 Ok(DirEntry {
97 depth,
98 file_name: root_name.to_owned(),
99 file_type: metadata.file_type(),
100 parent_path: Arc::from(path.parent().map(Path::to_path_buf).unwrap_or_default()),
101 read_children_path,
102 read_children_error: None,
103 client_state: C::DirEntryState::default(),
104 follow_link,
105 follow_link_ancestors,
106 })
107 }
108
109 /// Return the file type for the file that this entry points to.
110 ///
111 /// If this is a symbolic link and [`follow_links`] is `true`, then this
112 /// returns the type of the target.
113 ///
114 /// This never makes any system calls.
115 ///
116 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
117 pub fn file_type(&self) -> FileType {
118 self.file_type
119 }
120
121 /// Return the file name of this entry.
122 ///
123 /// If this entry has no file name (e.g., `/`), then the full path is
124 /// returned.
125 pub fn file_name(&self) -> &OsStr {
126 &self.file_name
127 }
128
129 /// Returns the depth at which this entry was created relative to the root.
130 ///
131 /// The smallest depth is `0` and always corresponds to the path given
132 /// to the `new` function on `WalkDir`. Its direct descendants have depth
133 /// `1`, and their descendants have depth `2`, and so on.
134 pub fn depth(&self) -> usize {
135 self.depth
136 }
137
138 /// Path to the file/directory represented by this entry.
139 ///
140 /// The path is created by joining `parent_path` with `file_name`.
141 pub fn path(&self) -> PathBuf {
142 self.parent_path.join(&self.file_name)
143 }
144
145 /// Returns `true` if and only if this entry was created from a symbolic
146 /// link. This is unaffected by the [`follow_links`] setting.
147 ///
148 /// When `true`, the value returned by the [`path`] method is a
149 /// symbolic link name. To get the full target path, you must call
150 /// [`std::fs::read_link(entry.path())`].
151 ///
152 /// [`path`]: struct.DirEntry.html#method.path
153 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
154 /// [`std::fs::read_link(entry.path())`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html
155 pub fn path_is_symlink(&self) -> bool {
156 self.file_type.is_symlink() || self.follow_link
157 }
158
159 /// Return the metadata for the file that this entry points to.
160 ///
161 /// This will follow symbolic links if and only if the [`WalkDir`] value
162 /// has [`follow_links`] enabled.
163 ///
164 /// # Platform behavior
165 ///
166 /// This always calls [`std::fs::symlink_metadata`].
167 ///
168 /// If this entry is a symbolic link and [`follow_links`] is enabled, then
169 /// [`std::fs::metadata`] is called instead.
170 ///
171 /// # Errors
172 ///
173 /// Similar to [`std::fs::metadata`], returns errors for path values that
174 /// the program does not have permissions to access or if the path does not
175 /// exist.
176 ///
177 /// [`WalkDir`]: struct.WalkDir.html
178 /// [`follow_links`]: struct.WalkDir.html#method.follow_links
179 /// [`std::fs::metadata`]: https://doc.rust-lang.org/std/fs/fn.metadata.html
180 /// [`std::fs::symlink_metadata`]: https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html
181 pub fn metadata(&self) -> Result<fs::Metadata> {
182 if self.follow_link {
183 fs::metadata(&self.path())
184 } else {
185 fs::symlink_metadata(&self.path())
186 }
187 .map_err(|err| Error::from_entry(self, err))
188 }
189
190 /// Reference to the path of the directory containing this entry.
191 pub fn parent_path(&self) -> &Path {
192 &self.parent_path
193 }
194
195 pub(crate) fn read_children_spec(
196 &self,
197 client_read_state: C::ReadDirState,
198 ) -> Option<ReadDirSpec<C>> {
199 self.read_children_path
200 .as_ref()
201 .map(|read_children_path| ReadDirSpec {
202 depth: self.depth,
203 client_read_state,
204 path: read_children_path.clone(),
205 follow_link_ancestors: self.follow_link_ancestors.clone(),
206 })
207 }
208
209 pub(crate) fn follow_symlink(&self) -> Result<Self> {
210 let path = self.path();
211 let origins = self.follow_link_ancestors.clone();
212 let dir_entry = DirEntry::from_path(self.depth, &path, true, origins)?;
213
214 if dir_entry.file_type.is_dir() {
215 let target = fs::read_link(&path).map_err(|err| Error::from_io(self.depth, err))?;
216 for ancestor in self.follow_link_ancestors.iter().rev() {
217 if target.as_path() == ancestor.as_ref() {
218 return Err(Error::from_loop(
219 self.depth,
220 ancestor.as_ref(),
221 path.as_ref(),
222 ));
223 }
224 }
225 }
226
227 Ok(dir_entry)
228 }
229}
230
231impl<C: ClientState> fmt::Debug for DirEntry<C> {
232 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233 write!(f, "DirEntry({:?})", self.path())
234 }
235}