diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs index aea0c26ee8b..5535682f6cd 100644 --- a/library/std/src/sys/unix/fs.rs +++ b/library/std/src/sys/unix/fs.rs @@ -149,11 +149,6 @@ unsafe fn try_statx( ) -> Option> { use crate::sync::atomic::{AtomicU8, Ordering}; - // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx` - // We store the availability in global to avoid unnecessary syscalls. - // 0: Unknown - // 1: Not available - // 2: Available static STATX_STATE: AtomicU8 = AtomicU8::new(0); syscall! { fn statx( @@ -165,30 +160,50 @@ fn statx( ) -> c_int } - match STATX_STATE.load(Ordering::Relaxed) { - 0 => { - // It is a trick to call `statx` with null pointers to check if the syscall - // is available. According to the manual, it is expected to fail with EFAULT. - // We do this mainly for performance, since it is nearly hundreds times - // faster than a normal successful call. - let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut())) - .err() - .and_then(|e| e.raw_os_error()); - // We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited - // and returns `EPERM`. Listing all possible errors seems not a good idea. - // See: https://github.com/rust-lang/rust/issues/65662 - if err != Some(libc::EFAULT) { - STATX_STATE.store(1, Ordering::Relaxed); - return None; - } - STATX_STATE.store(2, Ordering::Relaxed); - } - 1 => return None, - _ => {} + if STATX_STATE.load(Ordering::Relaxed) == 1 { + return None; } let mut buf: libc::statx = mem::zeroed(); if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { + // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`. + // We check for it on first failure and remember availability to avoid having to + // do it more times later. + // 0: Unknown + // 1: Not available + // 2: Available + + if STATX_STATE.load(Ordering::Relaxed) == 2 { + // Verified available. + return Some(Err(err)); + } + + // Availability not checked yet. + // + // First try the cheap way. + if err.raw_os_error() == Some(libc::ENOSYS) { + STATX_STATE.store(1, Ordering::Relaxed); + return None; + } + + // Error other than `ENOSYS` is not a good enough indicator -- it is + // known that `EPERM` can be returned as a result of using seccomp to + // block the syscall. + // Availability is checked by performing a call which guarantees `EFAULT` + // if the syscall is usable. + // See: https://github.com/rust-lang/rust/issues/65662 + // XXX this can probably just do the call if `EPERM` was received, but previous + // iteration of the code checked it for all errors. As this is a corner case + // which is expected to be rarely executed, said behavior is retained (modulo + // `ENOSYS` above). + let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut())) + .err() + .and_then(|e| e.raw_os_error()); + if err2 != Some(libc::EFAULT) { + STATX_STATE.store(1, Ordering::Relaxed); + return None; + } + STATX_STATE.store(2, Ordering::Relaxed); return Some(Err(err)); }