Skip to content

Commit

Permalink
Auto merge of #90846 - cuviper:weak, r=dtolnay
Browse files Browse the repository at this point in the history
Refactor weak symbols in std::sys::unix

This makes a few changes to the weak symbol macros in `sys::unix`:

- `dlsym!` is added to keep the functionality for runtime `dlsym`
  lookups, like for `__pthread_get_minstack@GLIBC_PRIVATE` that we don't
  want to show up in ELF symbol tables.
- `weak!` now uses `#[linkage = "extern_weak"]` symbols, so its runtime
  behavior is just a simple null check. This is also used by `syscall!`.
  - On non-ELF targets (macos/ios) where that linkage is not known to
    behave, `weak!` is just an alias to `dlsym!` for the old behavior.
- `raw_syscall!` is added to always call `libc::syscall` on linux and
  android, for cases like `clone3` that have no known libc wrapper.

The new `weak!` linkage does mean that you'll get versioned symbols if
you build with a newer glibc, like `WEAK DEFAULT UND statx@GLIBC_2.28`.
This might seem problematic, but old non-weak symbols can tie the build
to new versions too, like `dlsym@GLIBC_2.34` from their recent library
unification. If you build with an old glibc like `dist-x86_64-linux`
does, you'll still get unversioned `WEAK DEFAULT UND statx`, which may
be resolved based on the runtime glibc.

I also found a few functions that don't need to be weak anymore:

- Android can directly use `ftruncate64`, `pread64`, and `pwrite64`, as
  these were added in API 12, and our baseline is API 14.
- Linux can directly use `splice`, added way back in glibc 2.5 and
  similarly old musl. Android only added it in API 21 though.
  • Loading branch information
bors committed Nov 27, 2021
2 parents 84826fe + 5ff6ac4 commit 0881b3a
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 191 deletions.
90 changes: 2 additions & 88 deletions library/std/src/sys/unix/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@
#![cfg(target_os = "android")]

use libc::{c_int, c_void, sighandler_t, size_t, ssize_t};
use libc::{ftruncate, pread, pwrite};
use libc::{c_int, sighandler_t};

use super::{cvt, cvt_r, weak::weak};
use crate::io;
use super::weak::weak;

// The `log2` and `log2f` functions apparently appeared in android-18, or at
// least you can see they're not present in the android-17 header [1] and they
Expand Down Expand Up @@ -81,87 +79,3 @@ pub unsafe fn signal(signum: c_int, handler: sighandler_t) -> sighandler_t {
let f = f.expect("neither `signal` nor `bsd_signal` symbols found");
f(signum, handler)
}

// The `ftruncate64` symbol apparently appeared in android-12, so we do some
// dynamic detection to see if we can figure out whether `ftruncate64` exists.
//
// If it doesn't we just fall back to `ftruncate`, generating an error for
// too-large values.
#[cfg(target_pointer_width = "32")]
pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
weak!(fn ftruncate64(c_int, i64) -> c_int);

unsafe {
match ftruncate64.get() {
Some(f) => cvt_r(|| f(fd, size as i64)).map(drop),
None => {
if size > i32::MAX as u64 {
Err(io::Error::new_const(io::ErrorKind::InvalidInput, &"cannot truncate >2GB"))
} else {
cvt_r(|| ftruncate(fd, size as i32)).map(drop)
}
}
}
}
}

#[cfg(target_pointer_width = "64")]
pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
unsafe { cvt_r(|| ftruncate(fd, size as i64)).map(drop) }
}

#[cfg(target_pointer_width = "32")]
pub unsafe fn cvt_pread64(
fd: c_int,
buf: *mut c_void,
count: size_t,
offset: i64,
) -> io::Result<ssize_t> {
use crate::convert::TryInto;
weak!(fn pread64(c_int, *mut c_void, size_t, i64) -> ssize_t);
pread64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
if let Ok(o) = offset.try_into() {
cvt(pread(fd, buf, count, o))
} else {
Err(io::Error::new_const(io::ErrorKind::InvalidInput, &"cannot pread >2GB"))
}
})
}

#[cfg(target_pointer_width = "32")]
pub unsafe fn cvt_pwrite64(
fd: c_int,
buf: *const c_void,
count: size_t,
offset: i64,
) -> io::Result<ssize_t> {
use crate::convert::TryInto;
weak!(fn pwrite64(c_int, *const c_void, size_t, i64) -> ssize_t);
pwrite64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
if let Ok(o) = offset.try_into() {
cvt(pwrite(fd, buf, count, o))
} else {
Err(io::Error::new_const(io::ErrorKind::InvalidInput, &"cannot pwrite >2GB"))
}
})
}

#[cfg(target_pointer_width = "64")]
pub unsafe fn cvt_pread64(
fd: c_int,
buf: *mut c_void,
count: size_t,
offset: i64,
) -> io::Result<ssize_t> {
cvt(pread(fd, buf, count, offset))
}

#[cfg(target_pointer_width = "64")]
pub unsafe fn cvt_pwrite64(
fd: c_int,
buf: *const c_void,
count: size_t,
offset: i64,
) -> io::Result<ssize_t> {
cvt(pwrite(fd, buf, count, offset))
}
48 changes: 12 additions & 36 deletions library/std/src/sys/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,30 +99,18 @@ impl FileDesc {
}

pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
#[cfg(target_os = "android")]
use super::android::cvt_pread64;

#[cfg(not(target_os = "android"))]
unsafe fn cvt_pread64(
fd: c_int,
buf: *mut c_void,
count: usize,
offset: i64,
) -> io::Result<isize> {
#[cfg(not(target_os = "linux"))]
use libc::pread as pread64;
#[cfg(target_os = "linux")]
use libc::pread64;
cvt(pread64(fd, buf, count, offset))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use libc::pread as pread64;
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc::pread64;

unsafe {
cvt_pread64(
cvt(pread64(
self.as_raw_fd(),
buf.as_mut_ptr() as *mut c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as i64,
)
))
.map(|n| n as usize)
}
}
Expand Down Expand Up @@ -161,30 +149,18 @@ impl FileDesc {
}

pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
#[cfg(target_os = "android")]
use super::android::cvt_pwrite64;

#[cfg(not(target_os = "android"))]
unsafe fn cvt_pwrite64(
fd: c_int,
buf: *const c_void,
count: usize,
offset: i64,
) -> io::Result<isize> {
#[cfg(not(target_os = "linux"))]
use libc::pwrite as pwrite64;
#[cfg(target_os = "linux")]
use libc::pwrite64;
cvt(pwrite64(fd, buf, count, offset))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use libc::pwrite as pwrite64;
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc::pwrite64;

unsafe {
cvt_pwrite64(
cvt(pwrite64(
self.as_raw_fd(),
buf.as_ptr() as *const c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as i64,
)
))
.map(|n| n as usize)
}
}
Expand Down
20 changes: 7 additions & 13 deletions library/std/src/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ use libc::fstatat64;
use libc::readdir_r as readdir64_r;
#[cfg(target_os = "android")]
use libc::{
dirent as dirent64, fstat as fstat64, fstatat as fstatat64, lseek64, lstat as lstat64,
open as open64, stat as stat64,
dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
lstat as lstat64, off64_t, open as open64, stat as stat64,
};
#[cfg(not(any(
target_os = "linux",
Expand Down Expand Up @@ -835,16 +835,10 @@ impl File {
}

pub fn truncate(&self, size: u64) -> io::Result<()> {
#[cfg(target_os = "android")]
return crate::sys::android::ftruncate64(self.as_raw_fd(), size);

#[cfg(not(target_os = "android"))]
{
use crate::convert::TryInto;
let size: off64_t =
size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
}
use crate::convert::TryInto;
let size: off64_t =
size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
}

pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
Expand Down Expand Up @@ -1154,7 +1148,7 @@ pub fn link(original: &Path, link: &Path) -> io::Result<()> {
} else if #[cfg(target_os = "macos")] {
// On MacOS, older versions (<=10.9) lack support for linkat while newer
// versions have it. We want to use linkat if it is available, so we use weak!
// to check. `linkat` is preferable to `link` ecause it gives us a flag to
// to check. `linkat` is preferable to `link` because it gives us a flag to
// specify how symlinks should be handled. We pass 0 as the flags argument,
// meaning it shouldn't follow symlinks.
weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int);
Expand Down
6 changes: 6 additions & 0 deletions library/std/src/sys/unix/kernel_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,9 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) ->
static HAS_SENDFILE: AtomicBool = AtomicBool::new(true);
static HAS_SPLICE: AtomicBool = AtomicBool::new(true);

// Android builds use feature level 14, but the libc wrapper for splice is
// gated on feature level 21+, so we have to invoke the syscall directly.
#[cfg(target_os = "android")]
syscall! {
fn splice(
srcfd: libc::c_int,
Expand All @@ -625,6 +628,9 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) ->
) -> libc::ssize_t
}

#[cfg(target_os = "linux")]
use libc::splice;

match mode {
SpliceMode::Sendfile if !HAS_SENDFILE.load(Ordering::Relaxed) => {
return CopyResult::Fallback(0);
Expand Down
4 changes: 2 additions & 2 deletions library/std/src/sys/unix/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ impl FromRawFd for Socket {
// res_init unconditionally, we call it only when we detect we're linking
// against glibc version < 2.26. (That is, when we both know its needed and
// believe it's thread-safe).
#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn on_resolver_failure() {
use crate::sys;

Expand All @@ -513,5 +513,5 @@ fn on_resolver_failure() {
}
}

#[cfg(any(not(target_env = "gnu"), target_os = "vxworks"))]
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
fn on_resolver_failure() {}
24 changes: 8 additions & 16 deletions library/std/src/sys/unix/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![allow(unused_imports)] // lots of cfg code here

#[cfg(all(test, target_env = "gnu"))]
#[cfg(test)]
mod tests;

use crate::os::unix::prelude::*;
Expand Down Expand Up @@ -636,30 +636,22 @@ pub fn getppid() -> u32 {
unsafe { libc::getppid() as u32 }
}

#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
pub fn glibc_version() -> Option<(usize, usize)> {
if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) {
parse_glibc_version(version_str)
} else {
None
}
}

#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
fn glibc_version_cstr() -> Option<&'static CStr> {
weak! {
fn gnu_get_libc_version() -> *const libc::c_char
extern "C" {
fn gnu_get_libc_version() -> *const libc::c_char;
}
if let Some(f) = gnu_get_libc_version.get() {
unsafe { Some(CStr::from_ptr(f())) }
let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
if let Ok(version_str) = version_cstr.to_str() {
parse_glibc_version(version_str)
} else {
None
}
}

// Returns Some((major, minor)) if the string is a valid "x.y" version,
// ignoring any extra dot-separated parts. Otherwise return None.
#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
match (parsed_ints.next(), parsed_ints.next()) {
Expand Down
10 changes: 4 additions & 6 deletions library/std/src/sys/unix/os/tests.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use super::*;

#[test]
#[cfg(not(target_os = "vxworks"))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn test_glibc_version() {
// This mostly just tests that the weak linkage doesn't panic wildly...
glibc_version();
super::glibc_version();
}

#[test]
#[cfg(not(target_os = "vxworks"))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn test_parse_glibc_version() {
let cases = [
("0.0", Some((0, 0))),
Expand All @@ -20,6 +18,6 @@ fn test_parse_glibc_version() {
("foo.1", None),
];
for &(version_str, parsed) in cases.iter() {
assert_eq!(parsed, parse_glibc_version(version_str));
assert_eq!(parsed, super::parse_glibc_version(version_str));
}
}
4 changes: 2 additions & 2 deletions library/std/src/sys/unix/process/process_unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::sys::process::process_common::*;
use crate::os::linux::process::PidFd;

#[cfg(target_os = "linux")]
use crate::sys::weak::syscall;
use crate::sys::weak::raw_syscall;

#[cfg(any(
target_os = "macos",
Expand Down Expand Up @@ -162,7 +162,7 @@ impl Command {
cgroup: u64,
}

syscall! {
raw_syscall! {
fn clone3(cl_args: *mut clone_args, len: libc::size_t) -> libc::c_long
}

Expand Down
17 changes: 10 additions & 7 deletions library/std/src/sys/unix/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::ptr;
use crate::sys::{os, stack_overflow};
use crate::time::Duration;

#[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
use crate::sys::weak::dlsym;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
use crate::sys::weak::weak;
#[cfg(not(any(target_os = "l4re", target_os = "vxworks", target_os = "espidf")))]
pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
Expand Down Expand Up @@ -627,20 +629,21 @@ pub mod guard {
// We need that information to avoid blowing up when a small stack
// is created in an application with big thread-local storage requirements.
// See #6233 for rationale and details.
#[cfg(target_os = "linux")]
#[allow(deprecated)]
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
weak!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
// We use dlsym to avoid an ELF version dependency on GLIBC_PRIVATE. (#23628)
// We shouldn't really be using such an internal symbol, but there's currently
// no other way to account for the TLS size.
dlsym!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);

match __pthread_get_minstack.get() {
None => libc::PTHREAD_STACK_MIN,
Some(f) => unsafe { f(attr) },
}
}

// No point in looking up __pthread_get_minstack() on non-glibc
// platforms.
#[cfg(all(not(target_os = "linux"), not(target_os = "netbsd")))]
// No point in looking up __pthread_get_minstack() on non-glibc platforms.
#[cfg(all(not(all(target_os = "linux", target_env = "gnu")), not(target_os = "netbsd")))]
fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
libc::PTHREAD_STACK_MIN
}
Expand Down
Loading

0 comments on commit 0881b3a

Please sign in to comment.