//! Bindings for the Linux `prctl` system call.
//!
//! There are similarities (but also differences) with FreeBSD's `procctl`
//! system call, whose interface is located in the `procctl.rs` file.

#![allow(unsafe_code)]

use core::convert::{TryFrom, TryInto};
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::{mem, ptr};

use bitflags::bitflags;

use crate::backend::c::{c_int, c_uint, c_void};
use crate::backend::process::syscalls;
use crate::backend::process::types::Signal;
use crate::fd::{AsRawFd, BorrowedFd};
use crate::ffi::CStr;
use crate::io;
use crate::process::{Pid, RawPid};

//
// Helper functions.
//

#[inline]
pub(crate) unsafe fn prctl_1arg(option: c_int) -> io::Result<c_int> {
    const NULL: *mut c_void = ptr::null_mut();
    syscalls::prctl(option, NULL, NULL, NULL, NULL)
}

#[inline]
pub(crate) unsafe fn prctl_2args(option: c_int, arg2: *mut c_void) -> io::Result<c_int> {
    const NULL: *mut c_void = ptr::null_mut();
    syscalls::prctl(option, arg2, NULL, NULL, NULL)
}

#[inline]
pub(crate) unsafe fn prctl_3args(
    option: c_int,
    arg2: *mut c_void,
    arg3: *mut c_void,
) -> io::Result<c_int> {
    syscalls::prctl(option, arg2, arg3, ptr::null_mut(), ptr::null_mut())
}

#[inline]
pub(crate) unsafe fn prctl_get_at_arg2_optional<P>(option: i32) -> io::Result<P> {
    let mut value: MaybeUninit<P> = MaybeUninit::uninit();
    prctl_2args(option, value.as_mut_ptr().cast())?;
    Ok(value.assume_init())
}

#[inline]
pub(crate) unsafe fn prctl_get_at_arg2<P, T>(option: i32) -> io::Result<T>
where
    P: Default,
    T: TryFrom<P, Error = io::Errno>,
{
    let mut value: P = Default::default();
    prctl_2args(option, ((&mut value) as *mut P).cast())?;
    TryFrom::try_from(value)
}

//
// PR_GET_PDEATHSIG/PR_SET_PDEATHSIG
//

const PR_GET_PDEATHSIG: c_int = 2;

/// Get the current value of the parent process death signal.
///
/// # References
///  - [Linux: `prctl(PR_GET_PDEATHSIG,...)`]
///  - [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]
///
/// [Linux: `prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
/// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
#[inline]
pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
    unsafe { prctl_get_at_arg2_optional::<c_int>(PR_GET_PDEATHSIG) }.map(Signal::from_raw)
}

const PR_SET_PDEATHSIG: c_int = 1;

/// Set the parent-death signal of the calling process.
///
/// # References
///  - [Linux: `prctl(PR_SET_PDEATHSIG,...)`]
///  - [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]
///
/// [Linux: `prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
/// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
#[inline]
pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
    let signal = signal.map_or(0_usize, |signal| signal as usize);
    unsafe { prctl_2args(PR_SET_PDEATHSIG, signal as *mut _) }.map(|_r| ())
}

//
// PR_GET_DUMPABLE/PR_SET_DUMPABLE
//

const PR_GET_DUMPABLE: c_int = 3;

const SUID_DUMP_DISABLE: i32 = 0;
const SUID_DUMP_USER: i32 = 1;
const SUID_DUMP_ROOT: i32 = 2;

/// `SUID_DUMP_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum DumpableBehavior {
    /// Not dumpable.
    NotDumpable = SUID_DUMP_DISABLE,
    /// Dumpable.
    Dumpable = SUID_DUMP_USER,
    /// Dumpable but only readable by root.
    DumpableReadableOnlyByRoot = SUID_DUMP_ROOT,
}

impl TryFrom<i32> for DumpableBehavior {
    type Error = io::Errno;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        match value {
            SUID_DUMP_DISABLE => Ok(Self::NotDumpable),
            SUID_DUMP_USER => Ok(Self::Dumpable),
            SUID_DUMP_ROOT => Ok(Self::DumpableReadableOnlyByRoot),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get the current state of the calling process's `dumpable` attribute.
///
/// # References
///  - [`prctl(PR_GET_DUMPABLE,...)`]
///
/// [`prctl(PR_GET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn dumpable_behavior() -> io::Result<DumpableBehavior> {
    unsafe { prctl_1arg(PR_GET_DUMPABLE) }.and_then(TryInto::try_into)
}

const PR_SET_DUMPABLE: c_int = 4;

/// Set the state of the `dumpable` attribute, which determines whether the
/// process can be traced and whether core dumps are produced for the calling
/// process upon delivery of a signal whose default behavior is to produce a
/// core dump.
///
/// A similar function with the same name is available on FreeBSD (as part of
/// the `procctl` interface), but it has an extra argument which allows to
/// select a process other then the current process.
///
/// # References
///  - [`prctl(PR_SET_DUMPABLE,...)`]
///
/// [`prctl(PR_SET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_dumpable_behavior(config: DumpableBehavior) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_DUMPABLE, config as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_UNALIGN/PR_SET_UNALIGN
//

const PR_GET_UNALIGN: c_int = 5;

bitflags! {
    /// `PR_UNALIGN_*`.
    pub struct UnalignedAccessControl: u32 {
        /// Silently fix up unaligned user accesses.
        const NO_PRINT = 1;
        /// Generate `SIGBUS` on unaligned user access.
        const SIGBUS = 2;
    }
}

/// Get unaligned access control bits.
///
/// # References
///  - [`prctl(PR_GET_UNALIGN,...)`]
///
/// [`prctl(PR_GET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn unaligned_access_control() -> io::Result<UnalignedAccessControl> {
    let r = unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_UNALIGN)? };
    UnalignedAccessControl::from_bits(r).ok_or(io::Errno::RANGE)
}

const PR_SET_UNALIGN: c_int = 6;

/// Set unaligned access control bits.
///
/// # References
///  - [`prctl(PR_SET_UNALIGN,...)`]
///
/// [`prctl(PR_SET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_unaligned_access_control(config: UnalignedAccessControl) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_UNALIGN, config.bits() as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_FPEMU/PR_SET_FPEMU
//

const PR_GET_FPEMU: c_int = 9;

bitflags! {
    /// `PR_FPEMU_*`.
    pub struct FloatingPointEmulationControl: u32 {
        /// Silently emulate floating point operations accesses.
        const NO_PRINT = 1;
        /// Don't emulate floating point operations, send `SIGFPE` instead.
        const SIGFPE = 2;
    }
}

/// Get floating point emulation control bits.
///
/// # References
///  - [`prctl(PR_GET_FPEMU,...)`]
///
/// [`prctl(PR_GET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn floating_point_emulation_control() -> io::Result<FloatingPointEmulationControl> {
    let r = unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_FPEMU)? };
    FloatingPointEmulationControl::from_bits(r).ok_or(io::Errno::RANGE)
}

const PR_SET_FPEMU: c_int = 10;

/// Set floating point emulation control bits.
///
/// # References
///  - [`prctl(PR_SET_FPEMU,...)`]
///
/// [`prctl(PR_SET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_floating_point_emulation_control(
    config: FloatingPointEmulationControl,
) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_FPEMU, config.bits() as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_FPEXC/PR_SET_FPEXC
//

const PR_GET_FPEXC: c_int = 11;

bitflags! {
    /// Zero means floating point exceptions are disabled.
    pub struct FloatingPointExceptionMode: u32 {
        /// Async non-recoverable exception mode.
        const NONRECOV = 1;
        /// Async recoverable exception mode.
        const ASYNC = 2;
        /// Precise exception mode.
        const PRECISE = 3;

        /// Use FPEXC for floating point exception enables.
        const SW_ENABLE = 0x80;
        /// Floating point divide by zero.
        const DIV = 0x01_0000;
        /// Floating point overflow.
        const OVF = 0x02_0000;
        /// Floating point underflow.
        const UND = 0x04_0000;
        /// Floating point inexact result.
        const RES = 0x08_0000;
        /// Floating point invalid operation.
        const INV = 0x10_0000;
    }
}

/// Get floating point exception mode.
///
/// # References
///  - [`prctl(PR_GET_FPEXC,...)`]
///
/// [`prctl(PR_GET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn floating_point_exception_mode() -> io::Result<Option<FloatingPointExceptionMode>> {
    unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_FPEXC) }
        .map(FloatingPointExceptionMode::from_bits)
}

const PR_SET_FPEXC: c_int = 12;

/// Set floating point exception mode.
///
/// # References
///  - [`prctl(PR_SET_FPEXC,...)`]
///
/// [`prctl(PR_SET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_floating_point_exception_mode(
    config: Option<FloatingPointExceptionMode>,
) -> io::Result<()> {
    let config = config.as_ref().map_or(0, FloatingPointExceptionMode::bits);
    unsafe { prctl_2args(PR_SET_FPEXC, config as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_TIMING/PR_SET_TIMING
//

const PR_GET_TIMING: c_int = 13;

const PR_TIMING_STATISTICAL: i32 = 0;
const PR_TIMING_TIMESTAMP: i32 = 1;

/// `PR_TIMING_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum TimingMethod {
    /// Normal, traditional, statistical process timing.
    Statistical = PR_TIMING_STATISTICAL,
    /// Accurate timestamp based process timing.
    TimeStamp = PR_TIMING_TIMESTAMP,
}

impl TryFrom<i32> for TimingMethod {
    type Error = io::Errno;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        match value {
            PR_TIMING_STATISTICAL => Ok(Self::Statistical),
            PR_TIMING_TIMESTAMP => Ok(Self::TimeStamp),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get which process timing method is currently in use.
///
/// # References
///  - [`prctl(PR_GET_TIMING,...)`]
///
/// [`prctl(PR_GET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn timing_method() -> io::Result<TimingMethod> {
    unsafe { prctl_1arg(PR_GET_TIMING) }.and_then(TryInto::try_into)
}

const PR_SET_TIMING: c_int = 14;

/// Set whether to use (normal, traditional) statistical process timing or
/// accurate timestamp-based process timing.
///
/// # References
///  - [`prctl(PR_SET_TIMING,...)`]
///
/// [`prctl(PR_SET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_timing_method(method: TimingMethod) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_TIMING, method as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_ENDIAN/PR_SET_ENDIAN
//

const PR_GET_ENDIAN: c_int = 19;

const PR_ENDIAN_BIG: u32 = 0;
const PR_ENDIAN_LITTLE: u32 = 1;
const PR_ENDIAN_PPC_LITTLE: u32 = 2;

/// `PR_ENDIAN_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum EndianMode {
    /// Big endian mode.
    Big = PR_ENDIAN_BIG,
    /// True little endian mode.
    Little = PR_ENDIAN_LITTLE,
    /// `PowerPC` pseudo little endian.
    PowerPCLittle = PR_ENDIAN_PPC_LITTLE,
}

impl TryFrom<u32> for EndianMode {
    type Error = io::Errno;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            PR_ENDIAN_BIG => Ok(Self::Big),
            PR_ENDIAN_LITTLE => Ok(Self::Little),
            PR_ENDIAN_PPC_LITTLE => Ok(Self::PowerPCLittle),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get the endianness of the calling process.
///
/// # References
///  - [`prctl(PR_GET_ENDIAN,...)`]
///
/// [`prctl(PR_GET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn endian_mode() -> io::Result<EndianMode> {
    unsafe { prctl_get_at_arg2::<c_uint, _>(PR_GET_ENDIAN) }
}

const PR_SET_ENDIAN: c_int = 20;

/// Set the endianness of the calling process.
///
/// # References
///  - [`prctl(PR_SET_ENDIAN,...)`]
///
/// # Safety
///
/// Please ensure the conditions necessary to safely call this function,
/// as detailed in the references above.
///
/// [`prctl(PR_SET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub unsafe fn set_endian_mode(mode: EndianMode) -> io::Result<()> {
    prctl_2args(PR_SET_ENDIAN, mode as usize as *mut _).map(|_r| ())
}

//
// PR_GET_TSC/PR_SET_TSC
//

const PR_GET_TSC: c_int = 25;

const PR_TSC_ENABLE: u32 = 1;
const PR_TSC_SIGSEGV: u32 = 2;

/// `PR_TSC_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum TimeStampCounterReadability {
    /// Allow the use of the timestamp counter.
    Readable = PR_TSC_ENABLE,
    /// Throw a `SIGSEGV` instead of reading the TSC.
    RaiseSIGSEGV = PR_TSC_SIGSEGV,
}

impl TryFrom<u32> for TimeStampCounterReadability {
    type Error = io::Errno;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            PR_TSC_ENABLE => Ok(Self::Readable),
            PR_TSC_SIGSEGV => Ok(Self::RaiseSIGSEGV),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get the state of the flag determining if the timestamp counter can be read.
///
/// # References
///  - [`prctl(PR_GET_TSC,...)`]
///
/// [`prctl(PR_GET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn time_stamp_counter_readability() -> io::Result<TimeStampCounterReadability> {
    unsafe { prctl_get_at_arg2::<c_uint, _>(PR_GET_TSC) }
}

const PR_SET_TSC: c_int = 26;

/// Set the state of the flag determining if the timestamp counter can be read
/// by the process.
///
/// # References
///  - [`prctl(PR_SET_TSC,...)`]
///
/// [`prctl(PR_SET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_time_stamp_counter_readability(
    readability: TimeStampCounterReadability,
) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_TSC, readability as usize as *mut _) }.map(|_r| ())
}

//
// PR_TASK_PERF_EVENTS_DISABLE/PR_TASK_PERF_EVENTS_ENABLE
//

const PR_TASK_PERF_EVENTS_DISABLE: c_int = 31;
const PR_TASK_PERF_EVENTS_ENABLE: c_int = 32;

/// Enable or disable all performance counters attached to the calling process.
///
/// # References
///  - [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`]
///  - [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`]
///
/// [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
/// [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn configure_performance_counters(enable: bool) -> io::Result<()> {
    let option = if enable {
        PR_TASK_PERF_EVENTS_ENABLE
    } else {
        PR_TASK_PERF_EVENTS_DISABLE
    };

    unsafe { prctl_1arg(option) }.map(|_r| ())
}

//
// PR_MCE_KILL_GET/PR_MCE_KILL
//

const PR_MCE_KILL_GET: c_int = 34;

const PR_MCE_KILL_LATE: u32 = 0;
const PR_MCE_KILL_EARLY: u32 = 1;
const PR_MCE_KILL_DEFAULT: u32 = 2;

/// `PR_MCE_KILL_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum MachineCheckMemoryCorruptionKillPolicy {
    /// Late kill policy.
    Late = PR_MCE_KILL_LATE,
    /// Early kill policy.
    Early = PR_MCE_KILL_EARLY,
    /// System-wide default policy.
    Default = PR_MCE_KILL_DEFAULT,
}

impl TryFrom<u32> for MachineCheckMemoryCorruptionKillPolicy {
    type Error = io::Errno;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            PR_MCE_KILL_LATE => Ok(Self::Late),
            PR_MCE_KILL_EARLY => Ok(Self::Early),
            PR_MCE_KILL_DEFAULT => Ok(Self::Default),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get the current per-process machine check kill policy.
///
/// # References
///  - [`prctl(PR_MCE_KILL_GET,...)`]
///
/// [`prctl(PR_MCE_KILL_GET,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn machine_check_memory_corruption_kill_policy(
) -> io::Result<MachineCheckMemoryCorruptionKillPolicy> {
    let r = unsafe { prctl_1arg(PR_MCE_KILL_GET)? } as c_uint;
    MachineCheckMemoryCorruptionKillPolicy::try_from(r)
}

const PR_MCE_KILL: c_int = 33;

const PR_MCE_KILL_CLEAR: usize = 0;
const PR_MCE_KILL_SET: usize = 1;

/// Set the machine check memory corruption kill policy for the calling thread.
///
/// # References
///  - [`prctl(PR_MCE_KILL,...)`]
///
/// [`prctl(PR_MCE_KILL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_machine_check_memory_corruption_kill_policy(
    policy: Option<MachineCheckMemoryCorruptionKillPolicy>,
) -> io::Result<()> {
    let (sub_operation, policy) = if let Some(policy) = policy {
        (PR_MCE_KILL_SET, policy as usize as *mut _)
    } else {
        (PR_MCE_KILL_CLEAR, ptr::null_mut())
    };

    unsafe { prctl_3args(PR_MCE_KILL, sub_operation as *mut _, policy) }.map(|_r| ())
}

//
// PR_SET_MM
//

const PR_SET_MM: c_int = 35;

const PR_SET_MM_START_CODE: u32 = 1;
const PR_SET_MM_END_CODE: u32 = 2;
const PR_SET_MM_START_DATA: u32 = 3;
const PR_SET_MM_END_DATA: u32 = 4;
const PR_SET_MM_START_STACK: u32 = 5;
const PR_SET_MM_START_BRK: u32 = 6;
const PR_SET_MM_BRK: u32 = 7;
const PR_SET_MM_ARG_START: u32 = 8;
const PR_SET_MM_ARG_END: u32 = 9;
const PR_SET_MM_ENV_START: u32 = 10;
const PR_SET_MM_ENV_END: u32 = 11;
const PR_SET_MM_AUXV: usize = 12;
const PR_SET_MM_EXE_FILE: usize = 13;
const PR_SET_MM_MAP: usize = 14;
const PR_SET_MM_MAP_SIZE: usize = 15;

/// `PR_SET_MM_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum VirtualMemoryMapAddress {
    /// Set the address above which the program text can run.
    CodeStart = PR_SET_MM_START_CODE,
    /// Set the address below which the program text can run.
    CodeEnd = PR_SET_MM_END_CODE,
    /// Set the address above which initialized and uninitialized (bss) data
    /// are placed.
    DataStart = PR_SET_MM_START_DATA,
    /// Set the address below which initialized and uninitialized (bss) data
    /// are placed.
    DataEnd = PR_SET_MM_END_DATA,
    /// Set the start address of the stack.
    StackStart = PR_SET_MM_START_STACK,
    /// Set the address above which the program heap can be expanded with `brk`
    /// call.
    BrkStart = PR_SET_MM_START_BRK,
    /// Set the current `brk` value.
    BrkCurrent = PR_SET_MM_BRK,
    /// Set the address above which the program command line is placed.
    ArgStart = PR_SET_MM_ARG_START,
    /// Set the address below which the program command line is placed.
    ArgEnd = PR_SET_MM_ARG_END,
    /// Set the address above which the program environment is placed.
    EnvironmentStart = PR_SET_MM_ENV_START,
    /// Set the address below which the program environment is placed.
    EnvironmentEnd = PR_SET_MM_ENV_END,
}

/// Modify certain kernel memory map descriptor addresses of the calling
/// process.
///
/// # References
///  - [`prctl(PR_SET_MM,...)`]
///
/// # Safety
///
/// Please ensure the conditions necessary to safely call this function,
/// as detailed in the references above.
///
/// [`prctl(PR_SET_MM,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub unsafe fn set_virtual_memory_map_address(
    option: VirtualMemoryMapAddress,
    address: Option<NonNull<c_void>>,
) -> io::Result<()> {
    let address = address.map_or_else(ptr::null_mut, NonNull::as_ptr);
    prctl_3args(PR_SET_MM, option as usize as *mut _, address).map(|_r| ())
}

/// Supersede the `/proc/pid/exe` symbolic link with a new one pointing to a
/// new executable file.
///
/// # References
///  - [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`]
///
/// [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_executable_file(fd: BorrowedFd) -> io::Result<()> {
    let fd = usize::try_from(fd.as_raw_fd()).map_err(|_r| io::Errno::RANGE)?;
    unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_EXE_FILE as *mut _, fd as *mut _) }.map(|_r| ())
}

/// Set a new auxiliary vector.
///
/// # References
///  - [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`]
///
/// # Safety
///
/// Please ensure the conditions necessary to safely call this function,
/// as detailed in the references above.
///
/// [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub unsafe fn set_auxiliary_vector(auxv: &[*const c_void]) -> io::Result<()> {
    syscalls::prctl(
        PR_SET_MM,
        PR_SET_MM_AUXV as *mut _,
        auxv.as_ptr() as *mut _,
        auxv.len() as *mut _,
        ptr::null_mut(),
    )
    .map(|_r| ())
}

/// Get the size of the [`PrctlMmMap`] the kernel expects.
///
/// # References
///  - [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`]
///
/// [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn virtual_memory_map_config_struct_size() -> io::Result<usize> {
    let mut value: c_uint = 0;
    let value_ptr = (&mut value) as *mut c_uint;
    unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_MAP_SIZE as *mut _, value_ptr.cast())? };
    Ok(value as usize)
}

/// This structure provides new memory descriptor map which mostly modifies
/// `/proc/pid/stat[m]` output for a task.
/// This mostly done in a sake of checkpoint/restore functionality.
#[repr(C)]
#[derive(Debug, Clone)]
pub struct PrctlMmMap {
    /// Code section start address.
    pub start_code: u64,
    /// Code section end address.
    pub end_code: u64,
    /// Data section start address.
    pub start_data: u64,
    /// Data section end address.
    pub end_data: u64,
    /// brk() start address.
    pub start_brk: u64,
    /// brk() current address.
    pub brk: u64,
    /// Stack start address.
    pub start_stack: u64,
    /// Program command line start address.
    pub arg_start: u64,
    /// Program command line end address.
    pub arg_end: u64,
    /// Program environment start address.
    pub env_start: u64,
    /// Program environment end address.
    pub env_end: u64,
    /// Auxiliary vector start address.
    pub auxv: *mut u64,
    /// Auxiliary vector size.
    pub auxv_size: u32,
    /// File descriptor of executable file that was used to create this
    /// process.
    pub exe_fd: u32,
}

/// Provides one-shot access to all the addresses by passing in a
/// [`PrctlMmMap`].
///
/// # References
///  - [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`]
///
/// # Safety
///
/// Please ensure the conditions necessary to safely call this function,
/// as detailed in the references above.
///
/// [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub unsafe fn configure_virtual_memory_map(config: &PrctlMmMap) -> io::Result<()> {
    syscalls::prctl(
        PR_SET_MM,
        PR_SET_MM_MAP as *mut _,
        config as *const PrctlMmMap as *mut _,
        mem::size_of::<PrctlMmMap>() as *mut _,
        ptr::null_mut(),
    )
    .map(|_r| ())
}

//
// PR_SET_PTRACER
//

const PR_SET_PTRACER: c_int = 0x59_61_6d_61;

const PR_SET_PTRACER_ANY: usize = usize::MAX;

/// Process ptracer.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PTracer {
    /// None.
    None,
    /// Disable `ptrace` restrictions for the calling process.
    Any,
    /// Specific process.
    ProcessID(Pid),
}

/// Declare that the ptracer process can `ptrace` the calling process as if it
/// were a direct process ancestor.
///
/// # References
///  - [`prctl(PR_SET_PTRACER,...)`]
///
/// [`prctl(PR_SET_PTRACER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_ptracer(tracer: PTracer) -> io::Result<()> {
    let pid = match tracer {
        PTracer::None => ptr::null_mut(),
        PTracer::Any => PR_SET_PTRACER_ANY as *mut _,
        PTracer::ProcessID(pid) => pid.as_raw_nonzero().get() as usize as *mut _,
    };

    unsafe { prctl_2args(PR_SET_PTRACER, pid) }.map(|_r| ())
}

//
// PR_GET_CHILD_SUBREAPER/PR_SET_CHILD_SUBREAPER
//

const PR_GET_CHILD_SUBREAPER: c_int = 37;

/// Get the `child subreaper` setting of the calling process.
///
/// # References
///  - [`prctl(PR_GET_CHILD_SUBREAPER,...)`]
///
/// [`prctl(PR_GET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn child_subreaper() -> io::Result<Option<Pid>> {
    unsafe {
        let r = prctl_get_at_arg2_optional::<c_uint>(PR_GET_CHILD_SUBREAPER)?;
        Ok(Pid::from_raw(r as RawPid))
    }
}

const PR_SET_CHILD_SUBREAPER: c_int = 36;

/// Set the `child subreaper` attribute of the calling process.
///
/// # References
///  - [`prctl(PR_SET_CHILD_SUBREAPER,...)`]
///
/// [`prctl(PR_SET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_child_subreaper(pid: Option<Pid>) -> io::Result<()> {
    let pid = pid.map_or(0_usize, |pid| pid.as_raw_nonzero().get() as usize);
    unsafe { prctl_2args(PR_SET_CHILD_SUBREAPER, pid as *mut _) }.map(|_r| ())
}

//
// PR_GET_FP_MODE/PR_SET_FP_MODE
//

const PR_GET_FP_MODE: c_int = 46;

const PR_FP_MODE_FR: u32 = 1_u32 << 0;
const PR_FP_MODE_FRE: u32 = 1_u32 << 1;

/// `PR_FP_MODE_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum FloatingPointMode {
    /// 64-bit floating point registers.
    FloatingPointRegisters = PR_FP_MODE_FR,
    /// Enable emulation of 32-bit floating-point mode.
    FloatingPointEmulation = PR_FP_MODE_FRE,
}

impl TryFrom<u32> for FloatingPointMode {
    type Error = io::Errno;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            PR_FP_MODE_FR => Ok(Self::FloatingPointRegisters),
            PR_FP_MODE_FRE => Ok(Self::FloatingPointEmulation),
            _ => Err(io::Errno::RANGE),
        }
    }
}

/// Get the current floating point mode.
///
/// # References
///  - [`prctl(PR_GET_FP_MODE,...)`]
///
/// [`prctl(PR_GET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn floating_point_mode() -> io::Result<FloatingPointMode> {
    let r = unsafe { prctl_1arg(PR_GET_FP_MODE)? } as c_uint;
    FloatingPointMode::try_from(r)
}

const PR_SET_FP_MODE: c_int = 45;

/// Allow control of the floating point mode from user space.
///
/// # References
///  - [`prctl(PR_SET_FP_MODE,...)`]
///
/// [`prctl(PR_SET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn set_floating_point_mode(mode: FloatingPointMode) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_FP_MODE, mode as usize as *mut _) }.map(|_r| ())
}

//
// PR_GET_SPECULATION_CTRL/PR_SET_SPECULATION_CTRL
//

const PR_GET_SPECULATION_CTRL: c_int = 52;

const PR_SPEC_STORE_BYPASS: u32 = 0;
const PR_SPEC_INDIRECT_BRANCH: u32 = 1;
const PR_SPEC_L1D_FLUSH: u32 = 2;

/// `PR_SPEC_*`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum SpeculationFeature {
    /// Set the state of the speculative store bypass misfeature.
    SpeculativeStoreBypass = PR_SPEC_STORE_BYPASS,
    /// Set the state of the indirect branch speculation misfeature.
    IndirectBranchSpeculation = PR_SPEC_INDIRECT_BRANCH,
    /// Flush L1D Cache on context switch out of the task.
    FlushL1DCacheOnContextSwitchOutOfTask = PR_SPEC_L1D_FLUSH,
}

impl TryFrom<u32> for SpeculationFeature {
    type Error = io::Errno;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            PR_SPEC_STORE_BYPASS => Ok(Self::SpeculativeStoreBypass),
            PR_SPEC_INDIRECT_BRANCH => Ok(Self::IndirectBranchSpeculation),
            PR_SPEC_L1D_FLUSH => Ok(Self::FlushL1DCacheOnContextSwitchOutOfTask),
            _ => Err(io::Errno::RANGE),
        }
    }
}

bitflags! {
    /// `PR_SPEC_*`.
    pub struct SpeculationFeatureControl: u32 {
        /// The speculation feature is enabled, mitigation is disabled.
        const ENABLE = 1_u32 << 1;
        /// The speculation feature is disabled, mitigation is enabled.
        const DISABLE = 1_u32 << 2;
        /// The speculation feature is disabled, mitigation is enabled, and it cannot be undone.
        const FORCE_DISABLE = 1_u32 << 3;
        /// The speculation feature is disabled, mitigation is enabled, and the state will be cleared on `execve`.
        const DISABLE_NOEXEC = 1_u32 << 4;
    }
}

bitflags! {
    /// Zero means the processors are not vulnerable.
    pub struct SpeculationFeatureState: u32 {
        /// Mitigation can be controlled per thread by `PR_SET_SPECULATION_CTRL`.
        const PRCTL = 1_u32 << 0;
        /// The speculation feature is enabled, mitigation is disabled.
        const ENABLE = 1_u32 << 1;
        /// The speculation feature is disabled, mitigation is enabled.
        const DISABLE = 1_u32 << 2;
        /// The speculation feature is disabled, mitigation is enabled, and it cannot be undone.
        const FORCE_DISABLE = 1_u32 << 3;
        /// The speculation feature is disabled, mitigation is enabled, and the state will be cleared on `execve`.
        const DISABLE_NOEXEC = 1_u32 << 4;
    }
}

/// Get the state of the speculation misfeature.
///
/// # References
///  - [`prctl(PR_GET_SPECULATION_CTRL,...)`]
///
/// [`prctl(PR_GET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html
#[inline]
pub fn speculative_feature_state(
    feature: SpeculationFeature,
) -> io::Result<Option<SpeculationFeatureState>> {
    let r = unsafe { prctl_2args(PR_GET_SPECULATION_CTRL, feature as usize as *mut _)? } as c_uint;
    Ok(SpeculationFeatureState::from_bits(r))
}

const PR_SET_SPECULATION_CTRL: c_int = 53;

/// Sets the state of the speculation misfeature.
///
/// # References
///  - [`prctl(PR_SET_SPECULATION_CTRL,...)`]
///
/// [`prctl(PR_SET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html
#[inline]
pub fn control_speculative_feature(
    feature: SpeculationFeature,
    config: SpeculationFeatureControl,
) -> io::Result<()> {
    let feature = feature as usize as *mut _;
    let config = config.bits() as usize as *mut _;
    unsafe { prctl_3args(PR_SET_SPECULATION_CTRL, feature, config) }.map(|_r| ())
}

//
// PR_GET_IO_FLUSHER/PR_SET_IO_FLUSHER
//

const PR_GET_IO_FLUSHER: c_int = 58;

/// Get the `IO_FLUSHER` state of the caller.
///
/// # References
///  - [`prctl(PR_GET_IO_FLUSHER,...)`]
///
/// [`prctl(PR_GET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn is_io_flusher() -> io::Result<bool> {
    unsafe { prctl_1arg(PR_GET_IO_FLUSHER) }.map(|r| r != 0)
}

const PR_SET_IO_FLUSHER: c_int = 57;

/// Put the process in the `IO_FLUSHER` state, allowing it to make progress
/// when allocating memory.
///
/// # References
///  - [`prctl(PR_SET_IO_FLUSHER,...)`]
///
/// [`prctl(PR_SET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
#[inline]
pub fn configure_io_flusher_behavior(enable: bool) -> io::Result<()> {
    unsafe { prctl_2args(PR_SET_IO_FLUSHER, enable as usize as *mut _) }.map(|_r| ())
}

//
// PR_PAC_GET_ENABLED_KEYS/PR_PAC_SET_ENABLED_KEYS
//

const PR_PAC_GET_ENABLED_KEYS: c_int = 61;

bitflags! {
    /// `PR_PAC_AP*`.
    pub struct PointerAuthenticationKeys: u32 {
        /// Instruction authentication key `A`.
        const INSTRUCTION_AUTHENTICATION_KEY_A = 1_u32 << 0;
        /// Instruction authentication key `B`.
        const INSTRUCTION_AUTHENTICATION_KEY_B = 1_u32 << 1;
        /// Data authentication key `A`.
        const DATA_AUTHENTICATION_KEY_A = 1_u32 << 2;
        /// Data authentication key `B`.
        const DATA_AUTHENTICATION_KEY_B = 1_u32 << 3;
        /// Generic authentication `A` key.
        const GENERIC_AUTHENTICATION_KEY_A = 1_u32 << 4;
    }
}

/// Get enabled pointer authentication keys.
///
/// # References
///  - [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`]
///
/// [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html
#[inline]
pub fn enabled_pointer_authentication_keys() -> io::Result<PointerAuthenticationKeys> {
    let r = unsafe { prctl_1arg(PR_PAC_GET_ENABLED_KEYS)? } as c_uint;
    PointerAuthenticationKeys::from_bits(r).ok_or(io::Errno::RANGE)
}

const PR_PAC_SET_ENABLED_KEYS: c_int = 60;

/// Set enabled pointer authentication keys.
///
/// # References
///  - [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`]
///
/// # Safety
///
/// Please ensure the conditions necessary to safely call this function,
/// as detailed in the references above.
///
/// [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html
#[inline]
pub unsafe fn configure_pointer_authentication_keys(
    config: impl Iterator<Item = (PointerAuthenticationKeys, bool)>,
) -> io::Result<()> {
    let mut affected_keys: u32 = 0;
    let mut enabled_keys: u32 = 0;

    for (key, enable) in config {
        let key = key.bits();
        affected_keys |= key;

        if enable {
            enabled_keys |= key;
        } else {
            enabled_keys &= !key;
        }
    }

    if affected_keys == 0 {
        return Ok(()); // Nothing to do.
    }

    prctl_3args(
        PR_PAC_SET_ENABLED_KEYS,
        affected_keys as usize as *mut _,
        enabled_keys as usize as *mut _,
    )
    .map(|_r| ())
}

//
// PR_SET_VMA
//

const PR_SET_VMA: c_int = 0x53_56_4d_41;

const PR_SET_VMA_ANON_NAME: usize = 0;

/// Set the name for a virtual memory region.
///
/// # References
///  - [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`]
///
/// [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`]: https://lwn.net/Articles/867818/
#[inline]
pub fn set_virtual_memory_region_name(region: &[u8], name: Option<&CStr>) -> io::Result<()> {
    unsafe {
        syscalls::prctl(
            PR_SET_VMA,
            PR_SET_VMA_ANON_NAME as *mut _,
            region.as_ptr() as *mut _,
            region.len() as *mut _,
            name.map_or_else(ptr::null, CStr::as_ptr) as *mut _,
        )
        .map(|_r| ())
    }
}
