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

// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]

use std::os::fd::AsFd;

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, fcntl::AtFlags, unistd::AccessFlags, NixPath};

use crate::{
    fd::fdaccess,
    kernel::{syscall_path_handler, to_atflags},
    lookup::FsFlags,
    req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
};

pub(crate) fn sys_access(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // SAFETY: Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[1]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        path: Some(0),
        flags: SysFlags::CHECK_MAGIC,
        ..Default::default()
    }];
    syscall_path_handler(request, "access", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_access_handler(request, path_args, mode, AtFlags::empty())
    })
}

pub(crate) fn sys_faccessat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // SAFETY: Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[2]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: SysFlags::CHECK_MAGIC,
        ..Default::default()
    }];
    syscall_path_handler(request, "faccessat", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_access_handler(request, path_args, mode, AtFlags::empty())
    })
}

pub(crate) fn sys_faccessat2(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // SAFETY: Reject undefined/invalid/unused flags.
    let flags = match to_atflags(
        req.data.args[3],
        crate::compat::AT_EACCESS | AtFlags::AT_EMPTY_PATH | AtFlags::AT_SYMLINK_NOFOLLOW,
    ) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // SAFETY: Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[2]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let mut fsflags = FsFlags::MUST_PATH;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if flags.contains(AtFlags::AT_EMPTY_PATH) {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::CHECK_MAGIC
        },
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "faccessat2",
        argv,
        |path_args, request, sandbox| {
            drop(sandbox); // release the read-lock.
            syscall_access_handler(request, path_args, mode, flags)
        },
    )
}

/// A helper function to handle access, faccessat, and faccessat2 syscalls.
fn syscall_access_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    mode: AccessFlags,
    flags: AtFlags,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: SysArg has one element.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();

    // We use MUST_PATH, dir refers to the file.
    assert!(
        path.base().is_empty(),
        "BUG: MUST_PATH returned a directory for access, report a bug!"
    );
    let fd = path.dir.as_ref().map(|fd| fd.as_fd()).ok_or(Errno::EBADF)?;

    fdaccess(fd, mode, flags).map(|_| request.return_syscall(0))
}

#[inline]
fn to_accessflags(arg: u64) -> Result<AccessFlags, Errno> {
    let flags = arg.try_into().or(Err(Errno::EINVAL))?;
    AccessFlags::from_bits(flags).ok_or(Errno::EINVAL)
}
