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}