//
// Syd: rock-solid application kernel
// src/kernel/sigaction.rs: {,rt_}sigaction(2) handler
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, sys::signal::SaFlags};

use crate::{confine::is_valid_ptr, proc::proc_tgid, req::UNotifyEventRequest};

pub(crate) fn sys_sigaction(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Check if the handler is a restarting one.
    // This allows us to selectively unblock system calls.

    let req = request.scmpreq;

    // SAFETY: Ensure signal number is a valid signal.
    // We deliberately include reserved signals here.
    let sig_num: libc::c_int = match req.data.args[0].try_into() {
        Ok(libc::SIGKILL | libc::SIGSTOP) => return request.fail_syscall(Errno::EINVAL),
        Ok(sig_num) if sig_num < 1 || sig_num > libc::SIGRTMAX() => {
            return request.fail_syscall(Errno::EINVAL)
        }
        Ok(sig_num) => sig_num,
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };

    // SAFETY: We do not hook into sigaction
    // when the first argument is NULL.
    let addr = req.data.args[1];
    assert_ne!(addr, 0);

    // SAFETY: Check pointer against mmap_min_addr.
    if !is_valid_ptr(addr, req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let sa_flags = match request.read_sa_flags(addr) {
        Ok(sa_flags) => sa_flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // SAFETY: Signal handlers are per-process not per-thread!
    let tgid = match proc_tgid(request.scmpreq.pid()) {
        Ok(tgid) => tgid,
        Err(errno) => return request.fail_syscall(errno),
    };

    let _is_restart = if sa_flags.contains(SaFlags::SA_RESTART) {
        if let Err(errno) = request.cache.add_sig_restart(tgid, sig_num) {
            // This may only fail under memory-pressure.
            // Better to be on the safe side and deny the syscall.
            // TODO: Log an alert here.
            return request.fail_syscall(errno);
        }
        true
    } else {
        request.cache.del_sig_restart(tgid, sig_num);
        false
    };

    /*
    if log_enabled!(LogLevel::Debug) {
        let sandbox = request.get_sandbox();
        let verbose = sandbox.verbose;
        drop(sandbox); // release the read-lock.

        if verbose {
            debug!("ctx": "sigaction", "op": "add_handler",
                "msg": format!("added {}restarting handler for signal {sig_num}",
                    if is_restart { "" } else { "non " }),
                "sig": sig_num, "flags": format!("{sa_flags:?}"),
                "pid": tgid.as_raw(), "tid": req.pid,
                "req": &request);
        } else {
            debug!("ctx": "sigaction", "op": "add_handler",
                "msg": format!("added {}restarting handler for signal {sig_num}",
                    if is_restart { "" } else { "non " }),
                "sig": sig_num, "flags": format!("{sa_flags:?}"),
                "pid": tgid.as_raw(), "tid": req.pid);
        }
    }
    */

    // Let the syscall continue.
    // SAFETY: There's nothing we can do if the system call fails,
    // or if an attacker changes the sa_flags element of `struct sigaction`.
    // but we did our best by validating all the things we can.
    unsafe { request.continue_syscall() }
}
