cap_primitives/rustix/fs/
stat_unchecked.rs

1use crate::fs::{FollowSymlinks, ImplMetadataExt, Metadata};
2use rustix::fs::{statat, AtFlags};
3use std::path::Path;
4use std::{fs, io};
5
6#[cfg(target_os = "linux")]
7use rustix::fs::{statx, StatxFlags};
8#[cfg(target_os = "linux")]
9use std::sync::atomic::{AtomicU8, Ordering};
10
11/// *Unsandboxed* function similar to `stat`, but which does not perform
12/// sandboxing.
13pub(crate) fn stat_unchecked(
14    start: &fs::File,
15    path: &Path,
16    follow: FollowSymlinks,
17) -> io::Result<Metadata> {
18    let atflags = match follow {
19        FollowSymlinks::Yes => AtFlags::empty(),
20        FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW,
21    };
22
23    // `statx` is preferred on regular Linux because it can return creation
24    // times. Linux kernels prior to 4.11 don't have `statx` and return
25    // `ENOSYS`. Older versions of Docker/seccomp would return `EPERM` for
26    // `statx`; see <https://github.com/rust-lang/rust/pull/65685/>. We store
27    // the availability in a global to avoid unnecessary syscalls.
28    //
29    // On Android, the [seccomp policy] prevents us from even
30    // detecting whether `statx` is supported, so don't even try.
31    //
32    // [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html
33    #[cfg(target_os = "linux")]
34    {
35        // 0: Unknown
36        // 1: Not available
37        // 2: Available
38        static STATX_STATE: AtomicU8 = AtomicU8::new(0);
39        let state = STATX_STATE.load(Ordering::Relaxed);
40
41        if state != 1 {
42            let statx_result = statx(
43                start,
44                path,
45                atflags,
46                StatxFlags::BASIC_STATS | StatxFlags::BTIME,
47            );
48            match statx_result {
49                Ok(statx) => {
50                    if state == 0 {
51                        STATX_STATE.store(2, Ordering::Relaxed);
52                    }
53                    return Ok(ImplMetadataExt::from_rustix_statx(statx));
54                }
55                Err(rustix::io::Errno::NOSYS) => STATX_STATE.store(1, Ordering::Relaxed),
56                Err(rustix::io::Errno::PERM) if state == 0 => {
57                    // This is an unlikely case, as `statx` doesn't normally
58                    // return `PERM` errors. One way this can happen is when
59                    // running on old versions of seccomp/Docker. If `statx` on
60                    // the current working directory returns a similar error,
61                    // then stop using `statx`.
62                    if let Err(rustix::io::Errno::PERM) = statx(
63                        rustix::fs::CWD,
64                        "",
65                        AtFlags::EMPTY_PATH,
66                        StatxFlags::empty(),
67                    ) {
68                        STATX_STATE.store(1, Ordering::Relaxed);
69                    } else {
70                        return Err(rustix::io::Errno::PERM.into());
71                    }
72                }
73                Err(e) => return Err(e.into()),
74            }
75        }
76    }
77
78    Ok(statat(start, path, atflags).map(ImplMetadataExt::from_rustix)?)
79}