| //! linux_raw syscalls supporting `rustix::fs`. |
| //! |
| //! # Safety |
| //! |
| //! See the `rustix::backend` module documentation for details. |
| #![allow(unsafe_code)] |
| #![allow(dead_code)] |
| #![allow(clippy::undocumented_unsafe_blocks)] |
| |
| use super::super::c; |
| use super::super::conv::{ |
| by_ref, c_int, c_uint, dev_t, oflags_for_open_how, opt_mut, pass_usize, raw_fd, ret, ret_c_int, |
| ret_c_uint, ret_owned_fd, ret_usize, size_of, slice_mut, zero, |
| }; |
| #[cfg(target_pointer_width = "64")] |
| use super::super::conv::{loff_t, loff_t_from_u64, ret_u64}; |
| #[cfg(any( |
| target_arch = "aarch64", |
| target_arch = "riscv64", |
| target_arch = "mips64", |
| target_pointer_width = "32", |
| ))] |
| use crate::fd::AsFd; |
| use crate::fd::{BorrowedFd, OwnedFd}; |
| use crate::ffi::CStr; |
| use crate::fs::{ |
| Access, Advice, AtFlags, FallocateFlags, FileType, FlockOperation, MemfdFlags, Mode, OFlags, |
| RenameFlags, ResolveFlags, SealFlags, Stat, StatFs, StatVfs, StatVfsMountFlags, StatxFlags, |
| Timestamps, |
| }; |
| use crate::io::{self, SeekFrom}; |
| use crate::process::{Gid, Uid}; |
| use core::convert::TryInto; |
| use core::mem::MaybeUninit; |
| #[cfg(target_arch = "mips64")] |
| use linux_raw_sys::general::stat as linux_stat64; |
| use linux_raw_sys::general::{ |
| __kernel_fsid_t, __kernel_timespec, open_how, statx, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, |
| AT_SYMLINK_NOFOLLOW, F_ADD_SEALS, F_GETFL, F_GETLEASE, F_GETOWN, F_GETPIPE_SZ, F_GETSIG, |
| F_GET_SEALS, F_SETFL, F_SETPIPE_SZ, SEEK_CUR, SEEK_END, SEEK_SET, STATX__RESERVED, |
| }; |
| #[cfg(target_pointer_width = "32")] |
| use { |
| super::super::conv::{hi, lo, slice_just_addr}, |
| linux_raw_sys::general::stat64 as linux_stat64, |
| linux_raw_sys::general::timespec as __kernel_old_timespec, |
| }; |
| |
| #[inline] |
| pub(crate) fn open(filename: &CStr, flags: OFlags, mode: Mode) -> io::Result<OwnedFd> { |
| #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] |
| { |
| openat(crate::fs::cwd().as_fd(), filename, flags, mode) |
| } |
| #[cfg(all( |
| target_pointer_width = "32", |
| not(any(target_arch = "aarch64", target_arch = "riscv64")), |
| ))] |
| unsafe { |
| ret_owned_fd(syscall_readonly!(__NR_open, filename, flags, mode)) |
| } |
| #[cfg(all( |
| target_pointer_width = "64", |
| not(any(target_arch = "aarch64", target_arch = "riscv64")), |
| ))] |
| unsafe { |
| ret_owned_fd(syscall_readonly!(__NR_open, filename, flags, mode)) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn openat( |
| dirfd: BorrowedFd<'_>, |
| filename: &CStr, |
| flags: OFlags, |
| mode: Mode, |
| ) -> io::Result<OwnedFd> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_owned_fd(syscall_readonly!(__NR_openat, dirfd, filename, flags, mode)) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_owned_fd(syscall_readonly!(__NR_openat, dirfd, filename, flags, mode)) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn openat2( |
| dirfd: BorrowedFd<'_>, |
| pathname: &CStr, |
| flags: OFlags, |
| mode: Mode, |
| resolve: ResolveFlags, |
| ) -> io::Result<OwnedFd> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_owned_fd(syscall_readonly!( |
| __NR_openat2, |
| dirfd, |
| pathname, |
| by_ref(&open_how { |
| flags: oflags_for_open_how(flags), |
| mode: u64::from(mode.bits()), |
| resolve: resolve.bits(), |
| }), |
| size_of::<open_how, _>() |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_owned_fd(syscall_readonly!( |
| __NR_openat2, |
| dirfd, |
| pathname, |
| by_ref(&open_how { |
| flags: oflags_for_open_how(flags), |
| mode: u64::from(mode.bits()), |
| resolve: resolve.bits(), |
| }), |
| size_of::<open_how, _>() |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn chmod(filename: &CStr, mode: Mode) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fchmodat, |
| raw_fd(AT_FDCWD), |
| filename, |
| mode |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, filename: &CStr, mode: Mode) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_fchmodat, dirfd, filename, mode)) } |
| } |
| |
| #[inline] |
| pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_fchmod, fd, mode)) } |
| } |
| |
| #[inline] |
| pub(crate) fn chownat( |
| dirfd: BorrowedFd<'_>, |
| filename: &CStr, |
| owner: Option<Uid>, |
| group: Option<Gid>, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| unsafe { |
| let (ow, gr) = crate::process::translate_fchown_args(owner, group); |
| ret(syscall_readonly!( |
| __NR_fchownat, |
| dirfd, |
| filename, |
| c_uint(ow), |
| c_uint(gr), |
| flags |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> { |
| unsafe { |
| let (ow, gr) = crate::process::translate_fchown_args(owner, group); |
| ret(syscall_readonly!(__NR_fchown, fd, c_uint(ow), c_uint(gr))) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn mknodat( |
| dirfd: BorrowedFd<'_>, |
| filename: &CStr, |
| file_type: FileType, |
| mode: Mode, |
| dev: u64, |
| ) -> io::Result<()> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_mknodat, |
| dirfd, |
| filename, |
| (mode, file_type), |
| dev_t(dev)? |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_mknodat, |
| dirfd, |
| filename, |
| (mode, file_type), |
| dev_t(dev) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> { |
| let (whence, offset) = match pos { |
| SeekFrom::Start(pos) => { |
| let pos: u64 = pos; |
| // Silently cast; we'll get `EINVAL` if the value is negative. |
| (SEEK_SET, pos as i64) |
| } |
| SeekFrom::End(offset) => (SEEK_END, offset), |
| SeekFrom::Current(offset) => (SEEK_CUR, offset), |
| }; |
| _seek(fd, offset, whence) |
| } |
| |
| #[inline] |
| pub(crate) fn _seek(fd: BorrowedFd<'_>, offset: i64, whence: c::c_uint) -> io::Result<u64> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| let mut result = MaybeUninit::<u64>::uninit(); |
| ret(syscall!( |
| __NR__llseek, |
| fd, |
| // Don't use the hi/lo functions here because Linux's llseek |
| // takes its 64-bit argument differently from everything else. |
| pass_usize((offset >> 32) as usize), |
| pass_usize(offset as usize), |
| &mut result, |
| c_uint(whence) |
| ))?; |
| Ok(result.assume_init()) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_u64(syscall_readonly!( |
| __NR_lseek, |
| fd, |
| loff_t(offset), |
| c_uint(whence) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> { |
| _seek(fd, 0, SEEK_CUR).map(|x| x as u64) |
| } |
| |
| #[inline] |
| pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> { |
| // <https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/arch/arm64/kernel/sys32.c#L81-L83> |
| #[cfg(all( |
| target_pointer_width = "32", |
| any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc"), |
| ))] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_ftruncate64, |
| fd, |
| zero(), |
| hi(length), |
| lo(length) |
| )) |
| } |
| #[cfg(all( |
| target_pointer_width = "32", |
| not(any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc")), |
| ))] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_ftruncate64, |
| fd, |
| hi(length), |
| lo(length) |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_ftruncate, |
| fd, |
| loff_t_from_u64(length) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fallocate( |
| fd: BorrowedFd<'_>, |
| mode: FallocateFlags, |
| offset: u64, |
| len: u64, |
| ) -> io::Result<()> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fallocate, |
| fd, |
| mode, |
| hi(offset), |
| lo(offset), |
| hi(len), |
| lo(len) |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fallocate, |
| fd, |
| mode, |
| loff_t_from_u64(offset), |
| loff_t_from_u64(len) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fadvise(fd: BorrowedFd<'_>, pos: u64, len: u64, advice: Advice) -> io::Result<()> { |
| // On ARM, the arguments are reordered so that the len and pos argument |
| // pairs are aligned. And ARM has a custom syscall code for this. |
| #[cfg(target_arch = "arm")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_arm_fadvise64_64, |
| fd, |
| advice, |
| hi(pos), |
| lo(pos), |
| hi(len), |
| lo(len) |
| )) |
| } |
| |
| // On powerpc, the arguments are reordered as on ARM. |
| #[cfg(target_arch = "powerpc")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fadvise64_64, |
| fd, |
| advice, |
| hi(pos), |
| lo(pos), |
| hi(len), |
| lo(len) |
| )) |
| } |
| // On mips, the arguments are not reordered, and padding is inserted |
| // instead to ensure alignment. |
| #[cfg(target_arch = "mips")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fadvise64, |
| fd, |
| zero(), |
| hi(pos), |
| lo(pos), |
| hi(len), |
| lo(len), |
| advice |
| )) |
| } |
| #[cfg(all( |
| target_pointer_width = "32", |
| not(any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc")), |
| ))] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fadvise64_64, |
| fd, |
| hi(pos), |
| lo(pos), |
| hi(len), |
| lo(len), |
| advice |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fadvise64, |
| fd, |
| loff_t_from_u64(pos), |
| loff_t_from_u64(len), |
| advice |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_fsync, fd)) } |
| } |
| |
| #[inline] |
| pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_fdatasync, fd)) } |
| } |
| |
| #[inline] |
| pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> { |
| unsafe { ret(syscall!(__NR_flock, fd, c_uint(operation as c::c_uint))) } |
| } |
| |
| #[inline] |
| pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> { |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| { |
| match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { |
| Ok(x) => statx_to_stat(x), |
| Err(io::Errno::NOSYS) => fstat_old(fd), |
| Err(err) => Err(err), |
| } |
| } |
| |
| #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] |
| unsafe { |
| let mut result = MaybeUninit::<Stat>::uninit(); |
| ret(syscall!(__NR_fstat, fd, &mut result))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> { |
| let mut result = MaybeUninit::<linux_stat64>::uninit(); |
| |
| #[cfg(target_arch = "mips64")] |
| unsafe { |
| ret(syscall!(__NR_fstat, fd, &mut result))?; |
| stat_to_stat(result.assume_init()) |
| } |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall!(__NR_fstat64, fd, &mut result))?; |
| stat_to_stat(result.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn stat(filename: &CStr) -> io::Result<Stat> { |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| { |
| match statx( |
| crate::fs::cwd().as_fd(), |
| filename, |
| AtFlags::empty(), |
| StatxFlags::BASIC_STATS, |
| ) { |
| Ok(x) => statx_to_stat(x), |
| Err(io::Errno::NOSYS) => stat_old(filename), |
| Err(err) => Err(err), |
| } |
| } |
| |
| #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] |
| unsafe { |
| let mut result = MaybeUninit::<Stat>::uninit(); |
| ret(syscall!( |
| __NR_newfstatat, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(0) |
| ))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| fn stat_old(filename: &CStr) -> io::Result<Stat> { |
| let mut result = MaybeUninit::<linux_stat64>::uninit(); |
| |
| #[cfg(target_arch = "mips64")] |
| unsafe { |
| ret(syscall!( |
| __NR_newfstatat, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(0) |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall!( |
| __NR_fstatat64, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(0) |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn statat(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Result<Stat> { |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| { |
| match statx(dirfd, filename, flags, StatxFlags::BASIC_STATS) { |
| Ok(x) => statx_to_stat(x), |
| Err(io::Errno::NOSYS) => statat_old(dirfd, filename, flags), |
| Err(err) => Err(err), |
| } |
| } |
| |
| #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] |
| unsafe { |
| let mut result = MaybeUninit::<Stat>::uninit(); |
| ret(syscall!( |
| __NR_newfstatat, |
| dirfd, |
| filename, |
| &mut result, |
| flags |
| ))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| fn statat_old(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Result<Stat> { |
| let mut result = MaybeUninit::<linux_stat64>::uninit(); |
| |
| #[cfg(target_arch = "mips64")] |
| unsafe { |
| ret(syscall!( |
| __NR_newfstatat, |
| dirfd, |
| filename, |
| &mut result, |
| flags |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall!( |
| __NR_fstatat64, |
| dirfd, |
| filename, |
| &mut result, |
| flags |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn lstat(filename: &CStr) -> io::Result<Stat> { |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| { |
| match statx( |
| crate::fs::cwd().as_fd(), |
| filename, |
| AtFlags::SYMLINK_NOFOLLOW, |
| StatxFlags::BASIC_STATS, |
| ) { |
| Ok(x) => statx_to_stat(x), |
| Err(io::Errno::NOSYS) => lstat_old(filename), |
| Err(err) => Err(err), |
| } |
| } |
| |
| #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))] |
| unsafe { |
| let mut result = MaybeUninit::<Stat>::uninit(); |
| ret(syscall!( |
| __NR_newfstatat, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(AT_SYMLINK_NOFOLLOW) |
| ))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| fn lstat_old(filename: &CStr) -> io::Result<Stat> { |
| let mut result = MaybeUninit::<linux_stat64>::uninit(); |
| |
| #[cfg(target_arch = "mips64")] |
| unsafe { |
| ret(syscall!( |
| __NR_newfstatat, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(AT_SYMLINK_NOFOLLOW) |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall!( |
| __NR_fstatat64, |
| raw_fd(AT_FDCWD), |
| filename, |
| &mut result, |
| c_uint(AT_SYMLINK_NOFOLLOW) |
| ))?; |
| stat_to_stat(result.assume_init()) |
| } |
| } |
| |
| /// Convert from a Linux `statx` value to rustix's `Stat`. |
| #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] |
| fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> { |
| Ok(Stat { |
| st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor), |
| st_mode: x.stx_mode.into(), |
| st_nlink: x.stx_nlink.into(), |
| st_uid: x.stx_uid.into(), |
| st_gid: x.stx_gid.into(), |
| st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor), |
| st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_blksize: x.stx_blksize.into(), |
| st_blocks: x.stx_blocks.into(), |
| st_atime: x |
| .stx_atime |
| .tv_sec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_atime_nsec: x.stx_atime.tv_nsec.into(), |
| st_mtime: x |
| .stx_mtime |
| .tv_sec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_mtime_nsec: x.stx_mtime.tv_nsec.into(), |
| st_ctime: x |
| .stx_ctime |
| .tv_sec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_ctime_nsec: x.stx_ctime.tv_nsec.into(), |
| st_ino: x.stx_ino.into(), |
| }) |
| } |
| |
| /// Convert from a Linux `stat64` value to rustix's `Stat`. |
| #[cfg(target_pointer_width = "32")] |
| fn stat_to_stat(s64: linux_raw_sys::general::stat64) -> io::Result<Stat> { |
| Ok(Stat { |
| st_dev: s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_mode: s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_nlink: s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_uid: s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_gid: s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_rdev: s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_atime: s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_atime_nsec: s64 |
| .st_atime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_mtime: s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_mtime_nsec: s64 |
| .st_mtime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_ctime: s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_ctime_nsec: s64 |
| .st_ctime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_ino: s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| }) |
| } |
| |
| /// Convert from a Linux `stat` value to rustix's `Stat`. |
| #[cfg(target_arch = "mips64")] |
| fn stat_to_stat(s: linux_raw_sys::general::stat) -> io::Result<Stat> { |
| Ok(Stat { |
| st_dev: s.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_mode: s.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_nlink: s.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_uid: s.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_gid: s.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_rdev: s.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_size: s.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_blksize: s.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_blocks: s.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_atime: s.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_atime_nsec: s |
| .st_atime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_mtime: s.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_mtime_nsec: s |
| .st_mtime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_ctime: s.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| st_ctime_nsec: s |
| .st_ctime_nsec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| st_ino: s.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?, |
| }) |
| } |
| |
| #[inline] |
| pub(crate) fn statx( |
| dirfd: BorrowedFd<'_>, |
| pathname: &CStr, |
| flags: AtFlags, |
| mask: StatxFlags, |
| ) -> io::Result<statx> { |
| // If a future Linux kernel adds more fields to `struct statx` and users |
| // passing flags unknown to rustix in `StatxFlags`, we could end up |
| // writing outside of the buffer. To prevent this possibility, we mask off |
| // any flags that we don't know about. |
| // |
| // This includes `STATX__RESERVED`, which has a value that we know, but |
| // which could take on arbitrary new meaning in the future. Linux currently |
| // rejects this flag with `EINVAL`, so we do the same. |
| // |
| // This doesn't rely on `STATX_ALL` because [it's deprecated] and already |
| // doesn't represent all the known flags. |
| // |
| // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/[email protected]/ |
| if (mask.bits() & STATX__RESERVED) == STATX__RESERVED { |
| return Err(io::Errno::INVAL); |
| } |
| let mask = mask & StatxFlags::all(); |
| |
| unsafe { |
| let mut statx_buf = MaybeUninit::<statx>::uninit(); |
| ret(syscall!( |
| __NR_statx, |
| dirfd, |
| pathname, |
| flags, |
| mask, |
| &mut statx_buf |
| ))?; |
| Ok(statx_buf.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn is_statx_available() -> bool { |
| unsafe { |
| // Call `statx` with null pointers so that if it fails for any reason |
| // other than `EFAULT`, we know it's not supported. |
| matches!( |
| ret(syscall!( |
| __NR_statx, |
| raw_fd(AT_FDCWD), |
| zero(), |
| zero(), |
| zero(), |
| zero() |
| )), |
| Err(io::Errno::FAULT) |
| ) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| let mut result = MaybeUninit::<StatFs>::uninit(); |
| ret(syscall!( |
| __NR_fstatfs64, |
| fd, |
| size_of::<StatFs, _>(), |
| &mut result |
| ))?; |
| Ok(result.assume_init()) |
| } |
| |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| let mut result = MaybeUninit::<StatFs>::uninit(); |
| ret(syscall!(__NR_fstatfs, fd, &mut result))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> { |
| // Linux doesn't have an `fstatvfs` syscall; we have to do `fstatfs` and |
| // translate the fields as best we can. |
| let statfs = fstatfs(fd)?; |
| |
| Ok(statfs_to_statvfs(statfs)) |
| } |
| |
| #[inline] |
| pub(crate) fn statfs(filename: &CStr) -> io::Result<StatFs> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| let mut result = MaybeUninit::<StatFs>::uninit(); |
| ret(syscall!( |
| __NR_statfs64, |
| filename, |
| size_of::<StatFs, _>(), |
| &mut result |
| ))?; |
| Ok(result.assume_init()) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| let mut result = MaybeUninit::<StatFs>::uninit(); |
| ret(syscall!(__NR_statfs, filename, &mut result))?; |
| Ok(result.assume_init()) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn statvfs(filename: &CStr) -> io::Result<StatVfs> { |
| // Linux doesn't have a `statvfs` syscall; we have to do `statfs` and |
| // translate the fields as best we can. |
| let statfs = statfs(filename)?; |
| |
| Ok(statfs_to_statvfs(statfs)) |
| } |
| |
| fn statfs_to_statvfs(statfs: StatFs) -> StatVfs { |
| let __kernel_fsid_t { val } = statfs.f_fsid; |
| let [f_fsid_val0, f_fsid_val1]: [i32; 2] = val; |
| |
| StatVfs { |
| f_bsize: statfs.f_bsize as u64, |
| f_frsize: if statfs.f_frsize != 0 { |
| statfs.f_frsize |
| } else { |
| statfs.f_bsize |
| } as u64, |
| f_blocks: statfs.f_blocks as u64, |
| f_bfree: statfs.f_bfree as u64, |
| f_bavail: statfs.f_bavail as u64, |
| f_files: statfs.f_files as u64, |
| f_ffree: statfs.f_ffree as u64, |
| f_favail: statfs.f_ffree as u64, |
| f_fsid: f_fsid_val0 as u32 as u64 | ((f_fsid_val1 as u32 as u64) << 32), |
| f_flag: unsafe { StatVfsMountFlags::from_bits_unchecked(statfs.f_flags as u64) }, |
| f_namemax: statfs.f_namelen as u64, |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> { |
| let (buf_addr_mut, buf_len) = slice_mut(buf); |
| unsafe { |
| ret_usize(syscall!( |
| __NR_readlinkat, |
| raw_fd(AT_FDCWD), |
| path, |
| buf_addr_mut, |
| buf_len |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, buf: &mut [u8]) -> io::Result<usize> { |
| let (buf_addr_mut, buf_len) = slice_mut(buf); |
| unsafe { |
| ret_usize(syscall!( |
| __NR_readlinkat, |
| dirfd, |
| path, |
| buf_addr_mut, |
| buf_len |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_c_uint(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETFL))) |
| .map(OFlags::from_bits_truncate) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_c_uint(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETFL))) |
| .map(OFlags::from_bits_truncate) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_SETFL), flags)) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!(__NR_fcntl, fd, c_uint(F_SETFL), flags)) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_getlease(fd: BorrowedFd<'_>) -> io::Result<c::c_int> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETLEASE))) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETLEASE))) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_getown(fd: BorrowedFd<'_>) -> io::Result<c::c_int> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETOWN))) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETOWN))) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_getsig(fd: BorrowedFd<'_>) -> io::Result<c::c_int> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETSIG))) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETSIG))) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_getpipe_sz(fd: BorrowedFd<'_>) -> io::Result<usize> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_usize(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETPIPE_SZ))) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_usize(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETPIPE_SZ))) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_setpipe_sz(fd: BorrowedFd<'_>, size: c::c_int) -> io::Result<usize> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_usize(syscall_readonly!( |
| __NR_fcntl64, |
| fd, |
| c_uint(F_SETPIPE_SZ), |
| c_int(size) |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_usize(syscall_readonly!( |
| __NR_fcntl, |
| fd, |
| c_uint(F_SETPIPE_SZ), |
| c_int(size) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GET_SEALS))) |
| .map(|seals| SealFlags::from_bits_unchecked(seals as u32)) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GET_SEALS))) |
| .map(|seals| SealFlags::from_bits_unchecked(seals as u32)) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fcntl64, |
| fd, |
| c_uint(F_ADD_SEALS), |
| seals |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_fcntl, |
| fd, |
| c_uint(F_ADD_SEALS), |
| seals |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn rename(oldname: &CStr, newname: &CStr) -> io::Result<()> { |
| #[cfg(target_arch = "riscv64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_renameat2, |
| raw_fd(AT_FDCWD), |
| oldname, |
| raw_fd(AT_FDCWD), |
| newname, |
| c_uint(0) |
| )) |
| } |
| #[cfg(not(target_arch = "riscv64"))] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_renameat, |
| raw_fd(AT_FDCWD), |
| oldname, |
| raw_fd(AT_FDCWD), |
| newname |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn renameat( |
| old_dirfd: BorrowedFd<'_>, |
| oldname: &CStr, |
| new_dirfd: BorrowedFd<'_>, |
| newname: &CStr, |
| ) -> io::Result<()> { |
| #[cfg(target_arch = "riscv64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_renameat2, |
| old_dirfd, |
| oldname, |
| new_dirfd, |
| newname, |
| c_uint(0) |
| )) |
| } |
| #[cfg(not(target_arch = "riscv64"))] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_renameat, |
| old_dirfd, |
| oldname, |
| new_dirfd, |
| newname |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn renameat2( |
| old_dirfd: BorrowedFd<'_>, |
| oldname: &CStr, |
| new_dirfd: BorrowedFd<'_>, |
| newname: &CStr, |
| flags: RenameFlags, |
| ) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_renameat2, |
| old_dirfd, |
| oldname, |
| new_dirfd, |
| newname, |
| flags |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn unlink(pathname: &CStr) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_unlinkat, |
| raw_fd(AT_FDCWD), |
| pathname, |
| c_uint(0) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, pathname: &CStr, flags: AtFlags) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_unlinkat, dirfd, pathname, flags)) } |
| } |
| |
| #[inline] |
| pub(crate) fn rmdir(pathname: &CStr) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_unlinkat, |
| raw_fd(AT_FDCWD), |
| pathname, |
| c_uint(AT_REMOVEDIR) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn link(oldname: &CStr, newname: &CStr) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_linkat, |
| raw_fd(AT_FDCWD), |
| oldname, |
| raw_fd(AT_FDCWD), |
| newname, |
| c_uint(0) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn linkat( |
| old_dirfd: BorrowedFd<'_>, |
| oldname: &CStr, |
| new_dirfd: BorrowedFd<'_>, |
| newname: &CStr, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_linkat, |
| old_dirfd, |
| oldname, |
| new_dirfd, |
| newname, |
| flags |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn symlink(oldname: &CStr, newname: &CStr) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_symlinkat, |
| oldname, |
| raw_fd(AT_FDCWD), |
| newname |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn symlinkat(oldname: &CStr, dirfd: BorrowedFd<'_>, newname: &CStr) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_symlinkat, oldname, dirfd, newname)) } |
| } |
| |
| #[inline] |
| pub(crate) fn mkdir(pathname: &CStr, mode: Mode) -> io::Result<()> { |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_mkdirat, |
| raw_fd(AT_FDCWD), |
| pathname, |
| mode |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, pathname: &CStr, mode: Mode) -> io::Result<()> { |
| unsafe { ret(syscall_readonly!(__NR_mkdirat, dirfd, pathname, mode)) } |
| } |
| |
| #[inline] |
| pub(crate) fn getdents(fd: BorrowedFd<'_>, dirent: &mut [u8]) -> io::Result<usize> { |
| let (dirent_addr_mut, dirent_len) = slice_mut(dirent); |
| |
| unsafe { ret_usize(syscall!(__NR_getdents64, fd, dirent_addr_mut, dirent_len)) } |
| } |
| |
| #[inline] |
| pub(crate) fn utimensat( |
| dirfd: BorrowedFd<'_>, |
| pathname: &CStr, |
| times: &Timestamps, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| _utimensat(dirfd, Some(pathname), times, flags) |
| } |
| |
| #[inline] |
| fn _utimensat( |
| dirfd: BorrowedFd<'_>, |
| pathname: Option<&CStr>, |
| times: &Timestamps, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| // Assert that `Timestamps` has the expected layout. |
| let _ = unsafe { core::mem::transmute::<Timestamps, [__kernel_timespec; 2]>(times.clone()) }; |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| match ret(syscall_readonly!( |
| __NR_utimensat_time64, |
| dirfd, |
| pathname, |
| by_ref(times), |
| flags |
| )) { |
| Err(io::Errno::NOSYS) => _utimensat_old(dirfd, pathname, times, flags), |
| otherwise => otherwise, |
| } |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret(syscall_readonly!( |
| __NR_utimensat, |
| dirfd, |
| pathname, |
| by_ref(times), |
| flags |
| )) |
| } |
| } |
| |
| #[cfg(target_pointer_width = "32")] |
| unsafe fn _utimensat_old( |
| dirfd: BorrowedFd<'_>, |
| pathname: Option<&CStr>, |
| times: &Timestamps, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| // See the comments in `rustix_clock_gettime_via_syscall` about |
| // emulation. |
| let old_times = [ |
| __kernel_old_timespec { |
| tv_sec: times |
| .last_access |
| .tv_sec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| tv_nsec: times |
| .last_access |
| .tv_nsec |
| .try_into() |
| .map_err(|_| io::Errno::INVAL)?, |
| }, |
| __kernel_old_timespec { |
| tv_sec: times |
| .last_modification |
| .tv_sec |
| .try_into() |
| .map_err(|_| io::Errno::OVERFLOW)?, |
| tv_nsec: times |
| .last_modification |
| .tv_nsec |
| .try_into() |
| .map_err(|_| io::Errno::INVAL)?, |
| }, |
| ]; |
| // The length of the array is fixed and not passed into the syscall. |
| let old_times_addr = slice_just_addr(&old_times); |
| ret(syscall_readonly!( |
| __NR_utimensat, |
| dirfd, |
| pathname, |
| old_times_addr, |
| flags |
| )) |
| } |
| |
| #[inline] |
| pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> { |
| _utimensat(fd, None, times, AtFlags::empty()) |
| } |
| |
| pub(crate) fn accessat( |
| dirfd: BorrowedFd<'_>, |
| path: &CStr, |
| access: Access, |
| flags: AtFlags, |
| ) -> io::Result<()> { |
| // Linux's `faccessat` doesn't have a flags parameter. If we have |
| // `AT_EACCESS` and we're not setuid or setgid, we can emulate it. |
| if flags.is_empty() |
| || (flags.bits() == AT_EACCESS |
| && crate::process::getuid() == crate::process::geteuid() |
| && crate::process::getgid() == crate::process::getegid()) |
| { |
| return unsafe { ret(syscall_readonly!(__NR_faccessat, dirfd, path, access)) }; |
| } |
| |
| if flags.bits() != AT_EACCESS { |
| return Err(io::Errno::INVAL); |
| } |
| |
| // TODO: Use faccessat2 in newer Linux versions. |
| Err(io::Errno::NOSYS) |
| } |
| |
| #[inline] |
| pub(crate) fn copy_file_range( |
| fd_in: BorrowedFd<'_>, |
| off_in: Option<&mut u64>, |
| fd_out: BorrowedFd<'_>, |
| off_out: Option<&mut u64>, |
| len: u64, |
| ) -> io::Result<u64> { |
| let len: usize = len.try_into().unwrap_or(usize::MAX); |
| _copy_file_range(fd_in, off_in, fd_out, off_out, len, 0).map(|result| result as u64) |
| } |
| |
| #[inline] |
| fn _copy_file_range( |
| fd_in: BorrowedFd<'_>, |
| off_in: Option<&mut u64>, |
| fd_out: BorrowedFd<'_>, |
| off_out: Option<&mut u64>, |
| len: usize, |
| flags: c::c_uint, |
| ) -> io::Result<usize> { |
| unsafe { |
| ret_usize(syscall!( |
| __NR_copy_file_range, |
| fd_in, |
| opt_mut(off_in), |
| fd_out, |
| opt_mut(off_out), |
| pass_usize(len), |
| c_uint(flags) |
| )) |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> { |
| unsafe { ret_owned_fd(syscall_readonly!(__NR_memfd_create, name, flags)) } |
| } |
| |
| #[inline] |
| pub(crate) fn sendfile( |
| out_fd: BorrowedFd<'_>, |
| in_fd: BorrowedFd<'_>, |
| offset: Option<&mut u64>, |
| count: usize, |
| ) -> io::Result<usize> { |
| #[cfg(target_pointer_width = "32")] |
| unsafe { |
| ret_usize(syscall!( |
| __NR_sendfile64, |
| out_fd, |
| in_fd, |
| opt_mut(offset), |
| pass_usize(count) |
| )) |
| } |
| #[cfg(target_pointer_width = "64")] |
| unsafe { |
| ret_usize(syscall!( |
| __NR_sendfile, |
| out_fd, |
| in_fd, |
| opt_mut(offset), |
| pass_usize(count) |
| )) |
| } |
| } |