From 5687ca29b28d7461a92a7776853636cd4404f0b1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Jun 2022 11:43:28 -0700 Subject: [PATCH 1/5] Detect when `statx` isn't available on older versions of Docker. In older versions of Docker, `statx` can fail with `EPERM` meaning it isn't available. Recognize this, and translate it into `ENOSYS`. Also, have `statx` remember this and fail quickly in future calls. This is more emulation than rustix usually likes to do, but in this case, this is the behavior seems likely to be what most users will want, and we might as well put the code in one place. Fixes #352. --- src/fs/statx.rs | 73 +++++++++++++++++++++++-- src/imp/libc/fs/syscalls.rs | 71 +++++++++++++----------- src/imp/linux_raw/fs/syscalls.rs | 94 ++++++++++++++++---------------- src/lib.rs | 2 + 4 files changed, 156 insertions(+), 84 deletions(-) diff --git a/src/fs/statx.rs b/src/fs/statx.rs index c6165b2c7..fa1d36779 100644 --- a/src/fs/statx.rs +++ b/src/fs/statx.rs @@ -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] @@ -22,5 +26,66 @@ pub fn statx( flags: AtFlags, mask: StatxFlags, ) -> io::Result { - 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 { + 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 { + 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_STATE.store(1, Ordering::Relaxed); + Err(io::Errno::NOSYS) +} + +/// The first `statx` call failed with `PERM`. +#[cold] +fn statx_error_perm() -> io::Result { + // 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() + } } diff --git a/src/imp/libc/fs/syscalls.rs b/src/imp/libc/fs/syscalls.rs index 5684c0637..e64d7f3ae 100644 --- a/src/imp/libc/fs/syscalls.rs +++ b/src/imp/libc/fs/syscalls.rs @@ -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", @@ -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}; @@ -116,6 +116,8 @@ 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", @@ -351,14 +353,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::Error::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 @@ -904,13 +903,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 { // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use // `statx`. @@ -919,14 +911,11 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { 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::Error::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 @@ -1397,13 +1386,9 @@ fn stat64_to_stat(s64: c::stat64) -> io::Result { } #[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 { +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"))] @@ -1414,7 +1399,7 @@ 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, @@ -1422,10 +1407,19 @@ pub(crate) fn statx( 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 { let mut statx_buf = MaybeUninit::::uninit(); unsafe { - ret(statx( + ret(sys::statx( dirfd, c_str(path), flags.bits(), @@ -1436,6 +1430,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<'_>, diff --git a/src/imp/linux_raw/fs/syscalls.rs b/src/imp/linux_raw/fs/syscalls.rs index 004dbc8f2..fdbc3fa31 100644 --- a/src/imp/linux_raw/fs/syscalls.rs +++ b/src/imp/linux_raw/fs/syscalls.rs @@ -8,14 +8,9 @@ #![allow(clippy::undocumented_unsafe_blocks)] use super::super::c; -#[cfg(all( - target_pointer_width = "32", - any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc") -))] -use super::super::conv::zero; use super::super::conv::{ by_ref, c_int, c_uint, dev_t, oflags_for_open_how, opt_mut, pass_usize, raw_fd, ret, ret_c_int, - ret_c_uint, ret_owned_fd, ret_usize, size_of, slice_mut, + ret_c_uint, ret_owned_fd, ret_usize, size_of, slice_mut, zero, }; #[cfg(target_pointer_width = "64")] use super::super::conv::{loff_t, loff_t_from_u64, ret_u64}; @@ -416,22 +411,15 @@ pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result unsafe { ret(syscall!(__NR_flock, fd, c_uint(operation as c::c_uint))) } } -/// `statx` was introduced in Linux 4.11. -#[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] -static NO_STATX: AtomicBool = AtomicBool::new(false); - #[inline] pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { #[cfg(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) => statx_to_stat(x), + Err(io::Errno::NOSYS) => fstat_old(fd), + Err(e) => Err(e), } - fstat_old(fd) } #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] @@ -462,19 +450,16 @@ fn fstat_old(fd: BorrowedFd<'_>) -> io::Result { pub(crate) fn stat(filename: &CStr) -> io::Result { #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - if !NO_STATX.load(Relaxed) { - match statx( - crate::fs::cwd().as_fd(), - filename, - AtFlags::empty(), - 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( + crate::fs::cwd().as_fd(), + filename, + AtFlags::empty(), + StatxFlags::BASIC_STATS, + ) { + Ok(x) => return statx_to_stat(x), + Err(io::Errno::NOSYS) => stat_old(filename), + Err(e) => return Err(e), } - stat_old(filename) } #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] @@ -524,14 +509,11 @@ fn stat_old(filename: &CStr) -> io::Result { pub(crate) fn statat(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Result { #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - if !NO_STATX.load(Relaxed) { - match statx(dirfd, filename, 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, filename, flags, StatxFlags::BASIC_STATS) { + Ok(x) => return statx_to_stat(x), + Err(io::Errno::NOSYS) => statat_old(dirfd, filename, flags), + Err(e) => return Err(e), } - statat_old(dirfd, filename, flags) } #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] @@ -581,19 +563,16 @@ fn statat_old(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Res pub(crate) fn lstat(filename: &CStr) -> io::Result { #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - if !NO_STATX.load(Relaxed) { - match statx( - crate::fs::cwd().as_fd(), - filename, - AtFlags::SYMLINK_NOFOLLOW, - 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( + crate::fs::cwd().as_fd(), + filename, + AtFlags::SYMLINK_NOFOLLOW, + StatxFlags::BASIC_STATS, + ) { + Ok(x) => return statx_to_stat(x), + Err(io::Errno::NOSYS) => lstat_old(filename), + Err(e) => return Err(e), } - lstat_old(filename) } #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] @@ -759,6 +738,25 @@ pub(crate) fn statx( } } +#[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(syscall!( + __NR_statx, + raw_fd(AT_FDCWD), + zero(), + zero(), + zero(), + zero() + )), + Err(io::Errno::FAULT) + ) + } +} + #[inline] pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result { #[cfg(target_pointer_width = "32")] diff --git a/src/lib.rs b/src/lib.rs index f5999d088..264c39811 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,8 @@ //! are used (and documentation aliases are used so that the original names //! can still be searched for). //! - Provide y2038 compatibility, on platforms which support this. +//! - Correct selected platform bugs, such as behavioral differences when +//! running under seccomp. //! //! Things they don't do include: //! - Detecting whether functions are supported at runtime. From 17be8ab2c615eee0bbe6aeb383c6abdc129c2f1d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Jun 2022 11:59:09 -0700 Subject: [PATCH 2/5] Clean up unused imports. --- src/imp/libc/fs/syscalls.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/imp/libc/fs/syscalls.rs b/src/imp/libc/fs/syscalls.rs index e64d7f3ae..7ad2807e9 100644 --- a/src/imp/libc/fs/syscalls.rs +++ b/src/imp/libc/fs/syscalls.rs @@ -125,11 +125,6 @@ use core::ptr::null; 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, From 15376b8d0e0d7e00e74e493334b1a3d867c98c82 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Jun 2022 11:59:18 -0700 Subject: [PATCH 3/5] Update for `Error` => `Errno`. --- src/imp/libc/fs/syscalls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imp/libc/fs/syscalls.rs b/src/imp/libc/fs/syscalls.rs index 7ad2807e9..a6d87c63b 100644 --- a/src/imp/libc/fs/syscalls.rs +++ b/src/imp/libc/fs/syscalls.rs @@ -350,7 +350,7 @@ pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io:: { match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) { Ok(x) => return statx_to_stat(x), - Err(io::Error::NOSYS) => statat_old(dirfd, path, flags), + Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags), Err(e) => return Err(e), } } @@ -908,7 +908,7 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { { match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { Ok(x) => return statx_to_stat(x), - Err(io::Error::NOSYS) => fstat_old(fd), + Err(io::Errno::NOSYS) => fstat_old(fd), Err(e) => return Err(e), } } From ea9fcd5a0dd1b4d418b7249be4007282ccb3ea1a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Jun 2022 13:18:14 -0700 Subject: [PATCH 4/5] Fix a warning on Android. --- src/imp/libc/fs/syscalls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imp/libc/fs/syscalls.rs b/src/imp/libc/fs/syscalls.rs index a6d87c63b..5a7aef7e0 100644 --- a/src/imp/libc/fs/syscalls.rs +++ b/src/imp/libc/fs/syscalls.rs @@ -1381,6 +1381,7 @@ fn stat64_to_stat(s64: c::stat64) -> io::Result { } #[cfg(any(target_os = "android", target_os = "linux"))] +#[allow(non_upper_case_globals)] mod sys { use super::{c, BorrowedFd, Statx}; From 19832e2b3ff8d9f7247571e8f6d28a3cafbebcc0 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Jun 2022 13:25:37 -0700 Subject: [PATCH 5/5] Fix unused-import warnings. --- src/imp/linux_raw/fs/syscalls.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/imp/linux_raw/fs/syscalls.rs b/src/imp/linux_raw/fs/syscalls.rs index fdbc3fa31..d663058c8 100644 --- a/src/imp/linux_raw/fs/syscalls.rs +++ b/src/imp/linux_raw/fs/syscalls.rs @@ -31,8 +31,6 @@ use crate::io::{self, OwnedFd, SeekFrom}; use crate::process::{Gid, Uid}; use core::convert::TryInto; use core::mem::MaybeUninit; -#[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] -use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; #[cfg(target_arch = "mips64")] use linux_raw_sys::general::stat as linux_stat64; use linux_raw_sys::general::{