// Syd: rock-solid application kernel
// src/kernel/net/getsockopt.rs: getsockopt(2) handler
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{mem::size_of, os::fd::OwnedFd};

use libseccomp::ScmpNotifResp;
use nix::{
    errno::Errno,
    sys::socket::{getsockopt, sockopt::PeerCredentials},
    unistd::Pid,
};

use crate::{
    confine::{is_valid_ptr, scmp_arch_bits},
    fs::peer_inode,
    req::UNotifyEventRequest,
};

pub(crate) fn handle_getsockopt(
    fd: OwnedFd,
    request: &UNotifyEventRequest,
    args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
    if args[1] != libc::SOL_SOCKET as u64 || args[2] != libc::SO_PEERCRED as u64 {
        // SAFETY: Informational command.
        return Ok(unsafe { request.continue_syscall() });
    }

    // optval and optlen pointers in tracee
    let optval_ptr = args[3];
    let optlen_ptr = args[4];

    // optlen pointer must not be NULL.
    if !is_valid_ptr(optlen_ptr, request.scmpreq.data.arch) {
        return Err(Errno::EFAULT);
    }

    // Check for 32-bit tracee.
    let req = request.scmpreq;
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    // socklen_t is a 32-bit integer on both 32-bit and 64-bit.
    const SIZEOF_SOCKLEN_T: usize = size_of::<libc::socklen_t>();

    // Read *optlen from tracee.
    let mut len_buf = [0u8; SIZEOF_SOCKLEN_T];
    let read = request.read_mem(&mut len_buf, optlen_ptr, SIZEOF_SOCKLEN_T)?;
    if read != SIZEOF_SOCKLEN_T {
        return Err(Errno::EINVAL);
    }

    // Convert bytes to usize respecting native endianness.
    let orig_optlen = u32::from_ne_bytes([len_buf[0], len_buf[1], len_buf[2], len_buf[3]]) as usize;

    // If optval == NULL but *optlen > 0, kernel returns EFAULT.
    if !is_valid_ptr(optval_ptr, request.scmpreq.data.arch) && orig_optlen > 0 {
        return Err(Errno::EFAULT);
    }

    // Build the ucred to return.
    let ucred = getsockopt(&fd, PeerCredentials)?;
    if ucred.pid() != Pid::this().as_raw() {
        // Not a UNIX socket handled by Syd, continue.
        // SAFETY: Informational command.
        return unsafe { Ok(request.continue_syscall()) };
    }
    let uid = ucred.uid();
    let gid = ucred.gid();

    // Get socket inode, and lookup pid by inode in sandbox unix map.
    let pid = if let Some(pid) = peer_inode(fd)
        .ok()
        .and_then(|inode| request.get_unix(inode))
        .map(|unix| unix.pid.as_raw())
    {
        pid
    } else {
        // Not a UNIX socket, continue.
        // SAFETY: Informational command.
        return unsafe { Ok(request.continue_syscall()) };
    };

    // Prepare ucred size for tracee ABI
    let ucred_size = if is32 {
        12usize
    } else {
        size_of::<libc::ucred>()
    };

    // How many bytes we'll actually copy back.
    let to_copy = std::cmp::min(orig_optlen, ucred_size);

    if to_copy > 0 {
        // For 32-bit tracee: 3 x 32-bit little/big-endian values (pid, uid, gid)
        #[expect(clippy::cast_sign_loss)]
        if is32 {
            let mut b = [0u8; 12];

            // SAFETY: casting/truncation to 32-bit is intentional for 32-bit tracee ABI.
            // We preserve native endianness via to_ne_bytes().
            b[0..4].copy_from_slice(&(pid as i32 as u32).to_ne_bytes());
            b[4..8].copy_from_slice(&uid.to_ne_bytes());
            b[8..12].copy_from_slice(&gid.to_ne_bytes());

            // Write only the first to_copy bytes.
            request.write_mem(&b[..to_copy], optval_ptr)?;
        } else {
            // Native layout - use libc::ucred.
            let native = libc::ucred { pid, uid, gid };

            // SAFETY: Create a byte slice of native for write. native is on the stack
            // and we immediately use the slice to write into the tracee; there is no
            // escaping of the slice beyond this scope.
            let native_bytes: &[u8] = unsafe {
                // SAFETY: native is a plain-old-data repr provided by libc and
                // we read its bytes for the purpose of writing them to another process.
                std::slice::from_raw_parts(
                    (&raw const native) as *const u8,
                    size_of::<libc::ucred>(),
                )
            };
            request.write_mem(&native_bytes[..to_copy], optval_ptr)?;
        }
    }

    // Write back the resulting length into *optlen.
    #[expect(clippy::cast_possible_truncation)]
    let v = (to_copy as u32).to_ne_bytes();
    request.write_mem(&v, optlen_ptr)?;

    Ok(request.return_syscall(0))
}
