use bitflags::bitflags;
use std::ffi::{CStr, CString};
use std::io::{Error, Result};
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
fn check_retval<T: From<i8> + PartialEq>(t: T) -> Result<T> {
if t == T::from(-1_i8) {
Err(Error::last_os_error())
} else {
Ok(t)
}
}
pub struct OsFacts {
pub has_openat2: bool,
}
#[allow(clippy::new_without_default)]
impl OsFacts {
#[must_use]
pub fn new() -> Self {
let how: libc::open_how = unsafe { std::mem::zeroed() };
let cwd = CString::new(".").unwrap();
let fd = unsafe {
libc::syscall(
libc::SYS_openat2,
libc::AT_FDCWD,
cwd.as_ptr(),
std::ptr::addr_of!(how),
std::mem::size_of::<libc::open_how>(),
)
};
let has_openat2 = fd >= 0;
if has_openat2 {
unsafe {
libc::close(fd as libc::c_int);
}
}
Self { has_openat2 }
}
}
pub fn mount(source: Option<&str>, target: &str, fstype: Option<&str>, flags: u64) -> Result<()> {
let source = CString::new(source.unwrap_or("")).unwrap();
let source = source.as_ptr();
let target = CString::new(target).unwrap();
let target = target.as_ptr();
let fstype = CString::new(fstype.unwrap_or("")).unwrap();
let fstype = fstype.as_ptr();
check_retval(unsafe { libc::mount(source, target, fstype, flags, std::ptr::null()) })?;
Ok(())
}
pub fn umount2(target: &str, flags: i32) -> Result<()> {
let target = CString::new(target).unwrap();
let target = target.as_ptr();
check_retval(unsafe { libc::umount2(target, flags) })?;
Ok(())
}
pub fn fchdir(fd: RawFd) -> Result<()> {
check_retval(unsafe { libc::fchdir(fd) })?;
Ok(())
}
pub fn umask(mask: u32) -> u32 {
unsafe { libc::umask(mask) }
}
pub struct ScopedUmask {
umask: libc::mode_t,
}
impl ScopedUmask {
pub fn new(new_umask: u32) -> Self {
Self {
umask: umask(new_umask),
}
}
}
impl Drop for ScopedUmask {
fn drop(&mut self) {
umask(self.umask);
}
}
pub fn openat(dir: &impl AsRawFd, pathname: &CStr, flags: i32, mode: Option<u32>) -> Result<RawFd> {
let mode = u64::from(mode.unwrap_or(0));
check_retval(unsafe {
libc::openat(
dir.as_raw_fd(),
pathname.as_ptr(),
flags as libc::c_int,
mode,
)
})
}
pub fn do_open_relative_to(
dir: &impl AsRawFd,
pathname: &CStr,
flags: i32,
mode: Option<u32>,
) -> Result<RawFd> {
let mode = u64::from(mode.unwrap_or(0)) & 0o7777;
let mut how: libc::open_how = unsafe { std::mem::zeroed() };
how.resolve = libc::RESOLVE_IN_ROOT | libc::RESOLVE_NO_MAGICLINKS;
how.flags = flags as u64;
how.mode = mode;
check_retval(unsafe {
libc::syscall(
libc::SYS_openat2,
dir.as_raw_fd(),
pathname.as_ptr(),
std::ptr::addr_of!(how),
std::mem::size_of::<libc::open_how>(),
)
} as RawFd)
}
mod writev {
#[cfg(target_env = "gnu")]
pub use libc::pwritev2;
#[cfg(target_env = "musl")]
pub unsafe fn pwritev2(
fd: libc::c_int,
iov: *const libc::iovec,
iovcnt: libc::c_int,
offset: libc::off_t,
flags: libc::c_int,
) -> libc::ssize_t {
let lo_off = offset as libc::c_long; let hi_off = (offset as u64).checked_shr(libc::c_long::BITS).unwrap_or(0) as libc::c_long;
unsafe {
libc::syscall(libc::SYS_pwritev2, fd, iov, iovcnt, lo_off, hi_off, flags)
as libc::ssize_t
}
}
}
bitflags! {
pub struct WritevFlags: i32 {
const RWF_HIPRI = 0x00000001;
const RWF_DSYNC = 0x00000002;
const RWF_SYNC = 0x00000004;
const RWF_APPEND = 0x00000010;
}
}
#[cfg(target_env = "gnu")]
mod writev_test {
const _: () = assert!(
super::WritevFlags::RWF_HIPRI.bits() == libc::RWF_HIPRI,
"invalid RWF_HIPRI value"
);
const _: () = assert!(
super::WritevFlags::RWF_DSYNC.bits() == libc::RWF_DSYNC,
"invalid RWF_DSYNC value"
);
const _: () = assert!(
super::WritevFlags::RWF_SYNC.bits() == libc::RWF_SYNC,
"invalid RWF_SYNC value"
);
const _: () = assert!(
super::WritevFlags::RWF_APPEND.bits() == libc::RWF_APPEND,
"invalid RWF_APPEND value"
);
}
pub fn writev_at(
fd: BorrowedFd,
iovecs: &[libc::iovec],
offset: i64,
flags: Option<WritevFlags>,
) -> Result<usize> {
let flags = flags.unwrap_or(WritevFlags::empty());
let bytes_written = check_retval(unsafe {
writev::pwritev2(
fd.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
flags.bits(),
)
})?;
Ok(bytes_written as usize)
}