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

use libseccomp::ScmpNotifResp;
use nix::errno::Errno;

use crate::{
    ioctl::Ioctl,
    log::get_ioctl_log,
    log_enabled,
    req::UNotifyEventRequest,
    sandbox::{Action, Capability},
    syslog::LogLevel,
    warn,
};

#[expect(clippy::cognitive_complexity)]
pub(crate) fn sys_ioctl(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        let req = request.scmpreq;
        let sandbox = request.get_sandbox();

        // Check if the ioctl(2) request is allowed or denied.
        let arg = req.data.args[1];
        let cap = Capability::CAP_IOCTL;
        let action = sandbox.check_ioctl(arg, req.data.arch);

        let filter = action == Action::Filter;
        if !filter && action >= Action::Warn && log_enabled!(LogLevel::Warn) {
            let log_scmp = sandbox.log_scmp();
            let ctl = get_ioctl_log(arg as Ioctl, req.data.arch, sandbox.log_ioctl())
                .ok()
                .flatten();
            let grp = cap.to_string().to_ascii_lowercase();
            let tip = if let Some(name) = ctl.as_ref().and_then(|ctl| ctl.first()) {
                format!("configure `allow/{grp}+{name}'")
            } else {
                format!("configure `allow/{grp}+{arg:#x}'")
            };
            if log_scmp {
                warn!("ctx": "access", "cap": cap, "act": action,
                    "sys": "ioctl", "ctl": ctl, "tip": tip,
                    "req": &request);
            } else {
                warn!("ctx": "access", "cap": cap, "act": action,
                    "sys": "ioctl", "ctl": ctl, "tip": tip,
                    "pid": request.scmpreq.pid);
            }
        }
        drop(sandbox); // release the read-lock.

        match action {
            Action::Allow | Action::Warn => {
                // SAFETY:
                // Access check depends on the request number only,
                // which is saved in a register and inaccessible
                // to other threads.
                Ok(unsafe { request.continue_syscall() })
            }
            Action::Filter | Action::Deny => Err(Errno::ENOTTY),
            Action::Panic => panic!(),
            Action::Exit => std::process::exit(libc::ENOTTY),
            action => {
                // Stop|Kill
                let _ = request.kill(action);
                Err(Errno::EACCES)
            }
        }
    })
}
