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}