Skip to content

Commit f1d747a

Browse files
authored
Rollup merge of rust-lang#65685 - oxalica:statx-eperm, r=alexcrichton
Fix check of `statx` and handle EPERM Should fix rust-lang#65662 rust-lang#65662 (comment) > I think a reasonable solution might be to do something like try to stat AT_CWD initially and if that fails with EPERM or ENOSYS we disable the syscall entirely, otherwise it's cached as always good to use. r? @alexcrichton
2 parents 8bb039f + 10f1bc7 commit f1d747a

File tree

1 file changed

+55
-42
lines changed

1 file changed

+55
-42
lines changed

src/libstd/sys/unix/fs.rs

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,14 @@ cfg_has_statx! {{
105105
flags: i32,
106106
mask: u32,
107107
) -> Option<io::Result<FileAttr>> {
108-
use crate::sync::atomic::{AtomicBool, Ordering};
108+
use crate::sync::atomic::{AtomicU8, Ordering};
109109

110110
// Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
111-
// We store the availability in a global to avoid unnecessary syscalls
112-
static HAS_STATX: AtomicBool = AtomicBool::new(true);
111+
// We store the availability in global to avoid unnecessary syscalls.
112+
// 0: Unknown
113+
// 1: Not available
114+
// 2: Available
115+
static STATX_STATE: AtomicU8 = AtomicU8::new(0);
113116
syscall! {
114117
fn statx(
115118
fd: c_int,
@@ -120,50 +123,60 @@ cfg_has_statx! {{
120123
) -> c_int
121124
}
122125

123-
if !HAS_STATX.load(Ordering::Relaxed) {
124-
return None;
125-
}
126-
127-
let mut buf: libc::statx = mem::zeroed();
128-
let ret = cvt(statx(fd, path, flags, mask, &mut buf));
129-
match ret {
130-
Err(err) => match err.raw_os_error() {
131-
Some(libc::ENOSYS) => {
132-
HAS_STATX.store(false, Ordering::Relaxed);
126+
match STATX_STATE.load(Ordering::Relaxed) {
127+
0 => {
128+
// It is a trick to call `statx` with NULL pointers to check if the syscall
129+
// is available. According to the manual, it is expected to fail with EFAULT.
130+
// We do this mainly for performance, since it is nearly hundreds times
131+
// faster than a normal successfull call.
132+
let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut()))
133+
.err()
134+
.and_then(|e| e.raw_os_error());
135+
// We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited
136+
// and returns `EPERM`. Listing all possible errors seems not a good idea.
137+
// See: https://github.com/rust-lang/rust/issues/65662
138+
if err != Some(libc::EFAULT) {
139+
STATX_STATE.store(1, Ordering::Relaxed);
133140
return None;
134141
}
135-
_ => return Some(Err(err)),
142+
STATX_STATE.store(2, Ordering::Relaxed);
136143
}
137-
Ok(_) => {
138-
// We cannot fill `stat64` exhaustively because of private padding fields.
139-
let mut stat: stat64 = mem::zeroed();
140-
// `c_ulong` on gnu-mips, `dev_t` otherwise
141-
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
142-
stat.st_ino = buf.stx_ino as libc::ino64_t;
143-
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
144-
stat.st_mode = buf.stx_mode as libc::mode_t;
145-
stat.st_uid = buf.stx_uid as libc::uid_t;
146-
stat.st_gid = buf.stx_gid as libc::gid_t;
147-
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
148-
stat.st_size = buf.stx_size as off64_t;
149-
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
150-
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
151-
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
152-
// `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
153-
stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
154-
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
155-
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
156-
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
157-
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
158-
159-
let extra = StatxExtraFields {
160-
stx_mask: buf.stx_mask,
161-
stx_btime: buf.stx_btime,
162-
};
144+
1 => return None,
145+
_ => {}
146+
}
163147

164-
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
165-
}
148+
let mut buf: libc::statx = mem::zeroed();
149+
if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) {
150+
return Some(Err(err));
166151
}
152+
153+
// We cannot fill `stat64` exhaustively because of private padding fields.
154+
let mut stat: stat64 = mem::zeroed();
155+
// `c_ulong` on gnu-mips, `dev_t` otherwise
156+
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
157+
stat.st_ino = buf.stx_ino as libc::ino64_t;
158+
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
159+
stat.st_mode = buf.stx_mode as libc::mode_t;
160+
stat.st_uid = buf.stx_uid as libc::uid_t;
161+
stat.st_gid = buf.stx_gid as libc::gid_t;
162+
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
163+
stat.st_size = buf.stx_size as off64_t;
164+
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
165+
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
166+
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
167+
// `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
168+
stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
169+
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
170+
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
171+
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
172+
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
173+
174+
let extra = StatxExtraFields {
175+
stx_mask: buf.stx_mask,
176+
stx_btime: buf.stx_btime,
177+
};
178+
179+
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
167180
}
168181

169182
} else {

0 commit comments

Comments
 (0)