1
//! An iterator to resolve and canonicalize a filename.
2

            
3
use crate::{Error, Result};
4
use 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)]
17
pub(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)]
34
struct 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")]
47
const INVALID_FUNCTION: i32 = 1;
48

            
49
impl<'a> From<std::path::Component<'a>> for Component {
50
109546
    fn from(c: std::path::Component<'a>) -> Self {
51
109546
        #[cfg(target_family = "windows")]
52
109546
        let is_windows_prefix = matches!(c, std::path::Component::Prefix(_));
53
109546
        let text = c.as_os_str().to_owned();
54
109546
        Component {
55
109546
            #[cfg(target_family = "windows")]
56
109546
            is_windows_prefix,
57
109546
            text,
58
109546
        }
59
109546
    }
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)]
118
pub(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?
160
const MAX_STEPS: usize = 1024;
161

            
162
impl ResolvePath {
163
    /// Create a new empty ResolvePath.
164
22782
    fn empty() -> Self {
165
22782
        ResolvePath {
166
22782
            resolved: PathBuf::new(),
167
22782
            stack: Vec::new(),
168
22782
            terminated: false,
169
22782
            steps_remaining: MAX_STEPS,
170
22782
            already_inspected: HashMap::new(),
171
22782
        }
172
22782
    }
173
    /// Construct a new `ResolvePath` iterator to resolve the provided `path`.
174
22782
    pub(crate) fn new(path: impl AsRef<Path>) -> Result<Self> {
175
22782
        let mut resolve = Self::empty();
176
22782
        let path = path.as_ref();
177
22782
        // The path resolution algorithm will _end_ with resolving the path we
178
22782
        // were provided...
179
22782
        push_prefix(&mut resolve.stack, path);
180
22782
        // ...and if if the path is relative, we will first resolve the current
181
22782
        // directory.
182
22782
        if path.is_relative() {
183
            // This can fail, sadly.
184
713
            let cwd = std::env::current_dir().map_err(|e| Error::CurrentDirectory(Arc::new(e)))?;
185
713
            if !cwd.is_absolute() {
186
                // This should be impossible, but let's make sure.
187
                let ioe = io::Error::new(
188
                    io::ErrorKind::Other,
189
                    format!("Current directory {:?} was not absolute.", cwd),
190
                );
191
                return Err(Error::CurrentDirectory(Arc::new(ioe)));
192
713
            }
193
713
            push_prefix(&mut resolve.stack, cwd.as_ref());
194
22069
        }
195

            
196
22782
        Ok(resolve)
197
22782
    }
198

            
199
    /// Consume this ResolvePath and return as much work as it was able to
200
    /// complete.
201
    ///
202
    /// If the path was completely resolved, then we return the resolved
203
    /// canonical path, and None.
204
    ///
205
    /// If the path was _not_ completely resolved (the loop terminated early, or
206
    /// ended with an error), we return the part that we were able to resolve,
207
    /// and a path that would need to be joined onto it to reach the intended
208
    /// destination.
209
245
    pub(crate) fn into_result(self) -> (PathBuf, Option<PathBuf>) {
210
245
        let remainder = if self.stack.is_empty() {
211
4
            None
212
        } else {
213
487
            Some(self.stack.into_iter().rev().map(|c| c.text).collect())
214
        };
215

            
216
245
        (self.resolved, remainder)
217
245
    }
218
}
219

            
220
/// Push the string representation of each component of `path` onto `stack`,
221
/// from last to first, so that the first component of `path` winds up on the
222
/// top of the stack.
223
///
224
/// (This is a separate function rather than a method for borrow-checker
225
/// reasons.)
226
24949
fn push_prefix(stack: &mut Vec<Component>, path: &Path) {
227
110663
    stack.extend(path.components().rev().map(|component| component.into()));
228
24949
}
229

            
230
impl Iterator for ResolvePath {
231
    type Item = Result<(PathBuf, PathType, Metadata)>;
232

            
233
120606
    fn next(&mut self) -> Option<Self::Item> {
234
        // Usually we'll return a value from our first attempt at this loop; we
235
        // only call "continue" if we encounter a path that we have already
236
        // given the caller.
237
        loop {
238
            // If we're fused, we're fused.  Nothing more to do.
239
125274
            if self.terminated {
240
8
                return None;
241
125266
            }
242
125266
            // We will necessarily take at least `stack.len()` more steps: if we
243
125266
            // don't have that many steps left, we cannot succeed.  Probably
244
125266
            // this indicates a symlink loop, though it could also be a maze of
245
125266
            // some kind.
246
125266
            //
247
125266
            // TODO: Arguably, we should keep taking steps until we run out, but doing
248
125266
            // so might potentially lead to our stack getting huge.  This way we
249
125266
            // keep the stack depth under control.
250
125266
            if self.steps_remaining < self.stack.len() {
251
4
                self.terminated = true;
252
4
                return Some(Err(Error::StepsExceeded));
253
125262
            }
254

            
255
            // Look at the next component on the stack...
256
125262
            let next_part = match self.stack.pop() {
257
108072
                Some(p) => p,
258
                None => {
259
                    // This is the successful case: we have finished resolving every component on the stack.
260
17190
                    self.terminated = true;
261
17190
                    return None;
262
                }
263
            };
264
108072
            self.steps_remaining -= 1;
265

            
266
            // ..and add that component to our resolved path to see what we
267
            // should inspect next.
268
108072
            let inspecting: std::borrow::Cow<'_, Path> = if next_part.text == "." {
269
                // Do nothing.
270
1490
                self.resolved.as_path().into()
271
106582
            } else if next_part.text == ".." {
272
                // We can safely remove the last part of our path: We know it is
273
                // canonical, so ".." will not give surprising results.  (If we
274
                // are already at the root, "PathBuf::pop" will do nothing.)
275
868
                self.resolved
276
868
                    .parent()
277
868
                    .unwrap_or(self.resolved.as_path())
278
868
                    .into()
279
            } else {
280
                // We extend our path.  This may _temporarily_ make `resolved`
281
                // non-canonical if next_part is the name of a symlink; we'll
282
                // fix that in a minute.
283
                //
284
                // This is the only thing that can ever make `resolved` longer.
285
105714
                self.resolved.join(&next_part.text).into()
286
            };
287

            
288
            // Now "inspecting" is the path we want to look at.  Later in this
289
            // function, we should replace "self.resolved" with "inspecting" if we
290
            // find that "inspecting" is a good canonical path.
291

            
292
108072
            match self.already_inspected.get(inspecting.as_ref()) {
293
1424
                Some(Some(link_target)) => {
294
1424
                    // We already inspected this path, and it is a symlink.
295
1424
                    // Follow it, and loop.
296
1424
                    //
297
1424
                    // (See notes below starting with "This is a symlink!" for
298
1424
                    // more explanation of what we're doing here.)
299
1424
                    push_prefix(&mut self.stack, link_target.as_path());
300
1424
                    continue;
301
                }
302
                Some(None) => {
303
                    // We've already inspected this path, and it's canonical.
304
                    // We told the caller about it once before, so we just loop.
305
3244
                    self.resolved = inspecting.into_owned();
306
3244
                    continue;
307
                }
308
103404
                None => {
309
103404
                    // We haven't seen this path before. Carry on.
310
103404
                }
311
            }
312

            
313
            // Look up the lstat() of the file, to see if it's a symlink.
314
103404
            let metadata = match inspecting.symlink_metadata() {
315
98644
                Ok(m) => m,
316
                #[cfg(target_family = "windows")]
317
                Err(e)
318
                    if next_part.is_windows_prefix
319
                        && e.raw_os_error() == Some(INVALID_FUNCTION) =>
320
                {
321
                    // We expected an error here, and we got one. Skip over this
322
                    // path component and look at the next.
323
                    self.resolved = inspecting.into_owned();
324
                    continue;
325
                }
326
4760
                Err(e) => {
327
4760
                    // Oops: can't lstat.  Move the last component back on to the stack, and terminate.
328
4760
                    self.stack.push(next_part);
329
4760
                    self.terminated = true;
330
4760
                    return Some(Err(Error::inspecting(e, inspecting)));
331
                }
332
            };
333

            
334
98644
            if metadata.file_type().is_symlink() {
335
                // This is a symlink!
336
                //
337
                // We have to find out where it leads us...
338
30
                let link_target = match inspecting.read_link() {
339
30
                    Ok(t) => t,
340
                    Err(e) => {
341
                        // Oops: can't readlink.  Move the last component back on to the stack, and terminate.
342
                        self.stack.push(next_part);
343
                        self.terminated = true;
344
                        return Some(Err(Error::inspecting(e, inspecting)));
345
                    }
346
                };
347

            
348
                // We don't modify self.resolved here: we would be putting a
349
                // symlink onto it, and symlinks aren't canonical.  (If the
350
                // symlink is relative, then we'll continue resolving it from
351
                // its target on the next iteration.  If the symlink is
352
                // absolute, its first component will be "/" or the equivalent,
353
                // which will replace self.resolved.)
354
30
                push_prefix(&mut self.stack, link_target.as_path());
355
30
                self.already_inspected
356
30
                    .insert(inspecting.to_path_buf(), Some(link_target));
357
30
                // We yield the link name, not the value of resolved.
358
30
                return Some(Ok((inspecting.into_owned(), PathType::Symlink, metadata)));
359
            } else {
360
                // It's not a symlink: Therefore it is a real canonical
361
                // directory or file that exists.
362
98614
                self.already_inspected
363
98614
                    .insert(inspecting.to_path_buf(), None);
364
98614
                self.resolved = inspecting.into_owned();
365
98614
                let path_type = if self.stack.is_empty() {
366
17761
                    PathType::Final
367
                } else {
368
80853
                    PathType::Intermediate
369
                };
370
98614
                return Some(Ok((self.resolved.clone(), path_type, metadata)));
371
            }
372
        }
373
120606
    }
374
}
375

            
376
impl FusedIterator for ResolvePath {}
377

            
378
/*
379
   Not needed, but can be a big help with debugging.
380
impl std::fmt::Display for ResolvePath {
381
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382
        let remaining: PathBuf = self.stack.iter().rev().collect();
383
        write!(f, "{{ {:?} }}/{{ {:?} }}", &self.resolved, remaining,)
384
    }
385
}
386
*/
387

            
388
#[cfg(test)]
389
mod test {
390
    // @@ begin test lint list maintained by maint/add_warning @@
391
    #![allow(clippy::bool_assert_comparison)]
392
    #![allow(clippy::clone_on_copy)]
393
    #![allow(clippy::dbg_macro)]
394
    #![allow(clippy::mixed_attributes_style)]
395
    #![allow(clippy::print_stderr)]
396
    #![allow(clippy::print_stdout)]
397
    #![allow(clippy::single_char_pattern)]
398
    #![allow(clippy::unwrap_used)]
399
    #![allow(clippy::unchecked_duration_subtraction)]
400
    #![allow(clippy::useless_vec)]
401
    #![allow(clippy::needless_pass_by_value)]
402
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
403
    use super::*;
404
    use crate::testing;
405

            
406
    #[cfg(target_family = "unix")]
407
    use crate::testing::LinkType;
408

            
409
    /// Helper: skip `r` past the first occurrence of the path `p` in a
410
    /// successful return.
411
    fn skip_past(r: &mut ResolvePath, p: impl AsRef<Path>) {
412
        #[allow(clippy::manual_flatten)]
413
        for item in r {
414
            if let Ok((name, _, _)) = item {
415
                if name == p.as_ref() {
416
                    break;
417
                }
418
            }
419
        }
420
    }
421

            
422
    /// Helper: change the prefix on `path` (if any) to a verbatim prefix.
423
    ///
424
    /// We do this to match the output of `fs::canonicalize` on Windows, for
425
    /// testing.
426
    ///
427
    /// If this function proves to be hard-to-maintain, we should consider
428
    /// alternative ways of testing what it provides.
429
    fn make_prefix_verbatim(path: PathBuf) -> PathBuf {
430
        let mut components = path.components();
431
        if let Some(std::path::Component::Prefix(prefix)) = components.next() {
432
            use std::path::Prefix as P;
433
            let verbatim = match prefix.kind() {
434
                P::UNC(server, share) => {
435
                    let mut p = OsString::from(r"\\?\UNC\");
436
                    p.push(server);
437
                    p.push("/");
438
                    p.push(share);
439
                    p
440
                }
441
                P::Disk(disk) => format!(r"\\?\{}:", disk as char).into(),
442
                _ => return path, // original prefix is fine.
443
            };
444
            let mut newpath = PathBuf::from(verbatim);
445
            newpath.extend(components.map(|c| c.as_os_str()));
446
            newpath
447
        } else {
448
            path // nothing to do.
449
        }
450
    }
451

            
452
    #[test]
453
    fn simple_path() {
454
        let d = testing::Dir::new();
455
        let root = d.canonical_root();
456

            
457
        // Try resolving a simple path that exists.
458
        d.file("a/b/c");
459
        let mut r = ResolvePath::new(d.path("a/b/c")).unwrap();
460
        skip_past(&mut r, root);
461
        let mut so_far = root.to_path_buf();
462
        for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
463
            let (p, pt, meta) = p.unwrap();
464
            if pt == PathType::Final {
465
                assert_eq!(c.as_os_str(), "c");
466
                assert!(meta.is_file());
467
            } else {
468
                assert_eq!(pt, PathType::Intermediate);
469
                assert!(meta.is_dir());
470
            }
471
            so_far.push(c);
472
            assert_eq!(so_far, p);
473
        }
474
        let (canonical, rest) = r.into_result();
475
        assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
476
        assert!(rest.is_none());
477

            
478
        // Same as above, starting from a relative path to the target.
479
        let mut r = ResolvePath::new(d.relative_root().join("a/b/c")).unwrap();
480
        skip_past(&mut r, root);
481
        let mut so_far = root.to_path_buf();
482
        for (c, p) in Path::new("a/b/c").components().zip(&mut r) {
483
            let (p, pt, meta) = p.unwrap();
484
            if pt == PathType::Final {
485
                assert_eq!(c.as_os_str(), "c");
486
                assert!(meta.is_file());
487
            } else {
488
                assert_eq!(pt, PathType::Intermediate);
489
                assert!(meta.is_dir());
490
            }
491
            so_far.push(c);
492
            assert_eq!(so_far, p);
493
        }
494
        let (canonical, rest) = r.into_result();
495
        let canonical = make_prefix_verbatim(canonical);
496
        assert_eq!(canonical, d.path("a/b/c").canonicalize().unwrap());
497
        assert!(rest.is_none());
498

            
499
        // Try resolving a simple path that doesn't exist.
500
        let mut r = ResolvePath::new(d.path("a/xxx/yyy")).unwrap();
501
        skip_past(&mut r, root);
502
        let (p, pt, _) = r.next().unwrap().unwrap();
503
        assert_eq!(p, root.join("a"));
504
        assert_eq!(pt, PathType::Intermediate);
505
        let e = r.next().unwrap();
506
        match e {
507
            Err(Error::NotFound(p)) => assert_eq!(p, root.join("a/xxx")),
508
            other => panic!("{:?}", other),
509
        }
510
        let (start, rest) = r.into_result();
511
        assert_eq!(start, d.path("a").canonicalize().unwrap());
512
        assert_eq!(rest.unwrap(), Path::new("xxx/yyy"));
513
    }
514

            
515
    #[test]
516
    #[cfg(target_family = "unix")]
517
    fn repeats() {
518
        let d = testing::Dir::new();
519
        let root = d.canonical_root();
520

            
521
        // We're going to try a path with ..s in it, and make sure that we only
522
        // get each given path once.
523
        d.dir("a/b/c/d");
524
        let mut r = ResolvePath::new(root.join("a/b/../b/../b/c/../c/d")).unwrap();
525
        skip_past(&mut r, root);
526
        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
527
        assert_eq!(
528
            paths,
529
            vec![
530
                root.join("a"),
531
                root.join("a/b"),
532
                root.join("a/b/c"),
533
                root.join("a/b/c/d"),
534
            ]
535
        );
536

            
537
        // Now try a symlink to a higher directory, and make sure we only get
538
        // each path once.
539
        d.link_rel(LinkType::Dir, "../../", "a/b/c/rel_lnk");
540
        let mut r = ResolvePath::new(root.join("a/b/c/rel_lnk/b/c/d")).unwrap();
541
        skip_past(&mut r, root);
542
        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
543
        assert_eq!(
544
            paths,
545
            vec![
546
                root.join("a"),
547
                root.join("a/b"),
548
                root.join("a/b/c"),
549
                root.join("a/b/c/rel_lnk"),
550
                root.join("a/b/c/d"),
551
            ]
552
        );
553

            
554
        // Once more, with an absolute symlink.
555
        d.link_abs(LinkType::Dir, "a", "a/b/c/abs_lnk");
556
        let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/d")).unwrap();
557
        skip_past(&mut r, root);
558
        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
559
        assert_eq!(
560
            paths,
561
            vec![
562
                root.join("a"),
563
                root.join("a/b"),
564
                root.join("a/b/c"),
565
                root.join("a/b/c/abs_lnk"),
566
                root.join("a/b/c/d"),
567
            ]
568
        );
569

            
570
        // One more, with multiple links.
571
        let mut r = ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/d")).unwrap();
572
        skip_past(&mut r, root);
573
        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
574
        assert_eq!(
575
            paths,
576
            vec![
577
                root.join("a"),
578
                root.join("a/b"),
579
                root.join("a/b/c"),
580
                root.join("a/b/c/abs_lnk"),
581
                root.join("a/b/c/rel_lnk"),
582
                root.join("a/b/c/d"),
583
            ]
584
        );
585

            
586
        // Last time, visiting the same links more than once.
587
        let mut r =
588
            ResolvePath::new(root.join("a/b/c/abs_lnk/b/c/rel_lnk/b/c/rel_lnk/b/c/abs_lnk/b/c/d"))
589
                .unwrap();
590
        skip_past(&mut r, root);
591
        let paths: Vec<_> = r.map(|item| item.unwrap().0).collect();
592
        assert_eq!(
593
            paths,
594
            vec![
595
                root.join("a"),
596
                root.join("a/b"),
597
                root.join("a/b/c"),
598
                root.join("a/b/c/abs_lnk"),
599
                root.join("a/b/c/rel_lnk"),
600
                root.join("a/b/c/d"),
601
            ]
602
        );
603
    }
604

            
605
    #[test]
606
    #[cfg(target_family = "unix")]
607
    fn looping() {
608
        let d = testing::Dir::new();
609
        let root = d.canonical_root();
610

            
611
        d.dir("a/b/c");
612
        // This file links to itself.  We should hit our loop detector and barf.
613
        d.link_rel(LinkType::File, "../../b/c/d", "a/b/c/d");
614
        let mut r = ResolvePath::new(root.join("a/b/c/d")).unwrap();
615
        skip_past(&mut r, root);
616
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
617
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
618
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
619
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/d"));
620
        assert!(matches!(
621
            r.next().unwrap().unwrap_err(),
622
            Error::StepsExceeded
623
        ));
624
        assert!(r.next().is_none());
625

            
626
        // These directories link to each other.
627
        d.link_rel(LinkType::Dir, "./f", "a/b/c/e");
628
        d.link_rel(LinkType::Dir, "./e", "a/b/c/f");
629
        let mut r = ResolvePath::new(root.join("a/b/c/e/413")).unwrap();
630
        skip_past(&mut r, root);
631
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a"));
632
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b"));
633
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c"));
634
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/e"));
635
        assert_eq!(r.next().unwrap().unwrap().0, root.join("a/b/c/f"));
636
        assert!(matches!(
637
            r.next().unwrap().unwrap_err(),
638
            Error::StepsExceeded
639
        ));
640
        assert!(r.next().is_none());
641
    }
642

            
643
    #[cfg(target_family = "unix")]
644
    #[test]
645
    fn unix_permissions() {
646
        use std::os::unix::prelude::PermissionsExt;
647

            
648
        let d = testing::Dir::new();
649
        let root = d.canonical_root();
650
        d.dir("a/b/c/d/e");
651
        d.chmod("a", 0o751);
652
        d.chmod("a/b", 0o711);
653
        d.chmod("a/b/c", 0o715);
654
        d.chmod("a/b/c/d", 0o000);
655

            
656
        let mut r = ResolvePath::new(root.join("a/b/c/d/e/413")).unwrap();
657
        skip_past(&mut r, root);
658
        let resolvable: Vec<_> = (&mut r)
659
            .take(4)
660
            .map(|item| {
661
                let (p, _, m) = item.unwrap();
662
                (
663
                    p.strip_prefix(root).unwrap().to_string_lossy().into_owned(),
664
                    m.permissions().mode() & 0o777,
665
                )
666
            })
667
            .collect();
668
        let expected = vec![
669
            ("a", 0o751),
670
            ("a/b", 0o711),
671
            ("a/b/c", 0o715),
672
            ("a/b/c/d", 0o000),
673
        ];
674
        for ((p1, m1), (p2, m2)) in resolvable.iter().zip(expected.iter()) {
675
            assert_eq!(p1, p2);
676
            assert_eq!(m1, m2);
677
        }
678

            
679
        #[cfg(not(target_os = "android"))]
680
        if pwd_grp::getuid() == 0 {
681
            // We won't actually get a CouldNotInspect if we're running as root,
682
            // since root can read directories that are mode 000.
683
            return;
684
        }
685

            
686
        let err = r.next().unwrap();
687
        assert!(matches!(err, Err(Error::CouldNotInspect(_, _))));
688

            
689
        assert!(r.next().is_none());
690
    }
691

            
692
    #[test]
693
    fn past_root() {
694
        let d = testing::Dir::new();
695
        let root = d.canonical_root();
696
        d.dir("a/b");
697
        d.chmod("a", 0o700);
698
        d.chmod("a/b", 0o700);
699

            
700
        let root_as_relative: PathBuf = root
701
            .components()
702
            .filter(|c| matches!(c, std::path::Component::Normal(_)))
703
            .collect();
704
        let n = root.components().count();
705
        // Start with our the "root" directory of our Dir...
706
        let mut inspect_path = root.to_path_buf();
707
        // Then go way past the root of the filesystem
708
        for _ in 0..n * 2 {
709
            inspect_path.push("..");
710
        }
711
        // Then back down to the "root" directory of the dir..
712
        inspect_path.push(root_as_relative);
713
        // Then to a/b.
714
        inspect_path.push("a/b");
715

            
716
        let r = ResolvePath::new(inspect_path.clone()).unwrap();
717
        let final_path = r.last().unwrap().unwrap().0;
718
        assert_eq!(final_path, inspect_path.canonicalize().unwrap());
719
    }
720
}