#![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.duplicate() } pub fn read(&self, buf: &mut [u8]) -> io::Result { 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 { 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 { 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) -> io::Result { let mut me = self; (&mut me).read_to_end(buf) } pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { 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::(), 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::(), 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { #[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 { 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 { 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 { 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 { 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 { 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 { Ok(Self(self.0.try_clone()?)) } } impl<'a> Read for &'a FileDesc { fn read(&mut self, buf: &mut [u8]) -> io::Result { (**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 { (**self).read_vectored(bufs) } #[inline] fn is_read_vectored(&self) -> bool { (**self).is_read_vectored() } } impl AsInner for FileDesc { #[inline] fn as_inner(&self) -> &OwnedFd { &self.0 } } impl IntoInner for FileDesc { fn into_inner(self) -> OwnedFd { self.0 } } impl FromInner 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) }) } }