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 #![allow(clippy::string_slice)] // See arti#2571
404 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
405 use super::*;
406 use crate::testing;
407
408 #[cfg(target_family = "unix")]
409 use crate::testing::LinkType;
410
411 /// Helper: skip `r` past the first occurrence of the path `p` in a
412 /// successful return.
413 fn skip_past(r: &mut ResolvePath, p: impl AsRef<Path>) {
414 #[allow(clippy::manual_flatten)]
415 for item in r {
416 if let Ok((name, _, _)) = item {
417 if name == p.as_ref() {
418 break;
419 }
420 }
421 }
422 }
423
424 /// Helper: change the prefix on `path` (if any) to a verbatim prefix.
425 ///
426 /// We do this to match the output of `fs::canonicalize` on Windows, for
427 /// testing.
428 ///
429 /// If this function proves to be hard-to-maintain, we should consider
430 /// alternative ways of testing what it provides.
431 fn make_prefix_verbatim(path: PathBuf) -> PathBuf {
432 let mut components = path.components();
433 if let Some(std::path::Component::Prefix(prefix)) = components.next() {
434 use std::path::Prefix as P;
435 let verbatim = match prefix.kind() {
436 P::UNC(server, share) => {
437 let mut p = OsString::from(r"\\?\UNC\");
438 p.push(server);
439 p.push("/");
440 p.push(share);
441 p
442 }
443 P::Disk(disk) => format!(r"\\?\{}:", disk as char).into(),
444 _ => return path, // original prefix is fine.
445 };
446 let mut newpath = PathBuf::from(verbatim);
447 newpath.extend(components.map(|c| c.as_os_str()));
448 newpath
449 } else {
450 path // nothing to do.
451 }
452 }
453
454 #[test]
455 fn simple_path() {
456 let d = testing::Dir::new();
457 let root = d.canonical_root();
458
459 // Try resolving a simple path that exists.
460 d.file("a/b/c");
461 let mut r = ResolvePath::new(d.path("a/b/c")).unwrap();
462 skip_past(&mut r, root);
463 let mut so_far = root.to_path_buf();
464 for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
465 let (p, pt, meta) = p.unwrap();
466 if pt == PathType::Final {
467 assert_eq!(c.as_os_str(), "c");
468 assert!(meta.is_file());
469 } else {
470 assert_eq!(pt, PathType::Intermediate);
471 assert!(meta.is_dir());
472 }
473 so_far.push(c);
474 assert_eq!(so_far, p);
475 }
476 let (canonical, rest) = r.into_result();
477 assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
478 assert!(rest.is_none());
479
480 // Same as above, starting from a relative path to the target.
481 let mut r = ResolvePath::new(d.relative_root().join("a/b/c")).unwrap();
482 skip_past(&mut r, root);
483 let mut so_far = root.to_path_buf();
484 for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
485 let (p, pt, meta) = p.unwrap();
486 if pt == PathType::Final {
487 assert_eq!(c.as_os_str(), "c");
488 assert!(meta.is_file());
489 } else {
490 assert_eq!(pt, PathType::Intermediate);
491 assert!(meta.is_dir());
492 }
493 so_far.push(c);
494 assert_eq!(so_far, p);
495 }
496 let (canonical, rest) = r.into_result();
497 let canonical = make_prefix_verbatim(canonical);
498 assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
499 assert!(rest.is_none());
500
501 // Try resolving a simple path that doesn't exist.
502 let mut r = ResolvePath::new(d.path("a/xxx/yyy")).unwrap();
503 skip_past(&mut r, root);
504 let (p, pt, _) = r.next().unwrap().unwrap();
505 assert_eq!(p, root.join("a"));
506 assert_eq!(pt, PathType::Intermediate);
507 let e = r.next().unwrap();
508 match e {
509 Err(Error::NotFound(p)) => assert_eq!(p, root.join("a/xxx")),
510 other => panic!("{:?}", other),
511 }
512 let (start, rest) = r.into_result();
513 assert_eq!(start, d.path("a").canonicalize().unwrap());
514 assert_eq!(rest.unwrap(), Path::new("xxx/yyy"));
515 }
516
517 #[test]
518 #[cfg(target_family = "unix")]
519 fn repeats() {
520 let d = testing::Dir::new();
521 let root = d.canonical_root();
522
523 // We're going to try a path with ..s in it, and make sure that we only
524 // get each given path once.
525 d.dir("a/b/c/d");
526 let mut r = ResolvePath::new(root.join("a/b/../b/../b/c/../c/d")).unwrap();
527 skip_past(&mut r, root);
528 let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
529 assert_eq!(
530 paths,
531 vec![
532 root.join("a"),
533 root.join("a/b"),
534 root.join("a/b/c"),
535 root.join("a/b/c/d"),
536 ]
537 );
538
539 // Now try a symlink to a higher directory, and make sure we only get
540 // each path once.
541 d.link_rel(LinkType::Dir, "../../", "a/b/c/rel_lnk");
542 let mut r = ResolvePath::new(root.join("a/b/c/rel_lnk/b/c/d")).unwrap();
543 skip_past(&mut r, root);
544 let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
545 assert_eq!(
546 paths,
547 vec![
548 root.join("a"),
549 root.join("a/b"),
550 root.join("a/b/c"),
551 root.join("a/b/c/rel_lnk"),
552 root.join("a/b/c/d"),
553 ]
554 );
555
556 // Once more, with an absolute symlink.
557 d.link_abs(LinkType::Dir, "a", "a/b/c/abs_lnk");
558 let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/d")).unwrap();
559 skip_past(&mut r, root);
560 let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
561 assert_eq!(
562 paths,
563 vec![
564 root.join("a"),
565 root.join("a/b"),
566 root.join("a/b/c"),
567 root.join("a/b/c/abs_lnk"),
568 root.join("a/b/c/d"),
569 ]
570 );
571
572 // One more, with multiple links.
573 let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/d")).unwrap();
574 skip_past(&mut r, root);
575 let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
576 assert_eq!(
577 paths,
578 vec![
579 root.join("a"),
580 root.join("a/b"),
581 root.join("a/b/c"),
582 root.join("a/b/c/abs_lnk"),
583 root.join("a/b/c/rel_lnk"),
584 root.join("a/b/c/d"),
585 ]
586 );
587
588 // Last time, visiting the same links more than once.
589 let mut r =
590 ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/rel_lnk/b/c/abs_lnk/b/c/d"))
591 .unwrap();
592 skip_past(&mut r, root);
593 let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
594 assert_eq!(
595 paths,
596 vec![
597 root.join("a"),
598 root.join("a/b"),
599 root.join("a/b/c"),
600 root.join("a/b/c/abs_lnk"),
601 root.join("a/b/c/rel_lnk"),
602 root.join("a/b/c/d"),
603 ]
604 );
605 }
606
607 #[test]
608 #[cfg(target_family = "unix")]
609 fn looping() {
610 let d = testing::Dir::new();
611 let root = d.canonical_root();
612
613 d.dir("a/b/c");
614 // This file links to itself. We should hit our loop detector and barf.
615 d.link_rel(LinkType::File, "../../b/c/d", "a/b/c/d");
616 let mut r = ResolvePath::new(root.join("a/b/c/d")).unwrap();
617 skip_past(&mut r, root);
618 assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
619 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
620 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
621 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/d"));
622 assert!(matches!(
623 r.next().unwrap().unwrap_err(),
624 Error::StepsExceeded
625 ));
626 assert!(r.next().is_none());
627
628 // These directories link to each other.
629 d.link_rel(LinkType::Dir, "./f", "a/b/c/e");
630 d.link_rel(LinkType::Dir, "./e", "a/b/c/f");
631 let mut r = ResolvePath::new(root.join("a/b/c/e/413")).unwrap();
632 skip_past(&mut r, root);
633 assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
634 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
635 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
636 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/e"));
637 assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/f"));
638 assert!(matches!(
639 r.next().unwrap().unwrap_err(),
640 Error::StepsExceeded
641 ));
642 assert!(r.next().is_none());
643 }
644
645 #[cfg(target_family = "unix")]
646 #[test]
647 fn unix_permissions() {
648 use std::os::unix::prelude::PermissionsExt;
649
650 let d = testing::Dir::new();
651 let root = d.canonical_root();
652 d.dir("a/b/c/d/e");
653 d.chmod("a", 0o751);
654 d.chmod("a/b", 0o711);
655 d.chmod("a/b/c", 0o715);
656 d.chmod("a/b/c/d", 0o000);
657
658 let mut r = ResolvePath::new(root.join("a/b/c/d/e/413")).unwrap();
659 skip_past(&mut r, root);
660 let resolvable: Vec<_> = (&mut r)
661 .take(4)
662 .map(|item| {
663 let (p, _, m) = item.unwrap();
664 (
665 p.strip_prefix(root).unwrap().to_string_lossy().into_owned(),
666 m.permissions().mode() & 0o777,
667 )
668 })
669 .collect();
670 let expected = vec![
671 ("a", 0o751),
672 ("a/b", 0o711),
673 ("a/b/c", 0o715),
674 ("a/b/c/d", 0o000),
675 ];
676 for ((p1, m1), (p2, m2)) in resolvable.iter().zip(expected.iter()) {
677 assert_eq!(p1, p2);
678 assert_eq!(m1, m2);
679 }
680
681 #[cfg(not(target_os = "android"))]
682 if pwd_grp::getuid() == 0 {
683 // We won't actually get a CouldNotInspect if we're running as root,
684 // since root can read directories that are mode 000.
685 return;
686 }
687
688 let err = r.next().unwrap();
689 assert!(matches!(err, Err(Error::CouldNotInspect(_, _))));
690
691 assert!(r.next().is_none());
692 }
693
694 #[test]
695 fn past_root() {
696 let d = testing::Dir::new();
697 let root = d.canonical_root();
698 d.dir("a/b");
699 d.chmod("a", 0o700);
700 d.chmod("a/b", 0o700);
701
702 let root_as_relative: PathBuf = root
703 .components()
704 .filter(|c| matches!(c, std::path::Component::Normal(_)))
705 .collect();
706 let n = root.components().count();
707 // Start with our the "root" directory of our Dir...
708 let mut inspect_path = root.to_path_buf();
709 // Then go way past the root of the filesystem
710 for _ in 0..n * 2 {
711 inspect_path.push("..");
712 }
713 // Then back down to the "root" directory of the dir..
714 inspect_path.push(root_as_relative);
715 // Then to a/b.
716 inspect_path.push("a/b");
717
718 let r = ResolvePath::new(inspect_path.clone()).unwrap();
719 let final_path = r.last().unwrap().unwrap().0;
720 assert_eq!(final_path, inspect_path.canonicalize().unwrap());
721 }
722}