rust/library/std/src/sys/fd/unix.rs
Amanieu d'Antras 4bc6d75592 Revert #148937 (Remove initialized-bytes tracking from BorrowedBuf and BorrowedCursor)
This caused several performance regressions because of existing code
which uses `Read::read` and therefore requires full buffer
initialization. This is particularly a problem when the same buffer is
re-used for multiple read calls since this means it needs to be fully
re-initialized each time.

There is still some benefit to landing the API changes, but we will have
to add private APIs so that the existing infrastructure can
track and avoid redundant initialization.

(cherry picked from commit 4b07875505)
2025-12-20 13:58:49 -08:00

696 lines
21 KiB
Rust

#![unstable(reason = "not public", issue = "none", feature = "fd")]
#[cfg(test)]
mod tests;
#[cfg(not(any(
target_os = "linux",
target_os = "l4re",
target_os = "android",
target_os = "hurd",
)))]
use libc::off_t as off64_t;
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "l4re",
target_os = "hurd",
))]
use libc::off64_t;
cfg_select! {
any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "hurd",
) => {
// Prefer explicit pread64 for 64-bit offset independently of libc
// #[cfg(gnu_file_offset_bits64)].
use libc::pread64;
}
_ => {
use libc::pread as pread64;
}
}
use crate::cmp;
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read};
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use crate::sys::cvt;
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
use crate::sys::pal::weak::syscall;
#[cfg(any(all(target_os = "android", target_pointer_width = "32"), target_vendor = "apple"))]
use crate::sys::pal::weak::weak;
use crate::sys_common::{AsInner, FromInner, IntoInner};
#[derive(Debug)]
pub struct FileDesc(OwnedFd);
// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
// with the man page quoting that if the count of bytes to read is
// greater than `SSIZE_MAX` the result is "unspecified".
//
// On Apple targets however, apparently the 64-bit libc is either buggy or
// intentionally showing odd behavior by rejecting any read with a size
// larger than INT_MAX. To handle both of these the read size is capped on
// both platforms.
const READ_LIMIT: usize = if cfg!(target_vendor = "apple") {
libc::c_int::MAX as usize
} else {
libc::ssize_t::MAX as usize
};
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_vendor = "apple",
target_os = "cygwin",
))]
const fn max_iov() -> usize {
libc::IOV_MAX as usize
}
#[cfg(any(
target_os = "android",
target_os = "emscripten",
target_os = "linux",
target_os = "nto",
))]
const fn max_iov() -> usize {
libc::UIO_MAXIOV as usize
}
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "espidf",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "nuttx",
target_os = "nto",
target_os = "openbsd",
target_os = "horizon",
target_os = "vita",
target_vendor = "apple",
target_os = "cygwin",
)))]
const fn max_iov() -> usize {
16 // The minimum value required by POSIX.
}
impl FileDesc {
#[inline]
pub fn try_clone(&self) -> io::Result<Self> {
self.duplicate()
}
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::read(
self.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
cmp::min(buf.len(), READ_LIMIT),
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
)))]
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::readv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
)
})?;
Ok(ret as usize)
}
#[cfg(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
))]
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
io::default_read_vectored(|b| self.read(b), bufs)
}
#[inline]
pub fn is_read_vectored(&self) -> bool {
cfg!(not(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
)))
}
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
let mut me = self;
(&mut me).read_to_end(buf)
}
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
cvt(unsafe {
pread64(
self.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as off64_t, // EINVAL if offset + count overflows
)
})
.map(|n| n as usize)
}
pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
// SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes
let ret = cvt(unsafe {
libc::read(
self.as_raw_fd(),
cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(),
cmp::min(cursor.capacity(), READ_LIMIT),
)
})?;
// SAFETY: `ret` bytes were written to the initialized portion of the buffer
unsafe {
cursor.advance_unchecked(ret as usize);
}
Ok(())
}
pub fn read_buf_at(&self, mut cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
// SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes
let ret = cvt(unsafe {
pread64(
self.as_raw_fd(),
cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(),
cmp::min(cursor.capacity(), READ_LIMIT),
offset as off64_t, // EINVAL if offset + count overflows
)
})?;
// SAFETY: `ret` bytes were written to the initialized portion of the buffer
unsafe {
cursor.advance_unchecked(ret as usize);
}
Ok(())
}
#[cfg(any(
target_os = "aix",
target_os = "dragonfly", // DragonFly 1.5
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd", // OpenBSD 2.7
))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_vendor = "apple",
)))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
io::default_read_vectored(|b| self.read_at(b, offset), bufs)
}
// We support some old Android versions that do not have `preadv` in libc,
// so we use weak linkage and fallback to a direct syscall if not available.
//
// On 32-bit targets, we don't want to deal with weird ABI issues around
// passing 64-bits parameters to syscalls, so we fallback to the default
// implementation if `preadv` is not available.
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
syscall!(
fn preadv(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
let ret = cvt(unsafe {
preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
weak!(
fn preadv64(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
match preadv64.get() {
Some(preadv) => {
let ret = cvt(unsafe {
preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
}
}
// We support old MacOS, iOS, watchOS, tvOS and visionOS. `preadv` was added in the following
// Apple OS versions:
// ios 14.0
// tvos 14.0
// macos 11.0
// watchos 7.0
//
// These versions may be newer than the minimum supported versions of OS's we support so we must
// use "weak" linking.
#[cfg(target_vendor = "apple")]
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
weak!(
fn preadv(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
match preadv.get() {
Some(preadv) => {
let ret = cvt(unsafe {
preadv(
self.as_raw_fd(),
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
}
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::write(
self.as_raw_fd(),
buf.as_ptr() as *const libc::c_void,
cmp::min(buf.len(), READ_LIMIT),
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
)))]
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::writev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
)
})?;
Ok(ret as usize)
}
#[cfg(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
))]
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
io::default_write_vectored(|b| self.write(b), bufs)
}
#[inline]
pub fn is_write_vectored(&self) -> bool {
cfg!(not(any(
target_os = "espidf",
target_os = "horizon",
target_os = "vita",
target_os = "nuttx"
)))
}
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
#[cfg(not(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "hurd"
)))]
use libc::pwrite as pwrite64;
#[cfg(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "hurd"
))]
use libc::pwrite64;
unsafe {
cvt(pwrite64(
self.as_raw_fd(),
buf.as_ptr() as *const libc::c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as off64_t,
))
.map(|n| n as usize)
}
}
#[cfg(any(
target_os = "aix",
target_os = "dragonfly", // DragonFly 1.5
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd", // OpenBSD 2.7
))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_vendor = "apple",
)))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
io::default_write_vectored(|b| self.write_at(b, offset), bufs)
}
// We support some old Android versions that do not have `pwritev` in libc,
// so we use weak linkage and fallback to a direct syscall if not available.
//
// On 32-bit targets, we don't want to deal with weird ABI issues around
// passing 64-bits parameters to syscalls, so we fallback to the default
// implementation if `pwritev` is not available.
#[cfg(all(target_os = "android", target_pointer_width = "64"))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
syscall!(
fn pwritev(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
let ret = cvt(unsafe {
pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
#[cfg(all(target_os = "android", target_pointer_width = "32"))]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
weak!(
fn pwritev64(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
match pwritev64.get() {
Some(pwritev) => {
let ret = cvt(unsafe {
pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
}
}
// We support old MacOS, iOS, watchOS, tvOS and visionOS. `pwritev` was added in the following
// Apple OS versions:
// ios 14.0
// tvos 14.0
// macos 11.0
// watchos 7.0
//
// These versions may be newer than the minimum supported versions of OS's we support so we must
// use "weak" linking.
#[cfg(target_vendor = "apple")]
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
weak!(
fn pwritev(
fd: libc::c_int,
iovec: *const libc::iovec,
n_iovec: libc::c_int,
offset: off64_t,
) -> isize;
);
match pwritev.get() {
Some(pwritev) => {
let ret = cvt(unsafe {
pwritev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as libc::c_int,
offset as _,
)
})?;
Ok(ret as usize)
}
None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
}
}
#[cfg(not(any(
target_env = "newlib",
target_os = "solaris",
target_os = "illumos",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "l4re",
target_os = "linux",
target_os = "cygwin",
target_os = "haiku",
target_os = "redox",
target_os = "vxworks",
target_os = "nto",
)))]
pub fn set_cloexec(&self) -> io::Result<()> {
unsafe {
cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?;
Ok(())
}
}
#[cfg(any(
all(
target_env = "newlib",
not(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))
),
target_os = "solaris",
target_os = "illumos",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "l4re",
target_os = "linux",
target_os = "cygwin",
target_os = "haiku",
target_os = "redox",
target_os = "vxworks",
target_os = "nto",
))]
pub fn set_cloexec(&self) -> io::Result<()> {
unsafe {
let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
let new = previous | libc::FD_CLOEXEC;
if new != previous {
cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
}
Ok(())
}
}
#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
pub fn set_cloexec(&self) -> io::Result<()> {
// FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to,
// because none of them supports spawning processes.
Ok(())
}
#[cfg(target_os = "linux")]
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
unsafe {
let v = nonblocking as libc::c_int;
cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
Ok(())
}
}
#[cfg(not(target_os = "linux"))]
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
unsafe {
let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?;
let new = if nonblocking {
previous | libc::O_NONBLOCK
} else {
previous & !libc::O_NONBLOCK
};
if new != previous {
cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?;
}
Ok(())
}
}
#[inline]
pub fn duplicate(&self) -> io::Result<FileDesc> {
Ok(Self(self.0.try_clone()?))
}
}
impl<'a> Read for &'a FileDesc {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(**self).read(buf)
}
fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
(**self).read_buf(cursor)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
(**self).read_vectored(bufs)
}
#[inline]
fn is_read_vectored(&self) -> bool {
(**self).is_read_vectored()
}
}
impl AsInner<OwnedFd> for FileDesc {
#[inline]
fn as_inner(&self) -> &OwnedFd {
&self.0
}
}
impl IntoInner<OwnedFd> for FileDesc {
fn into_inner(self) -> OwnedFd {
self.0
}
}
impl FromInner<OwnedFd> for FileDesc {
fn from_inner(owned_fd: OwnedFd) -> Self {
Self(owned_fd)
}
}
impl AsFd for FileDesc {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for FileDesc {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for FileDesc {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
impl FromRawFd for FileDesc {
unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
Self(unsafe { FromRawFd::from_raw_fd(raw_fd) })
}
}