//! libc syscalls supporting `rustix::io`. use crate::backend::c; #[cfg(not(target_os = "wasi"))] use crate::backend::conv::ret_discarded_fd; use crate::backend::conv::{borrowed_fd, ret, ret_c_int, ret_owned_fd, ret_usize}; use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd}; #[cfg(not(any( target_os = "aix", target_os = "espidf", target_os = "nto", target_os = "vita", target_os = "wasi" )))] use crate::io::DupFlags; #[cfg(linux_kernel)] use crate::io::ReadWriteFlags; use crate::io::{self, FdFlags}; use crate::ioctl::{IoctlOutput, RawOpcode}; use core::cmp::min; #[cfg(all(feature = "fs", feature = "net"))] use libc_errno::errno; #[cfg(not(target_os = "espidf"))] use { crate::backend::MAX_IOV, crate::io::{IoSlice, IoSliceMut}, }; pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: *mut u8, len: usize) -> io::Result { ret_usize(c::read(borrowed_fd(fd), buf.cast(), min(len, READ_LIMIT))) } pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result { unsafe { ret_usize(c::write( borrowed_fd(fd), buf.as_ptr().cast(), min(buf.len(), READ_LIMIT), )) } } pub(crate) unsafe fn pread( fd: BorrowedFd<'_>, buf: *mut u8, len: usize, offset: u64, ) -> io::Result { let len = min(len, READ_LIMIT); // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; // ESP-IDF and Vita don't support 64-bit offsets. #[cfg(any(target_os = "espidf", target_os = "vita"))] let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?; ret_usize(c::pread(borrowed_fd(fd), buf.cast(), len, offset)) } pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result { let len = min(buf.len(), READ_LIMIT); // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; // ESP-IDF and Vita don't support 64-bit offsets. #[cfg(any(target_os = "espidf", target_os = "vita"))] let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?; unsafe { ret_usize(c::pwrite(borrowed_fd(fd), buf.as_ptr().cast(), len, offset)) } } #[cfg(not(target_os = "espidf"))] pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>]) -> io::Result { unsafe { ret_usize(c::readv( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, )) } } #[cfg(not(target_os = "espidf"))] pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result { unsafe { ret_usize(c::writev( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, )) } } #[cfg(not(any( target_os = "espidf", target_os = "haiku", target_os = "nto", target_os = "redox", target_os = "solaris", target_os = "vita" )))] pub(crate) fn preadv( fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>], offset: u64, ) -> io::Result { // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; unsafe { ret_usize(c::preadv( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, offset, )) } } #[cfg(not(any( target_os = "espidf", target_os = "haiku", target_os = "nto", target_os = "redox", target_os = "solaris", target_os = "vita" )))] pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; unsafe { ret_usize(c::pwritev( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, offset, )) } } #[cfg(linux_kernel)] pub(crate) fn preadv2( fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>], offset: u64, flags: ReadWriteFlags, ) -> io::Result { // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; unsafe { ret_usize(c::preadv2( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, offset, bitflags_bits!(flags), )) } } #[cfg(linux_kernel)] pub(crate) fn pwritev2( fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], offset: u64, flags: ReadWriteFlags, ) -> io::Result { // Silently cast; we'll get `EINVAL` if the value is negative. let offset = offset as i64; unsafe { ret_usize(c::pwritev2( borrowed_fd(fd), bufs.as_ptr().cast::(), min(bufs.len(), MAX_IOV) as c::c_int, offset, bitflags_bits!(flags), )) } } // These functions are derived from Rust's library/std/src/sys/unix/fd.rs at // revision 326ef470a8b379a180d6dc4bbef08990698a737a. // The maximum read limit on most POSIX-like systems is `SSIZE_MAX`, with the // manual page quoting that if the count of bytes to read is greater than // `SSIZE_MAX` the result is “unspecified”. // // On macOS, however, apparently the 64-bit libc is either buggy or // intentionally showing odd behavior by rejecting any read with a size larger // than or equal to `INT_MAX`. To handle both of these the read size is capped // on both platforms. #[cfg(target_os = "macos")] const READ_LIMIT: usize = c::c_int::MAX as usize - 1; #[cfg(not(target_os = "macos"))] const READ_LIMIT: usize = c::ssize_t::MAX as usize; pub(crate) unsafe fn close(raw_fd: RawFd) { let _ = c::close(raw_fd as c::c_int); } #[inline] pub(crate) unsafe fn ioctl( fd: BorrowedFd<'_>, request: RawOpcode, arg: *mut c::c_void, ) -> io::Result { ret_c_int(c::ioctl(borrowed_fd(fd), request, arg)) } #[inline] pub(crate) unsafe fn ioctl_readonly( fd: BorrowedFd<'_>, request: RawOpcode, arg: *mut c::c_void, ) -> io::Result { ioctl(fd, request, arg) } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] #[cfg(all(feature = "fs", feature = "net"))] pub(crate) fn is_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { use core::mem::MaybeUninit; let (mut read, mut write) = crate::fs::fd::_is_file_read_write(fd)?; let mut not_socket = false; if read { // Do a `recv` with `PEEK` and `DONTWAIT` for 1 byte. A 0 indicates // the read side is shut down; an `EWOULDBLOCK` indicates the read // side is still open. match unsafe { c::recv( borrowed_fd(fd), MaybeUninit::<[u8; 1]>::uninit() .as_mut_ptr() .cast::(), 1, c::MSG_PEEK | c::MSG_DONTWAIT, ) } { 0 => read = false, -1 => { #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK` match errno().0 { c::EAGAIN | c::EWOULDBLOCK => (), c::ENOTSOCK => not_socket = true, err => return Err(io::Errno(err)), } } _ => (), } } if write && !not_socket { // Do a `send` with `DONTWAIT` for 0 bytes. An `EPIPE` indicates // the write side is shut down. if unsafe { c::send(borrowed_fd(fd), [].as_ptr(), 0, c::MSG_DONTWAIT) } == -1 { #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK` match errno().0 { c::EAGAIN | c::EWOULDBLOCK | c::ENOTSOCK => (), c::EPIPE => write = false, err => return Err(io::Errno(err)), } } } Ok((read, write)) } #[cfg(target_os = "wasi")] #[cfg(all(feature = "fs", feature = "net"))] pub(crate) fn is_read_write(_fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { todo!("Implement is_read_write for WASI in terms of fd_fdstat_get"); } pub(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result { let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFD))? }; Ok(FdFlags::from_bits_retain(bitcast!(flags))) } pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> { unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFD, flags.bits())) } } #[cfg(not(any(target_os = "espidf", target_os = "wasi")))] pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result { unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD_CLOEXEC, min)) } } #[cfg(target_os = "espidf")] pub(crate) fn fcntl_dupfd(fd: BorrowedFd<'_>, min: RawFd) -> io::Result { unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD, min)) } } #[cfg(not(target_os = "wasi"))] pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result { unsafe { ret_owned_fd(c::dup(borrowed_fd(fd))) } } #[allow(clippy::needless_pass_by_ref_mut)] #[cfg(not(target_os = "wasi"))] pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> { unsafe { ret_discarded_fd(c::dup2(borrowed_fd(fd), borrowed_fd(new.as_fd()))) } } #[allow(clippy::needless_pass_by_ref_mut)] #[cfg(not(any( apple, target_os = "aix", target_os = "android", target_os = "dragonfly", target_os = "espidf", target_os = "haiku", target_os = "nto", target_os = "redox", target_os = "vita", target_os = "wasi", )))] pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> { unsafe { ret_discarded_fd(c::dup3( borrowed_fd(fd), borrowed_fd(new.as_fd()), bitflags_bits!(flags), )) } } #[cfg(any( apple, target_os = "android", target_os = "dragonfly", target_os = "haiku", target_os = "redox", ))] pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, _flags: DupFlags) -> io::Result<()> { // Android 5.0 has `dup3`, but libc doesn't have bindings. Emulate it // using `dup2`. We don't need to worry about the difference between // `dup2` and `dup3` when the file descriptors are equal because we // have an `&mut OwnedFd` which means `fd` doesn't alias it. dup2(fd, new) }