Skip to content

Commit

Permalink
File: preadv2: Lookup preadv2 symbol using dlsym
Browse files Browse the repository at this point in the history
To support linking against glibc <v2.26.
  • Loading branch information
wmanley committed Mar 26, 2021
1 parent 0073710 commit 5bd8ea4
Showing 1 changed file with 33 additions and 12 deletions.
45 changes: 33 additions & 12 deletions tokio/src/fs/file.rs
Expand Up @@ -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<std::io::Result<()>> {
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,
_ => {}
}
}
Expand All @@ -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);

This comment has been minimized.

Copy link
@Darksonn

Darksonn Mar 26, 2021

Contributor

Usually I like to be very explicit with transmutes.

let preadv2 = std::mem::transmute::<usize, PReadV2>(preadv2);
/* We have to defend against buffer overflows manually here. The slice API makes
* this fairly straightforward. */
let unfilled = dst.unfilled_mut();
Expand All @@ -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
Expand Down

0 comments on commit 5bd8ea4

Please sign in to comment.