Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  syscalls.rs

  Sprache: Rust
 

//! libc syscalls supporting `rustix::fs`.

use crate::backend::c;
#[cfg(any(
    not(target_os = "redox"),
    feature = "alloc",
    all(linux_kernel, feature = "procfs")
))]
use crate::backend::conv::ret_usize;
use crate::backend::conv::{borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd};
use crate::fd::{BorrowedFd, OwnedFd};
use crate::ffi::CStr;
#[cfg(all(apple, feature = "alloc"))]
use crate::ffi::CString;
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
use crate::fs::Access;
#[cfg(not(any(
    apple,
    netbsdlike,
    solarish,
    target_os = "dragonfly",
    target_os = "espidf",
    target_os = "haiku",
    target_os = "redox",
    target_os = "vita",
)))]
use crate::fs::Advice;
#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
use crate::fs::AtFlags;
#[cfg(not(any(
    netbsdlike,
    solarish,
    target_os = "dragonfly",
    target_os = "espidf",
    target_os = "nto",
    target_os = "redox",
    target_os = "vita",
)))]
use crate::fs::FallocateFlags;
#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
use crate::fs::FlockOperation;
#[cfg(any(linux_kernel, target_os = "freebsd"))]
use crate::fs::MemfdFlags;
#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
use crate::fs::SealFlags;
#[cfg(not(any(
    solarish,
    target_os = "espidf",
    target_os = "haiku",
    target_os = "netbsd",
    target_os = "nto",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi",
)))]
use crate::fs::StatFs;
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
use crate::fs::Timestamps;
#[cfg(not(any(
    apple,
    target_os = "espidf",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi"
)))]
use crate::fs::{Dev, FileType};
use crate::fs::{Mode, OFlags, SeekFrom, Stat};
#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
use crate::fs::{StatVfs, StatVfsMountFlags};
use crate::io;
#[cfg(all(target_env = "gnu", fix_y2038))]
use crate::timespec::LibcTimespec;
#[cfg(not(target_os = "wasi"))]
use crate::ugid::{Gid, Uid};
#[cfg(all(apple, feature = "alloc"))]
use alloc::vec;
use core::mem::MaybeUninit;
#[cfg(apple)]
use {
    crate::backend::conv::nonnegative_ret,
    crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags},
};
#[cfg(any(apple, linux_kernel))]
use {crate::fs::XattrFlags, core::mem::size_of, core::ptr::null_mut};
#[cfg(linux_kernel)]
use {
    crate::fs::{RenameFlags, ResolveFlags, Statx, StatxFlags, CWD},
    core::ptr::null,
};

#[cfg(all(target_env = "gnu", fix_y2038))]
weak!(fn __utimensat64(c::c_int, *const c::c_char, *const LibcTimespec, c::c_int) -> c::c_int);
#[cfg(all(target_env = "gnu", fix_y2038))]
weak!(fn __futimens64(c::c_int, *const LibcTimespec) -> c::c_int);

/// Use a direct syscall (via libc) for `open`.
///
/// This is only currently necessary as a workaround for old glibc; see below.
#[cfg(all(unix, target_env = "gnu"))]
fn open_via_syscall(path: &CStr, oflags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
    // Linux on aarch64, loongarch64 and riscv64 has no `open` syscall so use
    // `openat`.
    #[cfg(any(
        target_arch = "aarch64",
        target_arch = "riscv32",
        target_arch = "riscv64",
        target_arch = "csky",
        target_arch = "loongarch64"
    ))]
    {
        openat_via_syscall(CWD, path, oflags, mode)
    }

    // Use the `open` syscall.
    #[cfg(not(any(
        target_arch = "aarch64",
        target_arch = "riscv32",
        target_arch = "riscv64",
        target_arch = "csky",
        target_arch = "loongarch64"
    )))]
    unsafe {
        syscall! {
            fn open(
                pathname: *const c::c_char,
                oflags: c::c_int,
                mode: c::mode_t
            ) via SYS_open -> c::c_int
        }

        ret_owned_fd(open(
            c_str(path),
            bitflags_bits!(oflags),
            bitflags_bits!(mode),
        ))
    }
}

pub(cratefn open(path: &CStr, oflags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
    // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
    // glibc versions before 2.25 don't handle `O_TMPFILE` correctly.
    #[cfg(all(
        unix,
        target_env = "gnu",
        not(target_os = "hurd"),
        not(target_os = "freebsd")
    ))]
    if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() {
        return open_via_syscall(path, oflags, mode);
    }

    // On these platforms, `mode_t` is `u16` and can't be passed directly to a
    // variadic function.
    #[cfg(any(
        apple,
        freebsdlike,
        all(target_os = "android", target_pointer_width = "32")
    ))]
    let mode: c::c_uint = mode.bits().into();

    // Otherwise, cast to `mode_t` as that's what `open` is documented to take.
    #[cfg(not(any(
        apple,
        freebsdlike,
        all(target_os = "android", target_pointer_width = "32")
    )))]
    let mode: c::mode_t = mode.bits() as _;

    unsafe { ret_owned_fd(c::open(c_str(path), bitflags_bits!(oflags), mode)) }
}

/// Use a direct syscall (via libc) for `openat`.
///
/// This is only currently necessary as a workaround for old glibc; see below.
#[cfg(all(unix, target_env = "gnu", not(target_os = "hurd")))]
fn openat_via_syscall(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    oflags: OFlags,
    mode: Mode,
) -> io::Result<OwnedFd> {
    syscall! {
        fn openat(
            base_dirfd: c::c_int,
            pathname: *const c::c_char,
            oflags: c::c_int,
            mode: c::mode_t
        ) via SYS_openat -> c::c_int
    }

    unsafe {
        ret_owned_fd(openat(
            borrowed_fd(dirfd),
            c_str(path),
            bitflags_bits!(oflags),
            bitflags_bits!(mode),
        ))
    }
}

#[cfg(not(target_os = "redox"))]
pub(cratefn openat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    oflags: OFlags,
    mode: Mode,
) -> io::Result<OwnedFd> {
    // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
    // glibc versions before 2.25 don't handle `O_TMPFILE` correctly.
    #[cfg(all(
        unix,
        target_env = "gnu",
        not(target_os = "hurd"),
        not(target_os = "freebsd")
    ))]
    if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() {
        return openat_via_syscall(dirfd, path, oflags, mode);
    }

    // On these platforms, `mode_t` is `u16` and can't be passed directly to a
    // variadic function.
    #[cfg(any(
        apple,
        freebsdlike,
        all(target_os = "android", target_pointer_width = "32")
    ))]
    let mode: c::c_uint = mode.bits().into();

    // Otherwise, cast to `mode_t` as that's what `open` is documented to take.
    #[cfg(not(any(
        apple,
        freebsdlike,
        all(target_os = "android", target_pointer_width = "32")
    )))]
    let mode: c::mode_t = mode.bits() as _;

    unsafe {
        ret_owned_fd(c::openat(
            borrowed_fd(dirfd),
            c_str(path),
            bitflags_bits!(oflags),
            mode,
        ))
    }
}

#[cfg(not(any(
    solarish,
    target_os = "espidf",
    target_os = "haiku",
    target_os = "netbsd",
    target_os = "nto",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi",
)))]
#[inline]
pub(cratefn statfs(filename: &CStr) -> io::Result<StatFs> {
    unsafe {
        let mut result = MaybeUninit::<StatFs>::uninit();
        ret(c::statfs(c_str(filename), result.as_mut_ptr()))?;
        Ok(result.assume_init())
    }
}

#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
#[inline]
pub(cratefn statvfs(filename: &CStr) -> io::Result<StatVfs> {
    unsafe {
        let mut result = MaybeUninit::<c::statvfs>::uninit();
        ret(c::statvfs(c_str(filename), result.as_mut_ptr()))?;
        Ok(libc_statvfs_to_statvfs(result.assume_init()))
    }
}

#[cfg(feature = "alloc")]
#[inline]
pub(cratefn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
    unsafe {
        ret_usize(
            c::readlink(c_str(path), buf.as_mut_ptr().cast::<c::c_char>(), buf.len()) as isize,
        )
    }
}

#[cfg(not(target_os = "redox"))]
#[inline]
pub(cratefn readlinkat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    buf: &mut [MaybeUninit<u8>],
) -> io::Result<usize> {
    unsafe {
        ret_usize(c::readlinkat(
            borrowed_fd(dirfd),
            c_str(path),
            buf.as_mut_ptr().cast::<c::c_char>(),
            buf.len(),
        ) as isize)
    }
}

pub(cratefn mkdir(path: &CStr, mode: Mode) -> io::Result<()> {
    unsafe { ret(c::mkdir(c_str(path), mode.bits() as c::mode_t)) }
}

#[cfg(not(target_os = "redox"))]
pub(cratefn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
    unsafe {
        ret(c::mkdirat(
            borrowed_fd(dirfd),
            c_str(path),
            mode.bits() as c::mode_t,
        ))
    }
}

#[cfg(linux_kernel)]
pub(cratefn getdents_uninit(
    fd: BorrowedFd<'_>,
    buf: &mut [MaybeUninit<u8>],
) -> io::Result<usize> {
    syscall! {
        fn getdents64(
            fd: c::c_int,
            dirp: *mut c::c_void,
            count: usize
        ) via SYS_getdents64 -> c::ssize_t
    }
    unsafe {
        ret_usize(getdents64(
            borrowed_fd(fd),
            buf.as_mut_ptr().cast::<c::c_void>(),
            buf.len(),
        ))
    }
}

pub(cratefn link(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
    unsafe { ret(c::link(c_str(old_path), c_str(new_path))) }
}

#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
pub(cratefn linkat(
    old_dirfd: BorrowedFd<'_>,
    old_path: &CStr,
    new_dirfd: BorrowedFd<'_>,
    new_path: &CStr,
    flags: AtFlags,
) -> io::Result<()> {
    // macOS <= 10.9 lacks `linkat`.
    #[cfg(target_os = "macos")]
    unsafe {
        weak! {
            fn linkat(
                c::c_int,
                *const c::c_char,
                c::c_int,
                *const c::c_char,
                c::c_int
            ) -> c::c_int
        }
        // If we have `linkat`, use it.
        if let Some(libc_linkat) = linkat.get() {
            return ret(libc_linkat(
                borrowed_fd(old_dirfd),
                c_str(old_path),
                borrowed_fd(new_dirfd),
                c_str(new_path),
                bitflags_bits!(flags),
            ));
        }
        // Otherwise, see if we can emulate the `AT_FDCWD` case.
        if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
            return Err(io::Errno::NOSYS);
        }
        if flags.intersects(!AtFlags::SYMLINK_FOLLOW) {
            return Err(io::Errno::INVAL);
        }
        if !flags.is_empty() {
            return Err(io::Errno::OPNOTSUPP);
        }
        ret(c::link(c_str(old_path), c_str(new_path)))
    }

    #[cfg(not(target_os = "macos"))]
    unsafe {
        ret(c::linkat(
            borrowed_fd(old_dirfd),
            c_str(old_path),
            borrowed_fd(new_dirfd),
            c_str(new_path),
            bitflags_bits!(flags),
        ))
    }
}

pub(cratefn rmdir(path: &CStr) -> io::Result<()> {
    unsafe { ret(c::rmdir(c_str(path))) }
}

pub(cratefn unlink(path: &CStr) -> io::Result<()> {
    unsafe { ret(c::unlink(c_str(path))) }
}

#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
pub(cratefn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
    // macOS <= 10.9 lacks `unlinkat`.
    #[cfg(target_os = "macos")]
    unsafe {
        weak! {
            fn unlinkat(
                c::c_int,
                *const c::c_char,
                c::c_int
            ) -> c::c_int
        }
        // If we have `unlinkat`, use it.
        if let Some(libc_unlinkat) = unlinkat.get() {
            return ret(libc_unlinkat(
                borrowed_fd(dirfd),
                c_str(path),
                bitflags_bits!(flags),
            ));
        }
        // Otherwise, see if we can emulate the `AT_FDCWD` case.
        if borrowed_fd(dirfd) != c::AT_FDCWD {
            return Err(io::Errno::NOSYS);
        }
        if flags.intersects(!AtFlags::REMOVEDIR) {
            return Err(io::Errno::INVAL);
        }
        if flags.contains(AtFlags::REMOVEDIR) {
            ret(c::rmdir(c_str(path)))
        } else {
            ret(c::unlink(c_str(path)))
        }
    }

    #[cfg(not(target_os = "macos"))]
    unsafe {
        ret(c::unlinkat(
            borrowed_fd(dirfd),
            c_str(path),
            bitflags_bits!(flags),
        ))
    }
}

pub(cratefn rename(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
    unsafe { ret(c::rename(c_str(old_path), c_str(new_path))) }
}

#[cfg(not(target_os = "redox"))]
pub(cratefn renameat(
    old_dirfd: BorrowedFd<'_>,
    old_path: &CStr,
    new_dirfd: BorrowedFd<'_>,
    new_path: &CStr,
) -> io::Result<()> {
    // macOS <= 10.9 lacks `renameat`.
    #[cfg(target_os = "macos")]
    unsafe {
        weak! {
            fn renameat(
                c::c_int,
                *const c::c_char,
                c::c_int,
                *const c::c_char
            ) -> c::c_int
        }
        // If we have `renameat`, use it.
        if let Some(libc_renameat) = renameat.get() {
            return ret(libc_renameat(
                borrowed_fd(old_dirfd),
                c_str(old_path),
                borrowed_fd(new_dirfd),
                c_str(new_path),
            ));
        }
        // Otherwise, see if we can emulate the `AT_FDCWD` case.
        if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
            return Err(io::Errno::NOSYS);
        }
        ret(c::rename(c_str(old_path), c_str(new_path)))
    }

    #[cfg(not(target_os = "macos"))]
    unsafe {
        ret(c::renameat(
            borrowed_fd(old_dirfd),
            c_str(old_path),
            borrowed_fd(new_dirfd),
            c_str(new_path),
        ))
    }
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]
pub(cratefn renameat2(
    old_dirfd: BorrowedFd<'_>,
    old_path: &CStr,
    new_dirfd: BorrowedFd<'_>,
    new_path: &CStr,
    flags: RenameFlags,
) -> io::Result<()> {
    // `renameat2` wasn't supported in glibc until 2.28.
    weak_or_syscall! {
        fn renameat2(
            olddirfd: c::c_int,
            oldpath: *const c::c_char,
            newdirfd: c::c_int,
            newpath: *const c::c_char,
            flags: c::c_uint
        ) via SYS_renameat2 -> c::c_int
    }

    unsafe {
        ret(renameat2(
            borrowed_fd(old_dirfd),
            c_str(old_path),
            borrowed_fd(new_dirfd),
            c_str(new_path),
            flags.bits(),
        ))
    }
}

#[cfg(any(
    target_os = "android",
    all(target_os = "linux", not(target_env = "gnu")),
))]
#[inline]
pub(cratefn renameat2(
    old_dirfd: BorrowedFd<'_>,
    old_path: &CStr,
    new_dirfd: BorrowedFd<'_>,
    new_path: &CStr,
    flags: RenameFlags,
) -> io::Result<()> {
    // At present, `libc` only has `renameat2` defined for glibc. If we have
    // no flags, we can use plain `renameat`, but otherwise we use `syscall!`.
    // to call `renameat2` ourselves.
    if flags.is_empty() {
        renameat(old_dirfd, old_path, new_dirfd, new_path)
    } else {
        syscall! {
            fn renameat2(
                olddirfd: c::c_int,
                oldpath: *const c::c_char,
                newdirfd: c::c_int,
                newpath: *const c::c_char,
                flags: c::c_uint
            ) via SYS_renameat2 -> c::c_int
        }

        unsafe {
            ret(renameat2(
                borrowed_fd(old_dirfd),
                c_str(old_path),
                borrowed_fd(new_dirfd),
                c_str(new_path),
                flags.bits(),
            ))
        }
    }
}

pub(cratefn symlink(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
    unsafe { ret(c::symlink(c_str(old_path), c_str(new_path))) }
}

#[cfg(not(target_os = "redox"))]
pub(cratefn symlinkat(
    old_path: &CStr,
    new_dirfd: BorrowedFd<'_>,
    new_path: &CStr,
) -> io::Result<()> {
    unsafe {
        ret(c::symlinkat(
            c_str(old_path),
            borrowed_fd(new_dirfd),
            c_str(new_path),
        ))
    }
}

pub(cratefn stat(path: &CStr) -> io::Result<Stat> {
    // See the comments in `fstat` about using `crate::fs::statx` here.
    #[cfg(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    ))]
    {
        match crate::fs::statx(
            crate::fs::CWD,
            path,
            AtFlags::empty(),
            StatxFlags::BASIC_STATS,
        ) {
            Ok(x) => statx_to_stat(x),
            Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::empty()),
            Err(err) => Err(err),
        }
    }

    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
    // there's nothing practical we can do.
    #[cfg(not(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    )))]
    unsafe {
        let mut stat = MaybeUninit::<Stat>::uninit();
        ret(c::stat(c_str(path), stat.as_mut_ptr()))?;
        Ok(stat.assume_init())
    }
}

pub(cratefn lstat(path: &CStr) -> io::Result<Stat> {
    // See the comments in `fstat` about using `crate::fs::statx` here.
    #[cfg(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    ))]
    {
        match crate::fs::statx(
            crate::fs::CWD,
            path,
            AtFlags::SYMLINK_NOFOLLOW,
            StatxFlags::BASIC_STATS,
        ) {
            Ok(x) => statx_to_stat(x),
            Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::SYMLINK_NOFOLLOW),
            Err(err) => Err(err),
        }
    }

    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
    // there's nothing practical we can do.
    #[cfg(not(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    )))]
    unsafe {
        let mut stat = MaybeUninit::<Stat>::uninit();
        ret(c::lstat(c_str(path), stat.as_mut_ptr()))?;
        Ok(stat.assume_init())
    }
}

#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
pub(cratefn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
    // See the comments in `fstat` about using `crate::fs::statx` here.
    #[cfg(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    ))]
    {
        match crate::fs::statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
            Ok(x) => statx_to_stat(x),
            Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
            Err(err) => Err(err),
        }
    }

    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
    // there's nothing practical we can do.
    #[cfg(not(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    )))]
    unsafe {
        let mut stat = MaybeUninit::<Stat>::uninit();
        ret(c::fstatat(
            borrowed_fd(dirfd),
            c_str(path),
            stat.as_mut_ptr(),
            bitflags_bits!(flags),
        ))?;
        Ok(stat.assume_init())
    }
}

#[cfg(all(
    linux_kernel,
    any(
        target_pointer_width = "32",
        target_arch = "mips64",
        target_arch = "mips64r6"
    )
))]
fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
    unsafe {
        let mut result = MaybeUninit::<c::stat64>::uninit();
        ret(c::fstatat(
            borrowed_fd(dirfd),
            c_str(path),
            result.as_mut_ptr(),
            bitflags_bits!(flags),
        ))?;
        stat64_to_stat(result.assume_init())
    }
}

#[cfg(not(any(target_os = "espidf", target_os = "emscripten", target_os = "vita")))]
pub(cratefn access(path: &CStr, access: Access) -> io::Result<()> {
    unsafe { ret(c::access(c_str(path), access.bits())) }
}

#[cfg(not(any(
    target_os = "emscripten",
    target_os = "espidf",
    target_os = "redox",
    target_os = "vita"
)))]
pub(cratefn accessat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    access: Access,
    flags: AtFlags,
) -> io::Result<()> {
    // macOS <= 10.9 lacks `faccessat`.
    #[cfg(target_os = "macos")]
    unsafe {
        weak! {
            fn faccessat(
                c::c_int,
                *const c::c_char,
                c::c_int,
                c::c_int
            ) -> c::c_int
        }
        // If we have `faccessat`, use it.
        if let Some(libc_faccessat) = faccessat.get() {
            return ret(libc_faccessat(
                borrowed_fd(dirfd),
                c_str(path),
                bitflags_bits!(access),
                bitflags_bits!(flags),
            ));
        }
        // Otherwise, see if we can emulate the `AT_FDCWD` case.
        if borrowed_fd(dirfd) != c::AT_FDCWD {
            return Err(io::Errno::NOSYS);
        }
        if flags.intersects(!(AtFlags::EACCESS | AtFlags::SYMLINK_NOFOLLOW)) {
            return Err(io::Errno::INVAL);
        }
        if !flags.is_empty() {
            return Err(io::Errno::OPNOTSUPP);
        }
        ret(c::access(c_str(path), bitflags_bits!(access)))
    }

    #[cfg(not(target_os = "macos"))]
    unsafe {
        ret(c::faccessat(
            borrowed_fd(dirfd),
            c_str(path),
            bitflags_bits!(access),
            bitflags_bits!(flags),
        ))
    }
}

#[cfg(target_os = "emscripten")]
pub(cratefn access(_path: &CStr, _access: Access) -> io::Result<()> {
    Ok(())
}

#[cfg(target_os = "emscripten")]
pub(cratefn accessat(
    _dirfd: BorrowedFd<'_>,
    _path: &CStr,
    _access: Access,
    _flags: AtFlags,
) -> io::Result<()> {
    Ok(())
}

#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
pub(cratefn utimensat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    times: &Timestamps,
    flags: AtFlags,
) -> io::Result<()> {
    // Old 32-bit version: libc has `utimensat` but it is not y2038 safe by
    // default. But there may be a `__utimensat64` we can use.
    #[cfg(all(fix_y2038, not(apple)))]
    {
        #[cfg(target_env = "gnu")]
        if let Some(libc_utimensat) = __utimensat64.get() {
            let libc_times: [LibcTimespec; 2] = [
                times.last_access.clone().into(),
                times.last_modification.clone().into(),
            ];

            unsafe {
                return ret(libc_utimensat(
                    borrowed_fd(dirfd),
                    c_str(path),
                    libc_times.as_ptr(),
                    bitflags_bits!(flags),
                ));
            }
        }

        utimensat_old(dirfd, path, times, flags)
    }

    // Main version: libc is y2038 safe and has `utimensat`. Or, the platform
    // is not y2038 safe and there's nothing practical we can do.
    #[cfg(not(any(apple, fix_y2038)))]
    unsafe {
        use crate::utils::as_ptr;

        ret(c::utimensat(
            borrowed_fd(dirfd),
            c_str(path),
            as_ptr(times).cast(),
            bitflags_bits!(flags),
        ))
    }

    // Apple version: `utimensat` was introduced in macOS 10.13.
    #[cfg(apple)]
    unsafe {
        use crate::utils::as_ptr;

        // ABI details
        weak! {
            fn utimensat(
                c::c_int,
                *const c::c_char,
                *const c::timespec,
                c::c_int
            ) -> c::c_int
        }
        extern "C" {
            fn setattrlist(
                path: *const c::c_char,
                attr_list: *const Attrlist,
                attr_buf: *const c::c_void,
                attr_buf_size: c::size_t,
                options: c::c_ulong,
            ) -> c::c_int;
        }
        const FSOPT_NOFOLLOW: c::c_ulong = 0x0000_0001;

        // If we have `utimensat`, use it.
        if let Some(have_utimensat) = utimensat.get() {
            return ret(have_utimensat(
                borrowed_fd(dirfd),
                c_str(path),
                as_ptr(times).cast(),
                bitflags_bits!(flags),
            ));
        }

        // Convert `times`. We only need this in the child, but do it before
        // calling `fork` because it might fail.
        let (attrbuf_size, times, attrs) = times_to_attrlist(times)?;

        // `setattrlistat` was introduced in 10.13 along with `utimensat`, so
        // if we don't have `utimensat`, we don't have `setattrlistat` either.
        // Emulate it using `fork`, and `fchdir` and [`setattrlist`].
        //
        // [`setattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setattrlist.2.html
        match c::fork() {
            -1 => Err(io::Errno::IO),
            0 => {
                if c::fchdir(borrowed_fd(dirfd)) != 0 {
                    let code = match libc_errno::errno().0 {
                        c::EACCES => 2,
                        c::ENOTDIR => 3,
                        _ => 1,
                    };
                    c::_exit(code);
                }

                let mut flags_arg = 0;
                if flags.contains(AtFlags::SYMLINK_NOFOLLOW) {
                    flags_arg |= FSOPT_NOFOLLOW;
                }

                if setattrlist(
                    c_str(path),
                    &attrs,
                    as_ptr(×).cast(),
                    attrbuf_size,
                    flags_arg,
                ) != 0
                {
                    // Translate expected `errno` codes into ad-hoc integer
                    // values suitable for exit statuses.
                    let code = match libc_errno::errno().0 {
                        c::EACCES => 2,
                        c::ENOTDIR => 3,
                        c::EPERM => 4,
                        c::EROFS => 5,
                        c::ELOOP => 6,
                        c::ENOENT => 7,
                        c::ENAMETOOLONG => 8,
                        c::EINVAL => 9,
                        c::ESRCH => 10,
                        c::ENOTSUP => 11,
                        _ => 1,
                    };
                    c::_exit(code);
                }

                c::_exit(0);
            }
            child_pid => {
                let mut wstatus = 0;
                let _ = ret_c_int(c::waitpid(child_pid, &mut wstatus, 0))?;
                if c::WIFEXITED(wstatus) {
                    // Translate our ad-hoc exit statuses back to `errno`
                    // codes.
                    match c::WEXITSTATUS(wstatus) {
                        0 => Ok(()),
                        2 => Err(io::Errno::ACCESS),
                        3 => Err(io::Errno::NOTDIR),
                        4 => Err(io::Errno::PERM),
                        5 => Err(io::Errno::ROFS),
                        6 => Err(io::Errno::LOOP),
                        7 => Err(io::Errno::NOENT),
                        8 => Err(io::Errno::NAMETOOLONG),
                        9 => Err(io::Errno::INVAL),
                        10 => Err(io::Errno::SRCH),
                        11 => Err(io::Errno::NOTSUP),
                        _ => Err(io::Errno::IO),
                    }
                } else {
                    Err(io::Errno::IO)
                }
            }
        }
    }
}

#[cfg(all(fix_y2038, not(apple)))]
fn utimensat_old(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    times: &Timestamps,
    flags: AtFlags,
) -> io::Result<()> {
    let old_times = [
        c::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::OVERFLOW)?,
        },
        c::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::OVERFLOW)?,
        },
    ];
    unsafe {
        ret(c::utimensat(
            borrowed_fd(dirfd),
            c_str(path),
            old_times.as_ptr(),
            bitflags_bits!(flags),
        ))
    }
}

#[cfg(not(target_os = "wasi"))]
pub(cratefn chmod(path: &CStr, mode: Mode) -> io::Result<()> {
    unsafe { ret(c::chmod(c_str(path), mode.bits() as c::mode_t)) }
}

#[cfg(not(any(
    linux_kernel,
    target_os = "espidf",
    target_os = "redox",
    target_os = "wasi"
)))]
pub(cratefn chmodat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    mode: Mode,
    flags: AtFlags,
) -> io::Result<()> {
    unsafe {
        ret(c::fchmodat(
            borrowed_fd(dirfd),
            c_str(path),
            mode.bits() as c::mode_t,
            bitflags_bits!(flags),
        ))
    }
}

#[cfg(linux_kernel)]
pub(cratefn chmodat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    mode: Mode,
    flags: AtFlags,
) -> io::Result<()> {
    // Linux's `fchmodat` does not have a flags argument.
    //
    // Use `c::syscall` rather than `c::fchmodat` because some libc
    // implementations, such as musl, add extra logic to `fchmod` to emulate
    // support for `AT_SYMLINK_NOFOLLOW`, which uses `/proc` outside our
    // control.
    syscall! {
        fn fchmodat(
            base_dirfd: c::c_int,
            pathname: *const c::c_char,
            mode: c::mode_t
        ) via SYS_fchmodat -> c::c_int
    }
    if flags == AtFlags::SYMLINK_NOFOLLOW {
        return Err(io::Errno::OPNOTSUPP);
    }
    if !flags.is_empty() {
        return Err(io::Errno::INVAL);
    }
    unsafe {
        ret(fchmodat(
            borrowed_fd(dirfd),
            c_str(path),
            mode.bits() as c::mode_t,
        ))
    }
}

#[cfg(apple)]
pub(cratefn fclonefileat(
    srcfd: BorrowedFd<'_>,
    dst_dirfd: BorrowedFd<'_>,
    dst: &CStr,
    flags: CloneFlags,
) -> io::Result<()> {
    syscall! {
        fn fclonefileat(
            srcfd: BorrowedFd<'_>,
            dst_dirfd: BorrowedFd<'_>,
            dst: *const c::c_char,
            flags: c::c_int
        ) via SYS_fclonefileat -> c::c_int
    }

    unsafe {
        ret(fclonefileat(
            srcfd,
            dst_dirfd,
            c_str(dst),
            bitflags_bits!(flags),
        ))
    }
}

#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
pub(cratefn chownat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    owner: Option<Uid>,
    group: Option<Gid>,
    flags: AtFlags,
) -> io::Result<()> {
    unsafe {
        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
        ret(c::fchownat(
            borrowed_fd(dirfd),
            c_str(path),
            ow,
            gr,
            bitflags_bits!(flags),
        ))
    }
}

#[cfg(not(any(
    apple,
    target_os = "espidf",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi"
)))]
pub(cratefn mknodat(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    file_type: FileType,
    mode: Mode,
    dev: Dev,
) -> io::Result<()> {
    unsafe {
        ret(c::mknodat(
            borrowed_fd(dirfd),
            c_str(path),
            (mode.bits() | file_type.as_raw_mode()) as c::mode_t,
            dev.try_into().map_err(|_e| io::Errno::PERM)?,
        ))
    }
}

#[cfg(linux_kernel)]
pub(cratefn copy_file_range(
    fd_in: BorrowedFd<'_>,
    off_in: Option<&mut u64>,
    fd_out: BorrowedFd<'_>,
    off_out: Option<&mut u64>,
    len: usize,
) -> io::Result<usize> {
    syscall! {
        fn copy_file_range(
            fd_in: c::c_int,
            off_in: *mut c::loff_t,
            fd_out: c::c_int,
            off_out: *mut c::loff_t,
            len: usize,
            flags: c::c_uint
        ) via SYS_copy_file_range -> c::ssize_t
    }

    let mut off_in_val: c::loff_t = 0;
    let mut off_out_val: c::loff_t = 0;
    // Silently cast; we'll get `EINVAL` if the value is negative.
    let off_in_ptr = if let Some(off_in) = &off_in {
        off_in_val = **off_in as i64;
        &mut off_in_val
    } else {
        null_mut()
    };
    let off_out_ptr = if let Some(off_out) = &off_out {
        off_out_val = **off_out as i64;
        &mut off_out_val
    } else {
        null_mut()
    };
    let copied = unsafe {
        ret_usize(copy_file_range(
            borrowed_fd(fd_in),
            off_in_ptr,
            borrowed_fd(fd_out),
            off_out_ptr,
            len,
            0// no flags are defined yet
        ))?
    };
    if let Some(off_in) = off_in {
        *off_in = off_in_val as u64;
    }
    if let Some(off_out) = off_out {
        *off_out = off_out_val as u64;
    }
    Ok(copied)
}

#[cfg(not(any(
    apple,
    netbsdlike,
    solarish,
    target_os = "dragonfly",
    target_os = "espidf",
    target_os = "haiku",
    target_os = "redox",
    target_os = "vita",
)))]
pub(cratefn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
    let offset = offset as i64;
    let len = len as i64;

    // FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior.
    #[cfg(target_os = "freebsd")]
    let offset = if (offset as i64) < 0 {
        i64::MAX
    } else {
        offset
    };

    // FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior.
    #[cfg(target_os = "freebsd")]
    let len = if len > 0 && offset.checked_add(len).is_none() {
        i64::MAX - offset
    } else {
        len
    };

    let err = unsafe { c::posix_fadvise(borrowed_fd(fd), offset, len, advice as c::c_int) };

    // `posix_fadvise` returns its error status rather than using `errno`.
    if err == 0 {
        Ok(())
    } else {
        Err(io::Errno(err))
    }
}

pub(cratefn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
    let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFL))? };
    Ok(OFlags::from_bits_retain(bitcast!(flags)))
}

pub(cratefn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFL, flags.bits())) }
}

#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
pub(cratefn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
    let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GET_SEALS))? };
    Ok(SealFlags::from_bits_retain(bitcast!(flags)))
}

#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
pub(cratefn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
}

#[cfg(not(any(
    target_os = "emscripten",
    target_os = "espidf",
    target_os = "fuchsia",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi"
)))]
#[inline]
pub(cratefn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
    use c::{flock, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET};

    let (cmd, l_type) = match operation {
        FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
        FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
        FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
        FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
        FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
        FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
    };

    unsafe {
        let mut lock: flock = core::mem::zeroed();
        lock.l_type = l_type as _;

        // When `l_len` is zero, this locks all the bytes from
        // `l_whence`/`l_start` to the end of the file, even as the
        // file grows dynamically.
        lock.l_whence = SEEK_SET as _;
        lock.l_start = 0;
        lock.l_len = 0;

        ret(c::fcntl(borrowed_fd(fd), cmd, &lock))
    }
}

pub(cratefn 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.
            (c::SEEK_SET, pos as i64)
        }
        SeekFrom::End(offset) => (c::SEEK_END, offset),
        SeekFrom::Current(offset) => (c::SEEK_CUR, offset),
        #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
        SeekFrom::Data(offset) => (c::SEEK_DATA, offset),
        #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
        SeekFrom::Hole(offset) => (c::SEEK_HOLE, offset),
    };

    // ESP-IDF and Vita don't support 64-bit offsets.
    #[cfg(any(target_os = "espidf", target_os = "vita"))]
    let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;

    let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), offset, whence))? };
    Ok(offset as u64)
}

pub(cratefn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
    let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? };
    Ok(offset as u64)
}

#[cfg(not(any(linux_kernel, target_os = "wasi")))]
pub(cratefn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
    unsafe { ret(c::fchmod(borrowed_fd(fd), bitflags_bits!(mode))) }
}

#[cfg(linux_kernel)]
pub(cratefn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
    // Use `c::syscall` rather than `c::fchmod` because some libc
    // implementations, such as musl, add extra logic to `fchmod` to emulate
    // support for `O_PATH`, which uses `/proc` outside our control and
    // interferes with our own use of `O_PATH`.
    syscall! {
        fn fchmod(
            fd: c::c_int,
            mode: c::mode_t
        ) via SYS_fchmod -> c::c_int
    }
    unsafe { ret(fchmod(borrowed_fd(fd), mode.bits() as c::mode_t)) }
}

#[cfg(not(target_os = "wasi"))]
pub(cratefn chown(path: &CStr, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
    unsafe {
        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
        ret(c::chown(c_str(path), ow, gr))
    }
}

#[cfg(linux_kernel)]
pub(cratefn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
    // Use `c::syscall` rather than `c::fchown` because some libc
    // implementations, such as musl, add extra logic to `fchown` to emulate
    // support for `O_PATH`, which uses `/proc` outside our control and
    // interferes with our own use of `O_PATH`.
    syscall! {
        fn fchown(
            fd: c::c_int,
            owner: c::uid_t,
            group: c::gid_t
        ) via SYS_fchown -> c::c_int
    }
    unsafe {
        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
        ret(fchown(borrowed_fd(fd), ow, gr))
    }
}

#[cfg(not(any(linux_kernel, target_os = "wasi")))]
pub(cratefn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
    unsafe {
        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
        ret(c::fchown(borrowed_fd(fd), ow, gr))
    }
}

#[cfg(not(any(
    target_os = "espidf",
    target_os = "solaris",
    target_os = "vita",
    target_os = "wasi"
)))]
pub(cratefn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
    unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) }
}

#[cfg(linux_kernel)]
pub(cratefn syncfs(fd: BorrowedFd<'_>) -> io::Result<()> {
    // Some versions of Android libc lack a `syncfs` function.
    #[cfg(target_os = "android")]
    syscall! {
        fn syncfs(fd: c::c_int) via SYS_syncfs -> c::c_int
    }

    // `syncfs` was added to glibc in 2.20.
    #[cfg(not(target_os = "android"))]
    weak_or_syscall! {
        fn syncfs(fd: c::c_int) via SYS_syncfs -> c::c_int
    }

    unsafe { ret(syncfs(borrowed_fd(fd))) }
}

#[cfg(not(any(
    target_os = "espidf",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi"
)))]
pub(cratefn sync() {
    unsafe { c::sync() }
}

pub(cratefn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
    // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
    // `statx`.
    //
    // And, some old platforms don't support `statx`, and some fail with a
    // confusing error code, so we call `crate::fs::statx` to handle that. If
    // `statx` isn't available, fall back to the buggy system call.
    #[cfg(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    ))]
    {
        match crate::fs::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),
        }
    }

    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
    // there's nothing practical we can do.
    #[cfg(not(all(
        linux_kernel,
        any(
            target_pointer_width = "32",
            target_arch = "mips64",
            target_arch = "mips64r6"
        )
    )))]
    unsafe {
        let mut stat = MaybeUninit::<Stat>::uninit();
        ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
        Ok(stat.assume_init())
    }
}

#[cfg(all(
    linux_kernel,
    any(
        target_pointer_width = "32",
        target_arch = "mips64",
        target_arch = "mips64r6"
    )
))]
fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
    unsafe {
        let mut result = MaybeUninit::<c::stat64>::uninit();
        ret(c::fstat(borrowed_fd(fd), result.as_mut_ptr()))?;
        stat64_to_stat(result.assume_init())
    }
}

#[cfg(not(any(
    solarish,
    target_os = "espidf",
    target_os = "haiku",
    target_os = "netbsd",
    target_os = "nto",
    target_os = "redox",
    target_os = "vita",
    target_os = "wasi",
)))]
pub(cratefn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
    let mut statfs = MaybeUninit::<StatFs>::uninit();
    unsafe {
        ret(c::fstatfs(borrowed_fd(fd), statfs.as_mut_ptr()))?;
        Ok(statfs.assume_init())
    }
}

#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
pub(cratefn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> {
    let mut statvfs = MaybeUninit::<c::statvfs>::uninit();
    unsafe {
        ret(c::fstatvfs(borrowed_fd(fd), statvfs.as_mut_ptr()))?;
        Ok(libc_statvfs_to_statvfs(statvfs.assume_init()))
    }
}

#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
fn libc_statvfs_to_statvfs(from: c::statvfs) -> StatVfs {
    StatVfs {
        f_bsize: from.f_bsize as u64,
        f_frsize: from.f_frsize as u64,
        f_blocks: from.f_blocks as u64,
        f_bfree: from.f_bfree as u64,
        f_bavail: from.f_bavail as u64,
        f_files: from.f_files as u64,
        f_ffree: from.f_ffree as u64,
        f_favail: from.f_ffree as u64,
        #[cfg(not(target_os = "aix"))]
        f_fsid: from.f_fsid as u64,
        #[cfg(target_os = "aix")]
        f_fsid: ((from.f_fsid.val[0as u64) << 32) | from.f_fsid.val[1],
        f_flag: StatVfsMountFlags::from_bits_retain(from.f_flag as u64),
        f_namemax: from.f_namemax as u64,
    }
}

#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
pub(cratefn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
    // Old 32-bit version: libc has `futimens` but it is not y2038 safe by
    // default. But there may be a `__futimens64` we can use.
    #[cfg(all(fix_y2038, not(apple)))]
    {
        #[cfg(target_env = "gnu")]
        if let Some(libc_futimens) = __futimens64.get() {
            let libc_times: [LibcTimespec; 2] = [
                times.last_access.clone().into(),
                times.last_modification.clone().into(),
            ];

            unsafe {
                return ret(libc_futimens(borrowed_fd(fd), libc_times.as_ptr()));
            }
        }

        futimens_old(fd, times)
    }

    // Main version: libc is y2038 safe and has `futimens`. Or, the platform
    // is not y2038 safe and there's nothing practical we can do.
    #[cfg(not(any(apple, fix_y2038)))]
    unsafe {
        use crate::utils::as_ptr;

        ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast()))
    }

    // Apple version: `futimens` was introduced in macOS 10.13.
    #[cfg(apple)]
    unsafe {
        use crate::utils::as_ptr;

        // ABI details.
        weak! {
            fn futimens(c::c_int, *const c::timespec) -> c::c_int
        }
        extern "C" {
            fn fsetattrlist(
                fd: c::c_int,
                attr_list: *const Attrlist,
                attr_buf: *const c::c_void,
                attr_buf_size: c::size_t,
                options: c::c_ulong,
            ) -> c::c_int;
        }

        // If we have `futimens`, use it.
        if let Some(have_futimens) = futimens.get() {
            return ret(have_futimens(borrowed_fd(fd), as_ptr(times).cast()));
        }

        // Otherwise use `fsetattrlist`.
        let (attrbuf_size, times, attrs) = times_to_attrlist(times)?;

        ret(fsetattrlist(
            borrowed_fd(fd),
            &attrs,
            as_ptr(×).cast(),
            attrbuf_size,
            0,
        ))
    }
}

#[cfg(all(fix_y2038, not(apple)))]
fn futimens_old(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
    let old_times = [
        c::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::OVERFLOW)?,
        },
        c::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::OVERFLOW)?,
        },
    ];

    unsafe { ret(c::futimens(borrowed_fd(fd), old_times.as_ptr())) }
}

#[cfg(not(any(
    apple,
    netbsdlike,
    solarish,
    target_os = "dragonfly",
    target_os = "espidf",
    target_os = "nto",
    target_os = "redox",
    target_os = "vita",
)))]
pub(cratefn fallocate(
    fd: BorrowedFd<'_>,
    mode: FallocateFlags,
    offset: u64,
    len: u64,
) -> io::Result<()> {
    // Silently cast; we'll get `EINVAL` if the value is negative.
    let offset = offset as i64;
    let len = len as i64;

    #[cfg(any(linux_kernel, target_os = "fuchsia"))]
    unsafe {
        ret(c::fallocate(
            borrowed_fd(fd),
            bitflags_bits!(mode),
            offset,
            len,
        ))
    }

    #[cfg(not(any(linux_kernel, target_os = "fuchsia")))]
    {
        assert!(mode.is_empty());
        let err = unsafe { c::posix_fallocate(borrowed_fd(fd), offset, len) };

        // `posix_fallocate` returns its error status rather than using
        // `errno`.
        if err == 0 {
            Ok(())
        } else {
            Err(io::Errno(err))
        }
    }
}

#[cfg(apple)]
pub(cratefn fallocate(
    fd: BorrowedFd<'_>,
    mode: FallocateFlags,
    offset: u64,
    len: u64,
) -> io::Result<()> {
    let offset: i64 = offset.try_into().map_err(|_e| io::Errno::INVAL)?;
    let len = len as i64;

    assert!(mode.is_empty());

    let new_len = offset.checked_add(len).ok_or(io::Errno::FBIG)?;
    let mut store = c::fstore_t {
        fst_flags: c::F_ALLOCATECONTIG,
        fst_posmode: c::F_PEOFPOSMODE,
        fst_offset: 0,
        fst_length: new_len,
        fst_bytesalloc: 0,
    };
    unsafe {
        if c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store) == -1 {
            // Unable to allocate contiguous disk space; attempt to allocate
            // non-contiguously.
            store.fst_flags = c::F_ALLOCATEALL;
            let _ = ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?;
        }
        ret(c::ftruncate(borrowed_fd(fd), new_len))
    }
}

pub(cratefn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
    unsafe { ret(c::fsync(borrowed_fd(fd))) }
}

#[cfg(not(any(
    apple,
    target_os = "dragonfly",
    target_os = "espidf",
    target_os = "haiku",
    target_os = "redox",
    target_os = "vita",
)))]
pub(cratefn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
    unsafe { ret(c::fdatasync(borrowed_fd(fd))) }
}

pub(cratefn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
    let length = length.try_into().map_err(|_overflow_err| io::Errno::FBIG)?;
    unsafe { ret(c::ftruncate(borrowed_fd(fd), length)) }
}

#[cfg(any(linux_kernel, target_os = "freebsd"))]
pub(cratefn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> {
    #[cfg(target_os = "freebsd")]
    weakcall! {
        fn memfd_create(
            name: *const c::c_char,
            flags: c::c_uint
        ) -> c::c_int
    }

    #[cfg(linux_kernel)]
    weak_or_syscall! {
        fn memfd_create(
            name: *const c::c_char,
            flags: c::c_uint
        ) via SYS_memfd_create -> c::c_int
    }

    unsafe { ret_owned_fd(memfd_create(c_str(name), bitflags_bits!(flags))) }
}

#[cfg(linux_kernel)]
pub(cratefn openat2(
    dirfd: BorrowedFd<'_>,
    path: &CStr,
    oflags: OFlags,
    mode: Mode,
    resolve: ResolveFlags,
) -> io::Result<OwnedFd> {
    use linux_raw_sys::general::open_how;

    syscall! {
        fn openat2(
            base_dirfd: c::c_int,
            pathname: *const c::c_char,
            how: *mut open_how,
            size: usize
        ) via SYS_OPENAT2 -> c::c_int
    }

    let oflags = oflags.bits();
    let mut open_how = open_how {
        flags: u64::from(oflags),
        mode: u64::from(mode.bits()),
        resolve: resolve.bits(),
    };

    unsafe {
        ret_owned_fd(openat2(
            borrowed_fd(dirfd),
            c_str(path),
            &mut open_how,
            size_of::<open_how>(),
        ))
    }
}
#[cfg(all(linux_kernel, target_pointer_width = "32"))]
const SYS_OPENAT2: i32 = 437;
#[cfg(all(linux_kernel, target_pointer_width = "64"))]
const SYS_OPENAT2: i64 = 437;

#[cfg(target_os = "linux")]
pub(cratefn sendfile(
    out_fd: BorrowedFd<'_>,
    in_fd: BorrowedFd<'_>,
    offset: Option<&mut u64>,
    count: usize,
) -> io::Result<usize> {
    unsafe {
        ret_usize(c::sendfile64(
            borrowed_fd(out_fd),
            borrowed_fd(in_fd),
            offset.map_or(null_mut(), crate::utils::as_mut_ptr).cast(),
            count,
        ))
    }
}

/// Convert from a Linux `statx` value to rustix's `Stat`.
#[cfg(all(linux_kernel, target_pointer_width = "32"))]
#[allow(deprecated)] // for `st_[amc]time` u64->i64 transition
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).into(),
        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).into(),
        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: bitcast!(i64::from(x.stx_atime.tv_sec)),
        st_atime_nsec: x.stx_atime.tv_nsec as _,
        st_mtime: bitcast!(i64::from(x.stx_mtime.tv_sec)),
        st_mtime_nsec: x.stx_mtime.tv_nsec as _,
        st_ctime: bitcast!(i64::from(x.stx_ctime.tv_sec)),
        st_ctime_nsec: x.stx_ctime.tv_nsec as _,
        st_ino: x.stx_ino.into(),
    })
}

/// Convert from a Linux `statx` value to rustix's `Stat`.
///
/// mips64' `struct stat64` in libc has private fields, and `stx_blocks`
#[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))]
fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
    let mut result: Stat = unsafe { core::mem::zeroed() };

    result.st_dev = crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor);
    result.st_mode = x.stx_mode.into();
    result.st_nlink = x.stx_nlink.into();
    result.st_uid = x.stx_uid.into();
    result.st_gid = x.stx_gid.into();
    result.st_rdev = crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor);
    result.st_size = x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_blksize = x.stx_blksize.into();
    result.st_blocks = x.stx_blocks.try_into().map_err(|_e| io::Errno::OVERFLOW)?;
    result.st_atime = bitcast!(i64::from(x.stx_atime.tv_sec));
    result.st_atime_nsec = x.stx_atime.tv_nsec as _;
    result.st_mtime = bitcast!(i64::from(x.stx_mtime.tv_sec));
    result.st_mtime_nsec = x.stx_mtime.tv_nsec as _;
    result.st_ctime = bitcast!(i64::from(x.stx_ctime.tv_sec));
    result.st_ctime_nsec = x.stx_ctime.tv_nsec as _;
    result.st_ino = x.stx_ino.into();

    Ok(result)
}

/// Convert from a Linux `stat64` value to rustix's `Stat`.
#[cfg(all(linux_kernel, target_pointer_width = "32"))]
#[allow(deprecated)] // for `st_[amc]time` u64->i64 transition
fn stat64_to_stat(s64: c::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: bitcast!(i64::from(s64.st_atime)),
        st_atime_nsec: s64
            .st_atime_nsec
            .try_into()
            .map_err(|_| io::Errno::OVERFLOW)?,
        st_mtime: bitcast!(i64::from(s64.st_mtime)),
        st_mtime_nsec: s64
            .st_mtime_nsec
            .try_into()
            .map_err(|_| io::Errno::OVERFLOW)?,
        st_ctime: bitcast!(i64::from(s64.st_ctime)),
        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 `stat64` value to rustix's `Stat`.
///
/// mips64' `struct stat64` in libc has private fields, and `st_blocks` has
/// type `i64`.
#[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))]
fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {
    let mut result: Stat = unsafe { core::mem::zeroed() };

    result.st_dev = s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_mode = s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_nlink = s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_uid = s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_gid = s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_rdev = s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_size = s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_blksize = s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_blocks = s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?;
    result.st_atime = i64::from(s64.st_atime) as _;
    result.st_atime_nsec = s64
        .st_atime_nsec
        .try_into()
        .map_err(|_| io::Errno::OVERFLOW)?;
    result.st_mtime = i64::from(s64.st_mtime) as _;
    result.st_mtime_nsec = s64
        .st_mtime_nsec
        .try_into()
        .map_err(|_| io::Errno::OVERFLOW)?;
    result.st_ctime = i64::from(s64.st_ctime) as _;
    result.st_ctime_nsec = s64
        .st_ctime_nsec
        .try_into()
        .map_err(|_| io::Errno::OVERFLOW)?;
    result.st_ino = s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?;

    Ok(result)
}

#[cfg(linux_kernel)]
#[allow(non_upper_case_globals)]
mod sys {
    use super::{c, BorrowedFd, Statx};

    weak_or_syscall! {
        pub(superfn statx(
            dirfd_: BorrowedFd<'_>,
            path: *const c::c_char,
            flags: c::c_int,
            mask: c::c_uint,
            buf: *mut Statx
        ) via SYS_statx -> c::c_int
    }
}

#[cfg(linux_kernel)]
#[allow(non_upper_case_globals)]
pub(cratefn statx(
    dirfd: BorrowedFd<'_>,
    path: &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/20200505095915.11275-7-mszeredi@redhat.com/
    #[cfg(not(any(target_os = "android", target_env = "musl")))]
    const STATX__RESERVED: u32 = c::STATX__RESERVED as u32;
    #[cfg(any(target_os = "android", target_env = "musl"))]
    const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED;
    if (mask.bits() & STATX__RESERVED) == STATX__RESERVED {
        return Err(io::Errno::INVAL);
    }
    let mask = mask & StatxFlags::all();

    let mut statx_buf = MaybeUninit::<Statx>::uninit();
    unsafe {
        ret(sys::statx(
            dirfd,
            c_str(path),
            bitflags_bits!(flags),
            mask.bits(),
            statx_buf.as_mut_ptr(),
        ))?;
        Ok(statx_buf.assume_init())
    }
}

#[cfg(linux_kernel)]
#[inline]
pub(cratefn 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(sys::statx(CWD, null(), 00, null_mut())),
            Err(io::Errno::FAULT)
        )
    }
}

#[cfg(apple)]
pub(crateunsafe fn fcopyfile(
    from: BorrowedFd<'_>,
    to: BorrowedFd<'_>,
    state: copyfile_state_t,
    flags: CopyfileFlags,
) -> io::Result<()> {
    extern "C" {
        fn fcopyfile(
            from: c::c_int,
            to: c::c_int,
            state: copyfile_state_t,
            flags: c::c_uint,
        ) -> c::c_int;
    }

    nonnegative_ret(fcopyfile(
        borrowed_fd(from),
        borrowed_fd(to),
        state,
        bitflags_bits!(flags),
    ))
}

#[cfg(apple)]
pub(cratefn copyfile_state_alloc() -> io::Result<copyfile_state_t> {
    extern "C" {
        fn copyfile_state_alloc() -> copyfile_state_t;
    }

    let result = unsafe { copyfile_state_alloc() };
    if result.0.is_null() {
        Err(io::Errno::last_os_error())
    } else {
        Ok(result)
    }
}

#[cfg(apple)]
pub(crateunsafe fn copyfile_state_free(state: copyfile_state_t) -> io::Result<()> {
    extern "C" {
        fn copyfile_state_free(state: copyfile_state_t) -> c::c_int;
    }

    nonnegative_ret(copyfile_state_free(state))
}

#[cfg(apple)]
const COPYFILE_STATE_COPIED: u32 = 8;

#[cfg(apple)]
pub(crateunsafe fn copyfile_state_get_copied(state: copyfile_state_t) -> io::Result<u64> {
    let mut copied = MaybeUninit::<u64>::uninit();
    copyfile_state_get(state, COPYFILE_STATE_COPIED, copied.as_mut_ptr().cast())?;
    Ok(copied.assume_init())
}

#[cfg(apple)]
pub(crateunsafe fn copyfile_state_get(
    state: copyfile_state_t,
    flag: u32,
    dst: *mut c::c_void,
) -> io::Result<()> {
    extern "C" {
        fn copyfile_state_get(state: copyfile_state_t, flag: u32, dst: *mut c::c_void) -> c::c_int;
    }

    nonnegative_ret(copyfile_state_get(state, flag, dst))
}

#[cfg(all(apple, feature = "alloc"))]
pub(cratefn getpath(fd: BorrowedFd<'_>) -> io::Result<CString> {
    // The use of `PATH_MAX` is generally not encouraged, but it
    // is inevitable in this case because macOS defines `fcntl` with
    // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
    // alternatives. If a better method is invented, it should be used
    // instead.
    let mut buf = vec![0; c::PATH_MAX as usize];

    // From the [macOS `fcntl` manual page]:
    // `F_GETPATH` - Get the path of the file descriptor `Fildes`. The argument
    //               must be a buffer of size `MAXPATHLEN` or greater.
    //
    // [macOS `fcntl` manual page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
    unsafe {
        ret(c::fcntl(borrowed_fd(fd), c::F_GETPATH, buf.as_mut_ptr()))?;
    }

    let l = buf.iter().position(|&c| c == 0).unwrap();
    buf.truncate(l);
    buf.shrink_to_fit();

    Ok(CString::new(buf).unwrap())
}

#[cfg(apple)]
pub(cratefn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> {
    // From the [macOS `fcntl` manual page]:
    // `F_RDADVISE` - Issue an advisory read async with no copy to user.
    //
    // The `F_RDADVISE` command operates on the following structure which holds
    // information passed from the user to the system:
    //
    // ```c
    // struct radvisory {
    //      off_t   ra_offset;  /* offset into the file */
    //      int     ra_count;   /* size of the read     */
    // };
    // ```
    //
    // [macOS `fcntl` manual page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
    let ra_offset = match offset.try_into() {
        Ok(len) => len,
        // If this conversion fails, the user is providing an offset outside
        // any possible file extent, so just ignore it.
        Err(_) => return Ok(()),
    };
    let ra_count = match len.try_into() {
        Ok(len) => len,
        // If this conversion fails, the user is providing a dubiously large
        // hint which is unlikely to improve performance.
        Err(_) => return Ok(()),
    };
    unsafe {
        let radvisory = c::radvisory {
            ra_offset,
            ra_count,
        };
        ret(c::fcntl(borrowed_fd(fd), c::F_RDADVISE, &radvisory))
    }
}

#[cfg(apple)]
pub(cratefn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> {
    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) }
}

#[cfg(apple)]
pub(cratefn fcntl_nocache(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_NOCACHE, value as c::c_int)) }
}

#[cfg(apple)]
pub(cratefn fcntl_global_nocache(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
    unsafe {
        ret(c::fcntl(
            borrowed_fd(fd),
            c::F_GLOBAL_NOCACHE,
            value as c::c_int,
        ))
    }
}

/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist`
/// arguments.
#[cfg(apple)]
fn times_to_attrlist(times: &Timestamps) -> io::Result<(c::size_t, [c::timespec; 2], Attrlist)> {
    // ABI details.
    const ATTR_CMN_MODTIME: u32 = 0x0000_0400;
    const ATTR_CMN_ACCTIME: u32 = 0x0000_1000;
    const ATTR_BIT_MAP_COUNT: u16 = 5;

    let mut times = times.clone();

    // If we have any `UTIME_NOW` elements, replace them with the current time.
    if times.last_access.tv_nsec == c::UTIME_NOW.into()
        || times.last_modification.tv_nsec == c::UTIME_NOW.into()
    {
        let now = {
            let mut tv = c::timeval {
                tv_sec: 0,
                tv_usec: 0,
            };
            unsafe {
                let r = c::gettimeofday(&mut tv, null_mut());
                assert_eq!(r, 0);
            }
            c::timespec {
                tv_sec: tv.tv_sec,
                tv_nsec: (tv.tv_usec * 1000as _,
            }
        };
        if times.last_access.tv_nsec == c::UTIME_NOW.into() {
            times.last_access = crate::timespec::Timespec {
                tv_sec: now.tv_sec.into(),
                tv_nsec: now.tv_nsec as _,
            };
        }
        if times.last_modification.tv_nsec == c::UTIME_NOW.into() {
            times.last_modification = crate::timespec::Timespec {
                tv_sec: now.tv_sec.into(),
                tv_nsec: now.tv_nsec as _,
            };
        }
    }

    // Pack the return values following the rules for [`getattrlist`].
    //
    // [`getattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getattrlist.2.html
    let mut times_size = 0;
    let mut attrs = Attrlist {
        bitmapcount: ATTR_BIT_MAP_COUNT,
        reserved: 0,
        commonattr: 0,
        volattr: 0,
        dirattr: 0,
        fileattr: 0,
        forkattr: 0,
    };
    let mut return_times = [c::timespec {
        tv_sec: 0,
        tv_nsec: 0,
    }; 2];
    let mut times_index = 0;
    if times.last_modification.tv_nsec != c::UTIME_OMIT.into() {
        attrs.commonattr |= ATTR_CMN_MODTIME;
        return_times[times_index] = c::timespec {
            tv_sec: times
                .last_modification
                .tv_sec
                .try_into()
                .map_err(|_| io::Errno::OVERFLOW)?,
            tv_nsec: times.last_modification.tv_nsec as _,
        };
        times_index += 1;
        times_size += size_of::<c::timespec>();
    }
    if times.last_access.tv_nsec != c::UTIME_OMIT.into() {
        attrs.commonattr |= ATTR_CMN_ACCTIME;
        return_times[times_index] = c::timespec {
            tv_sec: times
                .last_access
                .tv_sec
                .try_into()
                .map_err(|_| io::Errno::OVERFLOW)?,
            tv_nsec: times.last_access.tv_nsec as _,
        };
        times_size += size_of::<c::timespec>();
    }

    Ok((times_size, return_times, attrs))
}

/// Support type for `Attrlist`.
#[cfg(apple)]
type Attrgroup = u32;

/// Attribute list for use with `setattrlist`.
#[cfg(apple)]
#[repr(C)]
struct Attrlist {
    bitmapcount: u16,
    reserved: u16,
    commonattr: Attrgroup,
    volattr: Attrgroup,
    dirattr: Attrgroup,
    fileattr: Attrgroup,
    forkattr: Attrgroup,
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn getxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
    let value_ptr = value.as_mut_ptr();

    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::getxattr(
            path.as_ptr(),
            name.as_ptr(),
            value_ptr.cast::<c::c_void>(),
            value.len(),
        ))
    }

    #[cfg(apple)]
    {
        // Passing an empty to slice to getxattr leads to ERANGE on macOS. Pass null
        // instead.
        let ptr = if value.is_empty() {
            core::ptr::null_mut()
        } else {
            value_ptr.cast::<c::c_void>()
        };
        unsafe {
            ret_usize(c::getxattr(
                path.as_ptr(),
                name.as_ptr(),
                ptr,
                value.len(),
                0,
                0,
            ))
        }
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn lgetxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
    let value_ptr = value.as_mut_ptr();

    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::lgetxattr(
            path.as_ptr(),
            name.as_ptr(),
            value_ptr.cast::<c::c_void>(),
            value.len(),
        ))
    }

    #[cfg(apple)]
    {
        // Passing an empty to slice to getxattr leads to ERANGE on macOS. Pass null
        // instead.
        let ptr = if value.is_empty() {
            core::ptr::null_mut()
        } else {
            value_ptr.cast::<c::c_void>()
        };

        unsafe {
            ret_usize(c::getxattr(
                path.as_ptr(),
                name.as_ptr(),
                ptr,
                value.len(),
                0,
                c::XATTR_NOFOLLOW,
            ))
        }
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn fgetxattr(fd: BorrowedFd<'_>, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
    let value_ptr = value.as_mut_ptr();

    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::fgetxattr(
            borrowed_fd(fd),
            name.as_ptr(),
            value_ptr.cast::<c::c_void>(),
            value.len(),
        ))
    }

    #[cfg(apple)]
    {
        // Passing an empty to slice to getxattr leads to ERANGE on macOS. Pass null
        // instead.
        let ptr = if value.is_empty() {
            core::ptr::null_mut()
        } else {
            value_ptr.cast::<c::c_void>()
        };
        unsafe {
            ret_usize(c::fgetxattr(
                borrowed_fd(fd),
                name.as_ptr(),
                ptr,
                value.len(),
                0,
                0,
            ))
        }
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn setxattr(
    path: &CStr,
    name: &CStr,
    value: &[u8],
    flags: XattrFlags,
) -> io::Result<()> {
    #[cfg(not(apple))]
    unsafe {
        ret(c::setxattr(
            path.as_ptr(),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            flags.bits() as i32,
        ))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::setxattr(
            path.as_ptr(),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            0,
            flags.bits() as i32,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn lsetxattr(
    path: &CStr,
    name: &CStr,
    value: &[u8],
    flags: XattrFlags,
) -> io::Result<()> {
    #[cfg(not(apple))]
    unsafe {
        ret(c::lsetxattr(
            path.as_ptr(),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            flags.bits() as i32,
        ))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::setxattr(
            path.as_ptr(),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            0,
            flags.bits() as i32 | c::XATTR_NOFOLLOW,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn fsetxattr(
    fd: BorrowedFd<'_>,
    name: &CStr,
    value: &[u8],
    flags: XattrFlags,
) -> io::Result<()> {
    #[cfg(not(apple))]
    unsafe {
        ret(c::fsetxattr(
            borrowed_fd(fd),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            flags.bits() as i32,
        ))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::fsetxattr(
            borrowed_fd(fd),
            name.as_ptr(),
            value.as_ptr().cast::<c::c_void>(),
            value.len(),
            0,
            flags.bits() as i32,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn listxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::listxattr(path.as_ptr(), list.as_mut_ptr(), list.len()))
    }

    #[cfg(apple)]
    unsafe {
        ret_usize(c::listxattr(
            path.as_ptr(),
            list.as_mut_ptr(),
            list.len(),
            0,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn llistxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::llistxattr(path.as_ptr(), list.as_mut_ptr(), list.len()))
    }

    #[cfg(apple)]
    unsafe {
        ret_usize(c::listxattr(
            path.as_ptr(),
            list.as_mut_ptr(),
            list.len(),
            c::XATTR_NOFOLLOW,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn flistxattr(fd: BorrowedFd<'_>, list: &mut [c::c_char]) -> io::Result<usize> {
    let fd = borrowed_fd(fd);

    #[cfg(not(apple))]
    unsafe {
        ret_usize(c::flistxattr(fd, list.as_mut_ptr(), list.len()))
    }

    #[cfg(apple)]
    unsafe {
        ret_usize(c::flistxattr(fd, list.as_mut_ptr(), list.len(), 0))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
    #[cfg(not(apple))]
    unsafe {
        ret(c::removexattr(path.as_ptr(), name.as_ptr()))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::removexattr(path.as_ptr(), name.as_ptr(), 0))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn lremovexattr(path: &CStr, name: &CStr) -> io::Result<()> {
    #[cfg(not(apple))]
    unsafe {
        ret(c::lremovexattr(path.as_ptr(), name.as_ptr()))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::removexattr(
            path.as_ptr(),
            name.as_ptr(),
            c::XATTR_NOFOLLOW,
        ))
    }
}

#[cfg(any(apple, linux_kernel))]
pub(cratefn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
    let fd = borrowed_fd(fd);

    #[cfg(not(apple))]
    unsafe {
        ret(c::fremovexattr(fd, name.as_ptr()))
    }

    #[cfg(apple)]
    unsafe {
        ret(c::fremovexattr(fd, name.as_ptr(), 0))
    }
}

#[test]
fn test_sizes() {
    #[cfg(linux_kernel)]
    assert_eq_size!(c::loff_t, u64);

    // Assert that `Timestamps` has the expected layout. If we're not fixing
    // y2038, libc's type should match ours. If we are, it's smaller.
    #[cfg(not(fix_y2038))]
    assert_eq_size!([c::timespec; 2], Timestamps);
    #[cfg(fix_y2038)]
    assert!(core::mem::size_of::<[c::timespec; 2]>() < core::mem::size_of::<Timestamps>());
}

Messung V0.5 in Prozent
C=93 H=74 G=83

¤ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet am  2026-06-20) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik