cap_primitives/fs/
stat.rs

1//! This defines `stat`, the primary entrypoint to sandboxed metadata querying.
2
3#[cfg(racy_asserts)]
4use crate::fs::{canonicalize, map_result, stat_unchecked};
5use crate::fs::{stat_impl, FollowSymlinks, Metadata};
6use std::path::Path;
7use std::{fs, io};
8
9/// Perform an `fstatat`-like operation, ensuring that the resolution of the
10/// path never escapes the directory tree rooted at `start`.
11#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
12#[inline]
13pub fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result<Metadata> {
14    // Call the underlying implementation.
15    let result = stat_impl(start, path, follow);
16
17    #[cfg(racy_asserts)]
18    let stat = stat_unchecked(start, path, follow);
19
20    #[cfg(racy_asserts)]
21    check_stat(start, path, follow, &result, &stat);
22
23    result
24}
25
26#[cfg(racy_asserts)]
27#[allow(clippy::enum_glob_use)]
28fn check_stat(
29    start: &fs::File,
30    path: &Path,
31    follow: FollowSymlinks,
32    result: &io::Result<Metadata>,
33    stat: &io::Result<Metadata>,
34) {
35    use io::ErrorKind::*;
36
37    match (map_result(result), map_result(stat)) {
38        (Ok(metadata), Ok(unchecked_metadata)) => {
39            assert_same_file_metadata!(
40                metadata,
41                unchecked_metadata,
42                "path resolution inconsistency: start='{:?}', path='{}'",
43                start,
44                path.display(),
45            );
46        }
47
48        (Err((PermissionDenied, message)), _) => {
49            if let FollowSymlinks::Yes = follow {
50                match map_result(&canonicalize(start, path)) {
51                    Ok(_) => (),
52                    Err((PermissionDenied, canon_message)) => {
53                        assert_eq!(message, canon_message);
54                    }
55                    err => panic!("stat failed where canonicalize succeeded: {:?}", err),
56                }
57            } else {
58                // TODO: Check that stat in the no-follow case got the right
59                // error.
60            }
61        }
62
63        (Err((kind, message)), Err((unchecked_kind, unchecked_message))) => {
64            assert_eq!(kind, unchecked_kind);
65            assert_eq!(
66                message,
67                unchecked_message,
68                "start='{:?}', path='{:?}'",
69                start,
70                path.display()
71            );
72        }
73
74        other => panic!(
75            "unexpected result from stat start='{:?}', path='{}':\n{:#?}",
76            start,
77            path.display(),
78            other,
79        ),
80    }
81}