fs_mistrust/
walk.rs

1//! An iterator to resolve and canonicalize a filename.
2
3use crate::{Error, Result};
4use std::{
5    collections::HashMap,
6    ffi::OsString,
7    fs::Metadata,
8    io,
9    iter::FusedIterator,
10    path::{Path, PathBuf},
11    sync::Arc,
12};
13
14/// The type of a single path inspected by [`Verifier`](crate::Verifier).
15#[derive(Debug, Copy, Clone, Eq, PartialEq)]
16#[allow(clippy::exhaustive_enums)]
17pub(crate) enum PathType {
18    /// This is indeed the final canonical path we were trying to resolve.
19    Final,
20    /// This is an intermediary canonical path.  It _should_ be a directory, but
21    /// it might not be if the path resolution is about to fail.
22    Intermediate,
23    /// This is a symbolic link.
24    Symlink,
25    /// This is a file _inside_ the target directory.
26    Content,
27}
28
29/// A single piece of a path.
30///
31/// We would use [`std::path::Component`] directly here, but we want an owned
32/// type.
33#[derive(Clone, Debug)]
34struct Component {
35    /// Is this a prefix of a windows path?
36    ///
37    /// We need to keep track of these, because we expect stat() to fail for
38    /// them.
39    #[cfg(target_family = "windows")]
40    is_windows_prefix: bool,
41    /// The textual value of the component.
42    text: OsString,
43}
44
45/// Windows error code that we expect to get when calling stat() on a prefix.
46#[cfg(target_family = "windows")]
47const INVALID_FUNCTION: i32 = 1;
48
49impl<'a> From<std::path::Component<'a>> for Component {
50    fn from(c: std::path::Component<'a>) -> Self {
51        #[cfg(target_family = "windows")]
52        let is_windows_prefix = matches!(c, std::path::Component::Prefix(_));
53        let text = c.as_os_str().to_owned();
54        Component {
55            #[cfg(target_family = "windows")]
56            is_windows_prefix,
57            text,
58        }
59    }
60}
61
62/// An iterator to resolve and canonicalize a filename, imitating the actual
63/// filesystem's lookup behavior.
64///
65/// A `ResolvePath` looks up a filename by visiting all intermediate steps in
66/// turn, starting from the root directory, and following symlinks.  It
67/// suppresses duplicates.  Every path that it yields will _either_ be:
68///   * A directory in canonical[^1] [^2] form.
69///   * `dir/link` where dir is a directory in canonical form, and `link` is a
70///     symlink in that directory.
71///   * `dir/file` where dir is a directory in canonical form, and `file` is a
72///     file in that directory.
73///
74/// [^1]: We define "canonical" in the same way as `Path::canonicalize`: a
75///   canonical path is an absolute path containing no "." or ".." elements, and
76///   no symlinks.
77/// [^2]: Strictly speaking, this iterator on its own cannot guarantee that the
78///   paths it yields are truly canonical.  or that they even represent the
79///   target.  It is possible that in between checking one path and the next,
80///   somebody will modify the first path to replace a directory with a symlink,
81///   or replace one symlink with another. To get this kind of guarantee, you
82///   have to use a [`Mistrust`](crate::Mistrust) to check the permissions on
83///   the directories as you go.  Even then, your guarantee is conditional on
84///   none of the intermediary directories having been changed by a trusted user
85///   at the wrong time.
86///   
87///
88/// # Implementation notes
89///
90/// Abstractly, at any given point, the directory that we're resolving looks
91/// like `"resolved"/"remaining"`, where `resolved` is the part that we've
92/// already looked at (in canonical form, with all symlinks resolved) and
93/// `remaining` is the part that we're still trying to resolve.
94///
95/// We represent `resolved` as a nice plain PathBuf, and  `remaining` as a stack
96/// of strings that we want to push on to the end of the path.  We initialize
97/// the algorithm with `resolved` empty and `remaining` seeded with the path we
98/// want to resolve.  Once there are no more parts to push, the path resolution
99/// is done.
100///
101/// The following invariants apply whenever we are outside of the `next`
102/// function:
103///    * `resolved.join(remaining)` is an alias for our target path.
104///    * `resolved` is in canonical form.
105///    * Every ancestor of `resolved` is a key of `already_inspected`.
106///
107/// # Limitations
108///
109/// Because we're using `Path::metadata` rather than something that would use
110/// `openat()` and `fstat()` under the hood, the permissions returned here are
111/// potentially susceptible to TOCTOU issues.  In this crate we address these
112/// issues by checking each yielded path immediately to make sure that only
113/// _trusted_ users can change it after it is checked.
114//
115// TODO: This code is potentially of use outside this crate.  Maybe it should be
116// public?
117#[derive(Clone, Debug)]
118pub(crate) struct ResolvePath {
119    /// The path that we have resolved so far.  It is always[^1] an absolute
120    /// path in canonical form: it contains no ".." or "." entries, and no
121    /// symlinks.
122    ///
123    /// [^1]: See note on [`ResolvePath`] about time-of-check/time-of-use
124    ///     issues.
125    resolved: PathBuf,
126
127    /// The parts of the path that we have _not yet resolved_.  The item on the
128    /// top of the stack (that is, the end), is the next element that we'd like
129    /// to add to `resolved`.
130    ///
131    /// This is in reverse order: later path components at the start of the `Vec` (bottom of stack)
132    //
133    // TODO: I'd like to have a more efficient representation of this; the
134    // current one has a lot of tiny little allocations.
135    stack: Vec<Component>,
136
137    /// If true, we have encountered a nonrecoverable error and cannot yield any
138    /// more items.
139    ///
140    /// We have a flag for this so that we know to stop when we've encountered
141    /// an error for `lstat()` or `readlink()`: If we can't do those, we can't
142    /// continue resolving the path.
143    terminated: bool,
144
145    /// How many more steps are we willing to take in resolving this path?  We
146    /// decrement this by 1 every time we pop an element from the stack.  If we
147    /// ever realize that we've run out of steps, we abort, since that's
148    /// probably a symlink loop.
149    steps_remaining: usize,
150
151    /// A cache of the paths that we have already yielded to the caller.  We keep
152    /// this cache so that we don't have to `lstat()` or `readlink()` any path
153    /// more than once.  If the path was a symlink, then the value associated
154    /// with it is the target of that symlink.  Otherwise, the value associated
155    /// with it is None.
156    already_inspected: HashMap<PathBuf, Option<PathBuf>>,
157}
158
159/// How many steps are we willing to take in resolving a path?
160const MAX_STEPS: usize = 1024;
161
162impl ResolvePath {
163    /// Create a new empty ResolvePath.
164    fn empty() -> Self {
165        ResolvePath {
166            resolved: PathBuf::new(),
167            stack: Vec::new(),
168            terminated: false,
169            steps_remaining: MAX_STEPS,
170            already_inspected: HashMap::new(),
171        }
172    }
173    /// Construct a new `ResolvePath` iterator to resolve the provided `path`.
174    pub(crate) fn new(path: impl AsRef<Path>) -> Result<Self> {
175        let mut resolve = Self::empty();
176        let path = path.as_ref();
177        // The path resolution algorithm will _end_ with resolving the path we
178        // were provided...
179        push_prefix(&mut resolve.stack, path);
180        if resolve.stack.is_empty() {
181            return Err(Error::NotFound(path.to_path_buf()));
182        }
183        // ...and if if the path is relative, we will first resolve the current
184        // directory.
185        if path.is_relative() {
186            // This can fail, sadly.
187            let cwd = std::env::current_dir().map_err(|e| Error::CurrentDirectory(Arc::new(e)))?;
188            if !cwd.is_absolute() {
189                // This should be impossible, but let's make sure.
190                let ioe =
191                    io::Error::other(format!("Current directory {:?} was not absolute.", cwd));
192                return Err(Error::CurrentDirectory(Arc::new(ioe)));
193            }
194            push_prefix(&mut resolve.stack, cwd.as_ref());
195        }
196
197        Ok(resolve)
198    }
199
200    /// Consume this ResolvePath and return as much work as it was able to
201    /// complete.
202    ///
203    /// If the path was completely resolved, then we return the resolved
204    /// canonical path, and None.
205    ///
206    /// If the path was _not_ completely resolved (the loop terminated early, or
207    /// ended with an error), we return the part that we were able to resolve,
208    /// and a path that would need to be joined onto it to reach the intended
209    /// destination.
210    pub(crate) fn into_result(self) -> (PathBuf, Option<PathBuf>) {
211        let remainder = if self.stack.is_empty() {
212            None
213        } else {
214            Some(self.stack.into_iter().rev().map(|c| c.text).collect())
215        };
216
217        (self.resolved, remainder)
218    }
219}
220
221/// Push the string representation of each component of `path` onto `stack`,
222/// from last to first, so that the first component of `path` winds up on the
223/// top of the stack.
224///
225/// (This is a separate function rather than a method for borrow-checker
226/// reasons.)
227fn push_prefix(stack: &mut Vec<Component>, path: &Path) {
228    stack.extend(path.components().rev().map(|component| component.into()));
229}
230
231impl Iterator for ResolvePath {
232    type Item = Result<(PathBuf, PathType, Metadata)>;
233
234    fn next(&mut self) -> Option<Self::Item> {
235        // Usually we'll return a value from our first attempt at this loop; we
236        // only call "continue" if we encounter a path that we have already
237        // given the caller.
238        loop {
239            // If we're fused, we're fused.  Nothing more to do.
240            if self.terminated {
241                return None;
242            }
243            // We will necessarily take at least `stack.len()` more steps: if we
244            // don't have that many steps left, we cannot succeed.  Probably
245            // this indicates a symlink loop, though it could also be a maze of
246            // some kind.
247            //
248            // TODO: Arguably, we should keep taking steps until we run out, but doing
249            // so might potentially lead to our stack getting huge.  This way we
250            // keep the stack depth under control.
251            if self.steps_remaining < self.stack.len() {
252                self.terminated = true;
253                return Some(Err(Error::StepsExceeded));
254            }
255
256            // Look at the next component on the stack...
257            let next_part = match self.stack.pop() {
258                Some(p) => p,
259                None => {
260                    // This is the successful case: we have finished resolving every component on the stack.
261                    self.terminated = true;
262                    return None;
263                }
264            };
265            self.steps_remaining -= 1;
266
267            // ..and add that component to our resolved path to see what we
268            // should inspect next.
269            let inspecting: std::borrow::Cow<'_, Path> = if next_part.text == "." {
270                // Do nothing.
271                self.resolved.as_path().into()
272            } else if next_part.text == ".." {
273                // We can safely remove the last part of our path: We know it is
274                // canonical, so ".." will not give surprising results.  (If we
275                // are already at the root, "PathBuf::pop" will do nothing.)
276                self.resolved
277                    .parent()
278                    .unwrap_or(self.resolved.as_path())
279                    .into()
280            } else {
281                // We extend our path.  This may _temporarily_ make `resolved`
282                // non-canonical if next_part is the name of a symlink; we'll
283                // fix that in a minute.
284                //
285                // This is the only thing that can ever make `resolved` longer.
286                self.resolved.join(&next_part.text).into()
287            };
288
289            // Now "inspecting" is the path we want to look at.  Later in this
290            // function, we should replace "self.resolved" with "inspecting" if we
291            // find that "inspecting" is a good canonical path.
292
293            match self.already_inspected.get(inspecting.as_ref()) {
294                Some(Some(link_target)) => {
295                    // We already inspected this path, and it is a symlink.
296                    // Follow it, and loop.
297                    //
298                    // (See notes below starting with "This is a symlink!" for
299                    // more explanation of what we're doing here.)
300                    push_prefix(&mut self.stack, link_target.as_path());
301                    continue;
302                }
303                Some(None) => {
304                    // We've already inspected this path, and it's canonical.
305                    // We told the caller about it once before, so we just loop.
306                    self.resolved = inspecting.into_owned();
307                    continue;
308                }
309                None => {
310                    // We haven't seen this path before. Carry on.
311                }
312            }
313
314            // Look up the lstat() of the file, to see if it's a symlink.
315            let metadata = match inspecting.symlink_metadata() {
316                Ok(m) => m,
317                #[cfg(target_family = "windows")]
318                Err(e)
319                    if next_part.is_windows_prefix
320                        && e.raw_os_error() == Some(INVALID_FUNCTION) =>
321                {
322                    // We expected an error here, and we got one. Skip over this
323                    // path component and look at the next.
324                    self.resolved = inspecting.into_owned();
325                    continue;
326                }
327                Err(e) => {
328                    // Oops: can't lstat.  Move the last component back on to the stack, and terminate.
329                    self.stack.push(next_part);
330                    self.terminated = true;
331                    return Some(Err(Error::inspecting(e, inspecting)));
332                }
333            };
334
335            if metadata.file_type().is_symlink() {
336                // This is a symlink!
337                //
338                // We have to find out where it leads us...
339                let link_target = match inspecting.read_link() {
340                    Ok(t) => t,
341                    Err(e) => {
342                        // Oops: can't readlink.  Move the last component back on to the stack, and terminate.
343                        self.stack.push(next_part);
344                        self.terminated = true;
345                        return Some(Err(Error::inspecting(e, inspecting)));
346                    }
347                };
348
349                // We don't modify self.resolved here: we would be putting a
350                // symlink onto it, and symlinks aren't canonical.  (If the
351                // symlink is relative, then we'll continue resolving it from
352                // its target on the next iteration.  If the symlink is
353                // absolute, its first component will be "/" or the equivalent,
354                // which will replace self.resolved.)
355                push_prefix(&mut self.stack, link_target.as_path());
356                self.already_inspected
357                    .insert(inspecting.to_path_buf(), Some(link_target));
358                // We yield the link name, not the value of resolved.
359                return Some(Ok((inspecting.into_owned(), PathType::Symlink, metadata)));
360            } else {
361                // It's not a symlink: Therefore it is a real canonical
362                // directory or file that exists.
363                self.already_inspected
364                    .insert(inspecting.to_path_buf(), None);
365                self.resolved = inspecting.into_owned();
366                let path_type = if self.stack.is_empty() {
367                    PathType::Final
368                } else {
369                    PathType::Intermediate
370                };
371                return Some(Ok((self.resolved.clone(), path_type, metadata)));
372            }
373        }
374    }
375}
376
377impl FusedIterator for ResolvePath {}
378
379/*
380   Not needed, but can be a big help with debugging.
381impl std::fmt::Display for ResolvePath {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383        let remaining: PathBuf = self.stack.iter().rev().collect();
384        write!(f, "{{ {:?} }}/{{ {:?} }}", &self.resolved, remaining,)
385    }
386}
387*/
388
389#[cfg(test)]
390mod test {
391    // @@ begin test lint list maintained by maint/add_warning @@
392    #![allow(clippy::bool_assert_comparison)]
393    #![allow(clippy::clone_on_copy)]
394    #![allow(clippy::dbg_macro)]
395    #![allow(clippy::mixed_attributes_style)]
396    #![allow(clippy::print_stderr)]
397    #![allow(clippy::print_stdout)]
398    #![allow(clippy::single_char_pattern)]
399    #![allow(clippy::unwrap_used)]
400    #![allow(clippy::unchecked_time_subtraction)]
401    #![allow(clippy::useless_vec)]
402    #![allow(clippy::needless_pass_by_value)]
403    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
404    use super::*;
405    use crate::testing;
406
407    #[cfg(target_family = "unix")]
408    use crate::testing::LinkType;
409
410    /// Helper: skip `r` past the first occurrence of the path `p` in a
411    /// successful return.
412    fn skip_past(r: &mut ResolvePath, p: impl AsRef<Path>) {
413        #[allow(clippy::manual_flatten)]
414        for item in r {
415            if let Ok((name, _, _)) = item {
416                if name == p.as_ref() {
417                    break;
418                }
419            }
420        }
421    }
422
423    /// Helper: change the prefix on `path` (if any) to a verbatim prefix.
424    ///
425    /// We do this to match the output of `fs::canonicalize` on Windows, for
426    /// testing.
427    ///
428    /// If this function proves to be hard-to-maintain, we should consider
429    /// alternative ways of testing what it provides.
430    fn make_prefix_verbatim(path: PathBuf) -> PathBuf {
431        let mut components = path.components();
432        if let Some(std::path::Component::Prefix(prefix)) = components.next() {
433            use std::path::Prefix as P;
434            let verbatim = match prefix.kind() {
435                P::UNC(server, share) => {
436                    let mut p = OsString::from(r"\\?\UNC\");
437                    p.push(server);
438                    p.push("/");
439                    p.push(share);
440                    p
441                }
442                P::Disk(disk) => format!(r"\\?\{}:", disk as char).into(),
443                _ => return path, // original prefix is fine.
444            };
445            let mut newpath = PathBuf::from(verbatim);
446            newpath.extend(components.map(|c| c.as_os_str()));
447            newpath
448        } else {
449            path // nothing to do.
450        }
451    }
452
453    #[test]
454    fn simple_path() {
455        let d = testing::Dir::new();
456        let root = d.canonical_root();
457
458        // Try resolving a simple path that exists.
459        d.file("a/b/c");
460        let mut r = ResolvePath::new(d.path("a/b/c")).unwrap();
461        skip_past(&mut r, root);
462        let mut so_far = root.to_path_buf();
463        for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
464            let (p, pt, meta) = p.unwrap();
465            if pt == PathType::Final {
466                assert_eq!(c.as_os_str(), "c");
467                assert!(meta.is_file());
468            } else {
469                assert_eq!(pt, PathType::Intermediate);
470                assert!(meta.is_dir());
471            }
472            so_far.push(c);
473            assert_eq!(so_far, p);
474        }
475        let (canonical, rest) = r.into_result();
476        assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
477        assert!(rest.is_none());
478
479        // Same as above, starting from a relative path to the target.
480        let mut r = ResolvePath::new(d.relative_root().join("a/b/c")).unwrap();
481        skip_past(&mut r, root);
482        let mut so_far = root.to_path_buf();
483        for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
484            let (p, pt, meta) = p.unwrap();
485            if pt == PathType::Final {
486                assert_eq!(c.as_os_str(), "c");
487                assert!(meta.is_file());
488            } else {
489                assert_eq!(pt, PathType::Intermediate);
490                assert!(meta.is_dir());
491            }
492            so_far.push(c);
493            assert_eq!(so_far, p);
494        }
495        let (canonical, rest) = r.into_result();
496        let canonical = make_prefix_verbatim(canonical);
497        assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
498        assert!(rest.is_none());
499
500        // Try resolving a simple path that doesn't exist.
501        let mut r = ResolvePath::new(d.path("a/xxx/yyy")).unwrap();
502        skip_past(&mut r, root);
503        let (p, pt, _) = r.next().unwrap().unwrap();
504        assert_eq!(p, root.join("a"));
505        assert_eq!(pt, PathType::Intermediate);
506        let e = r.next().unwrap();
507        match e {
508            Err(Error::NotFound(p)) => assert_eq!(p, root.join("a/xxx")),
509            other => panic!("{:?}", other),
510        }
511        let (start, rest) = r.into_result();
512        assert_eq!(start, d.path("a").canonicalize().unwrap());
513        assert_eq!(rest.unwrap(), Path::new("xxx/yyy"));
514    }
515
516    #[test]
517    #[cfg(target_family = "unix")]
518    fn repeats() {
519        let d = testing::Dir::new();
520        let root = d.canonical_root();
521
522        // We're going to try a path with ..s in it, and make sure that we only
523        // get each given path once.
524        d.dir("a/b/c/d");
525        let mut r = ResolvePath::new(root.join("a/b/../b/../b/c/../c/d")).unwrap();
526        skip_past(&mut r, root);
527        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
528        assert_eq!(
529            paths,
530            vec![
531                root.join("a"),
532                root.join("a/b"),
533                root.join("a/b/c"),
534                root.join("a/b/c/d"),
535            ]
536        );
537
538        // Now try a symlink to a higher directory, and make sure we only get
539        // each path once.
540        d.link_rel(LinkType::Dir, "../../", "a/b/c/rel_lnk");
541        let mut r = ResolvePath::new(root.join("a/b/c/rel_lnk/b/c/d")).unwrap();
542        skip_past(&mut r, root);
543        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
544        assert_eq!(
545            paths,
546            vec![
547                root.join("a"),
548                root.join("a/b"),
549                root.join("a/b/c"),
550                root.join("a/b/c/rel_lnk"),
551                root.join("a/b/c/d"),
552            ]
553        );
554
555        // Once more, with an absolute symlink.
556        d.link_abs(LinkType::Dir, "a", "a/b/c/abs_lnk");
557        let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/d")).unwrap();
558        skip_past(&mut r, root);
559        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
560        assert_eq!(
561            paths,
562            vec![
563                root.join("a"),
564                root.join("a/b"),
565                root.join("a/b/c"),
566                root.join("a/b/c/abs_lnk"),
567                root.join("a/b/c/d"),
568            ]
569        );
570
571        // One more, with multiple links.
572        let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/d")).unwrap();
573        skip_past(&mut r, root);
574        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
575        assert_eq!(
576            paths,
577            vec![
578                root.join("a"),
579                root.join("a/b"),
580                root.join("a/b/c"),
581                root.join("a/b/c/abs_lnk"),
582                root.join("a/b/c/rel_lnk"),
583                root.join("a/b/c/d"),
584            ]
585        );
586
587        // Last time, visiting the same links more than once.
588        let mut r =
589            ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/rel_lnk/b/c/abs_lnk/b/c/d"))
590                .unwrap();
591        skip_past(&mut r, root);
592        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
593        assert_eq!(
594            paths,
595            vec![
596                root.join("a"),
597                root.join("a/b"),
598                root.join("a/b/c"),
599                root.join("a/b/c/abs_lnk"),
600                root.join("a/b/c/rel_lnk"),
601                root.join("a/b/c/d"),
602            ]
603        );
604    }
605
606    #[test]
607    #[cfg(target_family = "unix")]
608    fn looping() {
609        let d = testing::Dir::new();
610        let root = d.canonical_root();
611
612        d.dir("a/b/c");
613        // This file links to itself.  We should hit our loop detector and barf.
614        d.link_rel(LinkType::File, "../../b/c/d", "a/b/c/d");
615        let mut r = ResolvePath::new(root.join("a/b/c/d")).unwrap();
616        skip_past(&mut r, root);
617        assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
618        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
619        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
620        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/d"));
621        assert!(matches!(
622            r.next().unwrap().unwrap_err(),
623            Error::StepsExceeded
624        ));
625        assert!(r.next().is_none());
626
627        // These directories link to each other.
628        d.link_rel(LinkType::Dir, "./f", "a/b/c/e");
629        d.link_rel(LinkType::Dir, "./e", "a/b/c/f");
630        let mut r = ResolvePath::new(root.join("a/b/c/e/413")).unwrap();
631        skip_past(&mut r, root);
632        assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
633        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
634        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
635        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/e"));
636        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/f"));
637        assert!(matches!(
638            r.next().unwrap().unwrap_err(),
639            Error::StepsExceeded
640        ));
641        assert!(r.next().is_none());
642    }
643
644    #[cfg(target_family = "unix")]
645    #[test]
646    fn unix_permissions() {
647        use std::os::unix::prelude::PermissionsExt;
648
649        let d = testing::Dir::new();
650        let root = d.canonical_root();
651        d.dir("a/b/c/d/e");
652        d.chmod("a", 0o751);
653        d.chmod("a/b", 0o711);
654        d.chmod("a/b/c", 0o715);
655        d.chmod("a/b/c/d", 0o000);
656
657        let mut r = ResolvePath::new(root.join("a/b/c/d/e/413")).unwrap();
658        skip_past(&mut r, root);
659        let resolvable: Vec<_> = (&mut r)
660            .take(4)
661            .map(|item| {
662                let (p, _, m) = item.unwrap();
663                (
664                    p.strip_prefix(root).unwrap().to_string_lossy().into_owned(),
665                    m.permissions().mode() & 0o777,
666                )
667            })
668            .collect();
669        let expected = vec![
670            ("a", 0o751),
671            ("a/b", 0o711),
672            ("a/b/c", 0o715),
673            ("a/b/c/d", 0o000),
674        ];
675        for ((p1, m1), (p2, m2)) in resolvable.iter().zip(expected.iter()) {
676            assert_eq!(p1, p2);
677            assert_eq!(m1, m2);
678        }
679
680        #[cfg(not(target_os = "android"))]
681        if pwd_grp::getuid() == 0 {
682            // We won't actually get a CouldNotInspect if we're running as root,
683            // since root can read directories that are mode 000.
684            return;
685        }
686
687        let err = r.next().unwrap();
688        assert!(matches!(err, Err(Error::CouldNotInspect(_, _))));
689
690        assert!(r.next().is_none());
691    }
692
693    #[test]
694    fn past_root() {
695        let d = testing::Dir::new();
696        let root = d.canonical_root();
697        d.dir("a/b");
698        d.chmod("a", 0o700);
699        d.chmod("a/b", 0o700);
700
701        let root_as_relative: PathBuf = root
702            .components()
703            .filter(|c| matches!(c, std::path::Component::Normal(_)))
704            .collect();
705        let n = root.components().count();
706        // Start with our the "root" directory of our Dir...
707        let mut inspect_path = root.to_path_buf();
708        // Then go way past the root of the filesystem
709        for _ in 0..n * 2 {
710            inspect_path.push("..");
711        }
712        // Then back down to the "root" directory of the dir..
713        inspect_path.push(root_as_relative);
714        // Then to a/b.
715        inspect_path.push("a/b");
716
717        let r = ResolvePath::new(inspect_path.clone()).unwrap();
718        let final_path = r.last().unwrap().unwrap().0;
719        assert_eq!(final_path, inspect_path.canonicalize().unwrap());
720    }
721}