//
// Syd: rock-solid application kernel
// src/fd.rs: File descriptor utilities
//
// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0

//! Set of functions to manage file descriptors.

use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};

use btoi::btoi;
use libc::{
    c_char, c_int, c_long, c_uint, c_ulong, syscall, SYS_execveat, SYS_faccessat2, SYS_ioctl,
    SYS_kcmp, SYS_pidfd_getfd, SYS_pidfd_open, SYS_pidfd_send_signal, EBADF, O_NONBLOCK,
};
use nix::{
    errno::Errno,
    fcntl::{fcntl, AtFlags, FcntlArg, FdFlag, OFlag, SealFlag},
    sys::{
        socket::{
            getsockopt,
            sockopt::{ReceiveTimeout, SendTimeout},
        },
        stat::Mode,
    },
    unistd::{AccessFlags, Pid},
};

use crate::{
    compat::{
        fstatx, getdents64, statx, FsType, STATX_BASIC_STATS, STATX_INO, STATX_SIZE, TIOCEXCL,
        TIOCGEXCL, TIOCNXCL,
    },
    config::{
        DIRENT_BUF_SIZE, HAVE_AT_EXECVE_CHECK, HAVE_PIDFD_THREAD, HAVE_PROC_PID_FD_STAT_SIZE,
    },
    cookie::safe_close_range,
    fs::oflag_accmode,
    path::{XPath, XPathBuf},
    proc::proc_tgid,
    retry::retry_on_eintr,
};

/// SAFETY: AT_BADFD to be used a safe alternative to AT_FDCWD.
pub const AT_BADFD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(-EBADF) };

/// Sets or clears the append (O_APPEND) flag on a file descriptor.
pub fn set_append<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
    let flags = fcntl(&fd, FcntlArg::F_GETFL)?;

    let mut new_flags = flags;
    if state {
        new_flags |= OFlag::O_APPEND.bits();
    } else {
        new_flags &= !OFlag::O_APPEND.bits();
    }

    fcntl(&fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(new_flags))).map(drop)
}

/// Returns `true` if the given file descriptor is set to non-blocking mode.
pub fn get_nonblock<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    fcntl(fd, FcntlArg::F_GETFL).map(|flags| flags & O_NONBLOCK != 0)
}

/// Sets or clears the non-blocking (O_NONBLOCK) flag on a file descriptor.
pub fn set_nonblock<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
    let flags = fcntl(&fd, FcntlArg::F_GETFL)?;

    let mut new_flags = flags;
    if state {
        new_flags |= OFlag::O_NONBLOCK.bits();
    } else {
        new_flags &= !OFlag::O_NONBLOCK.bits();
    }

    fcntl(&fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(new_flags))).map(drop)
}

/// Sets or clears the close-on-exec (FD_CLOEXEC) flag on a file descriptor.
pub fn set_cloexec<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
    let flags = fcntl(&fd, FcntlArg::F_GETFD)?;

    let mut new_flags = flags;
    if state {
        new_flags |= FdFlag::FD_CLOEXEC.bits();
    } else {
        new_flags &= !FdFlag::FD_CLOEXEC.bits();
    }

    fcntl(
        &fd,
        FcntlArg::F_SETFD(FdFlag::from_bits_truncate(new_flags)),
    )
    .map(drop)
}

/// Closes the given file descriptor, panics on `Err(Errno::EBADF)`.
pub fn close<Fd: IntoRawFd>(fd: Fd) -> Result<(), Errno> {
    let fd = fd.into_raw_fd();

    // SAFETY: In libc we trust.
    match Errno::result(unsafe { libc::close(fd) }) {
        Ok(_) => Ok(()),
        Err(Errno::EBADF) => panic!("BUG: Attempt to close bad fd:{fd}, report a bug!"),
        Err(errno) => Err(errno),
    }
}

/// Safe wrapper for close_range(2).
pub fn close_range(first: c_uint, last: c_uint, flags: c_uint) -> Result<(), Errno> {
    safe_close_range(first, last, flags)
}

/// Close all file descriptors >= `fd`, equivalent to BSD's closefrom(2).
///
/// # Errors
///
/// Propagates any error returned by `close_range`.
pub fn closefrom(fd: c_uint) -> Result<(), Errno> {
    close_range(fd, RawFd::MAX as c_uint, 0)
}

/// Close all file descriptors in `close`.
///
/// `closefds` must be sorted ascending and contain no duplicates;
/// otherwise returns `Err(Errno::EINVAL)`.
///
/// # Errors
///
/// Returns on the first syscall error encountered, or
/// `Err(Errno::EINVAL)` if `close` is not strictly ascending.
pub fn closeall(closefds: &[c_uint]) -> Result<(), Errno> {
    // no-op if close is empty.
    if closefds.is_empty() {
        return Ok(());
    }

    // Validate that `close` is strictly ascending and unique.
    if closefds.windows(2).any(|w| w[0] >= w[1]) {
        return Err(Errno::EINVAL);
    }

    let mut first = closefds[0];
    let mut last = first;

    #[expect(clippy::arithmetic_side_effects)]
    for &fd in &closefds[1..] {
        if fd != last + 1 {
            close_range(first, last, 0)?;
            first = fd;
        }
        last = fd;
    }
    close_range(first, last, 0)
}

/// Close all file descriptors except those in `exceptions`.
///
/// `exceptions` must be sorted ascending and contain no duplicates;
/// otherwise returns `Err(Errno::EINVAL)`.
///
/// Uses `close_range(2)` under the hood to efficiently close the
/// non-exempt descriptors.
///
/// # Errors
///
/// Returns on the first syscall error encountered, or
/// `Err(Errno::EINVAL)` if `exceptions` is not strictly ascending.
pub fn closeexcept(exceptions: &[c_uint]) -> Result<(), Errno> {
    // Validate that `exceptions` is strictly ascending and unique.
    if exceptions.windows(2).any(|w| w[0] >= w[1]) {
        return Err(Errno::EINVAL);
    }

    // If no exceptions, close everything.
    if exceptions.is_empty() {
        return closefrom(0);
    }

    // Use a wider integer for range computations to avoid overflow.
    let mut next: u64 = 0;

    #[expect(clippy::arithmetic_side_effects)]
    #[expect(clippy::cast_possible_truncation)]
    for &ex_fd in exceptions {
        let ex_fd = u64::from(ex_fd);

        // Close [next .. ex_fd - 1], if non-empty.
        if next < ex_fd {
            let first = next as c_uint;
            // Safe: ex_fd >= next + 1 ensures no underflow.
            let last = (ex_fd - 1) as c_uint;
            close_range(first, last, 0)?;
        }

        // Skip the exception itself.
        next = ex_fd.saturating_add(1);
    }

    // Finally close [next .. MAX_FD], if any remain.
    #[expect(clippy::cast_possible_truncation)]
    if next <= RawFd::MAX as u64 {
        let first = next as c_uint;
        closefrom(first)?;
    }

    Ok(())
}

const KCMP_FILE: c_long = 0;

/// Check if the given file descriptor is open for the given process.
pub fn is_open_fd(pid: Pid, fd: RawFd) -> Result<bool, Errno> {
    #[expect(clippy::cast_lossless)]
    #[expect(clippy::cast_possible_wrap)]
    #[expect(clippy::cast_sign_loss)]
    // SAFETY: There's no libc wrapper for kcmp.
    match Errno::result(unsafe {
        syscall(
            SYS_kcmp,
            pid.as_raw() as c_long,
            pid.as_raw() as c_long,
            KCMP_FILE,
            fd as c_ulong as c_long,
            fd as c_ulong as c_long,
        )
    }) {
        Ok(_) => Ok(true),
        Err(Errno::EBADF) => Ok(false),
        Err(errno) => Err(errno),
    }
}

/// Check two fds point to the same open file description for the given processes.
pub fn is_same_fd(pid1: Pid, pid2: Pid, fd1: RawFd, fd2: RawFd) -> Result<bool, Errno> {
    if pid1 == pid2 && fd1 == fd2 {
        // We do not check for open/valid FD in this function,
        // so we short-circuit here for efficiency.
        return Ok(true);
    }

    // SAFETY: There's no libc wrapper for kcmp.
    #[expect(clippy::cast_lossless)]
    #[expect(clippy::cast_possible_wrap)]
    #[expect(clippy::cast_sign_loss)]
    Ok(Errno::result(unsafe {
        syscall(
            SYS_kcmp,
            pid1.as_raw() as c_long,
            pid2.as_raw() as c_long,
            KCMP_FILE,
            fd1 as c_ulong as c_long,
            fd2 as c_ulong as c_long,
        )
    })? == 0)
}

/// Check if file resides on a hugetlbfs (e.g. memfds with MFD_HUGETLB)
pub fn is_huge_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    FsType::get(fd).map(|fs_type| fs_type.is_huge_file())
}

/// Check if file resides inside procfs(5).
pub fn is_proc<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    FsType::get(fd).map(|fs_type| fs_type.is_proc())
}

/// Check if file is the /dev/null character device.
pub fn is_dev_null<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    const NULL_MAJOR: u32 = 1;
    const NULL_MINOR: u32 = 3;
    is_char_dev(fd, NULL_MAJOR, NULL_MINOR)
}

/// Check if file is the AMD KFD character device (/dev/kfd).
pub fn is_dev_kfd<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    const KFD_MAJOR: u32 = 238;
    const KFD_MINOR: u32 = 0;
    is_char_dev(fd, KFD_MAJOR, KFD_MINOR)
}

/// Check if file is the /dev/ptmx character device.
pub fn is_dev_ptmx<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    const PTMX_MAJOR: u32 = 5;
    const PTMX_MINOR: u32 = 2;
    is_char_dev(fd, PTMX_MAJOR, PTMX_MINOR)
}

/// Check if file is a character device with the given major/minor numbers.
pub fn is_char_dev<Fd: AsFd>(fd: Fd, major: u32, minor: u32) -> Result<bool, Errno> {
    #[expect(clippy::cast_possible_truncation)]
    const S_IFCHR: u16 = libc::S_IFCHR as u16;

    let statx = fstatx(fd, STATX_BASIC_STATS)?;

    // Check if file is a character device,
    // and its device major/minor numbers
    // match the given parameters.
    Ok(statx.stx_mode & S_IFCHR == S_IFCHR
        && statx.stx_rdev_major == major
        && statx.stx_rdev_minor == minor)
}

/// Check if the given file is a regular file.
pub fn is_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    #[expect(clippy::cast_possible_truncation)]
    const S_IFREG: u16 = libc::S_IFREG as u16;

    let statx = fstatx(&fd, STATX_BASIC_STATS)?;

    Ok(statx.stx_mode & S_IFREG == S_IFREG)
}

/// Check if the given file is a regular empty file.
pub fn is_empty_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    #[expect(clippy::cast_possible_truncation)]
    const S_IFREG: u16 = libc::S_IFREG as u16;

    let statx = fstatx(&fd, STATX_BASIC_STATS)?;

    Ok(statx.stx_size == 0 && statx.stx_mode & S_IFREG == S_IFREG)
}

/// Parse a FD from a Path.
pub fn parse_fd(path: &XPath) -> Result<RawFd, Errno> {
    btoi::<RawFd>(path.as_bytes()).or(Err(Errno::EBADF))
}

/// Seals the memfd for write, grow, shrink and future seals.
pub fn seal_memfd_all<Fd: AsFd>(fd: Fd) -> Result<(), Errno> {
    seal_memfd(
        fd,
        SealFlag::F_SEAL_SEAL
            | SealFlag::F_SEAL_WRITE
            | SealFlag::F_SEAL_SHRINK
            | SealFlag::F_SEAL_GROW,
    )
}

/// Seals memfd with the given `SealFlag`.
///
/// Returns `Err(Errno::EINVAL)` if `flags` is empty.
pub fn seal_memfd<Fd: AsFd>(fd: Fd, flags: SealFlag) -> Result<(), Errno> {
    // Guard against nonsensical use.
    if flags.is_empty() {
        return Err(Errno::EINVAL);
    }

    // Seal memory fd.
    fcntl(fd, FcntlArg::F_ADD_SEALS(flags)).map(drop)
}

/// Set pipe max size of the given pipe.
pub fn set_pipemax<Fd: AsFd>(fd: Fd, size: c_int) -> Result<usize, Errno> {
    #[expect(clippy::cast_sign_loss)]
    fcntl(fd, FcntlArg::F_SETPIPE_SZ(size)).map(|r| r as usize)
}

/// Get exclusive mode for the given terminal.
pub fn get_exclusive<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    let mut set: c_int = 0;
    let fd = fd.as_fd().as_raw_fd();

    // SAFETY: TIOCGEXCL takes an int* to return 0 or nonzero.
    Errno::result(unsafe { syscall(SYS_ioctl, fd, TIOCGEXCL, std::ptr::addr_of_mut!(set)) })
        .map(|_| set != 0)
}

/// Set given terminal to exclusive mode, or disable exclusive mode.
pub fn set_exclusive<Fd: AsFd>(fd: Fd, enable: bool) -> Result<(), Errno> {
    let fd = fd.as_fd().as_raw_fd();
    let req = if enable { TIOCEXCL } else { TIOCNXCL };

    // SAFETY: TIOC{E,N}XCL take no extra arguments.
    Errno::result(unsafe { syscall(SYS_ioctl, fd, req) }).map(drop)
}

/// Checks if the given file descriptor has a send timeout set.
pub fn has_send_timeout<F: AsFd>(fd: &F) -> Result<bool, Errno> {
    let tv = getsockopt(fd, SendTimeout)?;
    Ok(tv.tv_sec() != 0 || tv.tv_usec() != 0)
}

/// Checks if the given file descriptor has a receive timeout set.
pub fn has_recv_timeout<F: AsFd>(fd: &F) -> Result<bool, Errno> {
    let tv = getsockopt(fd, ReceiveTimeout)?;
    Ok(tv.tv_sec() != 0 || tv.tv_usec() != 0)
}

/// Returns the inode for the given file descriptor.
pub fn fd_inode<Fd: AsFd>(fd: Fd) -> Result<u64, Errno> {
    retry_on_eintr(|| fstatx(&fd, STATX_INO)).map(|statx| statx.stx_ino)
}

/// Returns true if the given file descriptor is active.
pub fn is_active_fd<Fd: AsFd>(fd: Fd) -> bool {
    fcntl(fd, FcntlArg::F_GETFD).is_ok()
}

/// Returns true if the given file descriptor is syntactically valid.
///
/// Negative values, including AT_FDCWD, are not syntactically valid.
pub fn is_valid_fd(fd: u64) -> bool {
    to_valid_fd(fd).map(|fd| fd >= 0).unwrap_or(false)
}

/// Converts a system call argument to a RawFd.
///
/// Negative values, excluding AT_FDCWD, return an error.
#[expect(clippy::cast_possible_truncation)]
pub fn to_valid_fd(fd: u64) -> Result<RawFd, Errno> {
    let fd = fd as RawFd;

    if fd == libc::AT_FDCWD || fd >= 0 {
        Ok(fd)
    } else {
        Err(Errno::EBADF)
    }
}

/// Returns file access mode in status flags.
pub fn fd_status_flags<Fd: AsFd>(fd: Fd) -> Result<OFlag, Errno> {
    fcntl(fd, FcntlArg::F_GETFL).map(OFlag::from_bits_truncate)
}

/// Returns true if file is writable.
pub fn is_writable_fd<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
    fd_status_flags(fd)
        .map(oflag_accmode)
        .map(|mode| !mode.is_empty())
}

/// Get number of open file descriptors.
pub fn fd_count(pid: Option<Pid>) -> Result<u64, Errno> {
    let mut pfd = XPathBuf::from("/proc");
    if let Some(pid) = pid {
        pfd.push_pid(pid);
    } else {
        pfd.push(b"thread-self");
    }
    pfd.push(b"fd");

    if *HAVE_PROC_PID_FD_STAT_SIZE {
        let stx = statx(AT_BADFD, &pfd, 0, STATX_SIZE)?;
        return Ok(stx.stx_size);
    }

    #[expect(clippy::disallowed_methods)]
    let fd = nix::fcntl::openat(
        AT_BADFD,
        &pfd,
        OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
        Mode::empty(),
    )?;
    let mut nfds: u64 = 0;
    loop {
        match getdents64(&fd, DIRENT_BUF_SIZE) {
            Ok(entries) => {
                nfds = nfds
                    .checked_add(entries.count() as u64)
                    .ok_or(Errno::ERANGE)?
            }
            Err(Errno::ECANCELED) => break, // EOF or empty directory.
            Err(errno) => return Err(errno),
        };
    }

    Ok(nfds.saturating_sub(2))
}

/// Safe wrapper for faccessat2(2) with AT_EMPTY_PATH.
pub fn fdaccess<Fd: AsFd>(fd: Fd, mode: AccessFlags, mut flags: AtFlags) -> Result<(), Errno> {
    // Remove AT_SYMLINK_NOFOLLOW and add AT_EMPTY_PATH to flags.
    flags.remove(AtFlags::AT_SYMLINK_NOFOLLOW);
    flags.insert(AtFlags::AT_EMPTY_PATH);

    // SAFETY: No libc wrapper for faccessat2 yet.
    Errno::result(unsafe {
        syscall(
            SYS_faccessat2,
            fd.as_fd().as_raw_fd(),
            c"".as_ptr(),
            mode.bits(),
            flags.bits(),
        )
    })
    .map(drop)
}

// execveat(2): Only perform a check if execution would be allowed.
// Requires Linux>=6.14.
pub(crate) const AT_EXECVE_CHECK: AtFlags = AtFlags::from_bits_retain(0x10000);

/// Return true if the given File is executable.
pub fn is_executable<Fd: AsFd>(file: Fd) -> bool {
    check_executable(file).is_ok()
}

/// Check if the given File is executable.
pub fn check_executable<Fd: AsFd>(file: Fd) -> Result<(), Errno> {
    if *HAVE_AT_EXECVE_CHECK {
        let argv: [*const c_char; 2] = [c"".as_ptr(), std::ptr::null()];
        let envp: [*const c_char; 1] = [std::ptr::null()];
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            syscall(
                SYS_execveat,
                file.as_fd().as_raw_fd(),
                c"".as_ptr(),
                argv.as_ptr(),
                envp.as_ptr(),
                (AT_EXECVE_CHECK | AtFlags::AT_EMPTY_PATH).bits(),
            )
        })
        .map(drop)
    } else {
        fdaccess(file, AccessFlags::X_OK, crate::compat::AT_EACCESS)
    }
}

/// PIDFD_THREAD flag for pidfd_open(2).
#[expect(clippy::cast_sign_loss)]
pub const PIDFD_THREAD: u32 = OFlag::O_EXCL.bits() as u32;

/// Safe wrapper for pidfd_open(2).
///
/// This function requires Linux 5.3+.
pub fn pidfd_open(pid: Pid, mut flags: u32) -> Result<OwnedFd, Errno> {
    // Use PIDFD_THREAD if available.
    // Pass-through PIDFD_NONBLOCK.
    let pid = if *HAVE_PIDFD_THREAD || flags & PIDFD_THREAD == 0 {
        pid
    } else {
        flags &= !PIDFD_THREAD;
        proc_tgid(pid)?
    };

    // SAFETY: libc does not have a pidfd_open(2) wrapper yet.
    #[expect(clippy::cast_possible_truncation)]
    Errno::result(unsafe { syscall(SYS_pidfd_open, pid.as_raw(), flags) }).map(|fd| {
        // SAFETY: pidfd_open(2) returned success, fd is valid.
        unsafe { OwnedFd::from_raw_fd(fd as RawFd) }
    })
}

/// Safe wrapper for pidfd_getfd(2).
///
/// This function requires Linux 5.6+.
pub fn pidfd_getfd<Fd: AsFd>(pid_fd: Fd, remote_fd: RawFd) -> Result<OwnedFd, Errno> {
    // SAFETY: libc does not have a pidfd_getfd(2) wrapper yet.
    #[expect(clippy::cast_possible_truncation)]
    Errno::result(unsafe { syscall(SYS_pidfd_getfd, pid_fd.as_fd().as_raw_fd(), remote_fd, 0) })
        .map(|fd| {
            // SAFETY: pidfd_getfd(2) returned success, fd is valid.
            unsafe { OwnedFd::from_raw_fd(fd as RawFd) }
        })
}

/// Safe wrapper for pidfd_send_signal(2).
///
/// This function requires Linux 5.1+.
pub fn pidfd_send_signal<Fd: AsFd>(pid_fd: Fd, sig: i32) -> Result<(), Errno> {
    // SAFETY: libc does not have a wrapper for pidfd_send_signal yet.
    Errno::result(unsafe { syscall(SYS_pidfd_send_signal, pid_fd.as_fd().as_raw_fd(), sig, 0, 0) })
        .map(drop)
}

/// Safe wrapper for pidfd_send_signal(2) with signal 0.
///
/// This function requires Linux 5.1+.
pub fn pidfd_is_alive<Fd: AsFd>(pid_fd: Fd) -> Result<(), Errno> {
    pidfd_send_signal(pid_fd, 0)
}

#[cfg(test)]
mod tests {
    use std::fs::{File, OpenOptions};

    use libc::c_uint;
    use nix::{
        fcntl::open,
        unistd::{dup, pipe},
    };
    use tempfile::NamedTempFile;

    use super::*;

    #[test]
    fn test_fd_status_flags_file_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_file_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_file_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_RDWR));
        assert!(!flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_owned_fd_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let flags = fd_status_flags(&owned_fd).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_owned_fd_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let flags = fd_status_flags(&owned_fd).unwrap();
        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_owned_fd_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let flags = fd_status_flags(&owned_fd).unwrap();
        assert!(flags.contains(OFlag::O_RDWR));
        assert!(!flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_borrowed_fd_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let borrowed_fd = file.as_fd();

        let flags = fd_status_flags(borrowed_fd).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_borrowed_fd_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let borrowed_fd = file.as_fd();

        let flags = fd_status_flags(borrowed_fd).unwrap();
        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_borrowed_fd_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let borrowed_fd = file.as_fd();

        let flags = fd_status_flags(borrowed_fd).unwrap();
        assert!(flags.contains(OFlag::O_RDWR));
        assert!(!flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_dev_null_read() {
        let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_dev_null_write() {
        let file = OpenOptions::new().write(true).open("/dev/null").unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_dev_null_read_write() {
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open("/dev/null")
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_RDWR));
        assert!(!flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_pipe_read_end() {
        let (read_fd, _) = pipe().unwrap();

        let flags = fd_status_flags(&read_fd).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_pipe_write_end() {
        let (_, write_fd) = pipe().unwrap();

        let flags = fd_status_flags(&write_fd).unwrap();
        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_append_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .append(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(flags.contains(OFlag::O_APPEND));
    }

    #[test]
    fn test_fd_status_flags_create_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .create(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_truncate_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_read_append_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .append(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_RDWR));
        assert!(flags.contains(OFlag::O_APPEND));
    }

    #[test]
    fn test_fd_status_flags_create_new_mode() {
        let temp = NamedTempFile::new().unwrap();
        std::fs::remove_file(temp.path()).unwrap();
        let file = OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(temp.path())
            .unwrap();
        let flags = fd_status_flags(&file).unwrap();

        assert!(flags.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_reference_to_file() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let file_ref = &file;

        let flags = fd_status_flags(file_ref).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_mutable_reference_to_file() {
        let temp = NamedTempFile::new().unwrap();
        let mut file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let file_ref = &mut file;

        let flags = fd_status_flags(file_ref).unwrap();
        assert!(flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_box_file() {
        let temp = NamedTempFile::new().unwrap();
        let file = Box::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let flags = fd_status_flags(&file).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_arc_file() {
        use std::sync::Arc;
        let temp = NamedTempFile::new().unwrap();
        let file = Arc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let flags = fd_status_flags(&file).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_rc_file() {
        use std::rc::Rc;
        let temp = NamedTempFile::new().unwrap();
        let file = Rc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let flags = fd_status_flags(&file).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_fd_status_flags_invalid_fd() {
        let result = fd_status_flags(AT_BADFD);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), Errno::EBADF);
    }

    #[test]
    fn test_fd_status_flags_multiple_calls_consistency() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();

        let flags1 = fd_status_flags(&file).unwrap();
        let flags2 = fd_status_flags(&file).unwrap();
        let flags3 = fd_status_flags(&file).unwrap();

        assert_eq!(flags1, flags2);
        assert_eq!(flags2, flags3);
    }

    #[test]
    fn test_fd_status_flags_different_file_types() {
        let temp = NamedTempFile::new().unwrap();
        let file1 = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let file2 = OpenOptions::new().write(true).open("/dev/null").unwrap();

        let flags1 = fd_status_flags(&file1).unwrap();
        let flags2 = fd_status_flags(&file2).unwrap();

        assert!(flags1.contains(OFlag::O_WRONLY));
        assert!(flags2.contains(OFlag::O_WRONLY));
    }

    #[test]
    fn test_fd_status_flags_dup_file_descriptor() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let duped_fd = dup(&file).unwrap();

        let flags = fd_status_flags(&duped_fd).unwrap();
        assert!(!flags.contains(OFlag::O_WRONLY));
        assert!(!flags.contains(OFlag::O_RDWR));
    }

    #[test]
    fn test_is_writable_fd_file_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_file_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_file_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_owned_fd_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let result = is_writable_fd(&owned_fd).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_owned_fd_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let result = is_writable_fd(&owned_fd).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_owned_fd_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let owned_fd = unsafe { OwnedFd::from_raw_fd(file.as_raw_fd()) };
        std::mem::forget(file);

        let result = is_writable_fd(&owned_fd).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_borrowed_fd_read_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let borrowed_fd = file.as_fd();

        let result = is_writable_fd(borrowed_fd).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_borrowed_fd_write_only() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let borrowed_fd = file.as_fd();

        let result = is_writable_fd(borrowed_fd).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_borrowed_fd_read_write() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();
        let borrowed_fd = file.as_fd();

        let result = is_writable_fd(borrowed_fd).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_dev_null_read() {
        let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_dev_null_write() {
        let file = OpenOptions::new().write(true).open("/dev/null").unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_dev_null_read_write() {
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open("/dev/null")
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_pipe_read_end() {
        let (read_fd, _) = pipe().unwrap();

        let result = is_writable_fd(&read_fd).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_pipe_write_end() {
        let (_, write_fd) = pipe().unwrap();

        let result = is_writable_fd(&write_fd).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_append_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .append(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_create_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .create(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_truncate_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_read_append_mode() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .append(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_create_new_mode() {
        let temp = NamedTempFile::new().unwrap();
        std::fs::remove_file(temp.path()).unwrap();
        let file = OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(temp.path())
            .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_read_only_with_create() {
        let temp = NamedTempFile::new().unwrap();
        let file = open(
            temp.path(),
            OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
            Mode::empty(),
        )
        .map(File::from)
        .unwrap();
        let result = is_writable_fd(&file).unwrap();

        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_reference_to_file() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let file_ref = &file;

        let result = is_writable_fd(file_ref).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_mutable_reference_to_file() {
        let temp = NamedTempFile::new().unwrap();
        let mut file = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let file_ref = &mut file;

        let result = is_writable_fd(file_ref).unwrap();
        assert!(result);
    }

    #[test]
    fn test_is_writable_fd_box_file() {
        let temp = NamedTempFile::new().unwrap();
        let file = Box::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let result = is_writable_fd(&file).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_arc_file() {
        use std::sync::Arc;
        let temp = NamedTempFile::new().unwrap();
        let file = Arc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let result = is_writable_fd(&file).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_rc_file() {
        use std::rc::Rc;
        let temp = NamedTempFile::new().unwrap();
        let file = Rc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());

        let result = is_writable_fd(&file).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_is_writable_fd_invalid_fd() {
        let result = is_writable_fd(AT_BADFD);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), Errno::EBADF);
    }

    #[test]
    fn test_is_writable_fd_multiple_calls_consistency() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(temp.path())
            .unwrap();

        let result1 = is_writable_fd(&file).unwrap();
        let result2 = is_writable_fd(&file).unwrap();
        let result3 = is_writable_fd(&file).unwrap();

        assert_eq!(result1, result2);
        assert_eq!(result2, result3);
    }

    #[test]
    fn test_is_writable_fd_different_file_types() {
        let temp = NamedTempFile::new().unwrap();
        let file1 = OpenOptions::new().write(true).open(temp.path()).unwrap();
        let file2 = OpenOptions::new().write(true).open("/dev/null").unwrap();

        let result1 = is_writable_fd(&file1).unwrap();
        let result2 = is_writable_fd(&file2).unwrap();

        assert!(result1);
        assert!(result2);
    }

    #[test]
    fn test_is_writable_fd_dup_file_descriptor() {
        let temp = NamedTempFile::new().unwrap();
        let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
        let duped_fd = dup(&file).unwrap();

        let result = is_writable_fd(&duped_fd).unwrap();
        assert!(!result);
    }

    #[test]
    fn test_closeall() {
        let (r1, w1) = pipe().unwrap();
        let (r2, w2) = pipe().unwrap();
        let (r3, w3) = pipe().unwrap();

        let fds = vec![
            r1.as_raw_fd() as c_uint,
            w1.as_raw_fd() as c_uint,
            r2.as_raw_fd() as c_uint,
            w2.as_raw_fd() as c_uint,
            r3.as_raw_fd() as c_uint,
            w3.as_raw_fd() as c_uint,
        ];

        // Prevent double-close.
        std::mem::forget(r1);
        std::mem::forget(w1);
        std::mem::forget(r2);
        std::mem::forget(w2);
        std::mem::forget(r3);
        std::mem::forget(w3);

        // Ensure fds are sorted.
        let mut sorted_fds = fds.clone();
        sorted_fds.sort();

        // This should close all fds.
        assert!(closeall(&sorted_fds).is_ok());
    }

    #[test]
    fn test_closeall_invalid_input() {
        let (r, w) = pipe().unwrap();
        let r_fd = r.as_raw_fd() as c_uint;
        let w_fd = w.as_raw_fd() as c_uint;

        // Unsorted input.
        let mut unsorted = vec![w_fd, r_fd];
        if unsorted[0] < unsorted[1] {
            unsorted.swap(0, 1);
        }

        assert_eq!(closeall(&unsorted), Err(Errno::EINVAL));

        // Duplicate input.
        let dup = vec![r_fd, r_fd];
        assert_eq!(closeall(&dup), Err(Errno::EINVAL));
    }
}
