<commit_before>use crate::errno::Errno;
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
use std::ffi::OsString;
#[cfg(not(target_os = "redox"))]
use std::os::raw;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;

#[cfg(feature = "fs")]
use crate::{sys::stat::Mode, NixPath, Result};
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr; // For splice and copy_file_range

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "emscripten",
    target_os = "fuchsia",
    target_os = "wasi",
    target_env = "uclibc",
    target_os = "freebsd"
))]
#[cfg(feature = "fs")]
pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice};

#[cfg(not(target_os = "redox"))]
#[cfg(any(feature = "fs", feature = "process"))]
libc_bitflags! {
    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "process"))))]
    pub struct AtFlags: c_int {
        AT_REMOVEDIR;
        AT_SYMLINK_FOLLOW;
        AT_SYMLINK_NOFOLLOW;
        #[cfg(any(target_os = "android", target_os = "linux"))]
        AT_NO_AUTOMOUNT;
        #[cfg(any(target_os = "android", target_os = "linux"))]
        AT_EMPTY_PATH;
        #[cfg(any(target_os = "illumos", target_os = "solaris"))]
        AT_EACCESS;
    }
}

#[cfg(any(feature = "fs", feature = "term"))]
libc_bitflags!(
    /// Configuration options for opened files.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))]
    pub struct OFlag: c_int {
        /// Mask for the access mode of the file.
        O_ACCMODE;
        /// Use alternate I/O semantics.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_ALT_IO;
        /// Open the file in append-only mode.
        O_APPEND;
        /// Generate a signal when input or output becomes possible.
        #[cfg(not(any(target_os = "illumos", target_os = "solaris", target_os = "haiku")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_ASYNC;
        /// Closes the file descriptor once an `execve` call is made.
        ///
        /// Also sets the file offset to the beginning of the file.
        O_CLOEXEC;
        /// Create the file if it does not exist.
        O_CREAT;
        /// Try to minimize cache effects of the I/O for this file.
        #[cfg(any(target_os = "android",
                  target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "linux",
                  target_os = "netbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DIRECT;
        /// If the specified path isn't a directory, fail.
        #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DIRECTORY;
        /// Implicitly follow each `write()` with an `fdatasync()`.
        #[cfg(any(target_os = "android",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DSYNC;
        /// Error out if a file was not created.
        O_EXCL;
        /// Open for execute only.
        #[cfg(target_os = "freebsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_EXEC;
        /// Open with an exclusive file lock.
        #[cfg(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "ios",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_EXLOCK;
        /// Same as `O_SYNC`.
        #[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",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_FSYNC;
        /// Allow files whose sizes can't be represented in an `off_t` to be opened.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_LARGEFILE;
        /// Do not update the file last access time during `read(2)`s.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOATIME;
        /// Don't attach the device as the process' controlling terminal.
        #[cfg(not(target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOCTTY;
        /// Same as `O_NONBLOCK`.
        #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NDELAY;
        /// `open()` will fail if the given path is a symbolic link.
        O_NOFOLLOW;
        /// When possible, open the file in nonblocking mode.
        O_NONBLOCK;
        /// Don't deliver `SIGPIPE`.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOSIGPIPE;
        /// Obtain a file descriptor for low-level access.
        ///
        /// The file itself is not opened and other file operations will fail.
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_PATH;
        /// Only allow reading.
        ///
        /// This should not be combined with `O_WRONLY` or `O_RDWR`.
        O_RDONLY;
        /// Allow both reading and writing.
        ///
        /// This should not be combined with `O_WRONLY` or `O_RDONLY`.
        O_RDWR;
        /// Similar to `O_DSYNC` but applies to `read`s instead.
        #[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_RSYNC;
        /// Skip search permission checks.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SEARCH;
        /// Open with a shared file lock.
        #[cfg(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "ios",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SHLOCK;
        /// Implicitly follow each `write()` with an `fsync()`.
        #[cfg(not(target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SYNC;
        /// Create an unnamed temporary file.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_TMPFILE;
        /// Truncate an existing regular file to 0 length if it allows writing.
        O_TRUNC;
        /// Restore default TTY attributes.
        #[cfg(target_os = "freebsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_TTY_INIT;
        /// Only allow writing.
        ///
        /// This should not be combined with `O_RDONLY` or `O_RDWR`.
        O_WRONLY;
    }
);

feature! {
#![feature = "fs"]

// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: Mode) -> Result<RawFd> {
    let fd = path.with_nix_path(|cstr| {
        unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
    })?;

    Errno::result(fd)
}

// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
#[cfg(not(target_os = "redox"))]
pub fn openat<P: ?Sized + NixPath>(
    dirfd: RawFd,
    path: &P,
    oflag: OFlag,
    mode: Mode,
) -> Result<RawFd> {
    let fd = path.with_nix_path(|cstr| {
        unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
    })?;
    Errno::result(fd)
}

#[cfg(not(target_os = "redox"))]
pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
    old_dirfd: Option<RawFd>,
    old_path: &P1,
    new_dirfd: Option<RawFd>,
    new_path: &P2,
) -> Result<()> {
    let res = old_path.with_nix_path(|old_cstr| {
        new_path.with_nix_path(|new_cstr| unsafe {
            libc::renameat(
                at_rawfd(old_dirfd),
                old_cstr.as_ptr(),
                at_rawfd(new_dirfd),
                new_cstr.as_ptr(),
            )
        })
    })??;
    Errno::result(res).map(drop)
}
}

#[cfg(all(target_os = "linux", target_env = "gnu",))]
#[cfg(feature = "fs")]
libc_bitflags! {
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct RenameFlags: u32 {
        RENAME_EXCHANGE;
        RENAME_NOREPLACE;
        RENAME_WHITEOUT;
    }
}

feature! {
#![feature = "fs"]
#[cfg(all(
    target_os = "linux",
    target_env = "gnu",
))]
pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
    old_dirfd: Option<RawFd>,
    old_path: &P1,
    new_dirfd: Option<RawFd>,
    new_path: &P2,
    flags: RenameFlags,
) -> Result<()> {
    let res = old_path.with_nix_path(|old_cstr| {
        new_path.with_nix_path(|new_cstr| unsafe {
            libc::renameat2(
                at_rawfd(old_dirfd),
                old_cstr.as_ptr(),
                at_rawfd(new_dirfd),
                new_cstr.as_ptr(),
                flags.bits(),
            )
        })
    })??;
    Errno::result(res).map(drop)
}

fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
    unsafe { v.set_len(len as usize) }
    v.shrink_to_fit();
    Ok(OsString::from_vec(v.to_vec()))
}

fn readlink_maybe_at<P: ?Sized + NixPath>(
    dirfd: Option<RawFd>,
    path: &P,
    v: &mut Vec<u8>,
) -> Result<libc::ssize_t> {
    path.with_nix_path(|cstr| unsafe {
        match dirfd {
            #[cfg(target_os = "redox")]
            Some(_) => unreachable!(),
            #[cfg(not(target_os = "redox"))]
            Some(dirfd) => libc::readlinkat(
                dirfd,
                cstr.as_ptr(),
                v.as_mut_ptr() as *mut c_char,
                v.capacity() as size_t,
            ),
            None => libc::readlink(
                cstr.as_ptr(),
                v.as_mut_ptr() as *mut c_char,
                v.capacity() as size_t,
            ),
        }
    })
}

fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result<OsString> {
    let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
    // simple case: result is strictly less than `PATH_MAX`
    let res = readlink_maybe_at(dirfd, path, &mut v)?;
    let len = Errno::result(res)?;
    debug_assert!(len >= 0);
    if (len as usize) < v.capacity() {
        return wrap_readlink_result(v, res);
    }
    // Uh oh, the result is too long...
    // Let's try to ask lstat how many bytes to allocate.
    let reported_size = match dirfd {
        #[cfg(target_os = "redox")]
        Some(_) => unreachable!(),
        #[cfg(any(target_os = "android", target_os = "linux"))]
        Some(dirfd) => {
            let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() };
            super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW)
        },
        #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))]
        Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW),
        None => super::sys::stat::lstat(path)
    }
        .map(|x| x.st_size)
        .unwrap_or(0);
    let mut try_size = if reported_size > 0 {
        // Note: even if `lstat`'s apparently valid answer turns out to be
        // wrong, we will still read the full symlink no matter what.
        reported_size as usize + 1
    } else {
        // If lstat doesn't cooperate, or reports an error, be a little less
        // precise.
        (libc::PATH_MAX as usize).max(128) << 1
    };
    loop {
        v.reserve_exact(try_size);
        let res = readlink_maybe_at(dirfd, path, &mut v)?;
        let len = Errno::result(res)?;
        debug_assert!(len >= 0);
        if (len as usize) < v.capacity() {
            break wrap_readlink_result(v, res);
        } else {
            // Ugh! Still not big enough!
            match try_size.checked_shl(1) {
                Some(next_size) => try_size = next_size,
                // It's absurd that this would happen, but handle it sanely
                // anyway.
                None => break Err(Errno::ENAMETOOLONG),
            }
        }
    }
}

pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
    inner_readlink(None, path)
}

#[cfg(not(target_os = "redox"))]
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
    inner_readlink(Some(dirfd), path)
}

/// Computes the raw fd consumed by a function of the form `*at`.
#[cfg(not(target_os = "redox"))]
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
    match fd {
        None => libc::AT_FDCWD,
        Some(fd) => fd,
    }
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(feature = "fs")]
libc_bitflags!(
    /// Additional flags for file sealing, which allows for limiting operations on a file.
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct SealFlag: c_int {
        /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`.
        F_SEAL_SEAL;
        /// The file cannot be reduced in size.
        F_SEAL_SHRINK;
        /// The size of the file cannot be increased.
        F_SEAL_GROW;
        /// The file contents cannot be modified.
        F_SEAL_WRITE;
    }
);

#[cfg(feature = "fs")]
libc_bitflags!(
    /// Additional configuration flags for `fcntl`'s `F_SETFD`.
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct FdFlag: c_int {
        /// The file descriptor will automatically be closed during a successful `execve(2)`.
        FD_CLOEXEC;
    }
);

feature! {
#![feature = "fs"]

#[cfg(not(target_os = "redox"))]
#[derive(Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg<'a> {
    F_DUPFD(RawFd),
    F_DUPFD_CLOEXEC(RawFd),
    F_GETFD,
    F_SETFD(FdFlag), // FD_FLAGS
    F_GETFL,
    F_SETFL(OFlag), // O_NONBLOCK
    F_SETLK(&'a libc::flock),
    F_SETLKW(&'a libc::flock),
    F_GETLK(&'a mut libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_SETLK(&'a libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_SETLKW(&'a libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_GETLK(&'a mut libc::flock),
    #[cfg(any(target_os = "android", target_os = "linux"))]
    F_ADD_SEALS(SealFlag),
    #[cfg(any(target_os = "android", target_os = "linux"))]
    F_GET_SEALS,
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    F_FULLFSYNC,
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_GETPIPE_SZ,
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_SETPIPE_SZ(c_int),
    // TODO: Rest of flags
}

#[cfg(target_os = "redox")]
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg {
    F_DUPFD(RawFd),
    F_DUPFD_CLOEXEC(RawFd),
    F_GETFD,
    F_SETFD(FdFlag), // FD_FLAGS
    F_GETFL,
    F_SETFL(OFlag), // O_NONBLOCK
}
pub use self::FcntlArg::*;

// TODO: Figure out how to handle value fcntl returns
pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
    let res = unsafe {
        match arg {
            F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd),
            F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd),
            F_GETFD => libc::fcntl(fd, libc::F_GETFD),
            F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()),
            F_GETFL => libc::fcntl(fd, libc::F_GETFL),
            F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()),
            #[cfg(not(target_os = "redox"))]
            F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock),
            #[cfg(not(target_os = "redox"))]
            F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
            #[cfg(not(target_os = "redox"))]
            F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
            #[cfg(any(target_os = "macos", target_os = "ios"))]
            F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC),
            #[cfg(any(target_os = "linux", target_os = "android"))]
            F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),<commit_msg>feat nix-rust#1733: add F_GET_SEALS and F_ADD_SEALS on FreeBSD<commit_after>use crate::errno::Errno;
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
use std::ffi::OsString;
#[cfg(not(target_os = "redox"))]
use std::os::raw;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;

#[cfg(feature = "fs")]
use crate::{sys::stat::Mode, NixPath, Result};
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr; // For splice and copy_file_range

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "emscripten",
    target_os = "fuchsia",
    target_os = "wasi",
    target_env = "uclibc",
    target_os = "freebsd"
))]
#[cfg(feature = "fs")]
pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice};

#[cfg(not(target_os = "redox"))]
#[cfg(any(feature = "fs", feature = "process"))]
libc_bitflags! {
    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "process"))))]
    pub struct AtFlags: c_int {
        AT_REMOVEDIR;
        AT_SYMLINK_FOLLOW;
        AT_SYMLINK_NOFOLLOW;
        #[cfg(any(target_os = "android", target_os = "linux"))]
        AT_NO_AUTOMOUNT;
        #[cfg(any(target_os = "android", target_os = "linux"))]
        AT_EMPTY_PATH;
        #[cfg(any(target_os = "illumos", target_os = "solaris"))]
        AT_EACCESS;
    }
}

#[cfg(any(feature = "fs", feature = "term"))]
libc_bitflags!(
    /// Configuration options for opened files.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))]
    pub struct OFlag: c_int {
        /// Mask for the access mode of the file.
        O_ACCMODE;
        /// Use alternate I/O semantics.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_ALT_IO;
        /// Open the file in append-only mode.
        O_APPEND;
        /// Generate a signal when input or output becomes possible.
        #[cfg(not(any(target_os = "illumos", target_os = "solaris", target_os = "haiku")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_ASYNC;
        /// Closes the file descriptor once an `execve` call is made.
        ///
        /// Also sets the file offset to the beginning of the file.
        O_CLOEXEC;
        /// Create the file if it does not exist.
        O_CREAT;
        /// Try to minimize cache effects of the I/O for this file.
        #[cfg(any(target_os = "android",
                  target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "linux",
                  target_os = "netbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DIRECT;
        /// If the specified path isn't a directory, fail.
        #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DIRECTORY;
        /// Implicitly follow each `write()` with an `fdatasync()`.
        #[cfg(any(target_os = "android",
                  target_os = "ios",
                  target_os = "linux",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_DSYNC;
        /// Error out if a file was not created.
        O_EXCL;
        /// Open for execute only.
        #[cfg(target_os = "freebsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_EXEC;
        /// Open with an exclusive file lock.
        #[cfg(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "ios",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_EXLOCK;
        /// Same as `O_SYNC`.
        #[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",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_FSYNC;
        /// Allow files whose sizes can't be represented in an `off_t` to be opened.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_LARGEFILE;
        /// Do not update the file last access time during `read(2)`s.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOATIME;
        /// Don't attach the device as the process' controlling terminal.
        #[cfg(not(target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOCTTY;
        /// Same as `O_NONBLOCK`.
        #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NDELAY;
        /// `open()` will fail if the given path is a symbolic link.
        O_NOFOLLOW;
        /// When possible, open the file in nonblocking mode.
        O_NONBLOCK;
        /// Don't deliver `SIGPIPE`.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_NOSIGPIPE;
        /// Obtain a file descriptor for low-level access.
        ///
        /// The file itself is not opened and other file operations will fail.
        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_PATH;
        /// Only allow reading.
        ///
        /// This should not be combined with `O_WRONLY` or `O_RDWR`.
        O_RDONLY;
        /// Allow both reading and writing.
        ///
        /// This should not be combined with `O_WRONLY` or `O_RDONLY`.
        O_RDWR;
        /// Similar to `O_DSYNC` but applies to `read`s instead.
        #[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_RSYNC;
        /// Skip search permission checks.
        #[cfg(target_os = "netbsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SEARCH;
        /// Open with a shared file lock.
        #[cfg(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "ios",
                  target_os = "macos",
                  target_os = "netbsd",
                  target_os = "openbsd",
                  target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SHLOCK;
        /// Implicitly follow each `write()` with an `fsync()`.
        #[cfg(not(target_os = "redox"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_SYNC;
        /// Create an unnamed temporary file.
        #[cfg(any(target_os = "android", target_os = "linux"))]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_TMPFILE;
        /// Truncate an existing regular file to 0 length if it allows writing.
        O_TRUNC;
        /// Restore default TTY attributes.
        #[cfg(target_os = "freebsd")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        O_TTY_INIT;
        /// Only allow writing.
        ///
        /// This should not be combined with `O_RDONLY` or `O_RDWR`.
        O_WRONLY;
    }
);

feature! {
#![feature = "fs"]

// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: Mode) -> Result<RawFd> {
    let fd = path.with_nix_path(|cstr| {
        unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
    })?;

    Errno::result(fd)
}

// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
#[cfg(not(target_os = "redox"))]
pub fn openat<P: ?Sized + NixPath>(
    dirfd: RawFd,
    path: &P,
    oflag: OFlag,
    mode: Mode,
) -> Result<RawFd> {
    let fd = path.with_nix_path(|cstr| {
        unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
    })?;
    Errno::result(fd)
}

#[cfg(not(target_os = "redox"))]
pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
    old_dirfd: Option<RawFd>,
    old_path: &P1,
    new_dirfd: Option<RawFd>,
    new_path: &P2,
) -> Result<()> {
    let res = old_path.with_nix_path(|old_cstr| {
        new_path.with_nix_path(|new_cstr| unsafe {
            libc::renameat(
                at_rawfd(old_dirfd),
                old_cstr.as_ptr(),
                at_rawfd(new_dirfd),
                new_cstr.as_ptr(),
            )
        })
    })??;
    Errno::result(res).map(drop)
}
}

#[cfg(all(target_os = "linux", target_env = "gnu",))]
#[cfg(feature = "fs")]
libc_bitflags! {
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct RenameFlags: u32 {
        RENAME_EXCHANGE;
        RENAME_NOREPLACE;
        RENAME_WHITEOUT;
    }
}

feature! {
#![feature = "fs"]
#[cfg(all(
    target_os = "linux",
    target_env = "gnu",
))]
pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
    old_dirfd: Option<RawFd>,
    old_path: &P1,
    new_dirfd: Option<RawFd>,
    new_path: &P2,
    flags: RenameFlags,
) -> Result<()> {
    let res = old_path.with_nix_path(|old_cstr| {
        new_path.with_nix_path(|new_cstr| unsafe {
            libc::renameat2(
                at_rawfd(old_dirfd),
                old_cstr.as_ptr(),
                at_rawfd(new_dirfd),
                new_cstr.as_ptr(),
                flags.bits(),
            )
        })
    })??;
    Errno::result(res).map(drop)
}

fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
    unsafe { v.set_len(len as usize) }
    v.shrink_to_fit();
    Ok(OsString::from_vec(v.to_vec()))
}

fn readlink_maybe_at<P: ?Sized + NixPath>(
    dirfd: Option<RawFd>,
    path: &P,
    v: &mut Vec<u8>,
) -> Result<libc::ssize_t> {
    path.with_nix_path(|cstr| unsafe {
        match dirfd {
            #[cfg(target_os = "redox")]
            Some(_) => unreachable!(),
            #[cfg(not(target_os = "redox"))]
            Some(dirfd) => libc::readlinkat(
                dirfd,
                cstr.as_ptr(),
                v.as_mut_ptr() as *mut c_char,
                v.capacity() as size_t,
            ),
            None => libc::readlink(
                cstr.as_ptr(),
                v.as_mut_ptr() as *mut c_char,
                v.capacity() as size_t,
            ),
        }
    })
}

fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result<OsString> {
    let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
    // simple case: result is strictly less than `PATH_MAX`
    let res = readlink_maybe_at(dirfd, path, &mut v)?;
    let len = Errno::result(res)?;
    debug_assert!(len >= 0);
    if (len as usize) < v.capacity() {
        return wrap_readlink_result(v, res);
    }
    // Uh oh, the result is too long...
    // Let's try to ask lstat how many bytes to allocate.
    let reported_size = match dirfd {
        #[cfg(target_os = "redox")]
        Some(_) => unreachable!(),
        #[cfg(any(target_os = "android", target_os = "linux"))]
        Some(dirfd) => {
            let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() };
            super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW)
        },
        #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))]
        Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW),
        None => super::sys::stat::lstat(path)
    }
        .map(|x| x.st_size)
        .unwrap_or(0);
    let mut try_size = if reported_size > 0 {
        // Note: even if `lstat`'s apparently valid answer turns out to be
        // wrong, we will still read the full symlink no matter what.
        reported_size as usize + 1
    } else {
        // If lstat doesn't cooperate, or reports an error, be a little less
        // precise.
        (libc::PATH_MAX as usize).max(128) << 1
    };
    loop {
        v.reserve_exact(try_size);
        let res = readlink_maybe_at(dirfd, path, &mut v)?;
        let len = Errno::result(res)?;
        debug_assert!(len >= 0);
        if (len as usize) < v.capacity() {
            break wrap_readlink_result(v, res);
        } else {
            // Ugh! Still not big enough!
            match try_size.checked_shl(1) {
                Some(next_size) => try_size = next_size,
                // It's absurd that this would happen, but handle it sanely
                // anyway.
                None => break Err(Errno::ENAMETOOLONG),
            }
        }
    }
}

pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
    inner_readlink(None, path)
}

#[cfg(not(target_os = "redox"))]
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
    inner_readlink(Some(dirfd), path)
}

/// Computes the raw fd consumed by a function of the form `*at`.
#[cfg(not(target_os = "redox"))]
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
    match fd {
        None => libc::AT_FDCWD,
        Some(fd) => fd,
    }
}
}

#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
#[cfg(feature = "fs")]
libc_bitflags!(
    /// Additional flags for file sealing, which allows for limiting operations on a file.
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct SealFlag: c_int {
        /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`.
        F_SEAL_SEAL;
        /// The file cannot be reduced in size.
        F_SEAL_SHRINK;
        /// The size of the file cannot be increased.
        F_SEAL_GROW;
        /// The file contents cannot be modified.
        F_SEAL_WRITE;
    }
);

#[cfg(feature = "fs")]
libc_bitflags!(
    /// Additional configuration flags for `fcntl`'s `F_SETFD`.
    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
    pub struct FdFlag: c_int {
        /// The file descriptor will automatically be closed during a successful `execve(2)`.
        FD_CLOEXEC;
    }
);

feature! {
#![feature = "fs"]

#[cfg(not(target_os = "redox"))]
#[derive(Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg<'a> {
    F_DUPFD(RawFd),
    F_DUPFD_CLOEXEC(RawFd),
    F_GETFD,
    F_SETFD(FdFlag), // FD_FLAGS
    F_GETFL,
    F_SETFL(OFlag), // O_NONBLOCK
    F_SETLK(&'a libc::flock),
    F_SETLKW(&'a libc::flock),
    F_GETLK(&'a mut libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_SETLK(&'a libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_SETLKW(&'a libc::flock),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_OFD_GETLK(&'a mut libc::flock),
    #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
    F_ADD_SEALS(SealFlag),
    #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
    F_GET_SEALS,
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    F_FULLFSYNC,
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_GETPIPE_SZ,
    #[cfg(any(target_os = "linux", target_os = "android"))]
    F_SETPIPE_SZ(c_int),
    // TODO: Rest of flags
}

#[cfg(target_os = "redox")]
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg {
    F_DUPFD(RawFd),
    F_DUPFD_CLOEXEC(RawFd),
    F_GETFD,
    F_SETFD(FdFlag), // FD_FLAGS
    F_GETFL,
    F_SETFL(OFlag), // O_NONBLOCK
}
pub use self::FcntlArg::*;

// TODO: Figure out how to handle value fcntl returns
pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
    let res = unsafe {
        match arg {
            F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd),
            F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd),
            F_GETFD => libc::fcntl(fd, libc::F_GETFD),
            F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()),
            F_GETFL => libc::fcntl(fd, libc::F_GETFL),
            F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()),
            #[cfg(not(target_os = "redox"))]
            F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock),
            #[cfg(not(target_os = "redox"))]
            F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
            #[cfg(not(target_os = "redox"))]
            F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
            #[cfg(any(target_os = "android", target_os = "linux"))]
            F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
            #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
            F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
            #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
            F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
            #[cfg(any(target_os = "macos", target_os = "ios"))]
            F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC),
            #[cfg(any(target_os = "linux", target_os = "android"))]
            F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),
{'fraction_non_alphanumeric': 0.16055520432208747, 'fraction_numerical': 0.0016667624576125065, 'mean_word_length': 2.355737704918033, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 141, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '50841', 'n_tokens_mistral': 12640, 'n_tokens_neox': 12501, 'n_words': 2654}