Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect when statx isn't available on older versions of Docker. #357

Merged
merged 5 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 69 additions & 4 deletions src/fs/statx.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
//! Linux `statx`.

use crate::fd::{AsFd, BorrowedFd};
use crate::ffi::CStr;
use crate::fs::AtFlags;
use crate::{imp, io, path};
use imp::fd::AsFd;
use core::sync::atomic::{AtomicU8, Ordering};

pub use imp::fs::types::{Statx, StatxFlags, StatxTimestamp};

/// `statx(dirfd, path, flags, mask, statxbuf)`
///
/// This isn't available on Linux before 4.11; it returns `ENOSYS` in that
/// case.
/// This function returns [`io::Errno::NOSYS`] if `statx` is not available on
/// the platform, such as Linux before 4.11. This also includes older Docker
/// versions where the actual syscall fails with different error codes; Rustix
/// handles this and translates them into `NOSYS`.
///
/// # References
/// - [Linux]
Expand All @@ -22,5 +26,66 @@ pub fn statx<P: path::Arg, Fd: AsFd>(
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> {
path.into_with_c_str(|path| imp::fs::syscalls::statx(dirfd.as_fd(), path, flags, mask))
path.into_with_c_str(|path| _statx(dirfd.as_fd(), path, flags, mask))
}

// Linux kernel prior to 4.11 old versions of Docker don't support `statx`. We
// store the availability in a global to avoid unnecessary syscalls.
//
// 0: Unknown
// 1: Not available
// 2: Available
static STATX_STATE: AtomicU8 = AtomicU8::new(0);

#[inline]
fn _statx(
dirfd: BorrowedFd<'_>,
path: &CStr,
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> {
match STATX_STATE.load(Ordering::Relaxed) {
0 => statx_init(dirfd, path, flags, mask),
1 => Err(io::Errno::NOSYS),
_ => imp::fs::syscalls::statx(dirfd, path, flags, mask),
}
}

/// The first `statx` call. We don't know if `statx` is available yet.
fn statx_init(
dirfd: BorrowedFd<'_>,
path: &CStr,
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> {
match imp::fs::syscalls::statx(dirfd, path, flags, mask) {
Err(io::Errno::NOSYS) => statx_error_nosys(),
Err(io::Errno::PERM) => statx_error_perm(),
result => {
STATX_STATE.store(2, Ordering::Relaxed);
result
}
}
}

/// The first `statx` call failed with `NOSYS` (or something we're treating
/// like `NOSYS`).
#[cold]
fn statx_error_nosys() -> io::Result<Statx> {
STATX_STATE.store(1, Ordering::Relaxed);
Err(io::Errno::NOSYS)
}

/// The first `statx` call failed with `PERM`.
#[cold]
fn statx_error_perm() -> io::Result<Statx> {
// Some old versions of Docker have `statx` fail with `PERM` when it isn't
// recognized. Check whether `statx` really is available, and if so, fail
// with `PERM`, and if not, treat it like `NOSYS`.
if imp::fs::syscalls::is_statx_available() {
STATX_STATE.store(2, Ordering::Relaxed);
Err(io::Errno::PERM)
} else {
statx_error_nosys()
}
}
75 changes: 39 additions & 36 deletions src/imp/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ use crate::fs::SealFlags;
)))]
// not implemented in libc for netbsd yet
use crate::fs::StatFs;
#[cfg(any(target_os = "android", target_os = "linux"))]
use crate::fs::{cwd, RenameFlags, ResolveFlags, Statx, StatxFlags};
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
Expand All @@ -97,8 +99,6 @@ use crate::fs::StatFs;
)))]
use crate::fs::{Dev, FileType};
use crate::fs::{FdFlags, Mode, OFlags, Stat, Timestamps};
#[cfg(any(target_os = "android", target_os = "linux"))]
use crate::fs::{RenameFlags, ResolveFlags, Statx, StatxFlags};
use crate::io::{self, OwnedFd, SeekFrom};
#[cfg(not(target_os = "wasi"))]
use crate::process::{Gid, Uid};
Expand All @@ -116,18 +116,15 @@ use core::convert::TryInto;
))]
use core::mem::size_of;
use core::mem::MaybeUninit;
#[cfg(any(target_os = "android", target_os = "linux"))]
use core::ptr::null;
#[cfg(any(
target_os = "android",
target_os = "ios",
target_os = "linux",
target_os = "macos"
))]
use core::ptr::null_mut;
#[cfg(all(
any(target_os = "android", target_os = "linux"),
any(target_pointer_width = "32", target_arch = "mips64")
))]
use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
#[cfg(any(target_os = "ios", target_os = "macos"))]
use {
super::super::conv::nonnegative_ret,
Expand Down Expand Up @@ -351,14 +348,11 @@ pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::
any(target_pointer_width = "32", target_arch = "mips64")
))]
{
if !NO_STATX.load(Relaxed) {
match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
Ok(x) => return statx_to_stat(x),
Err(io::Errno::NOSYS) => NO_STATX.store(true, Relaxed),
Err(e) => return Err(e),
}
match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
Ok(x) => return statx_to_stat(x),
Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
Err(e) => return Err(e),
}
statat_old(dirfd, path, flags)
}

// Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
Expand Down Expand Up @@ -904,13 +898,6 @@ pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result
unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) }
}

/// `statx` was introduced in Linux 4.11.
#[cfg(all(
any(target_os = "android", target_os = "linux"),
any(target_pointer_width = "32", target_arch = "mips64")
))]
static NO_STATX: AtomicBool = AtomicBool::new(false);

pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
// 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
// `statx`.
Expand All @@ -919,14 +906,11 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
any(target_pointer_width = "32", target_arch = "mips64")
))]
{
if !NO_STATX.load(Relaxed) {
match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
Ok(x) => return statx_to_stat(x),
Err(io::Errno::NOSYS) => NO_STATX.store(true, Relaxed),
Err(e) => return Err(e),
}
match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
Ok(x) => return statx_to_stat(x),
Err(io::Errno::NOSYS) => fstat_old(fd),
Err(e) => return Err(e),
}
fstat_old(fd)
}

// Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
Expand Down Expand Up @@ -1398,12 +1382,9 @@ fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {

#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(non_upper_case_globals)]
pub(crate) fn statx(
dirfd: BorrowedFd<'_>,
path: &CStr,
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> {
mod sys {
use super::{c, BorrowedFd, Statx};

#[cfg(all(target_os = "android", target_arch = "arm"))]
const SYS_statx: c::c_long = 397;
#[cfg(all(target_os = "android", target_arch = "x86"))]
Expand All @@ -1414,18 +1395,27 @@ pub(crate) fn statx(
const SYS_statx: c::c_long = 332;

weak_or_syscall! {
fn statx(
pub(super) fn statx(
pirfd: BorrowedFd<'_>,
path: *const c::c_char,
flags: c::c_int,
mask: c::c_uint,
buf: *mut Statx
) via SYS_statx -> c::c_int
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(non_upper_case_globals)]
pub(crate) fn statx(
dirfd: BorrowedFd<'_>,
path: &CStr,
flags: AtFlags,
mask: StatxFlags,
) -> io::Result<Statx> {
let mut statx_buf = MaybeUninit::<Statx>::uninit();
unsafe {
ret(statx(
ret(sys::statx(
dirfd,
c_str(path),
flags.bits(),
Expand All @@ -1436,6 +1426,19 @@ pub(crate) fn statx(
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[inline]
pub(crate) fn is_statx_available() -> bool {
unsafe {
// Call `statx` with null pointers so that if it fails for any reason
// other than `EFAULT`, we know it's not supported.
matches!(
ret(sys::statx(cwd(), null(), 0, 0, null_mut())),
Err(io::Errno::FAULT)
)
}
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
pub(crate) unsafe fn fcopyfile(
from: BorrowedFd<'_>,
Expand Down
Loading