//
// 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::{
    io::{IoSlice, IoSliceMut},
    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
};

use btoi::btoi;
use libc::{
    c_int, c_long, c_uint, c_ulong, syscall, 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},
    sched::CloneFlags,
    sys::{
        socket::{
            cmsg_space, getsockopt, recvmsg, sendmsg,
            sockopt::{PeerCredentials, ReceiveTimeout, SendTimeout},
            ControlMessage, ControlMessageOwned, UnixCredentials,
        },
        stat::Mode,
    },
    unistd::{read, write, AccessFlags, Pid},
};

use crate::{
    compat::{
        fstatx, getdents64, statx, FsType, MsgFlags, 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, safe_execve_check, safe_faccess, safe_socket},
    fs::oflag_accmode,
    hash::SydHashSet,
    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))
}

// 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 {
        safe_execve_check(file)
    } else {
        safe_faccess(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)
}

/// Safe wrapper around `libc::clone` with `CLONE_PIDFD`.
///
/// # Safety
///
/// Because `fdclone` creates a child process with its stack located in
/// `stack` without specifying the size of the stack, special care must
/// be taken to ensure that the child process does not overflow the
/// provided stack space. See [`clone`](nix::unistd::clone) for
/// additional safety concerns related to executing child processes.
pub unsafe fn fdclone(
    func: extern "C" fn(*mut libc::c_void) -> libc::c_int,
    stack: &mut [u8],
    arg: *mut libc::c_void,
    flags: CloneFlags,
    signal: Option<c_int>,
) -> Result<(OwnedFd, Pid), Errno> {
    let mut pid_fd: libc::c_int = -1;
    let clone_flags = flags.bits() | signal.unwrap_or(0) | libc::CLONE_PIDFD;

    // SAFETY: In libc we trust.
    let child = Errno::result(unsafe {
        let ptr = stack.as_mut_ptr().add(stack.len());
        let ptr_aligned = ptr.sub(ptr as usize % 16);
        libc::clone(
            func,
            ptr_aligned as *mut libc::c_void,
            clone_flags,
            arg,
            &mut pid_fd,
        )
    })?;

    Ok((
        // SAFETY: clone(2) succeeded, pid_fd is a valid file descriptor.
        unsafe { OwnedFd::from_raw_fd(pid_fd) },
        Pid::from_raw(child),
    ))
}

/// Send bytes and file descriptors over a Unix stream socket.
///
/// Returns the number of bytes sent on success.
pub fn send_with_fd<Fd: AsFd>(sock: Fd, bytes: &[u8], fds: &[RawFd]) -> Result<usize, Errno> {
    let iov = [IoSlice::new(bytes)];
    let cmsgs: &[ControlMessage<'_>] = if fds.is_empty() {
        &[]
    } else {
        &[ControlMessage::ScmRights(fds)]
    };
    sendmsg::<()>(
        sock.as_fd().as_raw_fd(),
        &iov,
        cmsgs,
        MsgFlags::empty().into(),
        None,
    )
}

/// Receive bytes and file descriptors from a Unix stream socket.
///
/// Returns `(bytes_received, fds_received)` on success.
pub fn recv_with_fd<Fd: AsFd>(
    sock: Fd,
    bytes: &mut [u8],
    fds: &mut [RawFd],
) -> Result<(usize, usize), Errno> {
    let mut iov = [IoSliceMut::new(bytes)];

    let cmsg_siz = cmsg_space::<RawFd>()
        .checked_mul(fds.len())
        .ok_or(Errno::EOVERFLOW)?;
    let mut cmsg_buf = Vec::new();
    cmsg_buf.try_reserve(cmsg_siz).or(Err(Errno::ENOMEM))?;
    cmsg_buf.resize(cmsg_siz, 0);

    let msg = recvmsg::<()>(
        sock.as_fd().as_raw_fd(),
        &mut iov,
        if fds.is_empty() {
            None
        } else {
            Some(&mut cmsg_buf)
        },
        MsgFlags::empty().into(),
    )?;

    let mut fd_count = 0;
    if let Ok(cmsgs) = msg.cmsgs() {
        for cmsg in cmsgs {
            if let ControlMessageOwned::ScmRights(recv_fds) = cmsg {
                for &fd in &recv_fds {
                    if fd_count < fds.len() {
                        fds[fd_count] = fd;
                        fd_count = fd_count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
                    }
                }
            }
        }
    }

    Ok((msg.bytes, fd_count))
}

/// Get peer credentials for the given UNIX socket.
pub fn peer_creds<Fd: AsFd>(fd: Fd) -> Result<UnixCredentials, Errno> {
    getsockopt(&fd, PeerCredentials)
}

/// Netlink alignment helper: nlmsg_align.
#[expect(clippy::arithmetic_side_effects)]
pub fn nlmsg_align(v: usize) -> usize {
    (v + 3) & !3usize
}

/// Netlink alignment helper: nla_align.
#[expect(clippy::arithmetic_side_effects)]
pub fn nla_align(v: usize) -> usize {
    (v + 3) & !3usize
}

// Constants:
// SOCK_DIAG_BY_FAMILY is 20 in the kernel uapi.
const SOCK_DIAG_BY_FAMILY: u16 = 20;

// Netlink special message types.
#[expect(clippy::cast_possible_truncation)]
const NLMSG_DONE: u16 = libc::NLMSG_DONE as u16;
#[expect(clippy::cast_possible_truncation)]
const NLMSG_ERROR: u16 = libc::NLMSG_ERROR as u16;

// nlmsghdr (16) + unix_diag_req (24) = 40 bytes.
const NL_HDR_LEN: usize = 16;
const UD_REQ_LEN: usize = 24;
#[expect(clippy::cast_possible_truncation)]
const NL_MSG_LEN: u32 = (NL_HDR_LEN + UD_REQ_LEN) as u32;

// udiag flags / attributes
const UNIX_DIAG_VFS: u16 = 1;
const UNIX_DIAG_PEER: u16 = 2;
const UDIAG_SHOW_VFS: u32 = 0x0000_0002;
const UDIAG_SHOW_PEER: u32 = 0x0000_0004;

/// Return the peer socket inode (low 32 bits zero-extended) for a UNIX-domain
/// socket with the given `inode`. Uses NETLINK_SOCK_DIAG / unix diag and requests the
/// peer attribute. If peer socket inode is not available, returns local socket
/// inode as fallback. Requires Linux kernel to be configured with `CONFIG_UNIX_DIAG`.
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn peer_inode(inode: u64) -> Result<u64, Errno> {
    // Get local inode to filter diag results.
    let local_ino = inode;
    let local_ino32 = (local_ino & 0xffff_ffff) as u32;

    // Open NETLINK_SOCK_DIAG socket.
    let nl = safe_socket(
        libc::AF_NETLINK,
        libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
        libc::NETLINK_SOCK_DIAG,
    )?;

    // Build request into a stack buffer.
    let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];

    // Fill nlmsghdr.
    let mut p = 0usize;
    req[p..p + 4].copy_from_slice(&NL_MSG_LEN.to_ne_bytes()); // nlmsg_len
    p += 4;
    req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes()); // nlmsg_type
    p += 2;
    let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16;
    req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes()); // nlmsg_flags
    p += 2;
    req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes()); // nlmsg_seq
    p += 4;
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes()); // nlmsg_pid
    p += 4;

    // Fill unix_diag_req.
    req[p] = libc::AF_UNIX as u8;
    p += 1; // sdiag_family
    req[p] = 0u8;
    p += 1; // sdiag_protocol
    req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
    p += 2; // pad
    req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
    p += 4; // udiag_states
    req[p..p + 4].copy_from_slice(&local_ino32.to_ne_bytes());
    p += 4; // udiag_ino
    req[p..p + 4].copy_from_slice(&UDIAG_SHOW_PEER.to_ne_bytes());
    p += 4; // udiag_show
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[0]
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[1]
    assert_eq!(p, req.len());

    // Send loop: Retry short writes until full message sent.
    let mut sent_total = 0usize;
    while sent_total < req.len() {
        let slice = &req[sent_total..];
        let sent = retry_on_eintr(|| write(&nl, slice))?;
        if sent == 0 {
            return Err(Errno::EIO);
        }
        sent_total = sent_total.saturating_add(sent);
    }

    // Recv loop: Parse netlink messages until we find UNIX_DIAG_PEER or finish.
    //
    // Quoting https://docs.kernel.org/userspace-api/netlink/intro.html
    // Netlink expects that the user buffer will be at least 8kB or a page size
    // of the CPU architecture, whichever is bigger. Particular Netlink families
    // may, however, require a larger buffer. 32kB buffer is recommended for most
    // efficient handling of dumps (larger buffer fits more dumped objects and
    // therefore fewer recvmsg() calls are needed).
    let mut rbuf = [0u8; 0x8000];
    loop {
        let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
        if n == 0 {
            return Err(Errno::EIO);
        }
        let mut off = 0usize;
        while off + NL_HDR_LEN <= n {
            // Read nlmsg_len (u32) and nlmsg_type (u16) safely.
            let nlmsg_len = {
                let b: [u8; 4] = rbuf[off..off + 4].try_into().or(Err(Errno::EOVERFLOW))?;
                u32::from_ne_bytes(b) as usize
            };
            if nlmsg_len == 0 || off + nlmsg_len > n {
                return Err(Errno::EIO);
            }
            let nlmsg_type = {
                let b: [u8; 2] = rbuf[off + 4..off + 6]
                    .try_into()
                    .or(Err(Errno::EOVERFLOW))?;
                u16::from_ne_bytes(b)
            };

            if nlmsg_type == NLMSG_DONE {
                //
                // return Err(Errno::ENODATA);
                //
                // Best effort, return local inode.
                return Ok(local_ino);
            } else if nlmsg_type == NLMSG_ERROR {
                if nlmsg_len >= NL_HDR_LEN + 4 {
                    let err_b: [u8; 4] = rbuf[off + NL_HDR_LEN..off + NL_HDR_LEN + 4]
                        .try_into()
                        .or(Err(Errno::EOVERFLOW))?;
                    let nl_err = i32::from_ne_bytes(err_b);
                    // nlmsgerr.error is negative errno.
                    return Err(Errno::from_raw(-nl_err));
                } else {
                    return Err(Errno::EIO);
                }
            } else if nlmsg_type == SOCK_DIAG_BY_FAMILY {
                let payload_off = off + NL_HDR_LEN;
                let ud_min = 16usize;
                if payload_off + ud_min > off + nlmsg_len {
                    return Err(Errno::EIO);
                }
                // udiag_ino at payload_off + 4 (u32)
                let found_ino32 = {
                    let b: [u8; 4] = rbuf[payload_off + 4..payload_off + 8]
                        .try_into()
                        .or(Err(Errno::EOVERFLOW))?;
                    u64::from(u32::from_ne_bytes(b))
                };
                if (found_ino32 & 0xffff_ffff) != (local_ino & 0xffff_ffff) {
                    off = nlmsg_align(off + nlmsg_len);
                    continue;
                }

                // Parse attributes.
                let mut attr_off = payload_off + ud_min;
                while attr_off + 4 <= off + nlmsg_len {
                    let nla_len = {
                        let b: [u8; 2] = rbuf[attr_off..attr_off + 2]
                            .try_into()
                            .or(Err(Errno::EOVERFLOW))?;
                        u16::from_ne_bytes(b) as usize
                    };
                    let nla_type = {
                        let b: [u8; 2] = rbuf[attr_off + 2..attr_off + 4]
                            .try_into()
                            .or(Err(Errno::EOVERFLOW))?;
                        u16::from_ne_bytes(b)
                    };
                    if nla_len < 4 {
                        break;
                    }
                    let payload_start = attr_off + 4;
                    let payload_len = nla_len - 4;
                    if payload_start + payload_len > off + nlmsg_len {
                        break;
                    }

                    if nla_type == UNIX_DIAG_PEER && payload_len >= 4 {
                        let peer_b: [u8; 4] = rbuf[payload_start..payload_start + 4]
                            .try_into()
                            .or(Err(Errno::EOVERFLOW))?;
                        let peer_ino = u64::from(u32::from_ne_bytes(peer_b));
                        return Ok(peer_ino);
                    }

                    attr_off = attr_off.saturating_add(nla_align(nla_len));
                }
            }

            off = nlmsg_align(off + nlmsg_len);
        }
        // Continue read loop for multipart replies.
    }
}

/// Returns a set of all UNIX domain sockets using NETLINK_SOCK_DIAG.
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn unix_inodes() -> Result<SydHashSet<u64>, Errno> {
    // Open NETLINK_SOCK_DIAG socket.
    let nl = safe_socket(
        libc::AF_NETLINK,
        libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
        libc::NETLINK_SOCK_DIAG,
    )?;

    // Build request into a stack buffer.
    let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];

    // Fill nlmsghdr.
    let mut p = 0usize;
    req[p..p + 4].copy_from_slice(&NL_MSG_LEN.to_ne_bytes()); // nlmsg_len
    p += 4;
    req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes()); // nlmsg_type
    p += 2;
    let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16;
    req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes()); // nlmsg_flags
    p += 2;
    req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes()); // nlmsg_seq
    p += 4;
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes()); // nlmsg_pid
    p += 4;

    // Fill unix_diag_req for a full dump of AF_UNIX sockets.
    req[p] = libc::AF_UNIX as u8;
    p += 1; // sdiag_family
    req[p] = 0u8;
    p += 1; // sdiag_protocol
    req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
    p += 2; // pad
    req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
    p += 4; // udiag_states (all)
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // udiag_ino (0 => no inode filter; dump)
    req[p..p + 4].copy_from_slice(&UDIAG_SHOW_VFS.to_ne_bytes());
    p += 4; // udiag_show (no attributes needed)
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[0]
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[1]
    assert_eq!(p, req.len());

    // Send loop: retry short writes until full message is sent.
    let mut sent_total = 0usize;
    while sent_total < req.len() {
        let slice = &req[sent_total..];
        let sent = retry_on_eintr(|| write(&nl, slice))?;
        if sent == 0 {
            return Err(Errno::EIO);
        }
        sent_total = sent_total.saturating_add(sent);
    }

    // Recv loop: collect all udiag_ino values directly into a HashSet.
    //
    // Quoting https://docs.kernel.org/userspace-api/netlink/intro.html :
    // Use at least an 8kB buffer; 32kB recommended for dumps.
    let mut rbuf = [0u8; 0x8000];
    let mut iset = SydHashSet::default();
    'recv: loop {
        let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
        if n == 0 {
            return Err(Errno::EIO);
        }

        let mut off = 0usize;
        while off + NL_HDR_LEN <= n {
            // Read nlmsg_len (u32) and nlmsg_type (u16) safely.
            let nlmsg_len = {
                let b: [u8; 4] = rbuf[off..off + 4].try_into().or(Err(Errno::EOVERFLOW))?;
                u32::from_ne_bytes(b) as usize
            };
            if nlmsg_len == 0 || off + nlmsg_len > n {
                return Err(Errno::EIO);
            }
            let nlmsg_type = {
                let b: [u8; 2] = rbuf[off + 4..off + 6]
                    .try_into()
                    .or(Err(Errno::EOVERFLOW))?;
                u16::from_ne_bytes(b)
            };

            if nlmsg_type == NLMSG_DONE {
                break 'recv;
            } else if nlmsg_type == NLMSG_ERROR {
                if nlmsg_len >= NL_HDR_LEN + 4 {
                    let err_b: [u8; 4] = rbuf[off + NL_HDR_LEN..off + NL_HDR_LEN + 4]
                        .try_into()
                        .or(Err(Errno::EOVERFLOW))?;
                    let nl_err = i32::from_ne_bytes(err_b);
                    // nlmsgerr.error is negative errno.
                    return Err(Errno::from_raw(-nl_err));
                } else {
                    return Err(Errno::EIO);
                }
            } else if nlmsg_type == SOCK_DIAG_BY_FAMILY {
                // unix_diag_msg minimal payload is 16 bytes.
                let payload_off = off + NL_HDR_LEN;
                let ud_min = 16usize;
                if payload_off + ud_min > off + nlmsg_len {
                    return Err(Errno::EIO);
                }

                // udiag_ino (u32) at payload_off + 4
                let ino32 = {
                    let b: [u8; 4] = rbuf[payload_off + 4..payload_off + 8]
                        .try_into()
                        .or(Err(Errno::EOVERFLOW))?;
                    u32::from_ne_bytes(b)
                };

                // Walk NLAs; presence of UNIX_DIAG_VFS => path-based socket.
                let mut has_vfs = false;
                let mut attr_off = payload_off + ud_min;
                let attrs_end = off + nlmsg_len;
                while attr_off + 4 <= attrs_end {
                    let nla_len = {
                        let b: [u8; 2] = rbuf[attr_off..attr_off + 2]
                            .try_into()
                            .or(Err(Errno::EOVERFLOW))?;
                        u16::from_ne_bytes(b) as usize
                    };
                    let nla_type = {
                        let b: [u8; 2] = rbuf[attr_off + 2..attr_off + 4]
                            .try_into()
                            .or(Err(Errno::EOVERFLOW))?;
                        u16::from_ne_bytes(b)
                    };

                    if nla_len < 4 {
                        // Malformed NLA header;
                        // Stop parsing this message to avoid overrun.
                        break;
                    }

                    // Bounds-check this attribute's payload region. If it would overflow
                    // this message, break the attribute loop to skip the rest of this
                    // message safely (staying synchronized with the outer message parser).
                    let payload_start = attr_off + 4;
                    let payload_len = nla_len - 4;
                    if payload_start > attrs_end || payload_start + payload_len > attrs_end {
                        // Malformed/overrun; skip remainder of this message
                        break;
                    }

                    if nla_type == UNIX_DIAG_VFS {
                        has_vfs = true;
                        break;
                    }

                    // Advance to next attribute (aligned).
                    let next = attr_off.saturating_add(nla_align(nla_len));
                    if next <= attr_off {
                        break;
                    } // overflow guard
                    attr_off = next;
                }

                // Insert path-based sockets into the set.
                if has_vfs {
                    iset.try_reserve(1).or(Err(Errno::ENOMEM))?;
                    let _ = iset.insert(ino32.into());
                }
            }

            off = nlmsg_align(off + nlmsg_len);
        }
        // Continue read loop for multipart replies.
    }

    Ok(iset)
}

#[cfg(test)]
mod tests {
    use std::{
        fs::{File, OpenOptions},
        io::ErrorKind,
        os::unix::{
            ffi::OsStrExt,
            fs::OpenOptionsExt,
            net::{UnixListener, UnixStream},
        },
        sync::mpsc,
        thread,
        time::Duration,
    };

    use libc::c_uint;
    use nix::{
        fcntl::open,
        sys::socket::{
            accept, bind, connect, listen, socket, socketpair, AddressFamily, Backlog, SockFlag,
            SockType, UnixAddr,
        },
        unistd::{dup, pipe, read, write},
    };
    use tempfile::NamedTempFile;

    use super::*;
    use crate::confine::check_unix_diag;

    fn tempdir() -> Result<XPathBuf, Box<dyn std::error::Error>> {
        let tmp = tempfile::Builder::new()
            .disable_cleanup(true)
            .tempdir_in(".")?;
        let _ = OpenOptions::new()
            .write(true)
            .create(true)
            .mode(0o600)
            .open(tmp.path().join("test"))?;
        Ok(tmp
            .path()
            .to_path_buf()
            .file_name()
            .unwrap()
            .as_bytes()
            .into())
    }

    #[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));
    }

    #[test]
    fn test_send_recv_with_fd_single() {
        let (l, r) = UnixStream::pair().unwrap();
        let (read_fd, _write_fd) = pipe().unwrap();
        let sent_bytes = b"hello";
        let sent_fds = [read_fd.as_raw_fd()];

        let n = send_with_fd(&l, sent_bytes, &sent_fds).unwrap();
        assert_eq!(n, sent_bytes.len());

        let mut recv_bytes = [0u8; 64];
        let mut recv_fds = [0i32; 4];
        let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
        assert_eq!(nbytes, sent_bytes.len());
        assert_eq!(nfds, 1);
        assert_eq!(&recv_bytes[..nbytes], sent_bytes);
        assert_ne!(recv_fds[0], sent_fds[0]);
    }

    #[test]
    fn test_send_recv_with_fd_multiple() {
        let (l, r) = UnixStream::pair().unwrap();
        let (r1, w1) = pipe().unwrap();
        let (r2, w2) = pipe().unwrap();
        let sent_bytes = b"multi";
        let sent_fds = [
            r1.as_raw_fd(),
            w1.as_raw_fd(),
            r2.as_raw_fd(),
            w2.as_raw_fd(),
        ];

        let n = send_with_fd(&l, sent_bytes, &sent_fds).unwrap();
        assert_eq!(n, sent_bytes.len());

        let mut recv_bytes = [0u8; 64];
        let mut recv_fds = [0i32; 8];
        let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
        assert_eq!(nbytes, sent_bytes.len());
        assert_eq!(nfds, 4);
        assert_eq!(&recv_bytes[..nbytes], sent_bytes);
    }

    #[test]
    fn test_send_recv_with_fd_empty_fds() {
        let (l, r) = UnixStream::pair().unwrap();
        let sent_bytes = b"data only";

        let n = send_with_fd(&l, sent_bytes, &[]).unwrap();
        assert_eq!(n, sent_bytes.len());

        let mut recv_bytes = [0u8; 64];
        let mut recv_fds = [0i32; 4];
        let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
        assert_eq!(nbytes, sent_bytes.len());
        assert_eq!(nfds, 0);
        assert_eq!(&recv_bytes[..nbytes], sent_bytes);
    }

    #[test]
    fn test_send_with_fd_invalid_fd() {
        let (l, _r) = UnixStream::pair().unwrap();
        let sent_bytes = b"bad";
        let bad_fds = [RawFd::MAX];

        let result = send_with_fd(&l, sent_bytes, &bad_fds);
        assert!(result.is_err());
    }

    #[test]
    fn test_send_recv_with_fd_verify_fd() {
        let (l, r) = UnixStream::pair().unwrap();
        let (pipe_r, pipe_w) = pipe().unwrap();
        let sent_bytes = b"x";
        let sent_fds = [pipe_w.as_raw_fd()];

        send_with_fd(&l, sent_bytes, &sent_fds).unwrap();

        let mut recv_bytes = [0u8; 4];
        let mut recv_fds = [0i32; 2];
        let (_, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
        assert_eq!(nfds, 1);

        // Write through the received fd.
        let recv_pipe_w = unsafe { OwnedFd::from_raw_fd(recv_fds[0]) };
        write(&recv_pipe_w, b"hello").unwrap();
        drop(recv_pipe_w);
        drop(pipe_w);

        // Read from the original pipe read end.
        let mut buf = [0u8; 16];
        let n = read(pipe_r, &mut buf).unwrap();
        assert_eq!(&buf[..n], b"hello");
    }

    #[test]
    fn test_peer_inode_socketpair() {
        if !check_unix_diag().unwrap_or(false) {
            eprintln!("UNIX socket diagnostics are not supported, skipping!");
            return;
        }

        // Create socketpair.
        let (a_fd, b_fd) = socketpair(
            AddressFamily::Unix,
            SockType::Stream,
            None,
            SockFlag::SOCK_CLOEXEC,
        )
        .unwrap();

        // Expected peer inode (low 32 bits).
        let b_ino = fd_inode(&b_fd).unwrap();
        let expected = (b_ino & 0xffff_ffff) as u64;

        // Call peer_inode on the other side and compare.
        let got = fd_inode(&a_fd).and_then(peer_inode).unwrap();
        assert_eq!(got, expected);
    }

    #[test]
    fn test_peer_inode_listener_filesystem() {
        if !check_unix_diag().unwrap_or(false) {
            eprintln!("UNIX socket diagnostics are not supported, skipping!");
            return;
        }

        // Create a temporary directory for a unique socket path.
        let td = tempdir().unwrap();
        let sock_path = td.as_path().join("peer_inode.sock");

        // Spawn server thread:
        // bind, listen, accept, compute peer_inode on accepted socket and send it back.
        let (tx_ready, rx_ready) = mpsc::channel::<()>();
        let (tx_peer, rx_peer) = mpsc::channel::<u64>();
        let sock_path_clone = sock_path.clone();
        let server = thread::spawn(move || {
            // bind & listen to get a UnixListener.
            let listener = UnixListener::bind(&sock_path_clone).unwrap();
            // Inform main thread we're listening.
            tx_ready.send(()).unwrap();
            // Accept one connection (blocking).
            let (accepted, _addr) = listener.accept().unwrap();
            // Call peer_inode on accepted stream.
            let peer = fd_inode(&accepted).and_then(peer_inode).unwrap();
            // Send result back.
            tx_peer.send(peer).unwrap();
        });

        // Wait for server to be ready.
        rx_ready.recv_timeout(Duration::from_secs(10)).unwrap();

        // Connect client to socket path (retries briefly as necessary).
        let client = loop {
            match UnixStream::connect(&sock_path) {
                Ok(s) => break s,
                Err(e) => {
                    if e.kind() == ErrorKind::NotFound || e.kind() == ErrorKind::ConnectionRefused {
                        thread::sleep(Duration::from_millis(10));
                        continue;
                    } else {
                        panic!("connect failed: {e:?}");
                    }
                }
            }
        };

        // Compute expected from client fd.
        let client_ino = fd_inode(&client).unwrap();
        let expected = (client_ino & 0xffff_ffff) as u64;

        // Receive peer inode computed by server.
        let got = rx_peer.recv_timeout(Duration::from_secs(10)).unwrap();

        assert_eq!(got, expected);
        server.join().unwrap();
    }

    #[test]
    fn test_peer_inode_listener_abstract() {
        if !check_unix_diag().unwrap_or(false) {
            eprintln!("UNIX socket diagnostics are not supported, skipping!");
            return;
        }

        // Create an abstract socket name (no filesystem path).
        let name = b"peer_inode_test_abstract_12345";

        // Create server socket.
        let srv_fd = socket(
            AddressFamily::Unix,
            SockType::Stream,
            SockFlag::SOCK_CLOEXEC,
            None,
        )
        .unwrap();

        // Construct abstract address and bind/listen.
        let sockaddr = UnixAddr::new_abstract(name).unwrap();
        bind(srv_fd.as_raw_fd(), &sockaddr).unwrap();
        listen(&srv_fd, Backlog::new(1).unwrap()).unwrap();

        // Create client socket and connect.
        let cli_fd = socket(
            AddressFamily::Unix,
            SockType::Stream,
            SockFlag::SOCK_CLOEXEC,
            None,
        )
        .unwrap();
        connect(cli_fd.as_raw_fd(), &sockaddr).unwrap();

        // Server accept.
        let acc_fd = accept(srv_fd.as_raw_fd()).unwrap();
        let acc_fd = unsafe { OwnedFd::from_raw_fd(acc_fd) };

        // Expected is client's inode low 32 bits.
        let cli_ino = fd_inode(&cli_fd).unwrap();
        let expected = (cli_ino & 0xffff_ffff) as u64;

        // Call peer_inode on the accepted/server side.
        let got = fd_inode(&acc_fd).and_then(peer_inode).unwrap();

        assert_eq!(got, expected);
    }

    #[test]
    fn test_peer_inode_symmetry_socketpair() {
        if !check_unix_diag().unwrap_or(false) {
            eprintln!("UNIX socket diagnostics are not supported, skipping!");
            return;
        }

        // Create socketpair and verify mutual mapping.
        let (a_fd, b_fd) = socketpair(
            AddressFamily::Unix,
            SockType::Stream,
            None,
            SockFlag::SOCK_CLOEXEC,
        )
        .unwrap();

        // Expected low-32 inodes.
        let a_ino = fd_inode(&a_fd).unwrap();
        let b_ino = fd_inode(&b_fd).unwrap();
        let expected_a = (a_ino & 0xffff_ffff) as u64;
        let expected_b = (b_ino & 0xffff_ffff) as u64;

        let got_from_a = peer_inode(a_ino).unwrap();
        let got_from_b = peer_inode(b_ino).unwrap();

        assert_eq!(got_from_a, expected_b);
        assert_eq!(got_from_b, expected_a);
    }
}
