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

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

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

use crate::{
    compat::RenameFlags,
    cookie::safe_renameat2,
    kernel::syscall_path_handler,
    lookup::FsFlags,
    req::{PathArgs, SysArg, UNotifyEventRequest},
};

pub(crate) fn sys_rename(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SysArg {
            path: Some(0),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
            ..Default::default()
        },
        SysArg {
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, RenameFlags::empty())
    })
}

pub(crate) fn sys_renameat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SysArg {
            dirfd: Some(0),
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
            ..Default::default()
        },
        SysArg {
            dirfd: Some(2),
            path: Some(3),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, RenameFlags::empty())
    })
}

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

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

    // Note, (EXCHANGE | NOREPLACE) flags are mutually exclusive.
    let mut fsflags_new = FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE;
    if flags.contains(RenameFlags::RENAME_EXCHANGE) {
        fsflags_new.insert(FsFlags::MUST_PATH);
    } else if flags.contains(RenameFlags::RENAME_NOREPLACE) {
        fsflags_new.insert(FsFlags::MISS_LAST);
    }

    let argv = &[
        SysArg {
            dirfd: Some(0),
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
            ..Default::default()
        },
        SysArg {
            dirfd: Some(2),
            path: Some(3),
            dotlast: Some(Errno::EINVAL),
            fsflags: fsflags_new,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, flags)
    })
}

/// A helper function to handle rename and renameat syscalls.
fn syscall_rename_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    flags: RenameFlags,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: SysArg has two elements.
    #[expect(clippy::disallowed_methods)]
    let old_path = args.0.as_ref().unwrap();
    #[expect(clippy::disallowed_methods)]
    let new_path = args.1.as_ref().unwrap();

    safe_renameat2(
        old_path
            .dir
            .as_ref()
            .map(|fd| fd.as_fd())
            .ok_or(Errno::EBADF)?,
        old_path.base(),
        new_path
            .dir
            .as_ref()
            .map(|fd| fd.as_fd())
            .ok_or(Errno::EBADF)?,
        new_path.base(),
        flags,
    )
    .map(|_| request.return_syscall(0))
}

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

    // Fail if (NOREPLACE | WHITEOUT) is combined with EXCHANGE.
    if flags.contains(RenameFlags::RENAME_EXCHANGE)
        && flags.intersects(RenameFlags::RENAME_NOREPLACE | RenameFlags::RENAME_WHITEOUT)
    {
        return Err(Errno::EINVAL);
    }

    Ok(flags)
}
