jwalk/core/error.rs
1use std::error;
2use std::fmt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use crate::{ClientState, DirEntry};
7
8/// An error produced by recursively walking a directory.
9///
10/// This error type is a light wrapper around [`std::io::Error`]. In
11/// particular, it adds the following information:
12///
13/// * The depth at which the error occurred in the file tree, relative to the
14/// root.
15/// * The path, if any, associated with the IO error.
16/// * An indication that a loop occurred when following symbolic links. In this
17/// case, there is no underlying IO error.
18///
19/// To maintain good ergonomics, this type has a
20/// [`impl From<Error> for std::io::Error`][impl] defined which preserves the original context.
21/// This allows you to use an [`io::Result`] with methods in this crate if you don't care about
22/// accessing the underlying error data in a structured form.
23///
24/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
25/// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html
26/// [impl]: struct.Error.html#impl-From%3CError%3E
27#[derive(Debug)]
28pub struct Error {
29 depth: usize,
30 inner: ErrorInner,
31}
32
33#[derive(Debug)]
34enum ErrorInner {
35 Io {
36 path: Option<PathBuf>,
37 err: io::Error,
38 },
39 Loop {
40 ancestor: PathBuf,
41 child: PathBuf,
42 },
43 ThreadpoolBusy,
44}
45
46impl Error {
47 /// Returns the path associated with this error if one exists.
48 ///
49 /// For example, if an error occurred while opening a directory handle,
50 /// the error will include the path passed to [`std::fs::read_dir`].
51 ///
52 /// [`std::fs::read_dir`]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html
53 pub fn path(&self) -> Option<&Path> {
54 match self.inner {
55 ErrorInner::ThreadpoolBusy => None,
56 ErrorInner::Io { path: None, .. } => None,
57 ErrorInner::Io {
58 path: Some(ref path),
59 ..
60 } => Some(path),
61 ErrorInner::Loop { ref child, .. } => Some(child),
62 }
63 }
64
65 /// Returns the path at which a cycle was detected.
66 ///
67 /// If no cycle was detected, [`None`] is returned.
68 ///
69 /// A cycle is detected when a directory entry is equivalent to one of
70 /// its ancestors.
71 ///
72 /// To get the path to the child directory entry in the cycle, use the
73 /// [`path`] method.
74 ///
75 /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None
76 /// [`path`]: struct.Error.html#path
77 pub fn loop_ancestor(&self) -> Option<&Path> {
78 match self.inner {
79 ErrorInner::Loop { ref ancestor, .. } => Some(ancestor),
80 _ => None,
81 }
82 }
83
84 /// Returns the depth at which this error occurred relative to the root.
85 ///
86 /// The smallest depth is `0` and always corresponds to the path given to
87 /// the [`new`] function on [`WalkDir`]. Its direct descendants have depth
88 /// `1`, and their descendants have depth `2`, and so on.
89 ///
90 /// [`new`]: struct.WalkDir.html#method.new
91 /// [`WalkDir`]: struct.WalkDir.html
92 pub fn depth(&self) -> usize {
93 self.depth
94 }
95
96 /// Inspect the original [`io::Error`] if there is one.
97 ///
98 /// [`None`] is returned if the [`Error`] doesn't correspond to an
99 /// [`io::Error`]. This might happen, for example, when the error was
100 /// produced because a cycle was found in the directory tree while
101 /// following symbolic links.
102 ///
103 /// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To
104 /// obtain an owned value, the [`into_io_error`] can be used instead.
105 ///
106 /// > This is the original [`io::Error`] and is _not_ the same as
107 /// > [`impl From<Error> for std::io::Error`][impl] which contains additional context about the
108 /// error.
109 ///
110 /// # Example
111 ///
112 /// ```rust,no_run
113 /// use std::io;
114 /// use std::path::Path;
115 ///
116 /// use walkdir::WalkDir;
117 ///
118 /// for entry in WalkDir::new("foo") {
119 /// match entry {
120 /// Ok(entry) => println!("{}", entry.path().display()),
121 /// Err(err) => {
122 /// let path = err.path().unwrap_or(Path::new("")).display();
123 /// println!("failed to access entry {}", path);
124 /// if let Some(inner) = err.io_error() {
125 /// match inner.kind() {
126 /// io::ErrorKind::InvalidData => {
127 /// println!(
128 /// "entry contains invalid data: {}",
129 /// inner)
130 /// }
131 /// io::ErrorKind::PermissionDenied => {
132 /// println!(
133 /// "Missing permission to read entry: {}",
134 /// inner)
135 /// }
136 /// _ => {
137 /// println!(
138 /// "Unexpected error occurred: {}",
139 /// inner)
140 /// }
141 /// }
142 /// }
143 /// }
144 /// }
145 /// }
146 /// ```
147 ///
148 /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None
149 /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
150 /// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html
151 /// [`Error`]: struct.Error.html
152 /// [`into_io_error`]: struct.Error.html#method.into_io_error
153 /// [impl]: struct.Error.html#impl-From%3CError%3E
154 pub fn io_error(&self) -> Option<&io::Error> {
155 match self.inner {
156 ErrorInner::Io { ref err, .. } => Some(err),
157 _ => None,
158 }
159 }
160
161 /// Returns true if this error is due to a busy thread-pool that prevented its effective use.
162 ///
163 /// Note that business detection is timeout based, and we don't know if it would have been a deadlock or not.
164 pub fn is_busy(&self) -> bool {
165 matches!(self.inner, ErrorInner::ThreadpoolBusy)
166 }
167
168 /// Similar to [`io_error`] except consumes self to convert to the original
169 /// [`io::Error`] if one exists.
170 ///
171 /// [`io_error`]: struct.Error.html#method.io_error
172 /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
173 pub fn into_io_error(self) -> Option<io::Error> {
174 match self.inner {
175 ErrorInner::Io { err, .. } => Some(err),
176 _ => None,
177 }
178 }
179
180 pub(crate) fn busy() -> Self {
181 Error {
182 depth: 0,
183 inner: ErrorInner::ThreadpoolBusy,
184 }
185 }
186 pub(crate) fn from_path(depth: usize, pb: PathBuf, err: io::Error) -> Self {
187 Error {
188 depth,
189 inner: ErrorInner::Io {
190 path: Some(pb),
191 err,
192 },
193 }
194 }
195
196 pub(crate) fn from_entry<C: ClientState>(dent: &DirEntry<C>, err: io::Error) -> Self {
197 Error {
198 depth: dent.depth(),
199 inner: ErrorInner::Io {
200 path: Some(dent.path()),
201 err,
202 },
203 }
204 }
205
206 pub(crate) fn from_io(depth: usize, err: io::Error) -> Self {
207 Error {
208 depth,
209 inner: ErrorInner::Io { path: None, err },
210 }
211 }
212
213 pub(crate) fn from_loop(depth: usize, ancestor: &Path, child: &Path) -> Self {
214 Error {
215 depth,
216 inner: ErrorInner::Loop {
217 ancestor: ancestor.to_path_buf(),
218 child: child.to_path_buf(),
219 },
220 }
221 }
222}
223
224impl error::Error for Error {
225 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
226 match self.inner {
227 ErrorInner::Io { ref err, .. } => Some(err),
228 ErrorInner::Loop { .. } | ErrorInner::ThreadpoolBusy => None,
229 }
230 }
231
232 #[allow(deprecated)]
233 fn description(&self) -> &str {
234 match self.inner {
235 ErrorInner::Io { ref err, .. } => err.description(),
236 ErrorInner::Loop { .. } => "file system loop found",
237 ErrorInner::ThreadpoolBusy => "thread-pool busy",
238 }
239 }
240
241 fn cause(&self) -> Option<&dyn error::Error> {
242 self.source()
243 }
244}
245
246impl fmt::Display for Error {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 match self.inner {
249 ErrorInner::ThreadpoolBusy => f.write_str("rayon thread-pool too busy or dependency loop detected - aborting before possibility of deadlock"),
250 ErrorInner::Io {
251 path: None,
252 ref err,
253 } => err.fmt(f),
254 ErrorInner::Io {
255 path: Some(ref path),
256 ref err,
257 } => write!(f, "IO error for operation on {}: {}", path.display(), err),
258 ErrorInner::Loop {
259 ref ancestor,
260 ref child,
261 } => write!(
262 f,
263 "File system loop found: \
264 {} points to an ancestor {}",
265 child.display(),
266 ancestor.display()
267 ),
268 }
269 }
270}
271
272impl From<Error> for io::Error {
273 /// Convert the [`Error`] to an [`io::Error`], preserving the original
274 /// [`Error`] as the ["inner error"]. Note that this also makes the display
275 /// of the error include the context.
276 ///
277 /// This is different from [`into_io_error`] which returns the original
278 /// [`io::Error`].
279 ///
280 /// [`Error`]: struct.Error.html
281 /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
282 /// ["inner error"]: https://doc.rust-lang.org/std/io/struct.Error.html#method.into_inner
283 /// [`into_io_error`]: struct.WalkDir.html#method.into_io_error
284 fn from(walk_err: Error) -> io::Error {
285 let kind = match walk_err {
286 Error {
287 inner: ErrorInner::Io { ref err, .. },
288 ..
289 } => err.kind(),
290 Error {
291 inner: ErrorInner::Loop { .. },
292 ..
293 } => io::ErrorKind::Other,
294 Error {
295 inner: ErrorInner::ThreadpoolBusy,
296 ..
297 } => io::ErrorKind::Other,
298 };
299 io::Error::new(kind, walk_err)
300 }
301}