//
// Syd: rock-solid application kernel
// src/ptrace.rs: Utilities for ptrace(2)
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
// Based in part upon strace which is:
//   Copyright (c) 2016-2021 The strace developers.
//   SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-License-Identifier: GPL-3.0

use std::{ffi::CStr, mem};

use libseccomp_sys::seccomp_syscall_resolve_num_arch;
use nix::{errno::Errno, unistd::Pid};

use crate::path::XPath;

// Define the user_regs_struct for i386 as described in the system headers.
#[cfg(target_arch = "x86_64")]
#[repr(C)]
#[derive(Copy, Clone)]
struct I386UserRegsStruct {
    ebx: u32,
    ecx: u32,
    edx: u32,
    esi: u32,
    edi: u32,
    ebp: u32,
    eax: u32,
    ds: u32,
    es: u32,
    fs: u32,
    gs: u32,
    orig_eax: u32,
    eip: u32,
    cs: u32,
    eflags: u32,
    esp: u32,
    ss: u32,
}

// Define a X86UserRegsStruct union for multipersonality support.
#[repr(C)]
#[cfg(target_arch = "x86_64")]
union X86UserRegsStruct {
    x64: libc::user_regs_struct, // for x86_64 & x32 personalities
    x32: I386UserRegsStruct,     // for x86 personality
}

// Define the user_regs_struct for aarch64 as described in the system headers.
#[cfg(target_arch = "aarch64")]
#[repr(C)]
#[derive(Copy, Clone)]
struct Aarch64UserRegsStruct {
    regs: [u64; 31], // General-purpose registers
    sp: u64,         // Stack pointer
    pc: u64,         // Program counter
    pstate: u64,     // Processor state
}

// Define the user_regs_struct for m68k as described in the system headers.
// Careful, libc does not define user_regs_struct yet so we have to do this.
#[cfg(target_arch = "m68k")]
#[repr(C)]
#[derive(Copy, Clone)]
struct M68KUserRegsStruct {
    d1: libc::c_long,
    d2: libc::c_long,
    d3: libc::c_long,
    d4: libc::c_long,
    d5: libc::c_long,
    d6: libc::c_long,
    d7: libc::c_long,
    a0: libc::c_long,
    a1: libc::c_long,
    a2: libc::c_long,
    a3: libc::c_long,
    a4: libc::c_long,
    a5: libc::c_long,
    a6: libc::c_long,
    d0: libc::c_long,
    usp: libc::c_long,
    orig_d0: libc::c_long,
    stkadj: libc::c_short,
    sr: libc::c_short,
    pc: libc::c_long,
    fmtvec: libc::c_short,
    __fill: libc::c_short,
}

// Define pt_regs struct for mips as described in system headers.
#[cfg(any(
    target_arch = "mips",
    target_arch = "mips32r6",
    target_arch = "mips64",
    target_arch = "mips64r6"
))]
#[repr(C)]
#[derive(Copy, Clone)]
struct MipsPtRegs {
    regs: [u64; 32],   // gpr $0..$31
    lo: u64,           // LO
    hi: u64,           // HI
    cp0_epc: u64,      // EPC
    cp0_badvaddr: u64, // badvaddr
    cp0_status: u64,   // status
    cp0_cause: u64,    // cause
}

// Define pt_regs struct for powerpc64 as described in system headers.
#[cfg(target_arch = "powerpc64")]
#[repr(C)]
#[derive(Copy, Clone)]
struct PpcPtRegs64 {
    gpr: [libc::c_ulong; 32], // general-purpose registers
    nip: libc::c_ulong,       // next instruction pointer
    msr: libc::c_ulong,       // machine state register
    orig_gpr3: libc::c_ulong, // original r3 (syscall arg)
    ctr: libc::c_ulong,       // count register
    link: libc::c_ulong,      // link register
    xer: libc::c_ulong,       // fixed-point exception register
    ccr: libc::c_ulong,       // condition register
    softe: libc::c_ulong,     // "soft enabled" interrupt flag
    trap: libc::c_ulong,      // trap code
    dar: libc::c_ulong,       // data address register
    dsisr: libc::c_ulong,     // DSISR
    result: libc::c_ulong,    // syscall return value
}

// Define a PpcPtRegs union for multipersonality support.
#[cfg(target_arch = "powerpc64")]
#[repr(C)]
union PpcPtRegsUnion {
    ppc64: PpcPtRegs64,
    ppc32: PpcPtRegs32,
}

// Define pt_regs struct for powerpc as described in system headers.
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[repr(C)]
#[derive(Copy, Clone)]
struct PpcPtRegs32 {
    gpr: [u32; 32], // general-purpose registers (r0..r31)
    nip: u32,       // next instruction pointer
    msr: u32,       // machine state register
    orig_gpr3: u32, // original r3 (syscall arg)
    ctr: u32,       // count register
    link: u32,      // link register
    xer: u32,       // fixed-point exception register
    ccr: u32,       // condition register
    mq: u32,        // mq (present in 32-bit ABI only)
    trap: u32,      // trap code
    dar: u32,       // data address register
    dsisr: u32,     // DSISR
    result: u32,    // syscall return value
}

// Define the user_regs_struct for riscv64 as described in the system headers.
// Careful, musl does not define user_regs_struct yet so we have to do this.
// See: https://gitlab.alpinelinux.org/alpine/aports/-/jobs/1884899
#[cfg(target_arch = "riscv64")]
#[repr(C)]
#[derive(Copy, Clone)]
struct Riscv64UserRegsStruct {
    pc: u64,
    ra: u64,
    sp: u64,
    gp: u64,
    tp: u64,
    t0: u64,
    t1: u64,
    t2: u64,
    s0: u64,
    s1: u64,
    a0: u64,
    a1: u64,
    a2: u64,
    a3: u64,
    a4: u64,
    a5: u64,
    a6: u64,
    a7: u64,
    s2: u64,
    s3: u64,
    s4: u64,
    s5: u64,
    s6: u64,
    s7: u64,
    s8: u64,
    s9: u64,
    s10: u64,
    s11: u64,
    t3: u64,
    t4: u64,
    t5: u64,
    t6: u64,
}

/// Skip the syscall for the specified process.
/// Set the syscall to fail with the given errno or return 0 if None.
///
/// This function modifies the architecture-specific register that holds
/// the system call and the return value.
#[allow(unused)]
pub fn ptrace_skip_syscall(pid: Pid, arch: u32, errno: Option<Errno>) -> Result<(), Errno> {
    // Quoting seccomp(2):
    // The tracer can skip the system call by changing the system call
    // number to -1. Alternatively, the tracer can change the system
    // call requested by changing the system call to a valid system call
    // number.  If the tracer asks to skip the system call, then the
    // system call will appear to return the value that the tracer puts
    // in the return value register.
    #[cfg(any(
        target_arch = "x86", // TODO: provide per-arch implementation.
        target_arch = "aarch64",
        target_arch = "arm",
        target_arch = "powerpc64",
        target_arch = "powerpc",
        target_arch = "s390x",
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6",
        target_arch = "loongarch64",
    ))]
    {
        use crate::confine::{scmp_arch, scmp_arch_bits};

        // Define -1 for the target architecture.
        let sys_invalid = if cfg!(any(
            target_arch = "mips",
            target_arch = "mips32r6",
            target_arch = "mips64",
            target_arch = "mips64r6",
            target_arch = "s390x",
        )) {
            return ptrace_set_return(pid, arch, errno);
        } else if scmp_arch_bits(scmp_arch(arch)?) == 32 {
            u32::MAX.into()
        } else {
            u64::MAX
        };

        ptrace_set_syscall(pid, arch, sys_invalid)?;
        ptrace_set_return(pid, arch, errno)
    }

    #[cfg(target_arch = "x86_64")]
    {
        use libc::{c_void, iovec, ptrace, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::{SCMP_ARCH_X32, SCMP_ARCH_X86, SCMP_ARCH_X86_64};

        use crate::compat::NT_PRSTATUS;

        // Ensure the architecture matches.
        if !matches!(arch, SCMP_ARCH_X86_64 | SCMP_ARCH_X86 | SCMP_ARCH_X32) {
            return Err(Errno::EINVAL);
        }

        let mut regs = X86UserRegsStruct {
            // SAFETY: Zero-initialize the struct.
            x64: unsafe { mem::zeroed() },
        };
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<X86UserRegsStruct>(),
        };

        // SAFETY: Get registers.
        Errno::result(unsafe { ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })?;

        #[expect(clippy::arithmetic_side_effects)]
        let rval = -errno.map(|err| err as i32).unwrap_or(0);
        #[expect(clippy::cast_sign_loss)]
        match arch {
            SCMP_ARCH_X86_64 => {
                regs.x64.orig_rax = u64::MAX;
                regs.x64.rax = i64::from(rval) as u64;
            }
            SCMP_ARCH_X86 => {
                regs.x32.orig_eax = u32::MAX;
                regs.x32.eax = rval as u32;
            }
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Set registers.
        Errno::result(unsafe { ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "m68k")]
    {
        use libc::{c_long, c_void, PTRACE_GETREGS, PTRACE_SETREGS};
        use libseccomp_sys::SCMP_ARCH_M68K;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_M68K {
            return Err(Errno::EINVAL);
        }

        let mut regs = mem::MaybeUninit::<M68KUserRegsStruct>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        // Modify the syscall number (orig_d0 holds the syscall number on M68k)
        regs.orig_d0 = c_long::MAX;

        // Set negated errno in d0.
        regs.d0 = -(errno.map(|err| err as i32).unwrap_or(0) as c_long);

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_SETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(target_arch = "riscv64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_RISCV64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_RISCV64 {
            return Err(Errno::EINVAL);
        }

        // Define the user_regs_struct for the tracee.
        // SAFETY: Zero-initialize the struct.
        let mut regs: Riscv64UserRegsStruct = unsafe { mem::zeroed() };

        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<Riscv64UserRegsStruct>(),
        };

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Modify the syscall number (a7 holds the syscall number on RISC-V)
        regs.a7 = u64::MAX;

        // RISC-V requires to set return value for system call number tampering.
        regs.a0 = (-(errno.map(|err| err as i32).unwrap_or(0) as i64)) as u64;

        // SAFETY: Set the modified register state.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &io) })
            .map(drop)
    }

    #[cfg(not(any(
        target_arch = "x86_64",
        target_arch = "x86",
        target_arch = "aarch64",
        target_arch = "arm",
        target_arch = "s390x",
        target_arch = "riscv64",
        target_arch = "powerpc",
        target_arch = "powerpc64",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6",
        target_arch = "loongarch64",
    )))]
    {
        compile_error!("BUG: ptrace_skip_syscall is not implemented for this architecture!");
    }
}

/// Set the syscall return value for the specified process.
/// Sets success if `errno` is `None`.
///
/// This function modifies the architecture-specific register that holds
/// the return value.
#[allow(unused)]
pub fn ptrace_set_return(pid: Pid, arch: u32, errno: Option<Errno>) -> Result<(), Errno> {
    #[cfg(target_arch = "x86_64")]
    {
        use libc::{c_void, iovec, ptrace, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::{SCMP_ARCH_X32, SCMP_ARCH_X86, SCMP_ARCH_X86_64};

        use crate::compat::NT_PRSTATUS;

        // Ensure the architecture matches.
        if !matches!(arch, SCMP_ARCH_X86_64 | SCMP_ARCH_X86 | SCMP_ARCH_X32) {
            return Err(Errno::EINVAL);
        }

        let mut regs = X86UserRegsStruct {
            // SAFETY: Zero-initialize the struct.
            x64: unsafe { mem::zeroed() },
        };
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<X86UserRegsStruct>(),
        };

        // SAFETY: Get registers.
        Errno::result(unsafe { ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })?;

        // Determine the value to set.
        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            // Error case: Set the error code as a negative value.
            -(e as i64)
        } else {
            // Success case: Set the return value to 0.
            0
        };

        #[expect(clippy::cast_sign_loss)]
        #[expect(clippy::cast_possible_truncation)]
        match arch {
            SCMP_ARCH_X86_64 | SCMP_ARCH_X32 => regs.x64.rax = rval as u64,
            SCMP_ARCH_X86 => regs.x32.eax = (rval as i32) as u32,
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Set registers.
        Errno::result(unsafe { ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "x86")]
    {
        use libseccomp_sys::SCMP_ARCH_X86;
        use nix::{errno::Errno, sys::ptrace};

        // Define offset for EAX in the user area.
        const EAX_OFFSET: u64 = 6 * 4; // EAX offset (32-bit).

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_X86 {
            return Err(Errno::EINVAL);
        }

        // Determine the value to set.
        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            // Error case: Set the error code as a negative value.
            -(e as i32)
        } else {
            // Success case: Set the return value to 0.
            0
        };

        // Write the value into the EAX register
        ptrace::write_user(pid, EAX_OFFSET as ptrace::AddressType, rval.into())
    }

    #[cfg(target_arch = "aarch64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::{SCMP_ARCH_AARCH64, SCMP_ARCH_ARM};

        use crate::compat::NT_PRSTATUS;

        // Define the arm_pt_regs for arm as described in the system headers
        #[repr(C)]
        #[derive(Copy, Clone)]
        struct ArmPtRegs {
            uregs: [u32; 18], // ARM registers
        }

        // Allocate a union for multipersonality support.
        #[repr(C)]
        union ArmRegsUnion {
            aarch64: Aarch64UserRegsStruct,
            arm: ArmPtRegs,
        }

        let mut regs = ArmRegsUnion {
            // SAFETY: Zero initialize the ARM register union.
            aarch64: unsafe { mem::zeroed() },
        };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<ArmRegsUnion>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        match arch {
            SCMP_ARCH_AARCH64 => {
                // SAFETY: aarch64 personality
                let regs_ref = unsafe { &mut regs.aarch64 };
                #[expect(clippy::arithmetic_side_effects)]
                let rval = if let Some(e) = errno {
                    -(e as i64) // Error case
                } else {
                    0 // Success case
                };

                #[expect(clippy::cast_sign_loss)]
                {
                    // Set return value in X0.
                    regs_ref.regs[0] = rval as u64;
                }
            }
            SCMP_ARCH_ARM => {
                // SAFETY: arm personality
                let regs_ref = unsafe { &mut regs.arm };
                #[expect(clippy::arithmetic_side_effects)]
                let rval = if let Some(e) = errno {
                    -(e as i32) // Error case
                } else {
                    0 // Success case
                };

                #[expect(clippy::cast_sign_loss)]
                {
                    // Set return value in R0.
                    regs_ref.uregs[0] = rval as u32;
                }
            }
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "arm")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_ARM;
        use nix::errno::Errno;

        use crate::compat::NT_PRSTATUS;

        // Define the ARM register structure.
        #[repr(C)]
        struct ArmPtRegs {
            uregs: [u32; 18],
        }

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_ARM {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Zero initialize the register structuer.
        let mut regs: ArmPtRegs = unsafe { mem::zeroed() };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<ArmPtRegs>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Modify the return value in R0.
        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            -(e as i32) // Error case.
        } else {
            0 // Success case.
        };

        #[expect(clippy::cast_sign_loss)]
        {
            regs.uregs[0] = rval as u32;
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "m68k")]
    {
        use libc::{c_long, c_void, PTRACE_GETREGS, PTRACE_SETREGS};
        use libseccomp_sys::SCMP_ARCH_M68K;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_M68K {
            return Err(Errno::EINVAL);
        }

        let mut regs = mem::MaybeUninit::<M68KUserRegsStruct>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            -(e as c_long) // Error case
        } else {
            0 // Success case
        };

        // Modify the return value in d0.
        regs.d0 = rval;

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_SETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(any(
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6"
    ))]
    {
        use libc::{c_void, PTRACE_GETREGS, PTRACE_SETREGS};
        use libseccomp_sys::{
            SCMP_ARCH_MIPS, SCMP_ARCH_MIPS64, SCMP_ARCH_MIPS64N32, SCMP_ARCH_MIPSEL,
            SCMP_ARCH_MIPSEL64, SCMP_ARCH_MIPSEL64N32,
        };
        use nix::errno::Errno;

        // Ensure we're working with the correct architecture.
        if !matches!(
            arch,
            SCMP_ARCH_MIPS
                | SCMP_ARCH_MIPS64
                | SCMP_ARCH_MIPSEL
                | SCMP_ARCH_MIPSEL64
                | SCMP_ARCH_MIPS64N32
                | SCMP_ARCH_MIPSEL64N32
        ) {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the registers structure.
        let mut regs = mem::MaybeUninit::<MipsPtRegs>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        const REG_V0: usize = 2;
        const REG_A0: usize = 4;
        const REG_A3: usize = REG_A0 + 3;

        // Modify the return value.
        #[expect(clippy::arithmetic_side_effects)]
        if matches!(arch, SCMP_ARCH_MIPS | SCMP_ARCH_MIPSEL) {
            if let Some(e) = errno {
                // Error case
                regs.regs[REG_V0] = (e as u32) as u64;
                regs.regs[REG_A3] = u32::MAX as u64; // -1
            } else {
                // Success case
                regs.regs[REG_V0] = 0;
                regs.regs[REG_A3] = 0;
            }
        } else {
            if let Some(e) = errno {
                // Error case
                regs.regs[REG_V0] = e as u64;
                regs.regs[REG_A3] = u64::MAX; // -1
            } else {
                // Success case
                regs.regs[REG_V0] = 0;
                regs.regs[REG_A3] = 0;
            }
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_SETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(target_arch = "riscv64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_RISCV64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_RISCV64 {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the register structure.
        let mut regs: Riscv64UserRegsStruct = unsafe { mem::zeroed() };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<Riscv64UserRegsStruct>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            -(e as i64) // Error case
        } else {
            0 // Success case
        };

        #[expect(clippy::cast_sign_loss)]
        {
            // Modify the return value in A0.
            regs.a0 = rval as u64;
        }

        // SAFETY: Write the modified register state back
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "s390x")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_S390X;

        use crate::compat::NT_PRSTATUS;

        #[repr(C, align(8))]
        struct psw_t {
            mask: u64,
            addr: u64,
        }

        #[repr(C)]
        struct s390_regs {
            psw: psw_t,
            gprs: [u64; 16],
            acrs: [u32; 16],
            orig_gpr2: u64,
        }

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_S390X {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Zero-initialize the struct.
        let mut regs: s390_regs = unsafe { mem::zeroed() };

        // Define the IOVEC structure for the register set.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<s390_regs>(),
        };

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            -(e as i64) // Error case
        } else {
            0 // Success case
        };

        #[expect(clippy::cast_sign_loss)]
        {
            // Modify the return value in GPR2
            regs.gprs[2] = rval as u64;
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &io) })
            .map(drop)
    }

    #[cfg(target_arch = "powerpc")]
    {
        use libc::c_void;
        use libseccomp_sys::SCMP_ARCH_PPC;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_PPC {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the registers structure.
        let mut regs = mem::MaybeUninit::<PpcPtRegs32>::uninit();

        // SAFETY:
        // 1. Retrieve the current register state.
        // 2. libc may not define PTRACE_GETREGS.
        // 3. PTRACE_GETREGS may be uint or int.
        Errno::result(unsafe {
            libc::ptrace(
                12, // PTRACE_GETREGS
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        // Modify the return value in GPR3.
        #[expect(clippy::arithmetic_side_effects)]
        #[expect(clippy::cast_sign_loss)]
        if let Some(e) = errno {
            if (regs.trap & 0xfff0) == 0x3000 {
                // SCV case: Error value is negated.
                regs.gpr[3] = -(e as i32) as u32;
            } else {
                // Non-SCV case: Positive error value.
                regs.gpr[3] = e as i32 as u32;
                regs.ccr |= 0x10000000; // Set condition register.
            }
        } else {
            // Success case
            regs.gpr[3] = 0;
            if (regs.trap & 0xfff0) != 0x3000 {
                // Clear condition register.
                regs.ccr &= !0x10000000;
            }
        }

        // SAFETY:
        // 1. Write the modified register state back.
        // 2. libc may not define PTRACE_SETREGS.
        // 3. PTRACE_SETREGS may be uint or int.
        Errno::result(unsafe {
            libc::ptrace(
                13, // PTRACE_SETREGS
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(target_arch = "powerpc64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::{SCMP_ARCH_PPC, SCMP_ARCH_PPC64, SCMP_ARCH_PPC64LE};

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if !matches!(arch, SCMP_ARCH_PPC | SCMP_ARCH_PPC64 | SCMP_ARCH_PPC64LE) {
            return Err(Errno::EINVAL);
        }

        let mut regs = PpcPtRegsUnion {
            // SAFETY: Zero initialize the PPC register union.
            ppc64: unsafe { mem::zeroed() },
        };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<PpcPtRegsUnion>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        match arch {
            SCMP_ARCH_PPC64 | SCMP_ARCH_PPC64LE => {
                // SAFETY: ppc64 personality
                let regs = unsafe { &mut regs.ppc64 };

                // Modify the return value in GPR3.
                #[expect(clippy::arithmetic_side_effects)]
                #[expect(clippy::cast_sign_loss)]
                if let Some(e) = errno {
                    if (regs.trap & 0xfff0) == 0x3000 {
                        // SCV case: Error value is negated.
                        regs.gpr[3] = -(e as i32) as u64;
                    } else {
                        // Non-SCV case: Positive error value.
                        regs.gpr[3] = e as i32 as u64;
                        regs.ccr |= 0x10000000; // Set condition register.
                    }
                } else {
                    // Success case
                    regs.gpr[3] = 0;
                    if (regs.trap & 0xfff0) != 0x3000 {
                        // Clear condition register.
                        regs.ccr &= !0x10000000;
                    }
                }
            }
            SCMP_ARCH_PPC => {
                // SAFETY: ppc32 personality
                let regs = unsafe { &mut regs.ppc32 };

                // Modify the return value in GPR3.
                #[expect(clippy::arithmetic_side_effects)]
                #[expect(clippy::cast_sign_loss)]
                if let Some(e) = errno {
                    if (regs.trap & 0xfff0) == 0x3000 {
                        // SCV case: Error value is negated.
                        regs.gpr[3] = -(e as i32) as u32;
                    } else {
                        // Non-SCV case: Positive error value.
                        regs.gpr[3] = e as i32 as u32;
                        regs.ccr |= 0x10000000; // Set condition register.
                    }
                } else {
                    // Success case
                    regs.gpr[3] = 0;
                    if (regs.trap & 0xfff0) != 0x3000 {
                        // Clear condition register.
                        regs.ccr &= !0x10000000;
                    }
                }
            }
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "loongarch64")]
    {
        use libc::{c_void, iovec, user_regs_struct, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_LOONGARCH64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_LOONGARCH64 {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the register structure.
        let mut regs: user_regs_struct = unsafe { mem::zeroed() };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<user_regs_struct>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        #[expect(clippy::arithmetic_side_effects)]
        let rval = if let Some(e) = errno {
            -(e as i64) // Error case
        } else {
            0 // Success case
        };

        #[expect(clippy::cast_sign_loss)]
        {
            // Modify the return value in regs[4].
            regs.regs[4] = rval as u64;
        }

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(not(any(
        target_arch = "x86_64",
        target_arch = "x86",
        target_arch = "aarch64",
        target_arch = "arm",
        target_arch = "s390x",
        target_arch = "riscv64",
        target_arch = "powerpc",
        target_arch = "powerpc64",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6",
        target_arch = "loongarch64",
    )))]
    {
        compile_error!("BUG: ptrace_set_return is not implemented for this architecture!");
    }
}

/// Retrieve the system call return code from the tracee and determine
/// if it indicates an error or success.
#[allow(unused)]
pub fn ptrace_get_error(pid: Pid, arch: u32) -> Result<Option<Errno>, Errno> {
    #[cfg(target_arch = "x86_64")]
    {
        use libc::{c_void, iovec, ptrace, PTRACE_GETREGSET};
        use libseccomp_sys::{SCMP_ARCH_X32, SCMP_ARCH_X86, SCMP_ARCH_X86_64};

        use crate::compat::NT_PRSTATUS;

        // Ensure the architecture matches.
        if !matches!(arch, SCMP_ARCH_X86_64 | SCMP_ARCH_X86 | SCMP_ARCH_X32) {
            return Err(Errno::EINVAL);
        }

        let mut regs = X86UserRegsStruct {
            // SAFETY: Zero-initialize the struct.
            x64: unsafe { mem::zeroed() },
        };
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<X86UserRegsStruct>(),
        };

        // SAFETY: Get registers.
        Errno::result(unsafe { ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })?;

        #[expect(clippy::cast_possible_wrap)]
        let val: i64 = match arch {
            SCMP_ARCH_X86_64 | SCMP_ARCH_X32 => {
                // SAFETY: Keep it as 64 bits, interpret as signed.
                let r = unsafe { regs.x64 };
                r.rax as i64
            }
            SCMP_ARCH_X86 => {
                // SAFETY: Sign-extend the lower 32 bits.
                let r = unsafe { regs.x32 };
                i64::from(r.eax as i32)
            }
            _ => return Err(Errno::EINVAL),
        };

        // Check if it's a negated errno:
        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "x86")]
    {
        use libseccomp_sys::SCMP_ARCH_X86;
        use nix::sys::ptrace;

        // EAX offset in the user area on 32-bit x86.
        const EAX_OFFSET: u64 = 6 * 4;

        // Ensure the architecture matches.
        if arch != SCMP_ARCH_X86 {
            return Err(Errno::EINVAL);
        }

        // Read the raw EAX.
        let raw_eax = ptrace::read_user(pid, EAX_OFFSET as ptrace::AddressType)? as i32;
        let val = raw_eax as i64;

        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "aarch64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET};
        use libseccomp_sys::{SCMP_ARCH_AARCH64, SCMP_ARCH_ARM};

        use crate::compat::NT_PRSTATUS;

        // ARM user regs struct
        #[repr(C)]
        #[derive(Copy, Clone)]
        struct ArmPtRegs {
            uregs: [u32; 18],
        }

        // Union for retrieving either aarch64 or arm regs
        #[repr(C)]
        union ArmRegsUnion {
            aarch64: Aarch64UserRegsStruct,
            arm: ArmPtRegs,
        }

        let mut regs = ArmRegsUnion {
            aarch64: unsafe { mem::zeroed() },
        };

        let mut io = iovec {
            iov_base: (&mut regs) as *mut _ as *mut c_void,
            iov_len: mem::size_of::<ArmRegsUnion>(),
        };

        // Get registers
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        let val: i64 = match arch {
            // SCMP_ARCH_AARCH64 => 64-bit read from X0
            SCMP_ARCH_AARCH64 => {
                let a64 = unsafe { regs.aarch64 };
                a64.regs[0] as i64
            }
            // SCMP_ARCH_ARM => 32-bit read from R0
            SCMP_ARCH_ARM => {
                let arm = unsafe { regs.arm };
                // Sign-extend
                (arm.uregs[0] as i32) as i64
            }
            _ => return Err(Errno::EINVAL),
        };

        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "arm")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET};
        use libseccomp_sys::SCMP_ARCH_ARM;

        use crate::compat::NT_PRSTATUS;

        #[repr(C)]
        struct ArmPtRegs {
            uregs: [u32; 18],
        }

        if arch != SCMP_ARCH_ARM {
            return Err(Errno::EINVAL);
        }

        let mut regs: ArmPtRegs = unsafe { mem::zeroed() };

        let mut io = iovec {
            iov_base: (&mut regs) as *mut _ as *mut c_void,
            iov_len: mem::size_of::<ArmPtRegs>(),
        };

        // Get registers
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        let val = (regs.uregs[0] as i32) as i64;
        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "m68k")]
    {
        use libc::{c_void, PTRACE_GETREGS};
        use libseccomp_sys::SCMP_ARCH_M68K;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_M68K {
            return Err(Errno::EINVAL);
        }

        let mut regs = mem::MaybeUninit::<M68KUserRegsStruct>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let regs = unsafe { regs.assume_init() };

        if let Some(e) = check_negated_errno(regs.d0 as i64) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(any(
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6"
    ))]
    {
        use libc::{c_void, PTRACE_GETREGS};
        use libseccomp_sys::{
            SCMP_ARCH_MIPS, SCMP_ARCH_MIPS64, SCMP_ARCH_MIPS64N32, SCMP_ARCH_MIPSEL,
            SCMP_ARCH_MIPSEL64, SCMP_ARCH_MIPSEL64N32,
        };
        use nix::errno::Errno;

        // Ensure we're working with the correct architecture.
        if !matches!(
            arch,
            SCMP_ARCH_MIPS
                | SCMP_ARCH_MIPS64
                | SCMP_ARCH_MIPSEL
                | SCMP_ARCH_MIPSEL64
                | SCMP_ARCH_MIPS64N32
                | SCMP_ARCH_MIPSEL64N32
        ) {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the registers structure.
        let mut regs = mem::MaybeUninit::<MipsPtRegs>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let regs = unsafe { regs.assume_init() };

        const REG_V0: usize = 2;
        const REG_A0: usize = 4;
        const REG_A3: usize = REG_A0 + 3;

        if regs.regs[REG_A3] != 0 {
            Ok(Some(Errno::from_raw(regs.regs[REG_V0] as i32)))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "riscv64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET};
        use libseccomp_sys::SCMP_ARCH_RISCV64;

        use crate::compat::NT_PRSTATUS;

        if arch != SCMP_ARCH_RISCV64 {
            return Err(Errno::EINVAL);
        }

        let mut regs: Riscv64UserRegsStruct = unsafe { mem::zeroed() };
        let mut io = iovec {
            iov_base: (&mut regs) as *mut _ as *mut c_void,
            iov_len: mem::size_of::<Riscv64UserRegsStruct>(),
        };

        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        let val = regs.a0 as i64;
        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "s390x")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET};
        use libseccomp_sys::SCMP_ARCH_S390X;

        use crate::compat::NT_PRSTATUS;

        #[repr(C, align(8))]
        struct psw_t {
            mask: u64,
            addr: u64,
        }

        #[repr(C)]
        struct s390_regs {
            psw: psw_t,
            gprs: [u64; 16],
            acrs: [u32; 16],
            orig_gpr2: u64,
        }

        if arch != SCMP_ARCH_S390X {
            return Err(Errno::EINVAL);
        }

        let mut regs: s390_regs = unsafe { mem::zeroed() };
        let mut io = iovec {
            iov_base: (&mut regs) as *mut _ as *mut c_void,
            iov_len: mem::size_of::<s390_regs>(),
        };

        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Syscall return value is in gprs[2]
        let val = regs.gprs[2] as i64;
        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(target_arch = "powerpc")]
    {
        use libc::c_void;
        use libseccomp_sys::SCMP_ARCH_PPC;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_PPC {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the registers structure.
        let mut regs = mem::MaybeUninit::<PpcPtRegs32>::uninit();

        // SAFETY:
        // 1. Retrieve the current register state.
        // 2. libc may not define PTRACE_GETREGS.
        // 3. PTRACE_GETREGS may be uint or int.
        Errno::result(unsafe {
            libc::ptrace(
                12, // PTRACE_GETREGS
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let regs = unsafe { regs.assume_init() };

        // On PPC, the return value is always in gpr[3].
        let r3 = regs.gpr[3] as i64;

        // SCV syscalls have a signature: if (regs.trap & 0xfff0) == 0x3000 => SCV
        let scv = (regs.trap & 0xfff0) == 0x3000;

        if scv {
            // If SCV, negative => error
            if let Some(e) = check_negated_errno(r3) {
                Ok(Some(e))
            } else {
                Ok(None)
            }
        } else {
            // If not SCV, check CCR bit 0x10000000 for error
            // If set => error is positive in gpr[3]
            // If not set => success
            if (regs.ccr & 0x10000000) != 0 {
                // gpr[3] is the error code, not negated.
                let err = r3 as i32;
                Ok(Some(Errno::from_raw(err)))
            } else {
                // success
                Ok(None)
            }
        }
    }

    #[cfg(target_arch = "powerpc64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET};
        use libseccomp_sys::{SCMP_ARCH_PPC, SCMP_ARCH_PPC64, SCMP_ARCH_PPC64LE};

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if !matches!(arch, SCMP_ARCH_PPC | SCMP_ARCH_PPC64 | SCMP_ARCH_PPC64LE) {
            return Err(Errno::EINVAL);
        }

        let mut regs = PpcPtRegsUnion {
            // SAFETY: Zero initialize the PPC register union.
            ppc64: unsafe { mem::zeroed() },
        };

        // IOVEC for PTRACE_GETREGSET and PTRACE_SETREGSET.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<PpcPtRegsUnion>(),
        };

        // SAFETY: Retrieve the current register state
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // On PPC, the return value is always in gpr[3].
        #[expect(clippy::cast_possible_wrap)]
        let (r3, scv, ccr) = match arch {
            SCMP_ARCH_PPC64 | SCMP_ARCH_PPC64LE => {
                // SAFETY: ppc64 personality
                let regs = unsafe { &regs.ppc64 };

                // SCV syscalls have a signature: if (regs.trap & 0xfff0) == 0x3000 => SCV
                (
                    regs.gpr[3] as i64,
                    (regs.trap & 0xfff0) == 0x3000,
                    (regs.ccr & 0x10000000) != 0,
                )
            }
            SCMP_ARCH_PPC => {
                // SAFETY: ppc32 personality
                let regs = unsafe { &regs.ppc32 };

                // SCV syscalls have a signature: if (regs.trap & 0xfff0) == 0x3000 => SCV
                (
                    regs.gpr[3] as i64,
                    (regs.trap & 0xfff0) == 0x3000,
                    (regs.ccr & 0x10000000) != 0,
                )
            }
            _ => return Err(Errno::EINVAL),
        };

        if scv {
            // If SCV, negative => error
            if let Some(e) = check_negated_errno(r3) {
                Ok(Some(e))
            } else {
                Ok(None)
            }
        } else {
            // If not SCV, check CCR bit 0x10000000 for error
            // If set => error is positive in gpr[3]
            // If not set => success
            if ccr {
                // gpr[3] is the error code, not negated.
                let err = r3 as i32;
                Ok(Some(Errno::from_raw(err)))
            } else {
                // success
                Ok(None)
            }
        }
    }

    #[cfg(target_arch = "loongarch64")]
    {
        use libc::{c_void, iovec, user_regs_struct, PTRACE_GETREGSET};
        use libseccomp_sys::SCMP_ARCH_LOONGARCH64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_LOONGARCH64 {
            return Err(Errno::EINVAL);
        }

        let mut regs: user_regs_struct = unsafe { mem::zeroed() };
        let mut io = iovec {
            iov_base: (&mut regs) as *mut _ as *mut c_void,
            iov_len: mem::size_of::<user_regs_struct>(),
        };

        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        let val = regs.regs[4] as i64;
        if let Some(e) = check_negated_errno(val) {
            Ok(Some(e))
        } else {
            Ok(None)
        }
    }

    #[cfg(not(any(
        target_arch = "x86_64",
        target_arch = "x86",
        target_arch = "aarch64",
        target_arch = "arm",
        target_arch = "s390x",
        target_arch = "riscv64",
        target_arch = "powerpc",
        target_arch = "powerpc64",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6",
        target_arch = "loongarch64",
    )))]
    {
        compile_error!("BUG: ptrace_get_error is not implemented for this architecture!");
    }
}

/// Set the syscall number for the specified process.
///
/// This function modifies the architecture-specific register that holds
/// the syscall number.
#[allow(unused)]
pub fn ptrace_set_syscall(pid: Pid, arch: u32, sysno: u64) -> Result<(), Errno> {
    #[cfg(target_arch = "x86_64")]
    {
        use libc::{c_void, iovec, ptrace, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::{SCMP_ARCH_X32, SCMP_ARCH_X86, SCMP_ARCH_X86_64};

        use crate::{compat::NT_PRSTATUS, confine::X32_SYSCALL_BIT};

        // Ensure the architecture matches.
        if !matches!(arch, SCMP_ARCH_X86_64 | SCMP_ARCH_X86 | SCMP_ARCH_X32) {
            return Err(Errno::EINVAL);
        }

        let mut regs = X86UserRegsStruct {
            // SAFETY: Zero-initialize the struct.
            x64: unsafe { mem::zeroed() },
        };
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<X86UserRegsStruct>(),
        };

        // SAFETY: Get registers.
        Errno::result(unsafe { ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })?;

        #[expect(clippy::cast_possible_truncation)]
        match arch {
            SCMP_ARCH_X86_64 => regs.x64.orig_rax = sysno,
            SCMP_ARCH_X32 => regs.x64.orig_rax = sysno | (X32_SYSCALL_BIT as u64),
            SCMP_ARCH_X86 => regs.x32.orig_eax = sysno as u32,
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Set registers.
        Errno::result(unsafe { ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io) })
            .map(drop)
    }

    #[cfg(target_arch = "x86")]
    {
        use nix::sys::ptrace;

        // ORIG_EAX is at offset 11 * 4 bytes in the user area for x86.
        const ORIG_EAX_OFFSET: u64 = 11 * 4;

        // Write the syscall number into the ORIG_EAX register of the target process.
        ptrace::write_user(
            pid,
            ORIG_EAX_OFFSET as ptrace::AddressType,
            sysno as libc::c_long,
        )
    }

    #[cfg(target_arch = "aarch64")]
    {
        use libc::{c_void, iovec, PTRACE_SETREGSET};

        // Create an iovec structure to pass the syscall number.
        let io = iovec {
            iov_base: std::ptr::addr_of!(sysno) as *mut c_void,
            iov_len: mem::size_of::<u64>(),
        };

        // NT_ARM_SYSTEM_CALL is 0x404.
        // SAFETY: Use libc::ptrace to set the register set.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), 0x404, &io) }).map(drop)
    }

    #[cfg(target_arch = "arm")]
    {
        // PTRACE_SET_SYSCALL constant on ARM is 23.
        // SAFETY: Use libc::ptrace to set the syscall.
        Errno::result(unsafe { libc::ptrace(23, pid.as_raw(), 0, sysno as libc::c_uint) }).map(drop)
    }

    #[cfg(any(
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6"
    ))]
    {
        use libc::{c_void, PTRACE_GETREGS, PTRACE_SETREGS};
        use libseccomp_sys::{
            SCMP_ARCH_MIPS, SCMP_ARCH_MIPS64, SCMP_ARCH_MIPS64N32, SCMP_ARCH_MIPSEL,
            SCMP_ARCH_MIPSEL64, SCMP_ARCH_MIPSEL64N32,
        };
        use nix::errno::Errno;

        // Ensure we're working with the correct architecture.
        if !matches!(
            arch,
            SCMP_ARCH_MIPS
                | SCMP_ARCH_MIPS64
                | SCMP_ARCH_MIPSEL
                | SCMP_ARCH_MIPSEL64
                | SCMP_ARCH_MIPS64N32
                | SCMP_ARCH_MIPSEL64N32
        ) {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Allocate the registers structure.
        let mut regs = mem::MaybeUninit::<MipsPtRegs>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        // Modify the syscall number.
        const REG_V0: usize = 2;
        regs.regs[REG_V0] = sysno;

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_SETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
    {
        use libseccomp_sys::{SCMP_ARCH_PPC, SCMP_ARCH_PPC64, SCMP_ARCH_PPC64LE};
        use nix::sys::ptrace;

        // Ensure we're working with the correct architecture.
        if !matches!(arch, SCMP_ARCH_PPC | SCMP_ARCH_PPC64 | SCMP_ARCH_PPC64LE) {
            return Err(Errno::EINVAL);
        }

        // PT_R0 is at offset 0 in the user area.
        // Write the syscall number into the R0 register of the target process.
        ptrace::write_user(pid, std::ptr::null_mut(), sysno as libc::c_long)
    }

    #[cfg(target_arch = "riscv64")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_RISCV64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_RISCV64 {
            return Err(Errno::EINVAL);
        }

        // Define the user_regs_struct for the tracee.
        // SAFETY: Zero-initialize the struct.
        let mut regs: Riscv64UserRegsStruct = unsafe { mem::zeroed() };

        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<Riscv64UserRegsStruct>(),
        };

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Modify the syscall number (a7 holds the syscall number on RISC-V)
        regs.a7 = sysno;

        // RISC-V requires to set return value for system call number tampering.
        regs.a0 = (-(Errno::ENOSYS as i64)) as u64;

        // SAFETY: Set the modified register state.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &io) })
            .map(drop)
    }

    #[cfg(target_arch = "s390x")]
    {
        use libc::{c_void, iovec, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_S390X;

        use crate::compat::NT_PRSTATUS;

        #[repr(C, align(8))]
        struct psw_t {
            mask: u64,
            addr: u64,
        }

        #[repr(C)]
        struct s390_regs {
            psw: psw_t,
            gprs: [u64; 16],
            acrs: [u32; 16],
            orig_gpr2: u64,
        }

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_S390X {
            return Err(Errno::EINVAL);
        }

        // SAFETY: Zero-initialize the struct.
        let mut regs: s390_regs = unsafe { mem::zeroed() };

        // Define the IOVEC structure for the register set.
        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<s390_regs>(),
        };

        // SAFETY: Retrieve the current registers.
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Set the syscall number in GPR2.
        regs.gprs[2] = sysno;

        // SAFETY: Update the registers with the new syscall number.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &io) })
            .map(drop)
    }

    #[cfg(target_arch = "loongarch64")]
    {
        use libc::{c_void, iovec, user_regs_struct, PTRACE_GETREGSET, PTRACE_SETREGSET};
        use libseccomp_sys::SCMP_ARCH_LOONGARCH64;

        use crate::compat::NT_PRSTATUS;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_LOONGARCH64 {
            return Err(Errno::EINVAL);
        }

        // Define the user_regs_struct for the tracee.
        // SAFETY: Zero-initialize the struct.
        let mut regs: user_regs_struct = unsafe { mem::zeroed() };

        let mut io = iovec {
            iov_base: std::ptr::addr_of_mut!(regs) as *mut c_void,
            iov_len: mem::size_of::<user_regs_struct>(),
        };

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(PTRACE_GETREGSET, pid.as_raw(), NT_PRSTATUS, &mut io)
        })?;

        // Modify the syscall number (regs[11] holds the syscall number on LOONGARCH64)
        regs.regs[11] = sysno;

        // SAFETY: Set the modified register state.
        Errno::result(unsafe { libc::ptrace(PTRACE_SETREGSET, pid.as_raw(), NT_PRSTATUS, &io) })
            .map(drop)
    }

    #[cfg(target_arch = "m68k")]
    {
        use libc::{c_long, c_void, PTRACE_GETREGS, PTRACE_SETREGS};
        use libseccomp_sys::SCMP_ARCH_M68K;

        // Ensure we're working with the correct architecture.
        if arch != SCMP_ARCH_M68K {
            return Err(Errno::EINVAL);
        }

        let mut regs = mem::MaybeUninit::<M68KUserRegsStruct>::uninit();

        // SAFETY: Retrieve the current register state.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_GETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                regs.as_mut_ptr(),
            )
        })?;

        // SAFETY: PTRACE_GETREGS returned success.
        let mut regs = unsafe { regs.assume_init() };

        // Modify the syscall value in orig_d0.
        regs.orig_d0 = sysno as c_long;

        // SAFETY: Write the modified register state back.
        Errno::result(unsafe {
            libc::ptrace(
                PTRACE_SETREGS,
                pid.as_raw(),
                std::ptr::null_mut::<c_void>(),
                std::ptr::addr_of!(regs) as *const c_void,
            )
        })
        .map(drop)
    }

    #[cfg(not(any(
        target_arch = "x86_64",
        target_arch = "x86",
        target_arch = "aarch64",
        target_arch = "arm",
        target_arch = "s390x",
        target_arch = "riscv64",
        target_arch = "powerpc",
        target_arch = "powerpc64",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6",
        target_arch = "loongarch64",
    )))]
    {
        compile_error!("BUG: ptrace_set_syscall is not implemented for this architecture!");
    }
}

/// Retrieve information about the system call that caused a process to stop.
///
/// This function wraps the `PTRACE_GET_SYSCALL_INFO` ptrace request and returns
/// a `ptrace_syscall_info` structure containing the syscall information.
pub fn ptrace_get_syscall_info(pid: Pid) -> Result<ptrace_syscall_info, Errno> {
    let mut info = mem::MaybeUninit::<ptrace_syscall_info>::uninit();
    let info_size = mem::size_of::<ptrace_syscall_info>();

    // SAFETY: The ptrace call is inherently unsafe and must be
    // handled with care. We ensure `info` is properly initialized
    // before use and the size is correct.
    Errno::result(unsafe {
        libc::ptrace(
            0x420e, // PTRACE_GET_SYSCALL_INFO
            pid.as_raw(),
            info_size,
            info.as_mut_ptr() as *mut libc::c_void,
        )
    })?;

    // SAFETY: `info` is initialized by the ptrace call on success.
    Ok(unsafe { info.assume_init() })
}

// A small helper closure to check if a 64-bit value looks like -ERRNO.
// Specifically, if -4095 <= val < 0, we interpret it as an errno.
#[inline]
fn check_negated_errno(val: i64) -> Option<Errno> {
    // The largest possible negated errno we expect is -4095
    // (somewhat standard across Linux).
    // If val is in the range -4095..=-1, it's an error code.
    const MIN_ERRNO: i64 = -4095;
    #[expect(clippy::arithmetic_side_effects)]
    #[expect(clippy::cast_possible_truncation)]
    if (MIN_ERRNO..0).contains(&val) {
        // We flip the sign to get the positive errno.
        Some(Errno::from_raw((-val) as i32))
    } else {
        None
    }
}

/// Represents no entry.
///
/// You may get this e.g. when you don't set
/// PTRACE_O_TRACESYSGOOD in ptrace options.
pub const PTRACE_SYSCALL_INFO_NONE: u8 = 0;

/// Represents ptrace syscall entry stop.
pub const PTRACE_SYSCALL_INFO_ENTRY: u8 = 1;

/// Represents ptrace syscall exit stop.
pub const PTRACE_SYSCALL_INFO_EXIT: u8 = 2;

/// Represents ptrace seccomp stop.
pub const PTRACE_SYSCALL_INFO_SECCOMP: u8 = 3;

/// Representation of the `struct ptrace_syscall_info` for syscall information.
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ptrace_syscall_info {
    /// Type of system call stop
    pub op: u8,
    /// AUDIT_ARCH_* value; see seccomp(2)
    pub arch: u32,
    /// CPU instruction pointer
    pub instruction_pointer: u64,
    /// CPU stack pointer
    pub stack_pointer: u64,
    /// Holds ptrace syscall information data
    ///
    /// SAFETY: check `op` before accessing the union!
    pub data: ptrace_syscall_info_data,
}

/// This union holds ptrace syscall information data.
#[repr(C)]
#[derive(Copy, Clone)]
pub union ptrace_syscall_info_data {
    /// op == PTRACE_SYSCALL_INFO_ENTRY
    pub entry: ptrace_syscall_info_entry,
    /// op == PTRACE_SYSCALL_INFO_EXIT
    pub exit: ptrace_syscall_info_exit,
    /// op == PTRACE_SYSCALL_INFO_SECCOMP
    pub seccomp: ptrace_syscall_info_seccomp,
}

/// op == PTRACE_SYSCALL_INFO_ENTRY
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ptrace_syscall_info_entry {
    /// System call number
    pub nr: u64,
    /// System call arguments
    pub args: [u64; 6],
}

/// op == PTRACE_SYSCALL_INFO_EXIT
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ptrace_syscall_info_exit {
    /// System call return value
    pub rval: i64,
    /// System call error flag;
    /// Boolean: does rval contain an error value (-ERRCODE),
    /// or a nonerror return value?
    pub is_error: u8,
}

/// op == PTRACE_SYSCALL_INFO_SECCOMP
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ptrace_syscall_info_seccomp {
    /// System call number
    pub nr: u64,
    /// System call arguments
    pub args: [u64; 6],
    /// SECCOMP_RET_DATA portion of SECCOMP_RET_TRACE return value
    pub ret_data: u32,
}

impl std::fmt::Debug for ptrace_syscall_info {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ptrace_syscall_info")
            .field("op", &self.op)
            .field("arch", &self.arch)
            .field("ip", &self.instruction_pointer)
            .field("sp", &self.stack_pointer)
            // SAFETY: `op` is checked before union access.
            .field("data", unsafe {
                match self.op {
                    // Interpret the union based on the op field
                    PTRACE_SYSCALL_INFO_ENTRY => &self.data.entry,
                    PTRACE_SYSCALL_INFO_EXIT => &self.data.exit,
                    PTRACE_SYSCALL_INFO_SECCOMP => &self.data.seccomp,
                    _ => &"Unknown op",
                }
            })
            .finish()
    }
}

impl ptrace_syscall_info {
    /// Returns true if this `op` has no information on event.
    ///
    ///
    /// You may get this e.g. when you don't set
    /// PTRACE_O_TRACESYSGOOD in ptrace options.
    pub fn is_none(&self) -> bool {
        self.op == PTRACE_SYSCALL_INFO_NONE
    }

    /// Returns entry info if this is a system call entry.
    pub fn entry(&self) -> Option<ptrace_syscall_info_entry> {
        if self.op != PTRACE_SYSCALL_INFO_ENTRY {
            return None;
        }

        // SAFETY: The `op` check above asserts
        // the `entry` member of the union
        // is valid.
        Some(unsafe { self.data.entry })
    }

    /// Returns exit info if this is a system call exit.
    pub fn exit(&self) -> Option<ptrace_syscall_info_exit> {
        if self.op != PTRACE_SYSCALL_INFO_EXIT {
            return None;
        }

        // SAFETY: The `op` check above asserts
        // the `exit` member of the union
        // is valid.
        Some(unsafe { self.data.exit })
    }

    /// Returns seccomp info if this is a system call seccomp event.
    pub fn seccomp(&self) -> Option<ptrace_syscall_info_seccomp> {
        if self.op != PTRACE_SYSCALL_INFO_SECCOMP {
            return None;
        }

        // SAFETY: The `op` check above asserts
        // the `seccomp` member of the union
        // is valid.
        Some(unsafe { self.data.seccomp })
    }

    /// Returns the system call name if available.
    pub fn syscall(&self) -> Option<&'static XPath> {
        let nr = if let Some(info) = self.entry() {
            info.nr
        } else if let Some(info) = self.seccomp() {
            info.nr
        } else {
            return None;
        };

        // SAFETY: In libseccomp we trust.
        #[expect(clippy::cast_possible_truncation)]
        let ptr = unsafe { seccomp_syscall_resolve_num_arch(self.arch, nr as i32) };

        // Check for NULL.
        if ptr.is_null() {
            return None;
        }

        // SAFETY: libseccomp returned success, pointer is valid.
        Some(XPath::from_bytes(unsafe { CStr::from_ptr(ptr) }.to_bytes()))
    }
}
