logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use crate::fs::{FollowSymlinks, Metadata, MetadataExt};
use rustix::fs::{statat, AtFlags};
use std::path::Path;
use std::{fs, io};

#[cfg(any(target_os = "android", target_os = "linux"))]
use rustix::fs::{statx, StatxFlags};
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::sync::atomic::{AtomicU8, Ordering};

/// *Unsandboxed* function similar to `stat`, but which does not perform
/// sandboxing.
pub(crate) fn stat_unchecked(
    start: &fs::File,
    path: &Path,
    follow: FollowSymlinks,
) -> io::Result<Metadata> {
    let atflags = match follow {
        FollowSymlinks::Yes => AtFlags::empty(),
        FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW,
    };

    // `statx` is preferred on Linux because it can return creation times.
    // Linux kernels prior to 4.11 don't have `statx` and return `ENOSYS`.
    // Older versions of Docker/seccomp would return `EPERM` for `statx`; see
    // <https://github.com/rust-lang/rust/pull/65685/>. We store the
    // availability in a global to avoid unnecessary syscalls.
    #[cfg(any(target_os = "android", target_os = "linux"))]
    {
        // 0: Unknown
        // 1: Not available
        // 2: Available
        static STATX_STATE: AtomicU8 = AtomicU8::new(0);
        let state = STATX_STATE.load(Ordering::Relaxed);
        if state != 1 {
            let statx_result = statx(
                start,
                path,
                atflags,
                StatxFlags::BASIC_STATS | StatxFlags::BTIME,
            );
            match statx_result {
                Ok(statx) => {
                    if state == 0 {
                        STATX_STATE.store(2, Ordering::Relaxed);
                    }
                    return Ok(MetadataExt::from_rustix_statx(statx));
                }
                Err(rustix::io::Errno::NOSYS) => STATX_STATE.store(1, Ordering::Relaxed),
                Err(rustix::io::Errno::PERM) if state == 0 => {
                    // This is an unlikely case, as `statx` doesn't normally
                    // return `PERM` errors. One way this can happen is when
                    // running on old versions of seccomp/Docker. If `statx` on
                    // the current working directory returns a similar error,
                    // then stop using `statx`.
                    if let Err(rustix::io::Errno::PERM) = statx(
                        rustix::fs::cwd(),
                        "",
                        AtFlags::EMPTY_PATH,
                        StatxFlags::empty(),
                    ) {
                        STATX_STATE.store(1, Ordering::Relaxed);
                    } else {
                        return Err(rustix::io::Errno::PERM.into());
                    }
                }
                Err(e) => return Err(e.into()),
            }
        }
    }

    Ok(statat(start, path, atflags).map(MetadataExt::from_rustix)?)
}