rust/library/std/src/sys/net/connection/socket/solid.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

403 lines
12 KiB
Rust

use libc::{c_int, c_void, size_t};
use self::netc::{MSG_PEEK, sockaddr, socklen_t};
use super::{getsockopt, setsockopt, socket_addr_from_c, socket_addr_to_c};
use crate::ffi::CStr;
use crate::io::{self, BorrowedBuf, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut};
use crate::net::{Shutdown, SocketAddr};
use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd};
use crate::sys::abi;
use crate::sys_common::{FromInner, IntoInner};
use crate::time::Duration;
use crate::{cmp, mem, ptr, str};
pub(super) mod netc {
pub use crate::sys::abi::sockets::*;
}
#[expect(non_camel_case_types)]
pub type wrlen_t = size_t;
const fn max_iov() -> usize {
// Judging by the source code, it's unlimited, but specify a lower
// value just in case.
1024
}
#[doc(hidden)]
pub trait IsMinusOne {
fn is_minus_one(&self) -> bool;
}
macro_rules! impl_is_minus_one {
($($t:ident)*) => ($(impl IsMinusOne for $t {
fn is_minus_one(&self) -> bool {
*self == -1
}
})*)
}
impl_is_minus_one! { i8 i16 i32 i64 isize }
pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
}
/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
pub fn cvt_gai(err: c_int) -> io::Result<()> {
if err == 0 {
Ok(())
} else {
let msg: &dyn crate::fmt::Display = match err {
netc::EAI_NONAME => &"name or service not known",
netc::EAI_SERVICE => &"service not supported",
netc::EAI_FAIL => &"non-recoverable failure in name resolution",
netc::EAI_MEMORY => &"memory allocation failure",
netc::EAI_FAMILY => &"family not supported",
_ => &err,
};
Err(io::Error::new(
io::ErrorKind::Uncategorized,
&format!("failed to lookup address information: {msg}")[..],
))
}
}
/// Just to provide the same interface as sys/pal/unix/net.rs
pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
where
T: IsMinusOne,
F: FnMut() -> T,
{
cvt(f())
}
/// Returns the last error from the network subsystem.
fn last_error() -> io::Error {
io::Error::from_raw_os_error(unsafe { netc::SOLID_NET_GetLastError() })
}
pub fn error_name(er: abi::ER) -> Option<&'static str> {
unsafe { CStr::from_ptr(netc::strerror(er)) }.to_str().ok()
}
#[inline]
pub fn is_interrupted(er: abi::ER) -> bool {
er == netc::SOLID_NET_ERR_BASE - libc::EINTR
}
pub fn decode_error_kind(er: abi::ER) -> ErrorKind {
let errno = netc::SOLID_NET_ERR_BASE - er;
match errno as libc::c_int {
libc::ECONNREFUSED => ErrorKind::ConnectionRefused,
libc::ECONNRESET => ErrorKind::ConnectionReset,
libc::EPERM | libc::EACCES => ErrorKind::PermissionDenied,
libc::EPIPE => ErrorKind::BrokenPipe,
libc::ENOTCONN => ErrorKind::NotConnected,
libc::ECONNABORTED => ErrorKind::ConnectionAborted,
libc::EADDRNOTAVAIL => ErrorKind::AddrNotAvailable,
libc::EADDRINUSE => ErrorKind::AddrInUse,
libc::ENOENT => ErrorKind::NotFound,
libc::EINTR => ErrorKind::Interrupted,
libc::EINVAL => ErrorKind::InvalidInput,
libc::ETIMEDOUT => ErrorKind::TimedOut,
libc::EEXIST => ErrorKind::AlreadyExists,
libc::ENOSYS => ErrorKind::Unsupported,
libc::ENOMEM => ErrorKind::OutOfMemory,
libc::EAGAIN => ErrorKind::WouldBlock,
_ => ErrorKind::Uncategorized,
}
}
pub fn init() {}
pub struct Socket(OwnedFd);
impl Socket {
pub fn new(fam: c_int, ty: c_int) -> io::Result<Socket> {
let fd = cvt(unsafe { netc::socket(fam, ty, 0) })?;
Ok(unsafe { Self::from_raw_fd(fd) })
}
pub fn connect(&self, addr: &SocketAddr) -> io::Result<()> {
let (addr, len) = socket_addr_to_c(addr);
cvt(unsafe { netc::connect(self.as_raw_fd(), addr.as_ptr(), len) })?;
Ok(())
}
pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> {
self.set_nonblocking(true)?;
let r = self.connect(addr);
self.set_nonblocking(false)?;
match r {
Ok(_) => return Ok(()),
// there's no ErrorKind for EINPROGRESS
Err(ref e) if e.raw_os_error() == Some(netc::EINPROGRESS) => {}
Err(e) => return Err(e),
}
if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 {
return Err(io::Error::ZERO_TIMEOUT);
}
let mut timeout =
netc::timeval { tv_sec: timeout.as_secs() as _, tv_usec: timeout.subsec_micros() as _ };
if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
timeout.tv_usec = 1;
}
let fds = netc::fd_set { num_fds: 1, fds: [self.as_raw_fd()] };
let mut writefds = fds;
let mut errorfds = fds;
let n = unsafe {
cvt(netc::select(
self.as_raw_fd() + 1,
ptr::null_mut(),
&mut writefds,
&mut errorfds,
&mut timeout,
))?
};
match n {
0 => Err(io::const_error!(io::ErrorKind::TimedOut, "connection timed out")),
_ => {
let can_write = writefds.num_fds != 0;
if !can_write {
if let Some(e) = self.take_error()? {
return Err(e);
}
}
Ok(())
}
}
}
pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
let fd = cvt_r(|| unsafe { netc::accept(self.as_raw_fd(), storage, len) })?;
unsafe { Ok(Self::from_raw_fd(fd)) }
}
pub fn duplicate(&self) -> io::Result<Socket> {
Ok(Self(self.0.try_clone()?))
}
fn recv_with_flags(&self, mut buf: BorrowedCursor<'_>, flags: c_int) -> io::Result<()> {
let ret = cvt(unsafe {
netc::recv(self.as_raw_fd(), buf.as_mut().as_mut_ptr().cast(), buf.capacity(), flags)
})?;
unsafe {
buf.advance_unchecked(ret as usize);
}
Ok(())
}
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut buf = BorrowedBuf::from(buf);
self.recv_with_flags(buf.unfilled(), 0)?;
Ok(buf.len())
}
pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut buf = BorrowedBuf::from(buf);
self.recv_with_flags(buf.unfilled(), MSG_PEEK)?;
Ok(buf.len())
}
pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> {
self.recv_with_flags(buf, 0)
}
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
netc::readv(
self.as_raw_fd(),
bufs.as_ptr() as *const netc::iovec,
cmp::min(bufs.len(), max_iov()) as c_int,
)
})?;
Ok(ret as usize)
}
#[inline]
pub fn is_read_vectored(&self) -> bool {
true
}
fn recv_from_with_flags(
&self,
buf: &mut [u8],
flags: c_int,
) -> io::Result<(usize, SocketAddr)> {
let mut storage: netc::sockaddr_storage = unsafe { mem::zeroed() };
let mut addrlen = size_of_val(&storage) as netc::socklen_t;
let n = cvt(unsafe {
netc::recvfrom(
self.as_raw_fd(),
buf.as_mut_ptr() as *mut c_void,
buf.len(),
flags,
&mut storage as *mut _ as *mut _,
&mut addrlen,
)
})?;
Ok((n as usize, unsafe { socket_addr_from_c(&storage, addrlen as usize)? }))
}
pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
self.recv_from_with_flags(buf, 0)
}
pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
self.recv_from_with_flags(buf, MSG_PEEK)
}
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
netc::writev(
self.as_raw_fd(),
bufs.as_ptr() as *const netc::iovec,
cmp::min(bufs.len(), max_iov()) as c_int,
)
})?;
Ok(ret as usize)
}
#[inline]
pub fn is_write_vectored(&self) -> bool {
true
}
pub fn set_timeout(&self, dur: Option<Duration>, kind: c_int) -> io::Result<()> {
let timeout = match dur {
Some(dur) => {
if dur.as_secs() == 0 && dur.subsec_nanos() == 0 {
return Err(io::Error::ZERO_TIMEOUT);
}
let secs = if dur.as_secs() > netc::c_long::MAX as u64 {
netc::c_long::MAX
} else {
dur.as_secs() as netc::c_long
};
let mut timeout = netc::timeval { tv_sec: secs, tv_usec: dur.subsec_micros() as _ };
if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
timeout.tv_usec = 1;
}
timeout
}
None => netc::timeval { tv_sec: 0, tv_usec: 0 },
};
unsafe { setsockopt(self, netc::SOL_SOCKET, kind, timeout) }
}
pub fn timeout(&self, kind: c_int) -> io::Result<Option<Duration>> {
let raw: netc::timeval = unsafe { getsockopt(self, netc::SOL_SOCKET, kind)? };
if raw.tv_sec == 0 && raw.tv_usec == 0 {
Ok(None)
} else {
let sec = raw.tv_sec as u64;
let nsec = (raw.tv_usec as u32) * 1000;
Ok(Some(Duration::new(sec, nsec)))
}
}
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
let how = match how {
Shutdown::Write => netc::SHUT_WR,
Shutdown::Read => netc::SHUT_RD,
Shutdown::Both => netc::SHUT_RDWR,
};
cvt(unsafe { netc::shutdown(self.as_raw_fd(), how) })?;
Ok(())
}
pub fn set_linger(&self, linger: Option<Duration>) -> io::Result<()> {
let linger = netc::linger {
l_onoff: linger.is_some() as netc::c_int,
l_linger: linger.unwrap_or_default().as_secs() as netc::c_int,
};
unsafe { setsockopt(self, netc::SOL_SOCKET, netc::SO_LINGER, linger) }
}
pub fn linger(&self) -> io::Result<Option<Duration>> {
let val: netc::linger = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_LINGER)? };
Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64)))
}
pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
unsafe { setsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY, nodelay as c_int) }
}
pub fn nodelay(&self) -> io::Result<bool> {
let raw: c_int = unsafe { getsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY)? };
Ok(raw != 0)
}
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
let mut nonblocking = nonblocking as c_int;
cvt(unsafe {
netc::ioctl(self.as_raw_fd(), netc::FIONBIO, (&mut nonblocking) as *mut c_int as _)
})
.map(drop)
}
pub fn take_error(&self) -> io::Result<Option<io::Error>> {
let raw: c_int = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_ERROR)? };
if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
}
// This method is used by sys_common code to abstract over targets.
pub fn as_raw(&self) -> c_int {
self.as_raw_fd()
}
}
impl FromInner<OwnedFd> for Socket {
#[inline]
fn from_inner(sock: OwnedFd) -> Socket {
Socket(sock)
}
}
impl IntoInner<OwnedFd> for Socket {
#[inline]
fn into_inner(self) -> OwnedFd {
self.0
}
}
impl AsFd for Socket {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for Socket {
#[inline]
fn as_raw_fd(&self) -> c_int {
self.0.as_raw_fd()
}
}
impl FromRawFd for Socket {
#[inline]
unsafe fn from_raw_fd(fd: c_int) -> Socket {
unsafe { Self(FromRawFd::from_raw_fd(fd)) }
}
}
impl IntoRawFd for Socket {
#[inline]
fn into_raw_fd(self) -> c_int {
self.0.into_raw_fd()
}
}