summaryrefslogtreecommitdiffstats
path: root/vendor/rustix/src/imp/libc/fs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/rustix/src/imp/libc/fs')
-rw-r--r--vendor/rustix/src/imp/libc/fs/dir.rs402
-rw-r--r--vendor/rustix/src/imp/libc/fs/makedev.rs90
-rw-r--r--vendor/rustix/src/imp/libc/fs/mod.rs18
-rw-r--r--vendor/rustix/src/imp/libc/fs/syscalls.rs1670
-rw-r--r--vendor/rustix/src/imp/libc/fs/types.rs1028
5 files changed, 3208 insertions, 0 deletions
diff --git a/vendor/rustix/src/imp/libc/fs/dir.rs b/vendor/rustix/src/imp/libc/fs/dir.rs
new file mode 100644
index 000000000..1e6b6066f
--- /dev/null
+++ b/vendor/rustix/src/imp/libc/fs/dir.rs
@@ -0,0 +1,402 @@
+use super::super::c;
+use super::super::conv::owned_fd;
+#[cfg(not(target_os = "illumos"))]
+use super::types::FileType;
+use crate::fd::{AsFd, BorrowedFd};
+use crate::ffi::CStr;
+#[cfg(target_os = "wasi")]
+use crate::ffi::CString;
+use crate::fs::{fcntl_getfl, fstat, openat, Mode, OFlags, Stat};
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))] // not implemented in libc for netbsd yet
+use crate::fs::{fstatfs, StatFs};
+use crate::io;
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+use crate::process::fchdir;
+#[cfg(target_os = "wasi")]
+use alloc::borrow::ToOwned;
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "l4re",
+ target_os = "linux",
+ target_os = "openbsd",
+)))]
+use c::dirent as libc_dirent;
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "l4re",
+ target_os = "linux",
+)))]
+use c::readdir as libc_readdir;
+#[cfg(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "l4re",
+ target_os = "linux",
+))]
+use c::{dirent64 as libc_dirent, readdir64 as libc_readdir};
+use core::fmt;
+use core::mem::zeroed;
+use core::ptr::NonNull;
+use libc_errno::{errno, set_errno, Errno};
+
+/// `DIR*`
+#[repr(transparent)]
+pub struct Dir(NonNull<c::DIR>);
+
+impl Dir {
+ /// Construct a `Dir` that reads entries from the given directory
+ /// file descriptor.
+ #[inline]
+ pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
+ Self::_read_from(fd.as_fd())
+ }
+
+ #[inline]
+ fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
+ // Given an arbitrary `OwnedFd`, it's impossible to know whether the
+ // user holds a `dup`'d copy which could continue to modify the
+ // file description state, which would cause Undefined Behavior after
+ // our call to `fdopendir`. To prevent this, we obtain an independent
+ // `OwnedFd`.
+ let flags = fcntl_getfl(&fd)?;
+ let fd_for_dir = openat(&fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
+
+ let raw = owned_fd(fd_for_dir);
+ unsafe {
+ let libc_dir = c::fdopendir(raw);
+
+ if let Some(libc_dir) = NonNull::new(libc_dir) {
+ Ok(Self(libc_dir))
+ } else {
+ let e = io::Errno::last_os_error();
+ let _ = c::close(raw);
+ Err(e)
+ }
+ }
+ }
+
+ /// `rewinddir(self)`
+ #[inline]
+ pub fn rewind(&mut self) {
+ unsafe { c::rewinddir(self.0.as_ptr()) }
+ }
+
+ /// `readdir(self)`, where `None` means the end of the directory.
+ pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
+ set_errno(Errno(0));
+ let dirent_ptr = unsafe { libc_readdir(self.0.as_ptr()) };
+ if dirent_ptr.is_null() {
+ let curr_errno = errno().0;
+ if curr_errno == 0 {
+ // We successfully reached the end of the stream.
+ None
+ } else {
+ // `errno` is unknown or non-zero, so an error occurred.
+ Some(Err(io::Errno(curr_errno)))
+ }
+ } else {
+ // We successfully read an entry.
+ unsafe {
+ // We have our own copy of OpenBSD's dirent; check that the
+ // layout minimally matches libc's.
+ #[cfg(target_os = "openbsd")]
+ check_dirent_layout(&*dirent_ptr);
+
+ let result = DirEntry {
+ dirent: read_dirent(&*dirent_ptr.cast()),
+
+ #[cfg(target_os = "wasi")]
+ name: CStr::from_ptr((*dirent_ptr).d_name.as_ptr()).to_owned(),
+ };
+
+ Some(Ok(result))
+ }
+ }
+ }
+
+ /// `fstat(self)`
+ #[inline]
+ pub fn stat(&self) -> io::Result<Stat> {
+ fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
+ }
+
+ /// `fstatfs(self)`
+ #[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+ )))] // not implemented in libc for netbsd yet
+ #[inline]
+ pub fn statfs(&self) -> io::Result<StatFs> {
+ fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
+ }
+
+ /// `fchdir(self)`
+ #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+ #[inline]
+ pub fn chdir(&self) -> io::Result<()> {
+ fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) })
+ }
+}
+
+// A `dirent` pointer returned from `readdir` may not point to a full `dirent`
+// struct, as the name is NUL-terminated and memory may not be allocated for
+// the full extent of the struct. Copy the fields one at a time.
+unsafe fn read_dirent(input: &libc_dirent) -> libc_dirent {
+ #[cfg(not(target_os = "illumos"))]
+ let d_type = input.d_type;
+
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "wasi",
+ )))]
+ let d_off = input.d_off;
+
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ )))]
+ let d_ino = input.d_ino;
+
+ #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
+ let d_fileno = input.d_fileno;
+
+ #[cfg(not(target_os = "wasi"))]
+ let d_reclen = input.d_reclen;
+
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "ios",
+ target_os = "macos",
+ ))]
+ let d_namlen = input.d_namlen;
+
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ let d_seekoff = input.d_seekoff;
+
+ // Construct the input. Rust will give us an error if any OS has a input
+ // with a field that we missed here. And we can avoid blindly copying the
+ // whole `d_name` field, which may not be entirely allocated.
+ #[cfg_attr(target_os = "wasi", allow(unused_mut))]
+ let mut dirent = libc_dirent {
+ #[cfg(not(target_os = "illumos"))]
+ d_type,
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "wasi",
+ )))]
+ d_off,
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ )))]
+ d_ino,
+ #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
+ d_fileno,
+ #[cfg(not(target_os = "wasi"))]
+ d_reclen,
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ ))]
+ d_namlen,
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ d_seekoff,
+ // The `d_name` field is NUL-terminated, and we need to be careful not
+ // to read bytes past the NUL, even though they're within the nominal
+ // extent of the `struct dirent`, because they may not be allocated. So
+ // don't read it from `dirent_ptr`.
+ //
+ // In theory this could use `MaybeUninit::uninit().assume_init()`, but
+ // that [invokes undefined behavior].
+ //
+ // [invokes undefined behavior]: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#initialization-invariant
+ d_name: zeroed(),
+ #[cfg(target_os = "openbsd")]
+ __d_padding: zeroed(),
+ };
+
+ // Copy from d_name, reading up to and including the first NUL.
+ #[cfg(not(target_os = "wasi"))]
+ {
+ let name_len = CStr::from_ptr(input.d_name.as_ptr())
+ .to_bytes_with_nul()
+ .len();
+ dirent.d_name[..name_len].copy_from_slice(&input.d_name[..name_len]);
+ }
+
+ dirent
+}
+
+/// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
+/// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
+/// need `Sync`, which is effectively what'd need to do to implement `Sync`
+/// ourselves.
+unsafe impl Send for Dir {}
+
+impl Drop for Dir {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe { c::closedir(self.0.as_ptr()) };
+ }
+}
+
+impl Iterator for Dir {
+ type Item = io::Result<DirEntry>;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ Self::read(self)
+ }
+}
+
+impl fmt::Debug for Dir {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Dir")
+ .field("fd", unsafe { &c::dirfd(self.0.as_ptr()) })
+ .finish()
+ }
+}
+
+/// `struct dirent`
+#[derive(Debug)]
+pub struct DirEntry {
+ dirent: libc_dirent,
+
+ #[cfg(target_os = "wasi")]
+ name: CString,
+}
+
+impl DirEntry {
+ /// Returns the file name of this directory entry.
+ #[inline]
+ pub fn file_name(&self) -> &CStr {
+ #[cfg(not(target_os = "wasi"))]
+ unsafe {
+ CStr::from_ptr(self.dirent.d_name.as_ptr())
+ }
+
+ #[cfg(target_os = "wasi")]
+ &self.name
+ }
+
+ /// Returns the type of this directory entry.
+ #[cfg(not(target_os = "illumos"))]
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.dirent.d_type)
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ )))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ self.dirent.d_ino
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ ))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ #[allow(clippy::useless_conversion)]
+ self.dirent.d_fileno.into()
+ }
+}
+
+/// libc's OpenBSD `dirent` has a private field so we can't construct it
+/// directly, so we declare it ourselves to make all fields accessible.
+#[cfg(target_os = "openbsd")]
+#[repr(C)]
+#[derive(Debug)]
+struct libc_dirent {
+ d_fileno: c::ino_t,
+ d_off: c::off_t,
+ d_reclen: u16,
+ d_type: u8,
+ d_namlen: u8,
+ __d_padding: [u8; 4],
+ d_name: [c::c_char; 256],
+}
+
+/// We have our own copy of OpenBSD's dirent; check that the layout
+/// minimally matches libc's.
+#[cfg(target_os = "openbsd")]
+fn check_dirent_layout(dirent: &c::dirent) {
+ use crate::utils::as_ptr;
+ use core::mem::{align_of, size_of};
+
+ // Check that the basic layouts match.
+ assert_eq!(size_of::<libc_dirent>(), size_of::<c::dirent>());
+ assert_eq!(align_of::<libc_dirent>(), align_of::<c::dirent>());
+
+ // Check that the field offsets match.
+ assert_eq!(
+ {
+ let z = libc_dirent {
+ d_fileno: 0_u64,
+ d_off: 0_i64,
+ d_reclen: 0_u16,
+ d_type: 0_u8,
+ d_namlen: 0_u8,
+ __d_padding: [0_u8; 4],
+ d_name: [0 as c::c_char; 256],
+ };
+ let base = as_ptr(&z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ },
+ {
+ let z = dirent;
+ let base = as_ptr(z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ }
+ );
+}
diff --git a/vendor/rustix/src/imp/libc/fs/makedev.rs b/vendor/rustix/src/imp/libc/fs/makedev.rs
new file mode 100644
index 000000000..d9089e7f4
--- /dev/null
+++ b/vendor/rustix/src/imp/libc/fs/makedev.rs
@@ -0,0 +1,90 @@
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+use super::super::c;
+use crate::fs::Dev;
+
+#[cfg(not(any(target_os = "android", target_os = "emscripten")))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ unsafe { c::makedev(maj, min) }
+}
+
+#[cfg(all(target_os = "android", not(target_pointer_width = "32")))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // Android's `makedev` oddly has signed argument types.
+ unsafe { c::makedev(maj as i32, min as i32) }
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit,
+ // so we do it ourselves.
+ ((u64::from(maj) & 0xffff_f000_u64) << 32)
+ | ((u64::from(maj) & 0x0000_0fff_u64) << 8)
+ | ((u64::from(min) & 0xffff_ff00_u64) << 12)
+ | (u64::from(min) & 0x0000_00ff_u64)
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // Emscripten's `makedev` has a 32-bit return value.
+ Dev::from(unsafe { c::makedev(maj, min) })
+}
+
+#[cfg(not(any(target_os = "android", target_os = "emscripten")))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ unsafe { c::major(dev) }
+}
+
+#[cfg(all(target_os = "android", not(target_pointer_width = "32")))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // Android's `major` oddly has signed return types.
+ (unsafe { c::major(dev) }) as u32
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit,
+ // so we do it ourselves.
+ (((dev >> 31 >> 1) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff)) as u32
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // Emscripten's `major` has a 32-bit argument value.
+ unsafe { c::major(dev as u32) }
+}
+
+#[cfg(not(any(target_os = "android", target_os = "emscripten")))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ unsafe { c::minor(dev) }
+}
+
+#[cfg(all(target_os = "android", not(target_pointer_width = "32")))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // Android's `minor` oddly has signed return types.
+ (unsafe { c::minor(dev) }) as u32
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit,
+ // so we do it ourselves.
+ (((dev >> 12) & 0xffff_ff00) | (dev & 0x0000_00ff)) as u32
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // Emscripten's `minor` has a 32-bit argument value.
+ unsafe { c::minor(dev as u32) }
+}
diff --git a/vendor/rustix/src/imp/libc/fs/mod.rs b/vendor/rustix/src/imp/libc/fs/mod.rs
new file mode 100644
index 000000000..02b7b2d6a
--- /dev/null
+++ b/vendor/rustix/src/imp/libc/fs/mod.rs
@@ -0,0 +1,18 @@
+#[cfg(not(target_os = "redox"))]
+#[cfg(any(feature = "fs", feature = "procfs"))]
+pub(crate) mod dir;
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+pub(crate) mod makedev;
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/vendor/rustix/src/imp/libc/fs/syscalls.rs b/vendor/rustix/src/imp/libc/fs/syscalls.rs
new file mode 100644
index 000000000..0317367ab
--- /dev/null
+++ b/vendor/rustix/src/imp/libc/fs/syscalls.rs
@@ -0,0 +1,1670 @@
+//! libc syscalls supporting `rustix::fs`.
+
+use super::super::c;
+use super::super::conv::{
+ borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd, ret_ssize_t,
+};
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use super::super::conv::{syscall_ret, syscall_ret_owned_fd, syscall_ret_ssize_t};
+#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
+use super::super::offset::libc_fallocate;
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+use super::super::offset::libc_posix_fadvise;
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "fuchsia",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+use super::super::offset::libc_posix_fallocate;
+use super::super::offset::{libc_fstat, libc_fstatat, libc_ftruncate, libc_lseek, libc_off_t};
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+use super::super::offset::{libc_fstatfs, libc_statfs};
+#[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+))]
+use super::super::time::types::LibcTimespec;
+use crate::fd::BorrowedFd;
+#[cfg(not(target_os = "wasi"))]
+use crate::fd::RawFd;
+use crate::ffi::CStr;
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+use crate::ffi::CString;
+#[cfg(not(target_os = "illumos"))]
+use crate::fs::Access;
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+use crate::fs::Advice;
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+use crate::fs::FallocateFlags;
+#[cfg(not(target_os = "wasi"))]
+use crate::fs::FlockOperation;
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+use crate::fs::MemfdFlags;
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "linux",
+))]
+use crate::fs::SealFlags;
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+// not implemented in libc for netbsd yet
+use crate::fs::StatFs;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use crate::fs::{cwd, RenameFlags, ResolveFlags, Statx, StatxFlags};
+#[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+use crate::fs::{Dev, FileType};
+use crate::fs::{FdFlags, Mode, OFlags, Stat, Timestamps};
+use crate::io::{self, OwnedFd, SeekFrom};
+#[cfg(not(target_os = "wasi"))]
+use crate::process::{Gid, Uid};
+#[cfg(not(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+)))]
+use crate::utils::as_ptr;
+use core::convert::TryInto;
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+))]
+use core::mem::size_of;
+use core::mem::MaybeUninit;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use core::ptr::null;
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+))]
+use core::ptr::null_mut;
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+use {
+ super::super::conv::nonnegative_ret,
+ crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags},
+};
+#[cfg(not(target_os = "redox"))]
+use {super::super::offset::libc_openat, crate::fs::AtFlags};
+
+#[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+))]
+weak!(fn __utimensat64(c::c_int, *const c::c_char, *const LibcTimespec, c::c_int) -> c::c_int);
+#[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+))]
+weak!(fn __futimens64(c::c_int, *const LibcTimespec) -> c::c_int);
+
+/// 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"))]
+fn openat_via_syscall(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+) -> io::Result<OwnedFd> {
+ unsafe {
+ let dirfd = borrowed_fd(dirfd);
+ let path = c_str(path);
+ let oflags = oflags.bits();
+ let mode = c::c_uint::from(mode.bits());
+ ret_owned_fd(c::syscall(
+ c::SYS_openat,
+ c::c_long::from(dirfd),
+ path,
+ c::c_long::from(oflags),
+ mode as c::c_long,
+ ) as c::c_int)
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn openat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+) -> io::Result<OwnedFd> {
+ // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
+ // Basically old glibc versions don't handle O_TMPFILE correctly.
+ #[cfg(all(unix, target_env = "gnu"))]
+ if oflags.contains(OFlags::TMPFILE) && crate::imp::if_glibc_is_less_than_2_25() {
+ return openat_via_syscall(dirfd, path, oflags, mode);
+ }
+ unsafe {
+ // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since
+ // `libc_openat` is declared as a variadic function and narrower
+ // arguments are promoted.
+ ret_owned_fd(libc_openat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ oflags.bits(),
+ c::c_uint::from(mode.bits()),
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+#[inline]
+pub(crate) fn statfs(filename: &CStr) -> io::Result<StatFs> {
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(libc_statfs(c_str(filename), result.as_mut_ptr()))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+#[inline]
+pub(crate) fn readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ ret_ssize_t(c::readlinkat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ buf.as_mut_ptr().cast::<c::c_char>(),
+ buf.len(),
+ ))
+ .map(|nread| nread as usize)
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn 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(not(target_os = "redox"))]
+pub(crate) fn linkat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::linkat(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
+ unsafe { ret(c::unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits())) }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn renameat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+) -> io::Result<()> {
+ 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(crate) fn renameat2(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ // `getrandom` 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(),
+ ))
+ }
+}
+
+/// At present, `libc` only has `renameat2` defined for glibc. On other
+/// ABIs, `RenameFlags` has no flags defined, and we use plain `renameat`.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[inline]
+pub(crate) fn renameat2(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ assert!(flags.is_empty());
+ renameat(old_dirfd, old_path, new_dirfd, new_path)
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn 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),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
+ // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
+ // `statx`.
+ #[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+ ))]
+ {
+ match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
+ Ok(x) => return statx_to_stat(x),
+ Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
+ Err(e) => return Err(e),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(libc_fstatat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ stat.as_mut_ptr(),
+ flags.bits(),
+ ))?;
+ Ok(stat.assume_init())
+ }
+}
+
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+))]
+fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
+ unsafe {
+ let mut result = MaybeUninit::<c::stat64>::uninit();
+ ret(libc_fstatat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ result.as_mut_ptr(),
+ flags.bits(),
+ ))?;
+ stat64_to_stat(result.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "emscripten", target_os = "illumos", target_os = "redox")))]
+pub(crate) fn accessat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ access: Access,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::faccessat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ access.bits(),
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(target_os = "emscripten")]
+pub(crate) fn accessat(
+ _dirfd: BorrowedFd<'_>,
+ _path: &CStr,
+ _access: Access,
+ _flags: AtFlags,
+) -> io::Result<()> {
+ Ok(())
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn utimensat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // 32-bit gnu version: libc has `utimensat` but it is not y2038 safe by
+ // default.
+ #[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+ ))]
+ unsafe {
+ if let Some(libc_utimensat) = __utimensat64.get() {
+ let libc_times: [LibcTimespec; 2] = [
+ times.last_access.clone().into(),
+ times.last_modification.clone().into(),
+ ];
+
+ ret(libc_utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ libc_times.as_ptr(),
+ flags.bits(),
+ ))
+ } else {
+ 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(
+ target_os = "ios",
+ target_os = "macos",
+ all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+ )
+ )))]
+ unsafe {
+ // Assert that `Timestamps` has the expected layout.
+ let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
+
+ ret(c::utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ as_ptr(times).cast(),
+ flags.bits(),
+ ))
+ }
+
+ // `utimensat` was introduced in macOS 10.13.
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ unsafe {
+ // 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() {
+ // Assert that `Timestamps` has the expected layout.
+ let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
+
+ return ret(have_utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ as_ptr(times).cast(),
+ flags.bits(),
+ ));
+ }
+
+ // `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;
+ }
+
+ let (attrbuf_size, times, attrs) = times_to_attrlist(times);
+
+ if setattrlist(
+ c_str(path),
+ &attrs,
+ as_ptr(&times).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(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+))]
+unsafe 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,
+ },
+ c::timespec {
+ tv_sec: times
+ .last_modification
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times.last_modification.tv_nsec,
+ },
+ ];
+ ret(c::utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ old_times.as_ptr(),
+ flags.bits(),
+ ))
+}
+
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe { ret(c::fchmodat(borrowed_fd(dirfd), c_str(path), mode.bits(), 0)) }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
+ // Linux's `fchmodat` does not have a flags argument.
+ unsafe {
+ // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since
+ // `libc_openat` is declared as a variadic function and narrower
+ // arguments are promoted.
+ syscall_ret(c::syscall(
+ c::SYS_fchmodat,
+ borrowed_fd(dirfd),
+ c_str(path),
+ c::c_uint::from(mode.bits()),
+ ))
+ }
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn 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), flags.bits())) }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn chownat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ owner: Option<Uid>,
+ group: Option<Gid>,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ let (ow, gr) = crate::process::translate_fchown_args(owner, group);
+ ret(c::fchownat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ ow,
+ gr,
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+pub(crate) fn 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(any(target_os = "android", target_os = "linux"))]
+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> {
+ assert_eq!(size_of::<c::loff_t>(), size_of::<u64>());
+
+ 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 len: usize = len.try_into().unwrap_or(usize::MAX);
+ let copied = unsafe {
+ syscall_ret_ssize_t(c::syscall(
+ c::SYS_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 as u64)
+}
+
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+pub(crate) fn 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 { libc_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(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result<FdFlags> {
+ unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFD)).map(FdFlags::from_bits_truncate) }
+}
+
+pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFD, flags.bits())) }
+}
+
+pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
+ unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFL)).map(OFlags::from_bits_truncate) }
+}
+
+pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFL, flags.bits())) }
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "linux",
+))]
+pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
+ unsafe {
+ ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GET_SEALS))
+ .map(|flags| SealFlags::from_bits_unchecked(flags))
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "linux",
+))]
+pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD_CLOEXEC, min)) }
+}
+
+pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
+ let (whence, offset): (c::c_int, libc_off_t) = 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),
+ };
+ let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), offset, whence))? };
+ Ok(offset as u64)
+}
+
+pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
+ let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? };
+ Ok(offset as u64)
+}
+
+#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))]
+pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
+ unsafe { ret(c::fchmod(borrowed_fd(fd), mode.bits())) }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+pub(crate) fn 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`.
+ unsafe {
+ syscall_ret(c::syscall(
+ c::SYS_fchmod,
+ borrowed_fd(fd),
+ c::c_uint::from(mode.bits()),
+ ))
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+pub(crate) fn 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`.
+ unsafe {
+ let (ow, gr) = crate::process::translate_fchown_args(owner, group);
+ syscall_ret(c::syscall(c::SYS_fchown, borrowed_fd(fd), ow, gr))
+ }
+}
+
+#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))]
+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(c::fchown(borrowed_fd(fd), ow, gr))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
+ unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) }
+}
+
+pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
+ // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
+ // `statx`.
+ #[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+ ))]
+ {
+ match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
+ Ok(x) => return statx_to_stat(x),
+ Err(io::Errno::NOSYS) => fstat_old(fd),
+ Err(e) => return Err(e),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(libc_fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
+ Ok(stat.assume_init())
+ }
+}
+
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ any(target_pointer_width = "32", target_arch = "mips64"),
+))]
+fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
+ unsafe {
+ let mut result = MaybeUninit::<c::stat64>::uninit();
+ ret(libc_fstat(borrowed_fd(fd), result.as_mut_ptr()))?;
+ stat64_to_stat(result.assume_init())
+ }
+}
+
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))] // not implemented in libc for netbsd yet
+pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
+ let mut statfs = MaybeUninit::<StatFs>::uninit();
+ unsafe {
+ ret(libc_fstatfs(borrowed_fd(fd), statfs.as_mut_ptr()))?;
+ Ok(statfs.assume_init())
+ }
+}
+
+pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
+ // 32-bit gnu version: libc has `futimens` but it is not y2038 safe by default.
+ #[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+ ))]
+ unsafe {
+ if let Some(libc_futimens) = __futimens64.get() {
+ let libc_times: [LibcTimespec; 2] = [
+ times.last_access.clone().into(),
+ times.last_modification.clone().into(),
+ ];
+
+ ret(libc_futimens(borrowed_fd(fd), libc_times.as_ptr()))
+ } else {
+ 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(
+ target_os = "ios",
+ target_os = "macos",
+ all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+ )
+ )))]
+ unsafe {
+ // Assert that `Timestamps` has the expected layout.
+ let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
+
+ ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast()))
+ }
+
+ // `futimens` was introduced in macOS 10.13.
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ unsafe {
+ // 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() {
+ // Assert that `Timestamps` has the expected layout.
+ let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
+
+ 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(&times).cast(),
+ attrbuf_size,
+ 0,
+ ))
+ }
+}
+
+#[cfg(all(
+ any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
+ target_env = "gnu",
+))]
+unsafe 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,
+ },
+ c::timespec {
+ tv_sec: times
+ .last_modification
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times.last_modification.tv_nsec,
+ },
+ ];
+
+ ret(c::futimens(borrowed_fd(fd), old_times.as_ptr()))
+}
+
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+pub(crate) fn 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(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
+ unsafe {
+ ret(libc_fallocate(borrowed_fd(fd), mode.bits(), offset, len))
+ }
+
+ #[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
+ {
+ assert!(mode.is_empty());
+ let err = unsafe { libc_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(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn 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_else(|| 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 {
+ 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(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fsync(borrowed_fd(fd))) }
+}
+
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+)))]
+pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fdatasync(borrowed_fd(fd))) }
+}
+
+pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
+ let length = length.try_into().map_err(|_overflow_err| io::Errno::FBIG)?;
+ unsafe { ret(libc_ftruncate(borrowed_fd(fd), length)) }
+}
+
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+pub(crate) fn memfd_create(path: &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(any(target_os = "android", target_os = "linux"))]
+ 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(path), flags.bits())) }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+pub(crate) fn openat2(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+ resolve: ResolveFlags,
+) -> io::Result<OwnedFd> {
+ let oflags: i32 = oflags.bits();
+ let open_how = OpenHow {
+ oflag: u64::from(oflags as u32),
+ mode: u64::from(mode.bits()),
+ resolve: resolve.bits(),
+ };
+
+ unsafe {
+ syscall_ret_owned_fd(c::syscall(
+ SYS_OPENAT2,
+ borrowed_fd(dirfd),
+ c_str(path),
+ &open_how,
+ SIZEOF_OPEN_HOW,
+ ))
+ }
+}
+#[cfg(all(
+ target_pointer_width = "32",
+ any(target_os = "android", target_os = "linux"),
+))]
+const SYS_OPENAT2: i32 = 437;
+#[cfg(all(
+ target_pointer_width = "64",
+ any(target_os = "android", target_os = "linux"),
+))]
+const SYS_OPENAT2: i64 = 437;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[repr(C)]
+#[derive(Debug)]
+struct OpenHow {
+ oflag: u64,
+ mode: u64,
+ resolve: u64,
+}
+#[cfg(any(target_os = "android", target_os = "linux"))]
+const SIZEOF_OPEN_HOW: usize = size_of::<OpenHow>();
+
+#[cfg(target_os = "linux")]
+pub(crate) fn sendfile(
+ out_fd: BorrowedFd<'_>,
+ in_fd: BorrowedFd<'_>,
+ offset: Option<&mut u64>,
+ count: usize,
+) -> io::Result<usize> {
+ unsafe {
+ let nsent = ret_ssize_t(c::sendfile64(
+ borrowed_fd(out_fd),
+ borrowed_fd(in_fd),
+ offset.map_or(null_mut(), crate::utils::as_mut_ptr).cast(),
+ count,
+ ))?;
+ Ok(nsent as usize)
+ }
+}
+
+/// Convert from a Linux `statx` value to rustix's `Stat`.
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ target_pointer_width = "32",
+))]
+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: x
+ .stx_atime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_atime_nsec: x.stx_atime.tv_nsec as _,
+ st_mtime: x
+ .stx_mtime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_mtime_nsec: x.stx_mtime.tv_nsec as _,
+ st_ctime: x
+ .stx_ctime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ 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(
+ any(target_os = "android", target_os = "linux"),
+ target_arch = "mips64",
+))]
+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 = x
+ .stx_atime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_atime_nsec = x.stx_atime.tv_nsec as _;
+ result.st_mtime = x
+ .stx_mtime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_mtime_nsec = x.stx_mtime.tv_nsec as _;
+ result.st_ctime = x
+ .stx_ctime
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ 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(
+ any(target_os = "android", target_os = "linux"),
+ target_pointer_width = "32",
+))]
+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: 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 `stat64` value to rustix's `Stat`.
+///
+/// mips64' `struct stat64` in libc has private fields, and `st_blocks` has
+/// type `i64`.
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ target_arch = "mips64",
+))]
+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 = s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_atime_nsec = s64
+ .st_atime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_mtime = s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_mtime_nsec = s64
+ .st_mtime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_ctime = s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ 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(any(target_os = "android", target_os = "linux"))]
+#[allow(non_upper_case_globals)]
+mod sys {
+ use super::{c, BorrowedFd, Statx};
+
+ #[cfg(all(target_os = "android", target_arch = "arm"))]
+ const SYS_statx: c::c_long = 397;
+ #[cfg(all(target_os = "android", target_arch = "x86"))]
+ const SYS_statx: c::c_long = 383;
+ #[cfg(all(target_os = "android", target_arch = "aarch64"))]
+ const SYS_statx: c::c_long = 291;
+ #[cfg(all(target_os = "android", target_arch = "x86_64"))]
+ const SYS_statx: c::c_long = 332;
+
+ weak_or_syscall! {
+ pub(super) fn statx(
+ pirfd: BorrowedFd<'_>,
+ path: *const c::c_char,
+ flags: c::c_int,
+ mask: c::c_uint,
+ buf: *mut Statx
+ ) via SYS_statx -> c::c_int
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[allow(non_upper_case_globals)]
+pub(crate) fn statx(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: AtFlags,
+ mask: StatxFlags,
+) -> io::Result<Statx> {
+ let mut statx_buf = MaybeUninit::<Statx>::uninit();
+ unsafe {
+ ret(sys::statx(
+ dirfd,
+ c_str(path),
+ flags.bits(),
+ mask.bits(),
+ statx_buf.as_mut_ptr(),
+ ))?;
+ Ok(statx_buf.assume_init())
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[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(sys::statx(cwd(), null(), 0, 0, null_mut())),
+ Err(io::Errno::FAULT)
+ )
+ }
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+pub(crate) unsafe 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,
+ flags.bits(),
+ ))
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn 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(any(target_os = "ios", target_os = "macos"))]
+pub(crate) unsafe 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(any(target_os = "ios", target_os = "macos"))]
+const COPYFILE_STATE_COPIED: u32 = 8;
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+pub(crate) unsafe 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(any(target_os = "ios", target_os = "macos"))]
+pub(crate) unsafe 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(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn 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` man page]:
+ // `F_GETPATH` - Get the path of the file descriptor `Fildes`. The argument
+ // must be a buffer of size `MAXPATHLEN` or greater.
+ //
+ // [macOS `fcntl` man 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);
+
+ // TODO: On Rust 1.56, we can use `shrink_to` here.
+ //buf.shrink_to(l + 1);
+ buf.shrink_to_fit();
+
+ Ok(CString::new(buf).unwrap())
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> {
+ // From the [macOS `fcntl` man 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` man 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(any(target_os = "ios", target_os = "macos"))]
+pub(crate) fn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) }
+}
+
+/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist`
+/// arguments.
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+fn times_to_attrlist(times: &Timestamps) -> (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 || times.last_modification.tv_nsec == c::UTIME_NOW
+ {
+ 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 * 1000) as _,
+ }
+ };
+ if times.last_access.tv_nsec == c::UTIME_NOW {
+ times.last_access = now;
+ }
+ if times.last_modification.tv_nsec == c::UTIME_NOW {
+ times.last_modification = now;
+ }
+ }
+
+ // 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 {
+ attrs.commonattr |= ATTR_CMN_MODTIME;
+ return_times[times_index] = times.last_modification;
+ times_index += 1;
+ times_size += size_of::<c::timespec>();
+ }
+ if times.last_access.tv_nsec != c::UTIME_OMIT {
+ attrs.commonattr |= ATTR_CMN_ACCTIME;
+ return_times[times_index] = times.last_access;
+ times_size += size_of::<c::timespec>();
+ }
+
+ (times_size, return_times, attrs)
+}
+
+/// Support type for `Attrlist`.
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+type Attrgroup = u32;
+
+/// Attribute list for use with `setattrlist`.
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+#[repr(C)]
+struct Attrlist {
+ bitmapcount: u16,
+ reserved: u16,
+ commonattr: Attrgroup,
+ volattr: Attrgroup,
+ dirattr: Attrgroup,
+ fileattr: Attrgroup,
+ forkattr: Attrgroup,
+}
diff --git a/vendor/rustix/src/imp/libc/fs/types.rs b/vendor/rustix/src/imp/libc/fs/types.rs
new file mode 100644
index 000000000..9d892daf0
--- /dev/null
+++ b/vendor/rustix/src/imp/libc/fs/types.rs
@@ -0,0 +1,1028 @@
+use super::super::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `FD_*` constants for use with [`fcntl_getfd`] and [`fcntl_setfd`].
+ ///
+ /// [`fcntl_getfd`]: crate::fs::fcntl_getfd
+ /// [`fcntl_setfd`]: crate::fs::fcntl_setfd
+ pub struct FdFlags: c::c_int {
+ /// `FD_CLOEXEC`
+ const CLOEXEC = c::FD_CLOEXEC;
+ }
+}
+
+bitflags! {
+ /// `*_OK` constants for use with [`accessat`].
+ ///
+ /// [`accessat`]: fn.accessat.html
+ pub struct Access: c::c_int {
+ /// `R_OK`
+ const READ_OK = c::R_OK;
+
+ /// `W_OK`
+ const WRITE_OK = c::W_OK;
+
+ /// `X_OK`
+ const EXEC_OK = c::X_OK;
+
+ /// `F_OK`
+ const EXISTS = c::F_OK;
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+bitflags! {
+ /// `AT_*` constants for use with [`openat`], [`statat`], and other `*at`
+ /// functions.
+ ///
+ /// [`openat`]: crate::fs::openat
+ /// [`statat`]: crate::fs::statat
+ pub struct AtFlags: c::c_int {
+ /// `AT_REMOVEDIR`
+ const REMOVEDIR = c::AT_REMOVEDIR;
+
+ /// `AT_SYMLINK_FOLLOW`
+ const SYMLINK_FOLLOW = c::AT_SYMLINK_FOLLOW;
+
+ /// `AT_SYMLINK_NOFOLLOW`
+ const SYMLINK_NOFOLLOW = c::AT_SYMLINK_NOFOLLOW;
+
+ /// `AT_EMPTY_PATH`
+ #[cfg(any(
+ target_os = "android",
+ target_os = "fuchsia",
+ target_os = "linux",
+ ))]
+ const EMPTY_PATH = c::AT_EMPTY_PATH;
+
+ /// `AT_EACCESS`
+ #[cfg(not(any(target_os = "emscripten", target_os = "android")))]
+ const EACCESS = c::AT_EACCESS;
+
+ /// `AT_STATX_SYNC_AS_STAT`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_SYNC_AS_STAT = c::AT_STATX_SYNC_AS_STAT;
+
+ /// `AT_STATX_FORCE_SYNC`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_FORCE_SYNC = c::AT_STATX_FORCE_SYNC;
+
+ /// `AT_STATX_DONT_SYNC`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_DONT_SYNC = c::AT_STATX_DONT_SYNC;
+ }
+}
+
+bitflags! {
+ /// `S_I*` constants for use with [`openat`], [`chmodat`], and [`fchmod`].
+ ///
+ /// [`openat`]: crate::fs::openat
+ /// [`chmodat`]: crate::fs::chmodat
+ /// [`fchmod`]: crate::fs::fchmod
+ pub struct Mode: RawMode {
+ /// `S_IRWXU`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const RWXU = c::S_IRWXU as RawMode;
+
+ /// `S_IRUSR`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const RUSR = c::S_IRUSR as RawMode;
+
+ /// `S_IWUSR`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const WUSR = c::S_IWUSR as RawMode;
+
+ /// `S_IXUSR`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const XUSR = c::S_IXUSR as RawMode;
+
+ /// `S_IRWXG`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const RWXG = c::S_IRWXG as RawMode;
+
+ /// `S_IRGRP`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const RGRP = c::S_IRGRP as RawMode;
+
+ /// `S_IWGRP`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const WGRP = c::S_IWGRP as RawMode;
+
+ /// `S_IXGRP`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const XGRP = c::S_IXGRP as RawMode;
+
+ /// `S_IRWXO`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const RWXO = c::S_IRWXO as RawMode;
+
+ /// `S_IROTH`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const ROTH = c::S_IROTH as RawMode;
+
+ /// `S_IWOTH`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const WOTH = c::S_IWOTH as RawMode;
+
+ /// `S_IXOTH`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const XOTH = c::S_IXOTH as RawMode;
+
+ /// `S_ISUID`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const SUID = c::S_ISUID as RawMode;
+
+ /// `S_ISGID`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const SGID = c::S_ISGID as RawMode;
+
+ /// `S_ISVTX`
+ #[cfg(not(target_os = "wasi"))] // WASI doesn't have Unix-style mode flags.
+ const SVTX = c::S_ISVTX as RawMode;
+ }
+}
+
+impl Mode {
+ /// Construct a `Mode` from the mode bits of the `st_mode` field of
+ /// a `Stat`.
+ #[inline]
+ pub const fn from_raw_mode(st_mode: RawMode) -> Self {
+ Self::from_bits_truncate(st_mode)
+ }
+
+ /// Construct an `st_mode` value from `Stat`.
+ #[inline]
+ pub const fn as_raw_mode(self) -> RawMode {
+ self.bits()
+ }
+}
+
+bitflags! {
+ /// `O_*` constants for use with [`openat`].
+ ///
+ /// [`openat`]: crate::fs::openat
+ pub struct OFlags: c::c_int {
+ /// `O_ACCMODE`
+ const ACCMODE = c::O_ACCMODE;
+
+ /// Similar to `ACCMODE`, but just includes the read/write flags, and
+ /// no other flags.
+ ///
+ /// Some implementations include `O_PATH` in `O_ACCMODE`, when
+ /// sometimes we really just want the read/write bits. Caution is
+ /// indicated, as the presence of `O_PATH` may mean that the read/write
+ /// bits don't have their usual meaning.
+ const RWMODE = c::O_RDONLY | c::O_WRONLY | c::O_RDWR;
+
+ /// `O_APPEND`
+ const APPEND = c::O_APPEND;
+
+ /// `O_CREAT`
+ #[doc(alias = "CREAT")]
+ const CREATE = c::O_CREAT;
+
+ /// `O_DIRECTORY`
+ const DIRECTORY = c::O_DIRECTORY;
+
+ /// `O_DSYNC`
+ #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd", target_os = "redox")))]
+ const DSYNC = c::O_DSYNC;
+
+ /// `O_EXCL`
+ const EXCL = c::O_EXCL;
+
+ /// `O_FSYNC`
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ all(target_os = "linux", not(target_env = "musl")),
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ ))]
+ const FSYNC = c::O_FSYNC;
+
+ /// `O_NOFOLLOW`
+ const NOFOLLOW = c::O_NOFOLLOW;
+
+ /// `O_NONBLOCK`
+ const NONBLOCK = c::O_NONBLOCK;
+
+ /// `O_RDONLY`
+ const RDONLY = c::O_RDONLY;
+
+ /// `O_WRONLY`
+ const WRONLY = c::O_WRONLY;
+
+ /// `O_RDWR`
+ const RDWR = c::O_RDWR;
+
+ /// `O_NOCTTY`
+ #[cfg(not(target_os = "redox"))]
+ const NOCTTY = c::O_NOCTTY;
+
+ /// `O_RSYNC`
+ #[cfg(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "wasi",
+ ))]
+ const RSYNC = c::O_RSYNC;
+
+ /// `O_SYNC`
+ #[cfg(not(target_os = "redox"))]
+ const SYNC = c::O_SYNC;
+
+ /// `O_TRUNC`
+ const TRUNC = c::O_TRUNC;
+
+ /// `O_PATH`
+ #[cfg(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "linux",
+ target_os = "redox",
+ ))]
+ const PATH = c::O_PATH;
+
+ /// `O_CLOEXEC`
+ const CLOEXEC = c::O_CLOEXEC;
+
+ /// `O_TMPFILE`
+ #[cfg(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "linux",
+ ))]
+ const TMPFILE = c::O_TMPFILE;
+
+ /// `O_NOATIME`
+ #[cfg(any(
+ target_os = "android",
+ target_os = "fuchsia",
+ target_os = "linux",
+ ))]
+ const NOATIME = c::O_NOATIME;
+ }
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+bitflags! {
+ /// `CLONE_*` constants for use with [`fclonefileat`].
+ ///
+ /// [`fclonefileat`]: crate::fs::fclonefileat
+ pub struct CloneFlags: c::c_int {
+ /// `CLONE_NOFOLLOW`
+ const NOFOLLOW = 1;
+
+ /// `CLONE_NOOWNERCOPY`
+ const NOOWNERCOPY = 2;
+ }
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+mod copyfile {
+ pub(super) const ACL: u32 = 1 << 0;
+ pub(super) const STAT: u32 = 1 << 1;
+ pub(super) const XATTR: u32 = 1 << 2;
+ pub(super) const DATA: u32 = 1 << 3;
+ pub(super) const SECURITY: u32 = STAT | ACL;
+ pub(super) const METADATA: u32 = SECURITY | XATTR;
+ pub(super) const ALL: u32 = METADATA | DATA;
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+bitflags! {
+ /// `COPYFILE_*` constants.
+ pub struct CopyfileFlags: c::c_uint {
+ /// `COPYFILE_ACL`
+ const ACL = copyfile::ACL;
+
+ /// `COPYFILE_STAT`
+ const STAT = copyfile::STAT;
+
+ /// `COPYFILE_XATTR`
+ const XATTR = copyfile::XATTR;
+
+ /// `COPYFILE_DATA`
+ const DATA = copyfile::DATA;
+
+ /// `COPYFILE_SECURITY`
+ const SECURITY = copyfile::SECURITY;
+
+ /// `COPYFILE_METADATA`
+ const METADATA = copyfile::METADATA;
+
+ /// `COPYFILE_ALL`
+ const ALL = copyfile::ALL;
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+bitflags! {
+ /// `RESOLVE_*` constants for use with [`openat2`].
+ ///
+ /// [`openat2`]: crate::fs::openat2
+ #[derive(Default)]
+ pub struct ResolveFlags: u64 {
+ /// `RESOLVE_NO_XDEV`
+ const NO_XDEV = 0x01;
+
+ /// `RESOLVE_NO_MAGICLINKS`
+ const NO_MAGICLINKS = 0x02;
+
+ /// `RESOLVE_NO_SYMLINKS`
+ const NO_SYMLINKS = 0x04;
+
+ /// `RESOLVE_BENEATH`
+ const BENEATH = 0x08;
+
+ /// `RESOLVE_IN_ROOT`
+ const IN_ROOT = 0x10;
+
+ /// `RESOLVE_CACHED` (since Linux 5.12)
+ const CACHED = 0x20;
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+bitflags! {
+ /// `RENAME_*` constants for use with [`renameat_with`].
+ ///
+ /// [`renameat_with`]: crate::fs::renameat_with
+ pub struct RenameFlags: c::c_uint {
+ /// `RENAME_EXCHANGE`
+ const EXCHANGE = c::RENAME_EXCHANGE as _;
+
+ /// `RENAME_NOREPLACE`
+ const NOREPLACE = c::RENAME_NOREPLACE as _;
+
+ /// `RENAME_WHITEOUT`
+ const WHITEOUT = c::RENAME_WHITEOUT as _;
+ }
+}
+
+/// `S_IF*` constants for use with [`mknodat`] and [`Stat`]'s `st_mode` field.
+///
+/// [`mknodat`]: crate::fs::mknodat
+/// [`Stat`]: crate::fs::Stat
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FileType {
+ /// `S_IFREG`
+ RegularFile = c::S_IFREG as isize,
+
+ /// `S_IFDIR`
+ Directory = c::S_IFDIR as isize,
+
+ /// `S_IFLNK`
+ Symlink = c::S_IFLNK as isize,
+
+ /// `S_IFIFO`
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ #[doc(alias = "IFO")]
+ Fifo = c::S_IFIFO as isize,
+
+ /// `S_IFSOCK`
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ Socket = c::S_IFSOCK as isize,
+
+ /// `S_IFCHR`
+ CharacterDevice = c::S_IFCHR as isize,
+
+ /// `S_IFBLK`
+ BlockDevice = c::S_IFBLK as isize,
+
+ /// An unknown filesystem object.
+ Unknown,
+}
+
+impl FileType {
+ /// Construct a `FileType` from the `S_IFMT` bits of the `st_mode` field of
+ /// a `Stat`.
+ pub const fn from_raw_mode(st_mode: RawMode) -> Self {
+ match (st_mode as c::mode_t) & c::S_IFMT {
+ c::S_IFREG => Self::RegularFile,
+ c::S_IFDIR => Self::Directory,
+ c::S_IFLNK => Self::Symlink,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ c::S_IFIFO => Self::Fifo,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ c::S_IFSOCK => Self::Socket,
+ c::S_IFCHR => Self::CharacterDevice,
+ c::S_IFBLK => Self::BlockDevice,
+ _ => Self::Unknown,
+ }
+ }
+
+ /// Construct an `st_mode` value from `Stat`.
+ pub const fn as_raw_mode(self) -> RawMode {
+ match self {
+ Self::RegularFile => c::S_IFREG as RawMode,
+ Self::Directory => c::S_IFDIR as RawMode,
+ Self::Symlink => c::S_IFLNK as RawMode,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ Self::Fifo => c::S_IFIFO as RawMode,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ Self::Socket => c::S_IFSOCK as RawMode,
+ Self::CharacterDevice => c::S_IFCHR as RawMode,
+ Self::BlockDevice => c::S_IFBLK as RawMode,
+ Self::Unknown => c::S_IFMT as RawMode,
+ }
+ }
+
+ /// Construct a `FileType` from the `d_type` field of a `c::dirent`.
+ #[cfg(not(any(target_os = "illumos", target_os = "redox")))]
+ pub(crate) const fn from_dirent_d_type(d_type: u8) -> Self {
+ match d_type {
+ c::DT_REG => Self::RegularFile,
+ c::DT_DIR => Self::Directory,
+ c::DT_LNK => Self::Symlink,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `DT_SOCK`.
+ c::DT_SOCK => Self::Socket,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `DT_FIFO`.
+ c::DT_FIFO => Self::Fifo,
+ c::DT_CHR => Self::CharacterDevice,
+ c::DT_BLK => Self::BlockDevice,
+ // c::DT_UNKNOWN |
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// `POSIX_FADV_*` constants for use with [`fadvise`].
+///
+/// [`fadvise`]: crate::fs::fadvise
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub enum Advice {
+ /// `POSIX_FADV_NORMAL`
+ Normal = c::POSIX_FADV_NORMAL as c::c_uint,
+
+ /// `POSIX_FADV_SEQUENTIAL`
+ Sequential = c::POSIX_FADV_SEQUENTIAL as c::c_uint,
+
+ /// `POSIX_FADV_RANDOM`
+ Random = c::POSIX_FADV_RANDOM as c::c_uint,
+
+ /// `POSIX_FADV_NOREUSE`
+ NoReuse = c::POSIX_FADV_NOREUSE as c::c_uint,
+
+ /// `POSIX_FADV_WILLNEED`
+ WillNeed = c::POSIX_FADV_WILLNEED as c::c_uint,
+
+ /// `POSIX_FADV_DONTNEED`
+ DontNeed = c::POSIX_FADV_DONTNEED as c::c_uint,
+}
+
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+bitflags! {
+ /// `MFD_*` constants for use with [`memfd_create`].
+ ///
+ /// [`memfd_create`]: crate::fs::memfd_create
+ pub struct MemfdFlags: c::c_uint {
+ /// `MFD_CLOEXEC`
+ const CLOEXEC = c::MFD_CLOEXEC;
+
+ /// `MFD_ALLOW_SEALING`
+ const ALLOW_SEALING = c::MFD_ALLOW_SEALING;
+
+ /// `MFD_HUGETLB` (since Linux 4.14)
+ const HUGETLB = c::MFD_HUGETLB;
+
+ /// `MFD_HUGE_64KB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_64KB = c::MFD_HUGE_64KB;
+ /// `MFD_HUGE_512JB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_512KB = c::MFD_HUGE_512KB;
+ /// `MFD_HUGE_1MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_1MB = c::MFD_HUGE_1MB;
+ /// `MFD_HUGE_2MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_2MB = c::MFD_HUGE_2MB;
+ /// `MFD_HUGE_8MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_8MB = c::MFD_HUGE_8MB;
+ /// `MFD_HUGE_16MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_16MB = c::MFD_HUGE_16MB;
+ /// `MFD_HUGE_32MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_32MB = c::MFD_HUGE_32MB;
+ /// `MFD_HUGE_256MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_256MB = c::MFD_HUGE_256MB;
+ /// `MFD_HUGE_512MB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_512MB = c::MFD_HUGE_512MB;
+ /// `MFD_HUGE_1GB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_1GB = c::MFD_HUGE_1GB;
+ /// `MFD_HUGE_2GB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_2GB = c::MFD_HUGE_2GB;
+ /// `MFD_HUGE_16GB`
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const HUGE_16GB = c::MFD_HUGE_16GB;
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "linux",
+))]
+bitflags! {
+ /// `F_SEAL_*` constants for use with [`fcntl_add_seals`] and
+ /// [`fcntl_get_seals`].
+ ///
+ /// [`fcntl_add_seals`]: crate::fs::fcntl_add_seals
+ /// [`fcntl_get_seals`]: crate::fs::fcntl_get_seals
+ pub struct SealFlags: i32 {
+ /// `F_SEAL_SEAL`.
+ const SEAL = c::F_SEAL_SEAL;
+ /// `F_SEAL_SHRINK`.
+ const SHRINK = c::F_SEAL_SHRINK;
+ /// `F_SEAL_GROW`.
+ const GROW = c::F_SEAL_GROW;
+ /// `F_SEAL_WRITE`.
+ const WRITE = c::F_SEAL_WRITE;
+ /// `F_SEAL_FUTURE_WRITE` (since Linux 5.1)
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ const FUTURE_WRITE = c::F_SEAL_FUTURE_WRITE;
+ }
+}
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+bitflags! {
+ /// `STATX_*` constants for use with [`statx`].
+ ///
+ /// [`statx`]: crate::fs::statx
+ pub struct StatxFlags: u32 {
+ /// `STATX_TYPE`
+ const TYPE = c::STATX_TYPE;
+
+ /// `STATX_MODE`
+ const MODE = c::STATX_MODE;
+
+ /// `STATX_NLINK`
+ const NLINK = c::STATX_NLINK;
+
+ /// `STATX_UID`
+ const UID = c::STATX_UID;
+
+ /// `STATX_GID`
+ const GID = c::STATX_GID;
+
+ /// `STATX_ATIME`
+ const ATIME = c::STATX_ATIME;
+
+ /// `STATX_MTIME`
+ const MTIME = c::STATX_MTIME;
+
+ /// `STATX_CTIME`
+ const CTIME = c::STATX_CTIME;
+
+ /// `STATX_INO`
+ const INO = c::STATX_INO;
+
+ /// `STATX_SIZE`
+ const SIZE = c::STATX_SIZE;
+
+ /// `STATX_BLOCKS`
+ const BLOCKS = c::STATX_BLOCKS;
+
+ /// `STATX_BASIC_STATS`
+ const BASIC_STATS = c::STATX_BASIC_STATS;
+
+ /// `STATX_BTIME`
+ const BTIME = c::STATX_BTIME;
+
+ /// `STATX_MNT_ID` (since Linux 5.8)
+ const MNT_ID = c::STATX_MNT_ID;
+
+ /// `STATX_ALL`
+ const ALL = c::STATX_ALL;
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+bitflags! {
+ /// `STATX_*` constants for use with [`statx`].
+ ///
+ /// [`statx`]: crate::fs::statx
+ pub struct StatxFlags: u32 {
+ /// `STATX_TYPE`
+ const TYPE = 0x0001;
+
+ /// `STATX_MODE`
+ const MODE = 0x0002;
+
+ /// `STATX_NLINK`
+ const NLINK = 0x0004;
+
+ /// `STATX_UID`
+ const UID = 0x0008;
+
+ /// `STATX_GID`
+ const GID = 0x0010;
+
+ /// `STATX_ATIME`
+ const ATIME = 0x0020;
+
+ /// `STATX_MTIME`
+ const MTIME = 0x0040;
+
+ /// `STATX_CTIME`
+ const CTIME = 0x0080;
+
+ /// `STATX_INO`
+ const INO = 0x0100;
+
+ /// `STATX_SIZE`
+ const SIZE = 0x0200;
+
+ /// `STATX_BLOCKS`
+ const BLOCKS = 0x0400;
+
+ /// `STATX_BASIC_STATS`
+ const BASIC_STATS = 0x07ff;
+
+ /// `STATX_BTIME`
+ const BTIME = 0x800;
+
+ /// `STATX_MNT_ID` (since Linux 5.8)
+ const MNT_ID = 0x1000;
+
+ /// `STATX_ALL`
+ const ALL = 0xfff;
+ }
+}
+
+#[cfg(not(any(
+ target_os = "illumos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+)))]
+bitflags! {
+ /// `FALLOC_FL_*` constants for use with [`fallocate`].
+ ///
+ /// [`fallocate`]: crate::fs::fallocate
+ pub struct FallocateFlags: i32 {
+ /// `FALLOC_FL_KEEP_SIZE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "wasi",
+ )))]
+ const KEEP_SIZE = c::FALLOC_FL_KEEP_SIZE;
+ /// `FALLOC_FL_PUNCH_HOLE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "wasi",
+ )))]
+ const PUNCH_HOLE = c::FALLOC_FL_PUNCH_HOLE;
+ /// `FALLOC_FL_NO_HIDE_STALE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "wasi",
+ )))]
+ const NO_HIDE_STALE = c::FALLOC_FL_NO_HIDE_STALE;
+ /// `FALLOC_FL_COLLAPSE_RANGE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const COLLAPSE_RANGE = c::FALLOC_FL_COLLAPSE_RANGE;
+ /// `FALLOC_FL_ZERO_RANGE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const ZERO_RANGE = c::FALLOC_FL_ZERO_RANGE;
+ /// `FALLOC_FL_INSERT_RANGE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const INSERT_RANGE = c::FALLOC_FL_INSERT_RANGE;
+ /// `FALLOC_FL_UNSHARE_RANGE`
+ #[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const UNSHARE_RANGE = c::FALLOC_FL_UNSHARE_RANGE;
+ }
+}
+
+/// `LOCK_*` constants for use with [`flock`]
+///
+/// [`flock`]: crate::fs::flock
+#[cfg(not(target_os = "wasi"))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(i32)]
+pub enum FlockOperation {
+ /// `LOCK_SH`
+ LockShared = c::LOCK_SH,
+ /// `LOCK_EX`
+ LockExclusive = c::LOCK_EX,
+ /// `LOCK_UN`
+ Unlock = c::LOCK_UN,
+ /// `LOCK_SH | LOCK_NB`
+ NonBlockingLockShared = c::LOCK_SH | c::LOCK_NB,
+ /// `LOCK_EX | LOCK_NB`
+ NonBlockingLockExclusive = c::LOCK_EX | c::LOCK_NB,
+ /// `LOCK_UN | LOCK_NB`
+ NonBlockingUnlock = c::LOCK_UN | c::LOCK_NB,
+}
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "emscripten",
+ target_os = "l4re",
+)))]
+pub type Stat = c::stat;
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+#[cfg(any(
+ all(
+ any(target_os = "android", target_os = "linux"),
+ target_pointer_width = "64",
+ ),
+ target_os = "emscripten",
+ target_os = "l4re",
+))]
+pub type Stat = c::stat64;
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+// On 32-bit, Linux's `struct stat64` has a 32-bit `st_mtime` and friends, so
+// we use our own struct, populated from `statx` where possible, to avoid the
+// y2038 bug.
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ target_pointer_width = "32",
+))]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+#[allow(missing_docs)]
+pub struct Stat {
+ pub st_dev: u64,
+ pub st_mode: u32,
+ pub st_nlink: u32,
+ pub st_uid: u32,
+ pub st_gid: u32,
+ pub st_rdev: u64,
+ pub st_size: i64,
+ pub st_blksize: u32,
+ pub st_blocks: u64,
+ pub st_atime: u64,
+ pub st_atime_nsec: u32,
+ pub st_mtime: u64,
+ pub st_mtime_nsec: u32,
+ pub st_ctime: u64,
+ pub st_ctime_nsec: u32,
+ pub st_ino: u64,
+}
+
+/// `struct statfs` for use with [`fstatfs`].
+///
+/// [`fstatfs`]: crate::fs::fstatfs
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "illumos",
+ target_os = "linux",
+ target_os = "l4re",
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+#[allow(clippy::module_name_repetitions)]
+pub type StatFs = c::statfs;
+
+/// `struct statfs` for use with [`fstatfs`].
+///
+/// [`fstatfs`]: crate::fs::fstatfs
+#[cfg(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "emscripten",
+ target_os = "l4re",
+))]
+pub type StatFs = c::statfs64;
+
+/// `struct statx` for use with [`statx`].
+///
+/// [`statx`]: crate::fs::statx
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+// Use the glibc `struct statx`.
+pub type Statx = c::statx;
+
+/// `struct statx_timestamp` for use with [`Statx`].
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+// Use the glibc `struct statx_timestamp`.
+pub type StatxTimestamp = c::statx;
+
+/// `struct statx` for use with [`statx`].
+///
+/// [`statx`]: crate::fs::statx
+// Non-glibc ABIs don't currently declare a `struct statx`, so we declare it
+// ourselves.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct Statx {
+ pub stx_mask: u32,
+ pub stx_blksize: u32,
+ pub stx_attributes: u64,
+ pub stx_nlink: u32,
+ pub stx_uid: u32,
+ pub stx_gid: u32,
+ pub stx_mode: u16,
+ __statx_pad1: [u16; 1],
+ pub stx_ino: u64,
+ pub stx_size: u64,
+ pub stx_blocks: u64,
+ pub stx_attributes_mask: u64,
+ pub stx_atime: StatxTimestamp,
+ pub stx_btime: StatxTimestamp,
+ pub stx_ctime: StatxTimestamp,
+ pub stx_mtime: StatxTimestamp,
+ pub stx_rdev_major: u32,
+ pub stx_rdev_minor: u32,
+ pub stx_dev_major: u32,
+ pub stx_dev_minor: u32,
+ pub stx_mnt_id: u64,
+ __statx_pad2: u64,
+ __statx_pad3: [u64; 12],
+}
+
+/// `struct statx_timestamp` for use with [`Statx`].
+// Non-glibc ABIs don't currently declare a `struct statx_timestamp`, so we
+// declare it ourselves.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct StatxTimestamp {
+ pub tv_sec: i64,
+ pub tv_nsec: u32,
+ pub __statx_timestamp_pad1: [i32; 1],
+}
+
+/// `mode_t`
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+pub type RawMode = c::mode_t;
+
+/// `mode_t`
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+pub type RawMode = c::c_uint;
+
+/// `dev_t`
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+pub type Dev = c::dev_t;
+
+/// `dev_t`
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+pub type Dev = c::c_ulonglong;
+
+/// `__fsword_t`
+#[cfg(all(
+ target_os = "linux",
+ not(target_env = "musl"),
+ not(target_arch = "s390x"),
+))]
+pub type FsWord = c::__fsword_t;
+
+/// `__fsword_t`
+#[cfg(all(
+ any(target_os = "android", all(target_os = "linux", target_env = "musl")),
+ target_pointer_width = "32",
+))]
+pub type FsWord = u32;
+
+/// `__fsword_t`
+#[cfg(all(
+ any(target_os = "android", all(target_os = "linux", target_env = "musl")),
+ not(target_arch = "s390x"),
+ target_pointer_width = "64",
+))]
+pub type FsWord = u64;
+
+/// `__fsword_t`
+// s390x uses `u32` for `statfs` entries, even though `__fsword_t` is `u64`.
+#[cfg(all(target_os = "linux", target_arch = "s390x"))]
+pub type FsWord = u32;
+
+#[cfg(not(target_os = "redox"))]
+pub use c::{UTIME_NOW, UTIME_OMIT};
+
+/// `PROC_SUPER_MAGIC`—The magic number for the procfs filesystem.
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ not(target_env = "musl"),
+))]
+pub const PROC_SUPER_MAGIC: FsWord = c::PROC_SUPER_MAGIC as FsWord;
+
+/// `NFS_SUPER_MAGIC`—The magic number for the NFS filesystem.
+#[cfg(all(
+ any(target_os = "android", target_os = "linux"),
+ not(target_env = "musl"),
+))]
+pub const NFS_SUPER_MAGIC: FsWord = c::NFS_SUPER_MAGIC as FsWord;
+
+/// `PROC_SUPER_MAGIC`—The magic number for the procfs filesystem.
+#[cfg(all(any(target_os = "android", target_os = "linux"), target_env = "musl"))]
+pub const PROC_SUPER_MAGIC: FsWord = 0x0000_9fa0;
+
+/// `NFS_SUPER_MAGIC`—The magic number for the NFS filesystem.
+#[cfg(all(any(target_os = "android", target_os = "linux"), target_env = "musl"))]
+pub const NFS_SUPER_MAGIC: FsWord = 0x0000_6969;
+
+/// `copyfile_state_t`—State for use with [`fcopyfile`].
+///
+/// [`fcopyfile`]: crate::fs::fcopyfile
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+#[allow(non_camel_case_types)]
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct copyfile_state_t(pub(crate) *mut c::c_void);