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

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, fcntl::AtFlags, sys::time::TimeSpec, NixPath};

use crate::{
    confine::scmp_arch_bits,
    cookie::safe_utimensat,
    kernel::{syscall_path_handler, to_atflags},
    lookup::FsFlags,
    req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
};

pub(crate) fn sys_utime(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];

    syscall_path_handler(request, "utime", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        let req = request.scmpreq;
        let (atime, mtime) = request.remote_utimbuf(req.data.args[1])?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

pub(crate) fn sys_utimes(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];

    syscall_path_handler(request, "utime", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        let req = request.scmpreq;
        let (atime, mtime) = request.remote_utimbuf(req.data.args[1])?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

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

    let fsflags = FsFlags::MUST_PATH;
    let path = if req.data.args[1] != 0 { Some(1) } else { None };

    let argv = &[SysArg {
        dirfd: Some(0),
        path,
        fsflags,
        ..Default::default()
    }];

    syscall_path_handler(request, "futimesat", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        let (atime, mtime) = request.remote_timeval(req.data.args[2])?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

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

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

    let mut flags = SysFlags::empty();
    let mut fsflags = FsFlags::MUST_PATH;
    if atflags.contains(AtFlags::AT_EMPTY_PATH) {
        flags |= SysFlags::EMPTY_PATH;
    }
    if atflags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags |= FsFlags::NO_FOLLOW_LAST;
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        path: if req.data.args[1] != 0 { Some(1) } else { None },
        flags,
        fsflags,
        ..Default::default()
    }];

    syscall_path_handler(request, "utimensat", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        let addr = req.data.args[2];
        let is32 = scmp_arch_bits(req.data.arch) == 32;

        let (atime, mtime) = if is32 {
            request.remote_timespec32_2(addr)
        } else {
            request.remote_timespec64_2(addr)
        }?;

        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

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

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

    let mut flags = SysFlags::empty();
    let mut fsflags = FsFlags::MUST_PATH;
    if atflags.contains(AtFlags::AT_EMPTY_PATH) {
        flags |= SysFlags::EMPTY_PATH;
    }
    if atflags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags |= FsFlags::NO_FOLLOW_LAST;
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        path: if req.data.args[1] != 0 { Some(1) } else { None },
        flags,
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "utimensat_time64",
        argv,
        |path_args, request, sandbox| {
            drop(sandbox); // release the read-lock.

            let (atime, mtime) = request.remote_timespec64_2(req.data.args[2])?;
            syscall_utime_handler(request, path_args, &atime, &mtime)
        },
    )
}

/// A helper function to handle utime* syscalls.
fn syscall_utime_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    atime: &TimeSpec,
    mtime: &TimeSpec,
) -> 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().ok_or(Errno::EBADF)?;
    let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];

    safe_utimensat(fd, &times).map(|_| request.return_syscall(0))
}
