diff --git a/tokio/src/fs/file.rs b/tokio/src/fs/file.rs index 8eeee68eb99..b25863d96a0 100644 --- a/tokio/src/fs/file.rs +++ b/tokio/src/fs/file.rs @@ -764,29 +764,27 @@ impl Inner { #[cfg(all(target_os = "linux", not(test)))] mod read_nowait { use crate::io::ReadBuf; - use libc::{c_int, c_void, iovec, off_t, preadv2}; + use libc::{c_char, c_int, c_void, iovec, off_t, ssize_t}; use std::{ os::unix::prelude::AsRawFd, - sync::atomic::{AtomicBool, Ordering}, + sync::atomic::{AtomicUsize, Ordering}, }; - static NONBLOCKING_READ_SUPPORTED: AtomicBool = AtomicBool::new(true); + type PReadV2 = unsafe fn(c_int, *const iovec, c_int, off_t, c_int) -> ssize_t; + // Although this is a usize we actually store a function pointer in it of type PReadV2, or the + // NO_PREADV2 sentinel value: + static PREADV2: AtomicUsize = AtomicUsize::new(0); + // Sentinel value, this isn't a valid function pointer: + const NO_PREADV2: usize = 1; pub(crate) fn try_nonblocking_read( file: &crate::fs::sys::File, dst: &mut ReadBuf<'_>, ) -> Option> { - if !NONBLOCKING_READ_SUPPORTED.load(Ordering::Relaxed) { - return None; - } let out = preadv2_safe(file, dst, -1, libc::RWF_NOWAIT); if let Err(err) = &out { match err.raw_os_error() { - Some(libc::ENOSYS) => { - NONBLOCKING_READ_SUPPORTED.store(false, Ordering::Relaxed); - return None; - } - Some(libc::ENOTSUP) | Some(libc::EAGAIN) => return None, + Some(libc::ENOTSUP) | Some(libc::EAGAIN) | Some(libc::ENOSYS) => return None, _ => {} } } @@ -800,6 +798,24 @@ mod read_nowait { flags: c_int, ) -> std::io::Result<()> { unsafe { + /* Dynamically load preadv2 symbol so we still work on glibc < 2.26 */ + let preadv2 = match PREADV2.load(Ordering::Relaxed) { + NO_PREADV2 => return Err(std::io::Error::from_raw_os_error(libc::ENOSYS)), + 0 => { + let p = libc::dlsym(libc::RTLD_DEFAULT, "preadv2\0".as_ptr() as *const c_char); + if p.is_null() { + // libc doesn't support preadv2: + PREADV2.store(NO_PREADV2, Ordering::Relaxed); + return Err(std::io::Error::from_raw_os_error(libc::ENOSYS)); + } else { + let u = std::mem::transmute(p); + PREADV2.store(u, Ordering::Relaxed); + u + } + } + x => x, + }; + let preadv2: PReadV2 = std::mem::transmute(preadv2); /* We have to defend against buffer overflows manually here. The slice API makes * this fairly straightforward. */ let unfilled = dst.unfilled_mut(); @@ -811,7 +827,12 @@ mod read_nowait { * other unsafe code that assumes that only they have access to that fd. */ let bytes_read = preadv2(file.as_raw_fd(), &iov as *const iovec, 1, offset, flags); if bytes_read < 0 { - Err(std::io::Error::last_os_error()) + let err = std::io::Error::last_os_error(); + if err.raw_os_error() == Some(libc::ENOSYS) { + // libc supports preadv2, but the kernel doesn't: + PREADV2.store(NO_PREADV2, Ordering::Relaxed); + } + Err(err) } else { /* preadv2 returns the number of bytes read, e.g. the number of bytes that have * written into `unfilled`. So it's safe to assume that the data is now