//
// Syd: rock-solid application kernel
// src/proc.rs: /proc utilities
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
// Based in part upon procps-ng's library/devname.c which is:
//   Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
//   Copyright © 2017-2023 Craig Small <csmall@dropbear.xyz>
//   Copyright © 1998-2009 Albert Cahalan
// proc_auxv function is based in part upon procfs crate which is:
//   Copyright (c) 2015 The procfs Developers
//   SPDX-License-Identifier: MIT
// /proc/pid/stat* parsaing functions are based in part upon procinfo-rs crate which is:
//   Copyright (c) 2015 The Rust Project Developers
//   SPDX-License-Identifier: MIT
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    collections::{HashMap, HashSet},
    ffi::OsStr,
    fs::File,
    io::{BufReader, IoSlice, IoSliceMut, Read},
    ops::Range,
    os::{
        fd::{AsRawFd, RawFd},
        unix::ffi::OsStrExt,
    },
};

use ahash::RandomState;
use btoi::{btoi, btoi_radix};
use indexmap::IndexSet;
use memchr::{
    arch::all::{is_equal, is_prefix},
    memchr,
};
use nix::{
    errno::Errno,
    fcntl::{readlinkat, OFlag},
    libc::pid_t,
    sys::{
        stat::Mode,
        sysinfo::sysinfo,
        uio::{process_vm_readv, process_vm_writev, RemoteIoVec},
    },
    unistd::{lseek64, read, Gid, Pid, Uid, Whence},
};
use procfs::{
    process::{MMPermissions, MMapPath, MemoryMaps},
    FromBufRead,
};

use crate::{
    compat::{getdents64, trim_ascii},
    config::*,
    elf::ElfType,
    err::err2no,
    fs::safe_open,
    parsers::{map_result, parse_stat, parse_statm, parse_status, read_to_end},
    path::XPathBuf,
    SydMemoryMap, SydSigSet, XPath,
};

/// Process memory usage information.
///
/// All values are in units of pages.
///
/// See `man 5 proc` and `Linux/fs/proc/array.c`.
#[derive(Debug, Default, PartialEq, Eq, Hash)]
pub struct Statm {
    /// Total virtual memory size.
    pub size: u64,
}

/// Process status information.
///
/// See `man 5 proc` and `Linux/fs/proc/array.c`.
#[derive(Debug, Default, PartialEq, Eq, Hash)]
pub struct Stat {
    /// Number of threads in this process (since Linux 2.6).
    pub num_threads: u64,
    /// The address of the start (i.e., bottom) of the stack.
    pub startstack: u64,
    /// The controlling terminal of the process. (The minor device
    /// number is contained in the combination of bits 31 to 20 and 7 to
    /// 0; the major device number is in bits 15 to 8.)
    pub tty_nr: i32,
}

/// Process status information.
///
/// See `man 5 proc` and `Linux/fs/proc/array.c`.
#[derive(Default, Debug, PartialEq, Eq, Hash)]
pub struct Status {
    /// Filename of the executable.
    pub command: XPathBuf,
    /// File mode creation mask (since Linux 4.7).
    pub umask: libc::mode_t,
    /// Process ID (i.e., Thread Group ID).
    pub pid: libc::pid_t,
    /// Number of signals pending for the thread (see pthreads(7)).
    pub sig_pending_thread: SydSigSet,
    /// Number of signals pending for the process (see signal(7)).
    pub sig_pending_process: SydSigSet,
    /// Mask indicating signals being blocked.
    pub sig_blocked: SydSigSet,
    /// Mask indicating signals being ignored.
    pub sig_ignored: SydSigSet,
    /// Mask indicating signals being caught.
    pub sig_caught: SydSigSet,
}

// major 204 is a mess -- "Low-density serial ports"
const LOW_DENSITY_NAMES: &[&str] = &[
    "LU0", "LU1", "LU2", "LU3", "FB0", "SA0", "SA1", "SA2", "SC0", "SC1", "SC2", "SC3", "FW0",
    "FW1", "FW2", "FW3", "AM0", "AM1", "AM2", "AM3", "AM4", "AM5", "AM6", "AM7", "AM8", "AM9",
    "AM10", "AM11", "AM12", "AM13", "AM14", "AM15", "DB0", "DB1", "DB2", "DB3", "DB4", "DB5",
    "DB6", "DB7", "SG0", "SMX0", "SMX1", "SMX2", "MM0", "MM1", "CPM0", "CPM1", "CPM2",
    "CPM3", /* "CPM4", "CPM5", */
    // bad allocation?
    "IOC0", "IOC1", "IOC2", "IOC3", "IOC4", "IOC5", "IOC6", "IOC7", "IOC8", "IOC9", "IOC10",
    "IOC11", "IOC12", "IOC13", "IOC14", "IOC15", "IOC16", "IOC17", "IOC18", "IOC19", "IOC20",
    "IOC21", "IOC22", "IOC23", "IOC24", "IOC25", "IOC26", "IOC27", "IOC28", "IOC29", "IOC30",
    "IOC31", "VR0", "VR1", "IOC84", "IOC85", "IOC86", "IOC87", "IOC88", "IOC89", "IOC90", "IOC91",
    "IOC92", "IOC93", "IOC94", "IOC95", "IOC96", "IOC97", "IOC98", "IOC99", "IOC100", "IOC101",
    "IOC102", "IOC103", "IOC104", "IOC105", "IOC106", "IOC107", "IOC108", "IOC109", "IOC110",
    "IOC111", "IOC112", "IOC113", "IOC114", "IOC115", "SIOC0", "SIOC1", "SIOC2", "SIOC3", "SIOC4",
    "SIOC5", "SIOC6", "SIOC7", "SIOC8", "SIOC9", "SIOC10", "SIOC11", "SIOC12", "SIOC13", "SIOC14",
    "SIOC15", "SIOC16", "SIOC17", "SIOC18", "SIOC19", "SIOC20", "SIOC21", "SIOC22", "SIOC23",
    "SIOC24", "SIOC25", "SIOC26", "SIOC27", "SIOC28", "SIOC29", "SIOC30", "SIOC31", "PSC0", "PSC1",
    "PSC2", "PSC3", "PSC4", "PSC5", "AT0", "AT1", "AT2", "AT3", "AT4", "AT5", "AT6", "AT7", "AT8",
    "AT9", "AT10", "AT11", "AT12", "AT13", "AT14", "AT15", "NX0", "NX1", "NX2", "NX3", "NX4",
    "NX5", "NX6", "NX7", "NX8", "NX9", "NX10", "NX11", "NX12", "NX13", "NX14", "NX15",
    "J0", // minor is 186
    "UL0", "UL1", "UL2", "UL3", "xvc0", // FAIL -- "/dev/xvc0" lacks "tty" prefix
    "PZ0", "PZ1", "PZ2", "PZ3", "TX0", "TX1", "TX2", "TX3", "TX4", "TX5", "TX6", "TX7", "SC0",
    "SC1", "SC2", "SC3", "MAX0", "MAX1", "MAX2", "MAX3",
];

#[allow(clippy::disallowed_methods)]
#[inline(always)]
fn bytes_to_pid(bytes: &[u8]) -> Result<Pid, Errno> {
    btoi::<pid_t>(bytes)
        .map(Pid::from_raw)
        .or(Err(Errno::EINVAL))
}

#[allow(clippy::disallowed_methods)]
#[inline(always)]
fn bytes_to_fd(bytes: &[u8]) -> Result<RawFd, Errno> {
    btoi::<RawFd>(bytes).or(Err(Errno::EINVAL))
}

fn read_usize_from_ne_bytes(bytes: &[u8], ptr_size: usize) -> Result<usize, Errno> {
    match ptr_size {
        4 => {
            if bytes.len() < 4 {
                return Err(Errno::EFAULT);
            }
            Ok(usize::try_from(u32::from_ne_bytes(
                bytes[..4].try_into().or(Err(Errno::EINVAL))?,
            ))
            .or(Err(Errno::EINVAL))?)
        }
        8 => {
            if bytes.len() < 8 {
                return Err(Errno::EFAULT);
            }
            Ok(usize::try_from(u64::from_ne_bytes(
                bytes[..8].try_into().or(Err(Errno::EINVAL))?,
            ))
            .or(Err(Errno::EINVAL))?)
        }
        _ => Err(Errno::EINVAL),
    }
}

fn usize_to_ne_bytes(value: usize, sizeof_ptr: usize) -> Vec<u8> {
    #[allow(clippy::cast_possible_truncation)]
    match sizeof_ptr {
        4 => (value as u32).to_ne_bytes().to_vec(),
        8 => (value as u64).to_ne_bytes().to_vec(),
        _ => unreachable!("Invalid pointer size!"),
    }
}

/// Return the stat of the given process.
pub fn proc_stat(pid: Pid) -> Result<Stat, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"stat");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .or(Err(Errno::ESRCH))?;

    let mut buf = [0; 1024]; // A typical stat file is about 300 bytes.
    map_result(parse_stat(read_to_end(&mut file, &mut buf)?))
}

/// Return the memory stat of the given process.
pub fn proc_statm(pid: Pid) -> Result<Statm, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"statm");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .or(Err(Errno::ESRCH))?;

    let mut buf = [0; 256]; // A typical statm file is about 25 bytes.
    map_result(parse_statm(read_to_end(&mut file, &mut buf)?))
}

/// Return the status of the given process.
pub fn proc_status(pid: Pid) -> Result<Status, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"status");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .or(Err(Errno::ESRCH))?;

    let mut buf = [0; 2048]; // A typical status file is about 1000 bytes.
    map_result(parse_status(read_to_end(&mut file, &mut buf)?))
}

/// Retrieves the thread group ID (TGID) for the specified thread ID (TID).
pub fn proc_tgid(tid: Pid) -> Result<Pid, Errno> {
    proc_status(tid).map(|stat| stat.pid).map(Pid::from_raw)
}

/// Get the umask of the given `Pid`.
pub fn proc_umask(pid: Pid) -> Result<Mode, Errno> {
    proc_status(pid)
        .map(|stat| stat.umask)
        .map(Mode::from_bits_truncate)
}

/// Locate stack and return the stack memory address range.
pub fn proc_stack(pid: Pid) -> Result<Range<u64>, Errno> {
    let maps = proc_maps(pid)?;

    // Iterate over each memory map entry.
    for map in maps {
        if let MMapPath::Stack = map.0.pathname {
            return Ok(map.0.address.0..map.0.address.1);
        }
    }

    Err(Errno::ENOENT)
}

/// Retrieves the address of the start of stack for the specified process ID (pid).
pub fn proc_stack_start(pid: Pid) -> Result<u64, Errno> {
    proc_stat(pid).map(|stat| stat.startstack)
}

/// Retrieves the current stack pointer (rsp) for the specified process ID (pid).
///
/// If the process is "running", it returns `Errno::EBUSY`.
pub fn proc_stack_pointer(pid: Pid) -> Result<u64, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"syscall");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .map(BufReader::new)
        .or(Err(Errno::ESRCH))?;

    let mut data = Vec::with_capacity(128);
    file.read_to_end(&mut data).or(Err(Errno::EIO))?;

    // Check if the contents equal "running".
    if data.starts_with(b"running") {
        return Err(Errno::EBUSY);
    }

    // Split the contents by whitespace.
    let parts: Vec<&[u8]> = data.split(|&b| b == b' ').collect();
    if parts.len() >= 2 {
        // Convert the rsp value from hex to u64 using btoi.
        #[allow(clippy::arithmetic_side_effects)]
        let rsp = parts[parts.len() - 2];
        if let Some(rsp) = rsp.strip_prefix(b"0x") {
            return btoi_radix::<u64>(rsp, 16).or(Err(Errno::EINVAL));
        }
    }

    Err(Errno::ENOENT)
}

/// Get the auxiliary vector of the given `Pid`.
pub fn proc_auxv(pid: Pid) -> Result<HashMap<u64, u64, RandomState>, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"auxv");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .or(Err(Errno::ESRCH))?;

    let mut map = HashMap::default();

    let mut buf = Vec::new();
    let bytes_read = file.read_to_end(&mut buf).map_err(|err| err2no(&err))?;
    if bytes_read == 0 {
        // some kernel processes won't have any data for their auxv file
        return Ok(map);
    }
    buf.truncate(bytes_read);
    let mut file = std::io::Cursor::new(buf);

    let mut buf = 0usize.to_ne_bytes();
    loop {
        file.read_exact(&mut buf).map_err(|err| err2no(&err))?;
        let key = usize::from_ne_bytes(buf) as u64;
        file.read_exact(&mut buf).map_err(|err| err2no(&err))?;
        let value = usize::from_ne_bytes(buf) as u64;
        if key == 0 && value == 0 {
            break;
        }
        map.insert(key, value);
    }

    Ok(map)
}

/// Retrieves the current working directory (CWD) of the specified process ID (PID).
///
/// This function reads the symbolic link `/proc/<pid>/cwd` to determine the CWD.
pub fn proc_cwd(pid: Pid) -> Result<XPathBuf, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"cwd");
    readlinkat(Some(PROC_FILE().as_raw_fd()), &path)
        .map(XPathBuf::from)
        .or(Err(Errno::ESRCH))
}

/// Retrieves the command name (comm) of the specified process ID (PID)
/// as a single string.
pub fn proc_comm(pid: Pid) -> Result<XPathBuf, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"comm");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .or(Err(Errno::ESRCH))
        .map(File::from)?;

    // Read up to 16 characters or until EOF.
    let mut comm = [0u8; 16];
    let mut nread = 0;
    while nread < comm.len() {
        #[allow(clippy::arithmetic_side_effects)]
        match file.read(&mut comm[nread..]).map_err(|e| err2no(&e)) {
            Ok(0) => break,
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(_) => return Err(Errno::ESRCH),
        }
    }

    // Remove the trailing NUL-byte and return an `XPathBuf`.
    let idx = nread.saturating_sub(1);
    if comm[idx] == 0 {
        nread = idx;
    }
    Ok(XPathBuf::from(OsStr::from_bytes(trim_ascii(
        &comm[..nread],
    ))))
}

/// Retrieves the command line of the specified process ID (PID)
/// concatenated as a single string.
///
/// This function reads the `/proc/<pid>/cmdline` file and concatenates
/// the arguments using spaces. The function takes care of replacing null
/// bytes (`'\0'`) with spaces to format the command line as a readable string.
pub fn proc_cmdline(pid: Pid) -> Result<XPathBuf, Errno> {
    // Construct path to the appropriate cmdline file.
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"cmdline");

    let mut file = safe_open(Some(&PROC_FILE()), &path, OFlag::O_RDONLY)
        .or(Err(Errno::ESRCH))
        .map(File::from)?;

    // Read up to 256 bytes.
    const LIMIT: usize = 256;
    let mut data = [0u8; LIMIT];
    let mut nread = 0;
    while nread < LIMIT {
        #[allow(clippy::arithmetic_side_effects)]
        match file.read(&mut data[nread..]).map_err(|e| err2no(&e)) {
            Ok(0) => break,
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(_) => return Err(Errno::ESRCH),
        }
    }

    let mut data = data.to_vec();
    // Determine if EOF was reached or if we hit the limit
    #[allow(clippy::arithmetic_side_effects)]
    if nread <= 1 {
        // Empty cmdline.
        return Ok(XPathBuf::empty());
    } else if nread >= LIMIT - 1 {
        // Check if the last byte read is not a null byte,
        // indicating there's more data.
        if data[LIMIT - 1] != 0 {
            // Append ellipsis to indicate truncation.
            data.extend_from_slice("…".as_bytes());
        } else {
            // Remove the NUL-byte.
            data.pop();
        }
    } else {
        // If EOF was hit before the limit,
        // resize the buffer to nread - 1 (for null byte).
        data.resize(nread - 1, 0);
    }

    // Replace null bytes with spaces.
    for byte in &mut data {
        if *byte == 0 {
            *byte = b' ';
        }
    }

    Ok(data.into())
}

/// Return the memory maps of the given process.
pub fn proc_maps(pid: Pid) -> Result<Vec<SydMemoryMap>, Errno> {
    let mut pfd = XPathBuf::from_pid(pid);
    pfd.push(b"maps");

    let reader = safe_open(Some(&PROC_FILE()), &pfd, OFlag::empty())
        .map(File::from)
        .map(BufReader::new)?;

    MemoryMaps::from_buf_read(reader)
        .map(|maps| maps.0.into_iter().map(SydMemoryMap).collect::<Vec<_>>())
        .or(Err(Errno::ESRCH))
}

/// Return the memory maps of the given process.
pub fn proc_smaps(pid: Pid) -> Result<Vec<SydMemoryMap>, Errno> {
    let mut path = XPathBuf::from_pid(pid);
    path.push(b"smaps");

    let reader = safe_open(Some(&PROC_FILE()), &path, OFlag::empty())
        .map(File::from)
        .map(BufReader::new)
        .or(Err(Errno::ESRCH))?;

    MemoryMaps::from_buf_read(reader)
        .map(|maps| maps.0.into_iter().map(SydMemoryMap).collect::<Vec<_>>())
        .or(Err(Errno::ESRCH))
}

/// Returns the memory map of an address by reading `/proc/pid/maps`.
///
/// Returns EFAULT if the address does not fall within any range.
pub fn proc_mmap(pid: Pid, addr: u64) -> Result<SydMemoryMap, Errno> {
    let maps = proc_maps(pid)?;

    for mmap in maps {
        // Check if the address falls within range.
        if (mmap.0.address.0..mmap.0.address.1).contains(&addr) {
            return Ok(mmap);
        }
    }

    Err(Errno::EFAULT)
}

/// Retrieve the system pipe max limit.
pub fn proc_pipemax() -> Result<nix::libc::c_int, Errno> {
    let fd = safe_open(
        Some(&PROC_FILE()),
        XPath::from_bytes(b"sys/fs/pipe-max-size"),
        OFlag::empty(),
    )
    .or(Err(Errno::ESRCH))?;

    // Read up to 24 bytes.
    let mut data = [0u8; 24];
    let mut nread = 0;
    while nread < data.len() {
        #[allow(clippy::arithmetic_side_effects)]
        match read(fd.as_raw_fd(), &mut data[nread..]) {
            Ok(0) => break,
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(_) => return Err(Errno::ESRCH),
        }
    }

    btoi::<nix::libc::c_int>(&data[..nread]).or(Err(Errno::EINVAL))
}

/// Retrieves the value of /proc/sys/fs/file-max.
pub fn proc_fs_file_max() -> Result<u64, Errno> {
    let fd = safe_open(
        Some(&PROC_FILE()),
        XPath::from_bytes(b"sys/fs/file-max"),
        OFlag::empty(),
    )
    .or(Err(Errno::ESRCH))?;

    // Read up to 24 bytes + 1 bytes for \n.
    let mut data = [0u8; 25];
    let mut nread = 0;
    while nread < data.len() {
        #[allow(clippy::arithmetic_side_effects)]
        match read(fd.as_raw_fd(), &mut data[nread..]) {
            Ok(0) => break,
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(_) => return Err(Errno::ESRCH),
        }
    }

    btoi::<u64>(trim_ascii(&data[..nread])).or(Err(Errno::EINVAL))
}

/// Retrieves the value of /proc/sys/vm/mmap_min_addr.
#[allow(clippy::disallowed_methods)]
pub fn proc_mmap_min_addr() -> Result<u64, Errno> {
    let fd = safe_open(
        Some(&PROC_FILE()),
        XPath::from_bytes(b"sys/vm/mmap_min_addr"),
        OFlag::empty(),
    )
    .or(Err(Errno::ESRCH))?;

    // Read up to 24 bytes + 1 bytes for \n.
    let mut data = [0u8; 25];
    let mut nread = 0;
    while nread < data.len() {
        #[allow(clippy::arithmetic_side_effects)]
        match read(fd.as_raw_fd(), &mut data[nread..]) {
            Ok(0) => break,
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(_) => return Err(Errno::ESRCH),
        }
    }

    btoi::<u64>(trim_ascii(&data[..nread])).or(Err(Errno::EINVAL))
}

/// Reads the tty number from /proc/[pid]/stat and figures out the corresponding /dev/tty device node path.
#[allow(clippy::arithmetic_side_effects)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::disallowed_methods)]
pub fn proc_tty(pid: Pid) -> Result<XPathBuf, Errno> {
    let stat = proc_stat(pid)?;
    if stat.tty_nr <= 0 {
        // Process has no controlling terminal
        return Err(Errno::ENXIO);
    }

    // minor is bits 31-20 and 7-0
    // major is 15-8
    let tty_nr = stat.tty_nr;

    // mmmmmmmmmmmm____MMMMMMMMmmmmmmmm
    // 11111111111100000000000000000000
    let major = (tty_nr & 0xfff00) >> 8;
    let minor = (tty_nr & 0x000ff) | ((tty_nr >> 12) & 0xfff00);

    match major {
        3 => Ok(XPathBuf::from(format!(
            "/dev/tty{}{}",
            "pqrstuvwxyzabcde"[(minor >> 4) as usize..]
                .chars()
                .next()
                .unwrap(),
            "0123456789abcdef"[(minor & 0x0f) as usize..]
                .chars()
                .next()
                .unwrap()
        ))),
        4 => {
            if minor < 64 {
                Ok(XPathBuf::from(format!("/dev/tty{}", minor)))
            } else {
                Ok(XPathBuf::from(format!("/dev/ttyS{}", minor - 64)))
            }
        }
        11 => Ok(XPathBuf::from(format!("/dev/ttyB{}", minor))),
        14 => Ok(XPathBuf::from(format!("/dev/tty{}", minor))), // Standard TTYs
        17 => Ok(XPathBuf::from(format!("/dev/ttyH{}", minor))),
        19 | 22 | 23 => Ok(XPathBuf::from(format!("/dev/ttyD{}", minor))),
        24 => Ok(XPathBuf::from(format!("/dev/ttyE{}", minor))),
        32 => Ok(XPathBuf::from(format!("/dev/ttyX{}", minor))),
        43 => Ok(XPathBuf::from(format!("/dev/ttyI{}", minor))),
        46 => Ok(XPathBuf::from(format!("/dev/ttyR{}", minor))),
        48 => Ok(XPathBuf::from(format!("/dev/ttyL{}", minor))),
        57 => Ok(XPathBuf::from(format!("/dev/ttyP{}", minor))),
        71 => Ok(XPathBuf::from(format!("/dev/ttyF{}", minor))),
        75 => Ok(XPathBuf::from(format!("/dev/ttyW{}", minor))),
        78 | 112 => Ok(XPathBuf::from(format!("/dev/ttyM{}", minor))),
        105 => Ok(XPathBuf::from(format!("/dev/ttyV{}", minor))),
        136..=143 => Ok(XPathBuf::from(format!(
            "/dev/pts/{}",
            minor + (major - 136) * 256
        ))),
        148 => Ok(XPathBuf::from(format!("/dev/ttyT{}", minor))),
        154 | 156 => Ok(XPathBuf::from(format!(
            "/dev/ttySR{}",
            minor + if major == 156 { 256 } else { 0 }
        ))),
        164 => Ok(XPathBuf::from(format!("/dev/ttyCH{}", minor))),
        166 => Ok(XPathBuf::from(format!("/dev/ttyACM{}", minor))),
        172 => Ok(XPathBuf::from(format!("/dev/ttyMX{}", minor))),
        174 => Ok(XPathBuf::from(format!("/dev/ttySI{}", minor))),
        188 => Ok(XPathBuf::from(format!("/dev/ttyUSB{}", minor))),
        204 => {
            if minor as usize >= LOW_DENSITY_NAMES.len() {
                Err(Errno::ENXIO)
            } else {
                Ok(XPathBuf::from(format!(
                    "/dev/tty{}",
                    LOW_DENSITY_NAMES[minor as usize]
                )))
            }
        }
        208 => Ok(XPathBuf::from(format!("/dev/ttyU{}", minor))),
        216 => Ok(XPathBuf::from(format!("/dev/ttyUB{}", minor))),
        224 => Ok(XPathBuf::from(format!("/dev/ttyY{}", minor))),
        227 => Ok(XPathBuf::from(format!("/dev/3270/tty{}", minor))),
        229 => Ok(XPathBuf::from(format!("/dev/iseries/vtty{}", minor))),
        256 => Ok(XPathBuf::from(format!("/dev/ttyEQ{}", minor))),
        _ => Err(Errno::ENXIO),
    }
}

/// Returns the number of threads for the given process.
pub fn proc_task_nr(pid: Pid) -> Result<u64, Errno> {
    proc_stat(pid).map(|p| p.num_threads)
}

/// Returns the number of threads for the current process.
pub fn proc_task_nr_syd() -> Result<u64, Errno> {
    proc_stat(Pid::this()).map(|p| p.num_threads)
}

/// Returns the number of processes in the system.
pub fn proc_task_nr_sys() -> Result<u64, Errno> {
    Ok(sysinfo()?.process_count().into())
}

/// Checks if the number of tasks across all processes in the system
/// exceeds the given limit by inspecting the `/proc` filesystem.
/// Current process is not included into the limit.
pub fn proc_task_limit(pid: Pid, max: u64) -> Result<bool, Errno> {
    // Count tasks for the given process, return if limit hit.
    let mut count = proc_task_nr(pid)?;
    if count >= max {
        return Ok(true);
    }

    // This function is only called from the main thread.
    // Hence no concurrent readdir() is possible here.
    // Rewind the directory and get to work!
    let fd = PROC_FILE();
    lseek64(fd.as_raw_fd(), 0, Whence::SeekSet)?;

    let this = Pid::this().as_raw();

    // Allocate a large-enough buffer to read in one go.
    const GETDENTS_BUFSIZ: usize = 64 * 1024; // 64 KB.
    let mut tasks = Vec::with_capacity(GETDENTS_BUFSIZ);

    // Count processes in the global /proc namespace,
    // return if limit is hit.
    loop {
        let mut entries = match getdents64(&fd, GETDENTS_BUFSIZ) {
            Ok(entries) => entries,
            Err(
                Errno::UnknownErrno | Errno::EACCES | Errno::ENOENT | Errno::EPERM | Errno::ESRCH,
            ) => break,
            Err(errno) => return Err(errno),
        };

        #[allow(clippy::arithmetic_side_effects)]
        for entry in &mut entries {
            // PID paths are directories.
            if !entry.is_dir() {
                continue;
            }

            // Parse PID, continue on errors.
            let task = match btoi::<pid_t>(entry.name_bytes()) {
                Ok(pid) => pid,
                Err(_) => continue,
            };

            // Skip current process and the given process.
            if task == pid.as_raw() || task == this {
                continue;
            }

            // Push task to the list, return if limit is hit.
            tasks.push(task);
            count += 1;
            if count >= max {
                return Ok(true);
            }
        }
    }

    // Count tasks per-process.
    for task in tasks {
        #[allow(clippy::arithmetic_side_effects)]
        match proc_task_nr(Pid::from_raw(task)) {
            Ok(n) => count += n,
            Err(_) => continue, // task died mid-way?
        }
        if count >= max {
            return Ok(true);
        }
    }

    // If we reached here,
    // we did not hit the limit.
    Ok(false)
}

const FD: &[u8] = b"/fd/";
const PROC: &[u8] = b"/proc/";
const TASK: &[u8] = b"/task/";

/// 0. Assumes the given path is normalized.
/// 1. The path must start with /proc.
/// 2. The second component must be a numeric PID equal to the given PID.
/// 3. An optional task/[TID] part, where [TID] is a numeric PID.
/// 4. Finally, it checks for an fd component followed by a numeric file descriptor.
///    Note, This function does not allocate.
///    Note, use negated PID if you only need validation and not the exact FD value.
///
/// SAFETY: If `restrict_magiclinks` is `true`, this function returns
/// Err(Errno::EACCES) if PID is present but not equal to given PID.
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::type_complexity)]
pub fn proc_fd(
    pid: Pid,
    path: &XPath,
    restrict_magiclinks: bool,
) -> Result<Option<(RawFd, Pid)>, Errno> {
    let path = path.as_bytes();
    if !is_prefix(path, PROC) {
        // Not a /proc path.
        return Ok(None);
    }

    let path_without_proc = &path[PROC.len()..];
    let next_slash_index = memchr(b'/', path_without_proc).unwrap_or(path_without_proc.len());
    let pid_section = &path_without_proc[..next_slash_index];
    if pid_section.is_empty() || !pid_section.iter().all(|c| c.is_ascii_digit()) {
        // Skip /proc/not-a-pid paths.
        return Ok(None);
    }

    let mut pid = if restrict_magiclinks {
        if pid != bytes_to_pid(pid_section)? {
            // PID mismatch detected!
            //
            // SAFETY: Note, ideally we want to return
            // ENOENT here for stealth, however this
            // confuses programs such as pipewire when
            // they're checking for flatpak support.
            // Check for pw_check_flatpak() function
            // in pipewire source code for more information.
            return Err(Errno::EACCES);
        }
        pid
    } else {
        bytes_to_pid(pid_section)?
    };

    let after_pid_section = &path_without_proc[next_slash_index..];
    let start_of_interesting_part = if after_pid_section.starts_with(TASK) {
        let after_task_section = &after_pid_section[TASK.len()..];
        match memchr(b'/', after_task_section) {
            Some(idx) => {
                pid = bytes_to_pid(&after_task_section[..idx])?;
                idx
            }
            None => after_task_section.len(),
        }
        .saturating_add(TASK.len())
    } else {
        0
    };

    let remaining_path = &after_pid_section[start_of_interesting_part..];
    if is_prefix(remaining_path, FD) {
        // Check for valid file descriptor number after /fd/
        let fd_section = &remaining_path[FD.len()..];

        Ok(if fd_section.iter().all(|c| c.is_ascii_digit()) {
            Some((bytes_to_fd(fd_section)?, pid))
        } else {
            None
        })
    } else if is_equal(remaining_path, b"/cwd") {
        // Magic CWD.
        Ok(Some((nix::libc::AT_FDCWD, pid)))
    } else if is_equal(remaining_path, b"/root") {
        // Magic ROOT.
        Ok(Some((-1, pid)))
    } else if is_equal(remaining_path, b"/exe") {
        // Magic EXE.
        Ok(Some((-2, pid)))
    } else {
        // Not a magic symlink.
        Ok(None)
    }
}

/// Checks whether process memory usage is within the give maximum.
///
/// This function uses the `procfs` crate to obtain detailed memory maps
/// from `/proc/[pid]/smaps`. It sums multiple memory usage values reported in these maps
/// to calculate a more comprehensive total memory usage.
///
/// # Returns
///
/// This function returns a `Result<bool, Errno>`. It returns Ok(true)
/// if the limit was exceeded Ok(false) otherwise. On failure, it
/// returns `Errno`.
///
/// # Errors
///
/// This function returns an error if it fails to retrieve the process's memory maps,
/// typically due to insufficient permissions or an invalid process ID.
pub fn proc_mem_limit(pid: Pid, max: u64) -> Result<bool, Errno> {
    match proc_smaps(pid) {
        Ok(maps) => {
            let mut total_size: u64 = 0;
            for map in &maps {
                match &map.0.pathname {
                    MMapPath::Path(_)
                    | MMapPath::Anonymous
                    | MMapPath::Stack
                    | MMapPath::Other(_) => {
                        let pss = map.0.extension.map.get("Pss").copied().unwrap_or(0);
                        let private_dirty = map
                            .0
                            .extension
                            .map
                            .get("Private_Dirty")
                            .copied()
                            .unwrap_or(0);
                        let shared_dirty = map
                            .0
                            .extension
                            .map
                            .get("Shared_Dirty")
                            .copied()
                            .unwrap_or(0);

                        total_size = total_size.saturating_add(
                            pss.saturating_add(private_dirty)
                                .saturating_add(shared_dirty),
                        );

                        // Stop processing if total size exceeds or equals max
                        if total_size >= max {
                            return Ok(true);
                        }
                    }
                    _ => (),
                }
            }

            // If we're at this point, we did not hit the limit.
            Ok(false)
        }
        Err(_) => Err(Errno::last()),
    }
}

/// Collects all unique paths with executable permissions from the
/// memory maps of a process.
///
/// # Returns
///
/// A `Result` containing a vector of `(XPathBuf,u64,i32,i32)` objects
/// for all unique paths with executable permissions along with their
/// device ID and inode. On error, it returns an `Errno`.
#[allow(clippy::type_complexity)]
pub fn proc_executables(pid: Pid) -> Result<Vec<(XPathBuf, u64, i32, i32)>, Errno> {
    let maps = proc_maps(pid)?;

    // Create an IndexSet to store unique executable paths while
    // preserving insertion order.
    let mut paths = IndexSet::new();

    // Iterate over each memory map entry.
    for map in maps {
        // Match on the pathname to extract the path.
        if let MMapPath::Path(path) = map.0.pathname {
            // Check if the map has executable permissions.
            if map.0.perms.contains(MMPermissions::EXECUTE) {
                // Insert the path into the IndexSet.
                paths.insert((path.into(), map.0.inode, map.0.dev.0, map.0.dev.1));
            }
        }
    }

    // Convert the IndexSet to a vector and return.
    Ok(paths.into_iter().collect())
}

/// Parses /proc/net/unix and returns the inodes of all UNIX domain sockets.
pub fn proc_unix_get_inodes() -> Result<HashSet<u64>, Errno> {
    Ok(procfs::net::unix()
        .or(Err(Errno::EPERM))?
        .into_iter()
        .filter(|entry| {
            // The prefix '@' is for abstract sockets.
            entry
                .path
                .as_ref()
                .map(|p| p.as_os_str().as_bytes().first() != Some(&b'@'))
                .unwrap_or(false)
        })
        .map(|entry| entry.inode)
        .collect())
}

/// Sets the AT_SECURE value to 1 in the auxiliary vector of the
/// specified process.
///
/// This function locates the auxiliary vector in the target process's
/// memory and sets the AT_SECURE entry to 1. It uses the
/// `/proc/pid/stat` file to get the address of the start of the stack
/// and parses the stack according to the standard Linux process stack
/// layout.
///
/// # Arguments
///
/// * `pid` - The PID of the target process.
/// * `elf_type` - The ELF type (Elf32 or Elf64) of the target process.
///
/// # Returns
///
/// Returns `Ok(())` on success, or an `Err(Errno)` if an error occurs.
pub fn proc_set_at_secure(pid: Pid, elf_type: ElfType) -> Result<(), Errno> {
    // Get the address of the start of the stack for the process.
    let sp = proc_stack_start(pid)?;
    let sp = usize::try_from(sp).or(Err(Errno::EFAULT))?;

    let (sizeof_ptr, sizeof_ptr2) = match elf_type {
        ElfType::Elf32 => (4, 8),
        ElfType::Elf64 => (8, 16),
    };
    let mut offset = 0usize;

    // Read argc (the argument count).
    let mut buf = [0u8; 8]; // Max size needed for usize.
    let mut local_iov = [IoSliceMut::new(&mut buf[..sizeof_ptr])];
    let remote_iov = [RemoteIoVec {
        base: sp,
        len: sizeof_ptr,
    }];
    let bytes_read = process_vm_readv(pid, &mut local_iov, &remote_iov)?;
    if bytes_read != sizeof_ptr {
        return Err(Errno::EIO);
    }

    // SAFETY: Ensure argc is not zero.
    // The Linux kernel has a mitigation for this since version 5.18.
    // Syd is recommended to run on Linux-5.19 or later but technically
    // it is possible to run it on Linux-5.0 or later so we include the
    // check here for defense in depth.
    // See:
    // https://github.com/torvalds/linux/blob/88d324e69ea9f3ae1c1905ea75d717c08bdb8e15/fs/exec.c#L1962
    // https://lwn.net/Articles/882799/
    // https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
    let argc = read_usize_from_ne_bytes(&buf[..sizeof_ptr], sizeof_ptr)?;
    if argc == 0 {
        return Err(Errno::EINVAL);
    }
    offset = offset.checked_add(sizeof_ptr).ok_or(Errno::EINVAL)?;

    // Skip over argv pointers (argc pointers plus a NULL terminator).
    let argv_size = argc
        .checked_add(1)
        .ok_or(Errno::EINVAL)?
        .checked_mul(sizeof_ptr)
        .ok_or(Errno::EINVAL)?;
    offset = offset.checked_add(argv_size).ok_or(Errno::EINVAL)?;

    // Skip over envp pointers until NULL terminator is found.
    loop {
        // Read one pointer at a time.
        let mut envp_buf = [0u8; 8];
        let mut local_iov = [IoSliceMut::new(&mut envp_buf[..sizeof_ptr])];
        let remote_iov = [RemoteIoVec {
            base: sp.checked_add(offset).ok_or(Errno::EINVAL)?,
            len: sizeof_ptr,
        }];
        let bytes_read = process_vm_readv(pid, &mut local_iov, &remote_iov)?;
        if bytes_read != sizeof_ptr {
            return Err(Errno::EIO);
        }
        let envp_ptr = read_usize_from_ne_bytes(&envp_buf[..sizeof_ptr], sizeof_ptr)?;
        offset = offset.checked_add(sizeof_ptr).ok_or(Errno::EINVAL)?;
        if envp_ptr == 0 {
            break;
        }
    }

    // Read the auxiliary vector into a buffer starting from the current
    // offset. Technically ~256 bytes should be enough to locate
    // AT_SECURE because an auxv entry is at most 16 bytes (8 bytes on
    // 32-bit), and AT_SECURE is roughly the 16th on the list but we
    // leave a bit more room for safety and future-compat. Check
    // fs/exec.c in kernel sources for more information.
    const READ_SIZE: usize = 512; // Read up to 512 bytes.
    let mut buf = [0u8; READ_SIZE];
    let mut local_iov = [IoSliceMut::new(&mut buf)];
    let sp = sp.checked_add(offset).ok_or(Errno::EINVAL)?;
    let remote_iov = [RemoteIoVec {
        base: sp,
        len: READ_SIZE,
    }];

    let bytes_read = process_vm_readv(pid, &mut local_iov, &remote_iov)?;
    if bytes_read == 0 {
        return Err(Errno::EIO);
    }

    // Now parse the auxiliary vector.
    // For added validation, we ensure we have the sequence of keys:
    // AT_UID -> AT_EUID -> AT_GID -> AT_EGID -> AT_SECURE
    // We also ensure no other key is ever present in this sequence.
    // The AT_NULL key indicates the end of the vector.
    #[allow(clippy::cast_possible_truncation)]
    const AT_NULL: usize = nix::libc::AT_NULL as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_UID: usize = nix::libc::AT_UID as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_EUID: usize = nix::libc::AT_EUID as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_GID: usize = nix::libc::AT_GID as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_EGID: usize = nix::libc::AT_EGID as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_SECURE: usize = nix::libc::AT_SECURE as usize;
    #[allow(clippy::cast_possible_truncation)]
    const AT_REQKEY: &[usize] = &[AT_UID, AT_EUID, AT_GID, AT_EGID, AT_SECURE];
    let mut required_index = 0;

    // Store the UID/GID values for comparison.
    // We're going to check these values against ours
    // and fail as necessary.
    let mut at_uid_val = None;
    let mut at_euid_val = None;
    let mut at_gid_val = None;
    let mut at_egid_val = None;

    offset = 0;
    loop {
        // Determine key and value location, and check it's within bounds.
        let key_end = offset.checked_add(sizeof_ptr).ok_or(Errno::EINVAL)?;
        let val_end = key_end.checked_add(sizeof_ptr).ok_or(Errno::EINVAL)?;
        if val_end > bytes_read {
            break;
        }

        // Read key and value from the buffer.
        let key = read_usize_from_ne_bytes(&buf[offset..key_end], sizeof_ptr)?;
        let val = read_usize_from_ne_bytes(&buf[key_end..val_end], sizeof_ptr)?;

        if key == AT_REQKEY[required_index] {
            // Key matches the expected key in sequence.
            match key {
                AT_UID => at_uid_val = Some(val),
                AT_EUID => at_euid_val = Some(val),
                AT_GID => at_gid_val = Some(val),
                AT_EGID => at_egid_val = Some(val),
                _ => {}
            }

            required_index = required_index.checked_add(1).ok_or(Errno::EINVAL)?;
            if required_index >= AT_REQKEY.len() {
                // We've found the sequence ending with AT_SECURE!

                // Verify that the UID/GID values match ours.
                let uid = Uid::current().as_raw() as usize;
                let euid = Uid::effective().as_raw() as usize;
                let gid = Gid::current().as_raw() as usize;
                let egid = Gid::effective().as_raw() as usize;

                if at_uid_val != Some(uid)
                    || at_euid_val != Some(euid)
                    || at_gid_val != Some(gid)
                    || at_egid_val != Some(egid)
                {
                    // SAFETY:
                    // 1. Change return success.
                    // 2. Going and coming without error.
                    // 3. Action brings good fortune.
                    return Err(Errno::EACCES);
                }

                // Only modify AT_SECURE, if it's not already set.
                // SAFETY: We do this check only after UID/GID verification.
                if val != 0 {
                    return Ok(());
                }

                // Overwrite the value in the local buffer.
                let val = usize_to_ne_bytes(1, sizeof_ptr);
                buf[key_end..val_end].copy_from_slice(&val);

                // Prepare to write back the modified value.
                let local_iov = [IoSlice::new(&buf[key_end..val_end])];
                let remote_iov = [RemoteIoVec {
                    base: sp.checked_add(key_end).ok_or(Errno::EINVAL)?,
                    len: sizeof_ptr,
                }];

                // SAFETY: We have verified that the auxiliary vector
                // contains the expected keys in the correct order, and
                // that the UID/GID values match our own. This ensures
                // that we are modifying a trusted process. We also
                // ensure that the offsets are within the bounds of the
                // read buffer, preventing overflows.

                // Write the modified value back to the target process.
                if process_vm_writev(pid, &local_iov, &remote_iov)? != sizeof_ptr {
                    return Err(Errno::EIO);
                }

                // All done, return success.
                return Ok(());
            }
        } else if required_index > 0 {
            // An unexpected key appeared; validation fails!
            return Err(Errno::EACCES);
        } else if key == AT_NULL {
            break;
        }

        // SAFETY: We check for arithmetic overflow when advancing the
        // offset to prevent wrapping around.
        offset = offset.checked_add(sizeof_ptr2).ok_or(Errno::EINVAL)?;
    }

    Err(Errno::ENOENT)
}

#[cfg(test)]
mod tests {
    use std::{io::Write, os::unix::process::CommandExt, process::Command};

    use nix::{
        sys::{
            ptrace,
            ptrace::Options,
            signal::{kill, Signal},
            stat::umask,
            wait::{waitpid, WaitPidFlag, WaitStatus},
        },
        unistd::{fork, ForkResult},
    };
    use tempfile::tempdir_in;

    use super::*;
    use crate::{elf::ELFTYPE_NATIVE, xpath};

    fn setup() -> bool {
        let _ = crate::log::log_init_simple(crate::syslog::LogLevel::Warn);

        if let Err(error) = crate::config::proc_init() {
            eprintln!("Failed to initialize proc: {error:?}");
            return false;
        }

        true
    }

    #[test]
    fn test_invalid_pid() {
        if !setup() {
            return;
        }

        let result = proc_umask(Pid::from_raw(i32::MAX));
        assert!(result.is_err(), "{result:?}");
    }

    #[test]
    fn test_parsing_valid_umask_values() {
        if !setup() {
            return;
        }

        // This test sets various umask values and then checks if our function correctly identifies them.
        let umasks = [
            Mode::from_bits_truncate(0o0000),
            Mode::from_bits_truncate(0o0002),
            Mode::from_bits_truncate(0o0022),
            Mode::from_bits_truncate(0o0077),
            Mode::from_bits_truncate(0o0777),
        ];

        for &my_umask in &umasks {
            umask(my_umask);
            let result = proc_umask(Pid::this()).unwrap();
            assert_eq!(result, my_umask, "{result:o} != {my_umask:o}");
        }

        // Resetting the umask to a default value after test
        umask(Mode::from_bits_truncate(0o0022));
    }

    #[test]
    fn test_proc_fd() {
        let this = Pid::this();
        let that = Pid::from_raw(1);

        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/cwd"), true),
            Ok(Some((nix::libc::AT_FDCWD, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/exe"), true),
            Ok(Some((-2, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/root"), true),
            Ok(Some((-1, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/fd"), true),
            Err(Errno::EACCES)
        );
        assert_eq!(proc_fd(this, &xpath!("/proc/{that}/fd"), false), Ok(None));
        assert_eq!(proc_fd(this, &xpath!("/proc/{this}/fd"), true), Ok(None));
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/fd/0"), true),
            Ok(Some((0, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/fd/42"), true),
            Ok(Some((42, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/fd/1984"), true),
            Ok(Some((1984, this)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{this}/task/{that}/fd/7"), true),
            Ok(Some((7, that)))
        );

        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/cwd"), true),
            Err(Errno::EACCES)
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/exe"), true),
            Err(Errno::EACCES)
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/root"), true),
            Err(Errno::EACCES)
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/fd/0"), true),
            Err(Errno::EACCES)
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/task/{this}/fd/7"), true),
            Err(Errno::EACCES)
        );

        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/cwd"), false),
            Ok(Some((libc::AT_FDCWD, that)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/exe"), false),
            Ok(Some((-2, that)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/root"), false),
            Ok(Some((-1, that)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/fd/0"), false),
            Ok(Some((0, that)))
        );
        assert_eq!(
            proc_fd(this, &xpath!("/proc/{that}/task/{this}/fd/7"), false),
            Ok(Some((7, this)))
        );
    }

    #[test]
    fn test_proc_set_at_secure_test_native_dynamic() -> Result<(), Errno> {
        proc_set_at_secure_test(false, false, false)
    }

    #[test]
    fn test_proc_set_at_secure_test_native_static() -> Result<(), Errno> {
        proc_set_at_secure_test(false, true, false)
    }

    #[test]
    fn test_proc_set_at_secure_test_native_dynamic_pie() -> Result<(), Errno> {
        proc_set_at_secure_test(false, false, true)
    }

    #[test]
    fn test_proc_set_at_secure_test_native_static_pie() -> Result<(), Errno> {
        proc_set_at_secure_test(false, true, true)
    }

    #[test]
    fn test_proc_set_at_secure_test_32bit_dynamic() -> Result<(), Errno> {
        proc_set_at_secure_test(true, false, false)
    }

    #[test]
    fn test_proc_set_at_secure_test_32bit_static() -> Result<(), Errno> {
        proc_set_at_secure_test(true, true, false)
    }

    #[test]
    fn test_proc_set_at_secure_test_32bit_dynamic_pie() -> Result<(), Errno> {
        proc_set_at_secure_test(true, false, true)
    }

    #[test]
    fn test_proc_set_at_secure_test_32bit_static_pie() -> Result<(), Errno> {
        proc_set_at_secure_test(true, true, true)
    }

    fn proc_set_at_secure_test(arch32: bool, statik: bool, pie: bool) -> Result<(), Errno> {
        if !setup() {
            return Ok(());
        }

        // Write the C program.
        let c_program = r#"
        #include <stdlib.h>
        #include <sys/auxv.h>

        int main(void) {
            return getauxval(AT_SECURE) ? EXIT_SUCCESS : EXIT_FAILURE;
        }
        "#;

        // Write the C program to a file in the current directory.
        let temp_dir = tempdir_in("/tmp").expect("Failed to create temporary directory!");
        let src_path = temp_dir.path().join("at_secure_test.c");
        let exe_path = temp_dir.path().join("at_secure_test");
        let mut src_file = File::create(&src_path).expect("Failed to create C source file!");
        write!(src_file, "{c_program}").expect("Failed to write C source file!");

        // Compile the C program using "cc" command
        let mut cmd = Command::new("cc");
        let elf_type = if arch32 {
            cmd.arg("-m32");
            ElfType::Elf32
        } else {
            ELFTYPE_NATIVE
        };
        if statik && pie {
            cmd.arg("-static-pie");
        } else if statik {
            cmd.arg("-static");
        }
        cmd.arg(&src_path).arg("-o").arg(&exe_path);
        match cmd.output() {
            Ok(output) => {
                if !output.status.success() {
                    eprintln!(
                        "Compilation failed with arch32={arch32} static:{statik} pie:{pie}: {}",
                        String::from_utf8_lossy(&output.stderr)
                    );
                    return Ok(()); // Skip test if compilation failed.
                }
            }
            Err(e) => {
                eprintln!("Failed to execute cc command: {e}");
                return Ok(()); // Skip test if compiler not available.
            }
        }

        // Fork and execve the compiled program under ptrace.
        match unsafe { fork() } {
            Ok(ForkResult::Child) => {
                // Child process
                if ptrace::traceme().is_err() {
                    eprintln!("Child: ptrace::traceme failed!");
                    std::process::exit(1);
                }
                let _ = kill(Pid::this(), Signal::SIGSTOP);
                let _ = Command::new(&exe_path).env("LD_SHOW_AUXV", "1").exec();
                eprintln!("Child: exec failed!");
                std::process::exit(127);
            }
            Ok(ForkResult::Parent { child }) => {
                // Parent process
                match waitpid(child, None) {
                    Ok(WaitStatus::Stopped(_, Signal::SIGSTOP)) => {
                        // Child is stopped, proceed...
                    }
                    Ok(status) => {
                        eprintln!("Parent: Unexpected wait status: {status:?}");
                        return Err(Errno::EIO);
                    }
                    Err(e) => {
                        eprintln!("Parent: waitpid failed: {e}");
                        return Err(Errno::EIO);
                    }
                }

                // Set ptrace options to get PTRACE_EVENT_EXEC.
                // Set exit-kill to ensure no stray child processes.
                if let Err(e) = ptrace::setoptions(
                    child,
                    Options::PTRACE_O_TRACEEXEC | Options::PTRACE_O_EXITKILL,
                ) {
                    eprintln!("Parent: ptrace::setoptions failed: {e}");
                    return Err(e);
                }

                // Continue the child
                if let Err(e) = ptrace::cont(child, None) {
                    eprintln!("Parent: ptrace::cont failed: {e}");
                    return Err(e);
                }

                // Wait for PTRACE_EVENT_EXEC
                loop {
                    match waitpid(child, Some(WaitPidFlag::empty())) {
                        Ok(WaitStatus::PtraceEvent(_, Signal::SIGTRAP, event))
                            if event == ptrace::Event::PTRACE_EVENT_EXEC as i32 =>
                        {
                            break;
                        }
                        Ok(WaitStatus::Stopped(_, _)) => {
                            if let Err(e) = ptrace::cont(child, None) {
                                eprintln!("Parent: ptrace::cont failed during loop: {e}");
                                return Err(e);
                            }
                        }
                        Ok(WaitStatus::Exited(_, status)) => {
                            eprintln!("Child exited unexpectedly with status {status}");
                            return Err(Errno::EIO);
                        }
                        Ok(WaitStatus::Signaled(_, sig, _)) => {
                            eprintln!("Child terminated by signal {sig:?}");
                            return Err(Errno::EIO);
                        }
                        Ok(status) => {
                            eprintln!("Parent: Unexpected wait status: {status:?}");
                        }
                        Err(e) => {
                            eprintln!("Parent: waitpid failed: {e}");
                            return Err(Errno::EIO);
                        }
                    }
                }

                // Call our function to set AT_SECURE.
                if let Err(e) = proc_set_at_secure(child, elf_type) {
                    eprintln!("proc_set_at_secure failed: {e}");
                    return Err(e);
                }

                // Continue the child
                if let Err(e) = ptrace::cont(child, None) {
                    eprintln!("Parent: ptrace::cont failed after setting AT_SECURE: {e}");
                    return Err(e);
                }

                // Wait for the child to exit
                loop {
                    match waitpid(child, None) {
                        Ok(WaitStatus::Exited(_, status_code)) => {
                            if status_code != 0 {
                                eprintln!("Child exited with failure status {status_code}");
                                return Err(Errno::EIO);
                            }

                            // Test passed!
                            return Ok(());
                        }
                        Ok(WaitStatus::Signaled(_, sig, _)) => {
                            eprintln!("Child terminated by signal {sig:?}");
                            return Err(Errno::EIO);
                        }
                        Ok(WaitStatus::Stopped(_, _)) => {
                            // Continue the child.
                            if let Err(e) = ptrace::cont(child, None) {
                                eprintln!("Parent: ptrace::cont failed during final loop: {e}");
                                return Err(e);
                            }
                        }
                        Ok(status) => {
                            eprintln!("Parent: Unexpected wait status: {status:?}");
                        }
                        Err(e) => {
                            eprintln!("Parent: waitpid failed: {e}");
                            return Err(Errno::EIO);
                        }
                    }
                }
            }
            Err(e) => {
                eprintln!("Fork failed: {e}");
                return Err(Errno::EIO);
            }
        }
    }
}
