#![cfg(any(target_os = "linux", target_os = "android"))]
use rustix::fd::{BorrowedFd, IntoRawFd, OwnedFd};
use rustix::io::Errno;
use crate::{Fd, Mode};
use bun_core::ZStr;
#[inline(always)]
unsafe fn bfd<'a>(fd: i32) -> BorrowedFd<'a> {
unsafe { BorrowedFd::borrow_raw(fd) }
}
#[inline(always)]
fn raw(e: Errno) -> i32 {
e.raw_os_error()
}
#[inline(always)]
fn errno() -> i32 {
bun_core::ffi::errno()
}
#[inline(always)]
fn retry<T>(mut f: impl FnMut() -> rustix::io::Result<T>) -> Result<T, i32> {
loop {
match f() {
Ok(v) => return Ok(v),
Err(Errno::INTR) => continue,
Err(e) => return Err(raw(e)),
}
}
}
#[inline(always)]
#[cfg(target_os = "linux")]
fn once<T>(r: rustix::io::Result<T>) -> Result<T, i32> {
r.map_err(raw)
}
#[inline]
pub(crate) fn openat(dir: Fd, path: &ZStr, flags: i32, mode: Mode) -> Result<Fd, i32> {
let oflags = rustix::fs::OFlags::from_bits_retain(flags as u32);
let mode = rustix::fs::Mode::from_raw_mode(mode);
let dir = dir.as_borrowed_fd();
retry(|| rustix::fs::openat(dir, path.as_cstr(), oflags, mode)).map(own_fd)
}
#[inline]
pub(crate) fn openat2_beneath(dir: Fd, path: &ZStr, flags: i32, mode: Mode) -> Result<Fd, i32> {
let oflags = rustix::fs::OFlags::from_bits_retain(flags as u32);
let mode = rustix::fs::Mode::from_raw_mode(mode);
let dir = dir.as_borrowed_fd();
retry(|| {
rustix::fs::openat2(
dir,
path.as_cstr(),
oflags,
mode,
rustix::fs::ResolveFlags::BENEATH,
)
})
.map(own_fd)
}
#[inline]
pub(crate) fn read(fd: Fd, buf: &mut [u8]) -> Result<usize, i32> {
let fd = fd.as_borrowed_fd();
retry(|| rustix::io::read(fd, buf))
}
#[inline]
pub(crate) fn write(fd: Fd, buf: &[u8]) -> Result<usize, i32> {
let fd = fd.as_borrowed_fd();
retry(|| rustix::io::write(fd, buf))
}
#[inline]
pub(crate) fn pread(fd: Fd, buf: &mut [u8], off: i64) -> Result<usize, i32> {
let fd = fd.as_borrowed_fd();
retry(|| rustix::io::pread(fd, buf, off as u64))
}
#[inline]
pub(crate) fn pwrite(fd: Fd, buf: &[u8], off: i64) -> Result<usize, i32> {
let fd = fd.as_borrowed_fd();
retry(|| rustix::io::pwrite(fd, buf, off as u64))
}
#[inline]
pub(crate) fn close(fd: i32) -> Result<(), i32> {
let rc = unsafe { libc::syscall(libc::SYS_close, fd) };
if rc == 0 { Ok(()) } else { Err(errno()) }
}
#[inline]
pub(crate) fn fstat(fd: Fd) -> Result<libc::stat, i32> {
let fd = fd.as_borrowed_fd();
retry(|| rustix::fs::fstat(fd)).map(stat_to_libc)
}
#[inline]
pub(crate) fn stat(path: &ZStr) -> Result<libc::stat, i32> {
retry(|| rustix::fs::stat(path.as_cstr())).map(stat_to_libc)
}
#[inline]
pub(crate) fn lstat(path: &ZStr) -> Result<libc::stat, i32> {
retry(|| rustix::fs::lstat(path.as_cstr())).map(stat_to_libc)
}
#[inline]
pub(crate) fn fstatat(dir: i32, path: &ZStr, flags: i32) -> Result<libc::stat, i32> {
let dir = unsafe { bfd(dir) };
let at = rustix::fs::AtFlags::from_bits_retain(flags as u32);
retry(|| rustix::fs::statat(dir, path.as_cstr(), at)).map(stat_to_libc)
}
#[inline(always)]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn stat_to_libc(s: rustix::fs::Stat) -> libc::stat {
const _: () = assert!(
core::mem::size_of::<rustix::fs::Stat>() == core::mem::size_of::<libc::stat>()
&& core::mem::align_of::<rustix::fs::Stat>() == core::mem::align_of::<libc::stat>(),
"rustix::fs::Stat / libc::stat layout mismatch on this target — \
drop it from the cfg above so it takes the field-copy fallback",
);
unsafe { core::mem::transmute::<rustix::fs::Stat, libc::stat>(s) }
}
#[inline]
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
fn stat_to_libc(s: rustix::fs::Stat) -> libc::stat {
let mut out: libc::stat = bun_core::ffi::zeroed();
out.st_dev = s.st_dev as _;
out.st_ino = s.st_ino as _;
out.st_nlink = s.st_nlink as _;
out.st_mode = s.st_mode as _;
out.st_uid = s.st_uid as _;
out.st_gid = s.st_gid as _;
out.st_rdev = s.st_rdev as _;
out.st_size = s.st_size as _;
out.st_blksize = s.st_blksize as _;
out.st_blocks = s.st_blocks as _;
out.st_atime = s.st_atime as _;
out.st_atime_nsec = s.st_atime_nsec as _;
out.st_mtime = s.st_mtime as _;
out.st_mtime_nsec = s.st_mtime_nsec as _;
out.st_ctime = s.st_ctime as _;
out.st_ctime_nsec = s.st_ctime_nsec as _;
out
}
#[inline(always)]
fn sys_retry(mut f: impl FnMut() -> libc::c_long) -> Result<usize, i32> {
loop {
let rc = f();
if rc >= 0 {
return Ok(rc as usize);
}
let e = errno();
if e == libc::EINTR {
continue;
}
return Err(e);
}
}
#[inline]
pub(crate) unsafe fn readv(fd: Fd, vecs: *const libc::iovec, n: usize) -> Result<usize, i32> {
sys_retry(|| {
unsafe { libc::syscall(libc::SYS_readv, fd.native(), vecs, n as libc::c_long) }
})
}
#[inline]
pub(crate) unsafe fn writev(fd: Fd, vecs: *const libc::iovec, n: usize) -> Result<usize, i32> {
sys_retry(|| {
unsafe { libc::syscall(libc::SYS_writev, fd.native(), vecs, n as libc::c_long) }
})
}
#[inline]
pub(crate) unsafe fn preadv(
fd: Fd,
vecs: *const libc::iovec,
n: usize,
off: i64,
) -> Result<usize, i32> {
let lo = off as libc::c_long;
let hi = ((off as u64) >> 32) as libc::c_long;
sys_retry(|| {
unsafe {
libc::syscall(
libc::SYS_preadv,
fd.native(),
vecs,
n as libc::c_long,
lo,
hi,
)
}
})
}
#[inline]
pub(crate) unsafe fn pwritev(
fd: Fd,
vecs: *const libc::iovec,
n: usize,
off: i64,
) -> Result<usize, i32> {
let lo = off as libc::c_long;
let hi = ((off as u64) >> 32) as libc::c_long;
sys_retry(|| {
unsafe {
libc::syscall(
libc::SYS_pwritev,
fd.native(),
vecs,
n as libc::c_long,
lo,
hi,
)
}
})
}
#[inline]
pub(crate) unsafe fn read_raw(fd: i32, buf: *mut u8, count: usize) -> isize {
unsafe { libc::syscall(libc::SYS_read, fd, buf, count) as isize }
}
#[inline]
pub(crate) unsafe fn write_raw(fd: i32, buf: *const u8, count: usize) -> isize {
unsafe { libc::syscall(libc::SYS_write, fd, buf, count) as isize }
}
#[inline]
pub(crate) unsafe fn epoll_ctl(epfd: i32, op: i32, fd: i32, event: *mut libc::epoll_event) -> i32 {
unsafe { libc::syscall(libc::SYS_epoll_ctl, epfd, op, fd, event) as i32 }
}
#[inline]
pub(crate) unsafe fn sendfile(out_fd: i32, in_fd: i32, offset: *mut i64, count: usize) -> isize {
#[cfg(target_arch = "aarch64")]
const SYS_SENDFILE: libc::c_long = 71;
#[cfg(not(target_arch = "aarch64"))]
const SYS_SENDFILE: libc::c_long = libc::SYS_sendfile;
unsafe { libc::syscall(SYS_SENDFILE, out_fd, in_fd, offset, count) as isize }
}
#[inline]
pub(crate) unsafe fn copy_file_range(
in_: i32,
off_in: *mut i64,
out: i32,
off_out: *mut i64,
len: usize,
flags: u32,
) -> isize {
unsafe {
libc::syscall(
libc::SYS_copy_file_range,
in_,
off_in,
out,
off_out,
len,
flags as libc::c_long,
) as isize
}
}
#[inline]
#[cfg(target_os = "linux")]
pub(crate) fn pidfd_open(pid: i32, flags: u32) -> Result<Fd, i32> {
let pid = rustix::process::Pid::from_raw(pid).ok_or(libc::EINVAL)?;
let flags = rustix::process::PidfdFlags::from_bits_retain(flags);
once(rustix::process::pidfd_open(pid, flags)).map(own_fd)
}
#[inline]
#[cfg(target_os = "android")]
pub(crate) fn pidfd_open(pid: i32, flags: u32) -> Result<Fd, i32> {
if pid <= 0 {
return Err(libc::EINVAL);
}
const SYS_PIDFD_OPEN: libc::c_long = 434;
let rc = unsafe { libc::syscall(SYS_PIDFD_OPEN, pid, flags) };
if rc < 0 {
return Err(errno());
}
Ok(Fd::from_native(rc as i32))
}
#[inline]
pub(crate) unsafe fn getdents64(fd: i32, buf: *mut u8, len: usize) -> isize {
unsafe { libc::syscall(libc::SYS_getdents64, fd as libc::c_long, buf, len) as isize }
}
#[inline(always)]
fn own_fd(fd: OwnedFd) -> Fd {
Fd::from_native(fd.into_raw_fd())
}