diff options
| author | Thalia Archibald <thalia@archibald.dev> | 2025-03-24 23:01:13 -0700 |
|---|---|---|
| committer | Thalia Archibald <thalia@archibald.dev> | 2025-04-04 20:11:08 -0700 |
| commit | 4085af01836c3af3bd7c68060274ba7a0c334261 (patch) | |
| tree | 4b279b982b05de0b3b19a914b556719c2863e3a3 /library/std/src/sys/fd | |
| parent | 5cc60728e7ee10eb2ae5f61f7d412d9805b22f0c (diff) | |
Move fd into sys
Diffstat (limited to 'library/std/src/sys/fd')
| -rw-r--r-- | library/std/src/sys/fd/hermit.rs | 175 | ||||
| -rw-r--r-- | library/std/src/sys/fd/mod.rs | 19 | ||||
| -rw-r--r-- | library/std/src/sys/fd/sgx.rs | 85 | ||||
| -rw-r--r-- | library/std/src/sys/fd/unix.rs | 679 | ||||
| -rw-r--r-- | library/std/src/sys/fd/unix/tests.rs | 12 | ||||
| -rw-r--r-- | library/std/src/sys/fd/wasi.rs | 331 |
6 files changed, 1301 insertions, 0 deletions
diff --git a/library/std/src/sys/fd/hermit.rs b/library/std/src/sys/fd/hermit.rs new file mode 100644 index 00000000000..7e8ba065f1b --- /dev/null +++ b/library/std/src/sys/fd/hermit.rs @@ -0,0 +1,175 @@ +#![unstable(reason = "not public", issue = "none", feature = "fd")] + +use crate::cmp; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, SeekFrom}; +use crate::os::hermit::hermit_abi; +use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::{cvt, unsupported}; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +const fn max_iov() -> usize { + hermit_abi::IOV_MAX +} + +#[derive(Debug)] +pub struct FileDesc { + fd: OwnedFd, +} + +impl FileDesc { + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + let result = + cvt(unsafe { hermit_abi::read(self.fd.as_raw_fd(), buf.as_mut_ptr(), buf.len()) })?; + Ok(result as usize) + } + + pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + // SAFETY: The `read` syscall does not read from the buffer, so it is + // safe to use `&mut [MaybeUninit<u8>]`. + let result = cvt(unsafe { + hermit_abi::read( + self.fd.as_raw_fd(), + buf.as_mut().as_mut_ptr() as *mut u8, + buf.capacity(), + ) + })?; + // SAFETY: Exactly `result` bytes have been filled. + unsafe { buf.advance_unchecked(result as usize) }; + Ok(()) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + let ret = cvt(unsafe { + hermit_abi::readv( + self.as_raw_fd(), + bufs.as_mut_ptr() as *mut hermit_abi::iovec as *const hermit_abi::iovec, + cmp::min(bufs.len(), max_iov()), + ) + })?; + Ok(ret as usize) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + 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 write(&self, buf: &[u8]) -> io::Result<usize> { + let result = + cvt(unsafe { hermit_abi::write(self.fd.as_raw_fd(), buf.as_ptr(), buf.len()) })?; + Ok(result as usize) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + let ret = cvt(unsafe { + hermit_abi::writev( + self.as_raw_fd(), + bufs.as_ptr() as *const hermit_abi::iovec, + cmp::min(bufs.len(), max_iov()), + ) + })?; + Ok(ret as usize) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + let (whence, pos) = match pos { + // Casting to `i64` is fine, too large values will end up as + // negative which will cause an error in `lseek`. + SeekFrom::Start(off) => (hermit_abi::SEEK_SET, off as i64), + SeekFrom::End(off) => (hermit_abi::SEEK_END, off), + SeekFrom::Current(off) => (hermit_abi::SEEK_CUR, off), + }; + let n = cvt(unsafe { hermit_abi::lseek(self.as_raw_fd(), pos as isize, whence) })?; + Ok(n as u64) + } + + pub fn tell(&self) -> io::Result<u64> { + self.seek(SeekFrom::Current(0)) + } + + pub fn duplicate(&self) -> io::Result<FileDesc> { + self.duplicate_path(&[]) + } + + pub fn duplicate_path(&self, _path: &[u8]) -> io::Result<FileDesc> { + unsupported() + } + + pub fn nonblocking(&self) -> io::Result<bool> { + Ok(false) + } + + pub fn set_cloexec(&self) -> io::Result<()> { + unsupported() + } + + pub fn set_nonblocking(&self, _nonblocking: bool) -> io::Result<()> { + unsupported() + } + + pub fn fstat(&self, stat: *mut hermit_abi::stat) -> io::Result<()> { + cvt(unsafe { hermit_abi::fstat(self.fd.as_raw_fd(), stat) })?; + Ok(()) + } +} + +impl<'a> Read for &'a FileDesc { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + (**self).read(buf) + } +} + +impl IntoInner<OwnedFd> for FileDesc { + fn into_inner(self) -> OwnedFd { + self.fd + } +} + +impl FromInner<OwnedFd> for FileDesc { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self { fd: owned_fd } + } +} + +impl FromRawFd for FileDesc { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) }; + Self { fd } + } +} + +impl AsInner<OwnedFd> for FileDesc { + #[inline] + fn as_inner(&self) -> &OwnedFd { + &self.fd + } +} + +impl AsFd for FileDesc { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for FileDesc { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for FileDesc { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} diff --git a/library/std/src/sys/fd/mod.rs b/library/std/src/sys/fd/mod.rs new file mode 100644 index 00000000000..86cfa44c39d --- /dev/null +++ b/library/std/src/sys/fd/mod.rs @@ -0,0 +1,19 @@ +//! Platform-dependent file descriptor abstraction. + +#![deny(unsafe_op_in_unsafe_fn)] + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix; + pub use unix::*; + } else if #[cfg(target_os = "hermit")] { + mod hermit; + pub use hermit::*; + } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + mod sgx; + pub use sgx::*; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + pub use wasi::*; + } +} diff --git a/library/std/src/sys/fd/sgx.rs b/library/std/src/sys/fd/sgx.rs new file mode 100644 index 00000000000..1ef768db64c --- /dev/null +++ b/library/std/src/sys/fd/sgx.rs @@ -0,0 +1,85 @@ +use fortanix_sgx_abi::Fd; + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::mem::ManuallyDrop; +use crate::sys::pal::abi::usercalls; +use crate::sys::{AsInner, FromInner, IntoInner}; + +#[derive(Debug)] +pub struct FileDesc { + fd: Fd, +} + +impl FileDesc { + pub fn new(fd: Fd) -> FileDesc { + FileDesc { fd } + } + + pub fn raw(&self) -> Fd { + self.fd + } + + /// Extracts the actual file descriptor without closing it. + pub fn into_raw(self) -> Fd { + ManuallyDrop::new(self).fd + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + usercalls::read(self.fd, &mut [IoSliceMut::new(buf)]) + } + + pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> { + usercalls::read_buf(self.fd, buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + usercalls::read(self.fd, bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + usercalls::write(self.fd, &[IoSlice::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + usercalls::write(self.fd, bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn flush(&self) -> io::Result<()> { + usercalls::flush(self.fd) + } +} + +impl AsInner<Fd> for FileDesc { + #[inline] + fn as_inner(&self) -> &Fd { + &self.fd + } +} + +impl IntoInner<Fd> for FileDesc { + fn into_inner(self) -> Fd { + ManuallyDrop::new(self).fd + } +} + +impl FromInner<Fd> for FileDesc { + fn from_inner(fd: Fd) -> FileDesc { + FileDesc { fd } + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + usercalls::close(self.fd) + } +} diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs new file mode 100644 index 00000000000..3fd92ccbff3 --- /dev/null +++ b/library/std/src/sys/fd/unix.rs @@ -0,0 +1,679 @@ +#![unstable(reason = "not public", issue = "none", feature = "fd")] +#![allow(unsafe_op_in_unsafe_fn)] + +#[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; + +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 or equal to 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 - 1 +} 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 = "freebsd", + target_os = "linux", + target_os = "netbsd", + 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) + } + + #[cfg_attr(target_os = "vxworks", allow(unused_unsafe))] + pub fn read_at(&self, buf: &mut [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::pread as pread64; + #[cfg(any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "hurd" + ))] + use libc::pread64; + + unsafe { + cvt(pread64( + self.as_raw_fd(), + buf.as_mut_ptr() as *mut libc::c_void, + cmp::min(buf.len(), READ_LIMIT), + offset as off64_t, + )) + .map(|n| n as usize) + } + } + + pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + let ret = cvt(unsafe { + libc::read( + self.as_raw_fd(), + cursor.as_mut().as_mut_ptr() as *mut 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(()) + } + + #[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"))] + // FIXME(#115199): Rust currently omits weak function definitions + // and its metadata from LLVM IR. + #[no_sanitize(cfi)] + 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" + ))) + } + + #[cfg_attr(target_os = "vxworks", allow(unused_unsafe))] + 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(FromRawFd::from_raw_fd(raw_fd)) + } +} diff --git a/library/std/src/sys/fd/unix/tests.rs b/library/std/src/sys/fd/unix/tests.rs new file mode 100644 index 00000000000..fcd66c71707 --- /dev/null +++ b/library/std/src/sys/fd/unix/tests.rs @@ -0,0 +1,12 @@ +use core::mem::ManuallyDrop; + +use super::FileDesc; +use crate::io::IoSlice; +use crate::os::unix::io::FromRawFd; + +#[test] +fn limit_vector_count() { + let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) }); + let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>(); + assert!(stdout.write_vectored(&bufs).is_ok()); +} diff --git a/library/std/src/sys/fd/wasi.rs b/library/std/src/sys/fd/wasi.rs new file mode 100644 index 00000000000..80a5143ff0b --- /dev/null +++ b/library/std/src/sys/fd/wasi.rs @@ -0,0 +1,331 @@ +#![expect(dead_code)] + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::mem; +use crate::net::Shutdown; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::pal::err2io; +use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; + +#[derive(Debug)] +pub struct WasiFd { + fd: OwnedFd, +} + +fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { + assert_eq!(size_of::<IoSliceMut<'_>>(), size_of::<wasi::Iovec>()); + assert_eq!(align_of::<IoSliceMut<'_>>(), align_of::<wasi::Iovec>()); + // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout. + // We decorate our `IoSliceMut` with `repr(transparent)` (see `io.rs`), and + // `crate::io::IoSliceMut` is a `repr(transparent)` wrapper around our type, so this is + // guaranteed. + unsafe { mem::transmute(a) } +} + +fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { + assert_eq!(size_of::<IoSlice<'_>>(), size_of::<wasi::Ciovec>()); + assert_eq!(align_of::<IoSlice<'_>>(), align_of::<wasi::Ciovec>()); + // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout. + // We decorate our `IoSlice` with `repr(transparent)` (see `io.rs`), and + // `crate::io::IoSlice` is a `repr(transparent)` wrapper around our type, so this is + // guaranteed. + unsafe { mem::transmute(a) } +} + +impl WasiFd { + pub fn datasync(&self) -> io::Result<()> { + unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { + unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) } + } + + pub fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { + unsafe { + wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io) + } + } + + pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) } + } + + pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { + let bufs = [wasi::Iovec { + buf: buf.as_mut().as_mut_ptr() as *mut u8, + buf_len: buf.capacity(), + }]; + match wasi::fd_read(self.as_raw_fd() as wasi::Fd, &bufs) { + Ok(n) => { + buf.advance_unchecked(n); + Ok(()) + } + Err(e) => Err(err2io(e)), + } + } + } + + pub fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) } + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + let (whence, offset) = match pos { + SeekFrom::Start(pos) => (wasi::WHENCE_SET, pos as i64), + SeekFrom::End(pos) => (wasi::WHENCE_END, pos), + SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos), + }; + unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) } + } + + pub fn tell(&self) -> io::Result<u64> { + unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + // FIXME: __wasi_fd_fdstat_get + + pub fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> { + unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + } + + pub fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> { + unsafe { + wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting) + .map_err(err2io) + } + } + + pub fn sync(&self) -> io::Result<()> { + unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub(crate) fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { + unsafe { + wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) + } + } + + pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } + } + + pub fn create_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn link( + &self, + old_flags: wasi::Lookupflags, + old_path: &str, + new_fd: &WasiFd, + new_path: &str, + ) -> io::Result<()> { + unsafe { + wasi::path_link( + self.as_raw_fd() as wasi::Fd, + old_flags, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub fn open( + &self, + dirflags: wasi::Lookupflags, + path: &str, + oflags: wasi::Oflags, + fs_rights_base: wasi::Rights, + fs_rights_inheriting: wasi::Rights, + fs_flags: wasi::Fdflags, + ) -> io::Result<WasiFd> { + unsafe { + wasi::path_open( + self.as_raw_fd() as wasi::Fd, + dirflags, + path, + oflags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ) + .map(|fd| WasiFd::from_raw_fd(fd as RawFd)) + .map_err(err2io) + } + } + + pub fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result<usize> { + unsafe { + wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie) + .map_err(err2io) + } + } + + pub fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result<usize> { + unsafe { + wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len()) + .map_err(err2io) + } + } + + pub fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_rename( + self.as_raw_fd() as wasi::Fd, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub(crate) fn filestat_get(&self) -> io::Result<wasi::Filestat> { + unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub fn filestat_set_times( + &self, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags) + .map_err(err2io) + } + } + + pub fn filestat_set_size(&self, size: u64) -> io::Result<()> { + unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } + } + + pub(crate) fn path_filestat_get( + &self, + flags: wasi::Lookupflags, + path: &str, + ) -> io::Result<wasi::Filestat> { + unsafe { + wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io) + } + } + + pub fn path_filestat_set_times( + &self, + flags: wasi::Lookupflags, + path: &str, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::path_filestat_set_times( + self.as_raw_fd() as wasi::Fd, + flags, + path, + atim, + mtim, + fstflags, + ) + .map_err(err2io) + } + } + + pub fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io) + } + } + + pub fn unlink_file(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn remove_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result<wasi::Fd> { + unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + } + + pub fn sock_recv( + &self, + ri_data: &mut [IoSliceMut<'_>], + ri_flags: wasi::Riflags, + ) -> io::Result<(usize, wasi::Roflags)> { + unsafe { + wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io) + } + } + + pub fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result<usize> { + unsafe { + wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io) + } + } + + pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> { + let how = match how { + Shutdown::Read => wasi::SDFLAGS_RD, + Shutdown::Write => wasi::SDFLAGS_WR, + Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, + }; + unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) } + } +} + +impl AsInner<OwnedFd> for WasiFd { + #[inline] + fn as_inner(&self) -> &OwnedFd { + &self.fd + } +} + +impl AsInnerMut<OwnedFd> for WasiFd { + #[inline] + fn as_inner_mut(&mut self) -> &mut OwnedFd { + &mut self.fd + } +} + +impl IntoInner<OwnedFd> for WasiFd { + fn into_inner(self) -> OwnedFd { + self.fd + } +} + +impl FromInner<OwnedFd> for WasiFd { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self { fd: owned_fd } + } +} + +impl AsFd for WasiFd { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for WasiFd { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for WasiFd { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +impl FromRawFd for WasiFd { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } + } +} |
