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

use std::os::fd::{AsRawFd, RawFd};

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

use crate::{
    lookup::{CanonicalPath, FileInfo},
    req::UNotifyEventRequest,
    sandbox::Capability,
};

pub(crate) fn sys_fcntl(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        // We only hook into F_SETFL requests
        // which do not have O_APPEND set!
        let req = request.scmpreq;
        let args = req.data.args;

        let fd = RawFd::try_from(args[0]).or(Err(Errno::EBADF))?;
        if fd < 0 {
            return Err(Errno::EBADF);
        }
        let fd = request.get_fd(fd)?;

        let path = match CanonicalPath::new_fd(fd.into(), req.pid()) {
            Ok(path) => {
                if !request.is_valid() {
                    return Err(Errno::ESRCH);
                }
                path
            }
            Err(_) => return Err(Errno::EBADF),
        };

        let sandbox = request.get_sandbox();
        let is_crypt = sandbox.enabled(Capability::CAP_CRYPT);
        let is_append = sandbox.is_append(path.abs());
        drop(sandbox); // release the read-lock.

        if is_append {
            // Deny silently.
            return Ok(request.return_syscall(0));
        }

        #[expect(clippy::disallowed_methods)]
        let fd = path.dir.as_ref().unwrap();

        #[expect(clippy::disallowed_methods)]
        if is_crypt {
            if let Ok(info) = FileInfo::from_fd(fd) {
                let files = request.cache.crypt_map.as_ref().unwrap();
                let check = {
                    let files = files.0.lock().unwrap_or_else(|err| err.into_inner());
                    files.values().any(|map| map.info == info)
                }; // Lock is released here.

                if check {
                    // Deny with EACCES, caller should know.
                    return Err(Errno::EACCES);
                }
            }
        }

        // Perform the allowed fcntl(2) call.
        // SAFETY: In libc we trust.
        #[expect(clippy::cast_possible_truncation)]
        match Errno::result(unsafe {
            libc::fcntl(
                fd.as_raw_fd(),
                args[1] as libc::c_int,
                args[2],
                args[3],
                args[4],
                args[5],
            )
        }) {
            Ok(ret) => Ok(request.return_syscall(ret.into())),
            Err(errno) => Err(errno),
        }
    })
}
