//! linux_raw syscalls supporting `rustix::io`. //! //! # Safety //! //! See the `rustix::imp` module documentation for details. #![allow(unsafe_code)] #![allow(clippy::undocumented_unsafe_blocks)] use super::super::c; #[cfg(target_pointer_width = "64")] use super::super::conv::loff_t_from_u64; use super::super::conv::{ by_ref, c_int, c_uint, pass_usize, raw_fd, ret, ret_discarded_fd, ret_owned_fd, ret_usize, slice, slice_mut, zero, }; #[cfg(target_pointer_width = "32")] use super::super::conv::{hi, lo}; use crate::fd::{AsFd, BorrowedFd, RawFd}; use crate::io::{ self, epoll, DupFlags, EventfdFlags, IoSlice, IoSliceMut, OwnedFd, PipeFlags, PollFd, ReadWriteFlags, }; #[cfg(feature = "net")] use crate::net::{RecvFlags, SendFlags}; use core::cmp; use core::mem::MaybeUninit; use linux_raw_sys::general::{ epoll_event, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD, UIO_MAXIOV, }; use linux_raw_sys::ioctl::{BLKPBSZGET, BLKSSZGET, FIONBIO, FIONREAD, TIOCEXCL, TIOCNXCL}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use { super::super::conv::{opt_ref, size_of}, linux_raw_sys::general::{__kernel_timespec, sigset_t}, }; #[inline] pub(crate) fn read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result { let (buf_addr_mut, buf_len) = slice_mut(buf); unsafe { ret_usize(syscall!(__NR_read, fd, buf_addr_mut, buf_len)) } } #[inline] pub(crate) fn pread(fd: BorrowedFd<'_>, buf: &mut [u8], pos: u64) -> io::Result { let (buf_addr_mut, buf_len) = slice_mut(buf); // #[cfg(all( target_pointer_width = "32", any(target_arch = "arm", target_arch = "mips", target_arch = "power"), ))] unsafe { ret_usize(syscall!( __NR_pread64, fd, buf_addr_mut, buf_len, zero(), hi(pos), lo(pos) )) } #[cfg(all( target_pointer_width = "32", not(any(target_arch = "arm", target_arch = "mips", target_arch = "power")), ))] unsafe { ret_usize(syscall!( __NR_pread64, fd, buf_addr_mut, buf_len, hi(pos), lo(pos) )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall!( __NR_pread64, fd, buf_addr_mut, buf_len, loff_t_from_u64(pos) )) } } #[inline] pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>]) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); unsafe { ret_usize(syscall!(__NR_readv, fd, bufs_addr, bufs_len)) } } #[inline] pub(crate) fn preadv( fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>], pos: u64, ) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); #[cfg(target_pointer_width = "32")] unsafe { ret_usize(syscall!( __NR_preadv, fd, bufs_addr, bufs_len, hi(pos), lo(pos) )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall!( __NR_preadv, fd, bufs_addr, bufs_len, loff_t_from_u64(pos) )) } } #[inline] pub(crate) fn preadv2( fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>], pos: u64, flags: ReadWriteFlags, ) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); #[cfg(target_pointer_width = "32")] unsafe { ret_usize(syscall!( __NR_preadv2, fd, bufs_addr, bufs_len, hi(pos), lo(pos), flags )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall!( __NR_preadv2, fd, bufs_addr, bufs_len, loff_t_from_u64(pos), flags )) } } #[inline] pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result { let (buf_addr, buf_len) = slice(buf); unsafe { ret_usize(syscall_readonly!(__NR_write, fd, buf_addr, buf_len)) } } #[inline] pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], pos: u64) -> io::Result { let (buf_addr, buf_len) = slice(buf); // #[cfg(all( target_pointer_width = "32", any(target_arch = "arm", target_arch = "mips", target_arch = "power"), ))] unsafe { ret_usize(syscall_readonly!( __NR_pwrite64, fd, buf_addr, buf_len, zero(), hi(pos), lo(pos) )) } #[cfg(all( target_pointer_width = "32", not(any(target_arch = "arm", target_arch = "mips", target_arch = "power")), ))] unsafe { ret_usize(syscall_readonly!( __NR_pwrite64, fd, buf_addr, buf_len, hi(pos), lo(pos) )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall_readonly!( __NR_pwrite64, fd, buf_addr, buf_len, loff_t_from_u64(pos) )) } } #[inline] pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); unsafe { ret_usize(syscall_readonly!(__NR_writev, fd, bufs_addr, bufs_len)) } } #[inline] pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], pos: u64) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); #[cfg(target_pointer_width = "32")] unsafe { ret_usize(syscall_readonly!( __NR_pwritev, fd, bufs_addr, bufs_len, hi(pos), lo(pos) )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall_readonly!( __NR_pwritev, fd, bufs_addr, bufs_len, loff_t_from_u64(pos) )) } } #[inline] pub(crate) fn pwritev2( fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], pos: u64, flags: ReadWriteFlags, ) -> io::Result { let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), max_iov())]); #[cfg(target_pointer_width = "32")] unsafe { ret_usize(syscall_readonly!( __NR_pwritev2, fd, bufs_addr, bufs_len, hi(pos), lo(pos), flags )) } #[cfg(target_pointer_width = "64")] unsafe { ret_usize(syscall_readonly!( __NR_pwritev2, fd, bufs_addr, bufs_len, loff_t_from_u64(pos), flags )) } } /// The maximum number of buffers that can be passed into a vectored I/O system /// call on the current platform. const fn max_iov() -> usize { UIO_MAXIOV as usize } #[inline] pub(crate) unsafe fn close(fd: RawFd) { // See the documentation for [`io::close`] for why errors are ignored. syscall_readonly!(__NR_close, raw_fd(fd)).decode_void(); } #[inline] pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result { unsafe { ret_owned_fd(syscall_readonly!(__NR_eventfd2, c_uint(initval), flags)) } } #[inline] pub(crate) fn ioctl_fionread(fd: BorrowedFd<'_>) -> io::Result { unsafe { let mut result = MaybeUninit::::uninit(); ret(syscall!(__NR_ioctl, fd, c_uint(FIONREAD), &mut result)) .map(|()| result.assume_init() as u64) } } #[inline] pub(crate) fn ioctl_fionbio(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { unsafe { let data = c::c_int::from(value); ret(syscall_readonly!( __NR_ioctl, fd, c_uint(FIONBIO), by_ref(&data) )) } } #[inline] pub(crate) fn ioctl_tiocexcl(fd: BorrowedFd<'_>) -> io::Result<()> { unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TIOCEXCL))) } } #[inline] pub(crate) fn ioctl_tiocnxcl(fd: BorrowedFd<'_>) -> io::Result<()> { unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TIOCNXCL))) } } #[inline] pub(crate) fn ioctl_blksszget(fd: BorrowedFd) -> io::Result { let mut result = MaybeUninit::::uninit(); unsafe { ret(syscall!(__NR_ioctl, fd, c_uint(BLKSSZGET), &mut result)) .map(|()| result.assume_init() as u32) } } #[inline] pub(crate) fn ioctl_blkpbszget(fd: BorrowedFd) -> io::Result { let mut result = MaybeUninit::::uninit(); unsafe { ret(syscall!(__NR_ioctl, fd, c_uint(BLKPBSZGET), &mut result)) .map(|()| result.assume_init() as u32) } } #[cfg(feature = "net")] pub(crate) fn is_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { 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. // // TODO: This code would benefit from having a better way to read into // uninitialized memory. let mut buf = [0]; match super::super::net::syscalls::recv(fd, &mut buf, RecvFlags::PEEK | RecvFlags::DONTWAIT) { Ok(0) => read = false, Err(err) => { #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK` match err { io::Errno::AGAIN | io::Errno::WOULDBLOCK => (), io::Errno::NOTSOCK => not_socket = true, _ => return Err(err), } } Ok(_) => (), } } if write && !not_socket { // Do a `send` with `DONTWAIT` for 0 bytes. An `EPIPE` indicates // the write side is shut down. #[allow(unreachable_patterns)] // `EAGAIN` equals `EWOULDBLOCK` match super::super::net::syscalls::send(fd, &[], SendFlags::DONTWAIT) { // TODO or-patterns when we don't need 1.51 Err(io::Errno::AGAIN) => (), Err(io::Errno::WOULDBLOCK) => (), Err(io::Errno::NOTSOCK) => (), Err(io::Errno::PIPE) => write = false, Err(err) => return Err(err), Ok(_) => (), } } Ok((read, write)) } #[inline] pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result { unsafe { ret_owned_fd(syscall_readonly!(__NR_dup, fd)) } } #[inline] pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> { #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { // 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. dup3(fd, new, DupFlags::empty()) } #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] unsafe { ret_discarded_fd(syscall_readonly!(__NR_dup2, fd, new.as_fd())) } } #[inline] pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> { unsafe { ret_discarded_fd(syscall_readonly!(__NR_dup3, fd, new.as_fd(), flags)) } } #[inline] pub(crate) fn pipe_with(flags: PipeFlags) -> io::Result<(OwnedFd, OwnedFd)> { unsafe { let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit(); ret(syscall!(__NR_pipe2, &mut result, flags))?; let [p0, p1] = result.assume_init(); Ok((p0, p1)) } } #[inline] pub(crate) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> { // aarch64 and risc64 omit `__NR_pipe`. On mips, `__NR_pipe` uses a special // calling convention, but using it is not worth complicating our syscall // wrapping infrastructure at this time. #[cfg(any( target_arch = "aarch64", target_arch = "mips", target_arch = "mips64", target_arch = "riscv64", ))] { pipe_with(PipeFlags::empty()) } #[cfg(not(any( target_arch = "aarch64", target_arch = "mips", target_arch = "mips64", target_arch = "riscv64", )))] unsafe { let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit(); ret(syscall!(__NR_pipe, &mut result))?; let [p0, p1] = result.assume_init(); Ok((p0, p1)) } } #[inline] pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result { let (fds_addr_mut, fds_len) = slice_mut(fds); #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] unsafe { let timeout = if timeout >= 0 { Some(__kernel_timespec { tv_sec: (timeout as i64) / 1000, tv_nsec: (timeout as i64) % 1000 * 1_000_000, }) } else { None }; ret_usize(syscall!( __NR_ppoll, fds_addr_mut, fds_len, opt_ref(timeout.as_ref()), zero(), size_of::() )) } #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] unsafe { ret_usize(syscall!(__NR_poll, fds_addr_mut, fds_len, c_int(timeout))) } } #[inline] pub(crate) fn epoll_create(flags: epoll::CreateFlags) -> io::Result { unsafe { ret_owned_fd(syscall_readonly!(__NR_epoll_create1, flags)) } } #[inline] pub(crate) unsafe fn epoll_add( epfd: BorrowedFd<'_>, fd: c::c_int, event: &epoll_event, ) -> io::Result<()> { ret(syscall_readonly!( __NR_epoll_ctl, epfd, c_uint(EPOLL_CTL_ADD), raw_fd(fd), by_ref(event) )) } #[inline] pub(crate) unsafe fn epoll_mod( epfd: BorrowedFd<'_>, fd: c::c_int, event: &epoll_event, ) -> io::Result<()> { ret(syscall_readonly!( __NR_epoll_ctl, epfd, c_uint(EPOLL_CTL_MOD), raw_fd(fd), by_ref(event) )) } #[inline] pub(crate) unsafe fn epoll_del(epfd: BorrowedFd<'_>, fd: c::c_int) -> io::Result<()> { ret(syscall_readonly!( __NR_epoll_ctl, epfd, c_uint(EPOLL_CTL_DEL), raw_fd(fd), zero() )) } #[inline] pub(crate) fn epoll_wait( epfd: BorrowedFd<'_>, events: *mut epoll_event, num_events: usize, timeout: c::c_int, ) -> io::Result { #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] unsafe { ret_usize(syscall!( __NR_epoll_wait, epfd, events, pass_usize(num_events), c_int(timeout) )) } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] unsafe { ret_usize(syscall!( __NR_epoll_pwait, epfd, events, pass_usize(num_events), c_int(timeout), zero() )) } }