// SPDX-License-Identifier: MPL-2.0

use aster_rights::WriteOp;
use ostd::{
    arch::cpu::context::{FpuContext, GeneralRegs, UserContext},
    mm::VmIo,
    sync::Waiter,
    user::UserContextApi,
};

use crate::{
    fs::{
        fs_resolver::{FsResolver, PathOrInode},
        utils::Inode,
    },
    prelude::*,
    process::{
        ContextUnshareAdminApi, Credentials, Process, ProgramToLoad,
        posix_thread::{PosixThread, ThreadLocal, ThreadName, sigkill_other_threads, thread_table},
        process_vm::{MAX_LEN_STRING_ARG, MAX_NR_STRING_ARGS, unshare_and_renew_vmar},
        program_loader::elf::ElfLoadInfo,
        signal::{
            HandlePendingSignal, PauseReason, SigStack,
            constants::{SIGCHLD, SIGKILL},
            signals::kernel::KernelSignal,
        },
    },
};

pub fn do_execve(
    elf_file: PathOrInode,
    argv_ptr_ptr: Vaddr,
    envp_ptr_ptr: Vaddr,
    ctx: &Context,
    user_context: &mut UserContext,
) -> Result<()> {
    // FIXME: A malicious user could cause a kernel panic by exhausting available memory.
    // Currently, the implementation reads up to `MAX_NR_STRING_ARGS` arguments, each up to
    // `MAX_LEN_STRING_ARG` in length, without first verifying the total combined size.
    // To prevent excessive memory allocation, a preliminary check should sum the lengths
    // of all strings to enforce a sensible overall limit.
    let argv = read_cstring_vec(argv_ptr_ptr, MAX_NR_STRING_ARGS, MAX_LEN_STRING_ARG, ctx)?;
    let envp = read_cstring_vec(envp_ptr_ptr, MAX_NR_STRING_ARGS, MAX_LEN_STRING_ARG, ctx)?;
    debug!(
        "filename: {:?}, argv = {:?}, envp = {:?}",
        elf_file.display_name(),
        argv,
        envp
    );

    let fs_ref = ctx.thread_local.borrow_fs();
    let fs_resolver = fs_ref.resolver().read();
    let elf_inode = elf_file.inode();
    let program_to_load = ProgramToLoad::build_from_inode(elf_inode, &fs_resolver, argv, envp, 1)?;

    // Ensure no other thread is concurrently performing exit_group or execve.
    // If such an operation is in progress, return EAGAIN.
    let mut task_set = ctx.process.tasks().lock();
    if task_set.has_exited_group() || task_set.in_execve() {
        return_errno_with_message!(
            Errno::EAGAIN,
            "the process has exited or has already executed a new program"
        );
    }
    task_set.start_execve();

    // Terminate all other threads
    sigkill_other_threads(ctx.task, &task_set);
    drop(task_set);

    // After this point, failures in subsequent operations are fatal: the process
    // state may be left inconsistent and it can never return to user mode.

    let res = do_execve_no_return(ctx, user_context, elf_file, &fs_resolver, program_to_load);

    if res.is_err() {
        ctx.posix_thread
            .enqueue_signal(Box::new(KernelSignal::new(SIGKILL)));
    }

    ctx.process.tasks().lock().finish_execve();

    res
}

fn read_cstring_vec(
    array_ptr: Vaddr,
    max_string_number: usize,
    max_string_len: usize,
    ctx: &Context,
) -> Result<Vec<CString>> {
    // On Linux, argv pointer and envp pointer can be specified as NULL.
    if array_ptr == 0 {
        return Ok(Vec::new());
    }

    let mut res = Vec::new();
    let mut read_addr = array_ptr;

    let user_space = ctx.user_space();
    for _ in 0..max_string_number {
        let cstring_ptr = user_space.read_val::<usize>(read_addr)?;
        read_addr += 8;

        if cstring_ptr == 0 {
            return Ok(res);
        }

        let cstring = user_space
            .read_cstring(cstring_ptr, max_string_len)
            .map_err(|err| {
                if err.error() == Errno::ENAMETOOLONG {
                    Error::with_message(Errno::E2BIG, "there are too many bytes in the argument")
                } else {
                    err
                }
            })?;
        res.push(cstring);
    }

    return_errno_with_message!(Errno::E2BIG, "there are too many arguments");
}

fn do_execve_no_return(
    ctx: &Context,
    user_context: &mut UserContext,
    elf_file: PathOrInode,
    fs_resolver: &FsResolver,
    program_to_load: ProgramToLoad,
) -> Result<()> {
    let Context {
        process,
        thread_local,
        posix_thread,
        ..
    } = ctx;

    // Wait for all other threads to terminate,
    // then promote the current thread to be the process's main thread if necessary.
    wait_other_threads_exit(ctx)?;
    thread_table::make_current_main_thread(ctx);

    let elf_load_info = {
        let mut vmar = ctx.process.lock_vmar();
        // Reset the virtual memory state.
        unshare_and_renew_vmar(ctx, &mut vmar, elf_file.clone());
        // Load the binary into the process's address space
        program_to_load.load_to_vmar(vmar.unwrap(), fs_resolver)?
    };
    // After the program has been successfully loaded, the virtual memory of the current process
    // is initialized. Hence, it is necessary to clear the previously recorded robust list.
    *thread_local.robust_list().borrow_mut() = None;
    thread_local.clear_child_tid().set(0);

    // Set up the CPU context.
    set_cpu_context(thread_local, user_context, &elf_load_info);

    // Apply file-capability changes.
    apply_caps_from_exec(process, posix_thread, elf_file.inode())?;

    // If this was a vfork child, reset vfork-specific state.
    reset_vfork_child(process);

    // Unshare file descriptor table and close files with O_CLOEXEC flag.
    unshare_and_close_files(ctx);

    // Update the process's executable path and set the thread name
    let executable_path = elf_file.display_name();
    *posix_thread.thread_name().lock() = ThreadName::new_from_executable_path(&executable_path);

    // Unshare and reset signal dispositions to their default actions.
    unshare_and_reset_sigdispositions(process);
    // Reset the alternate signal stack to its default state.
    *thread_local.sig_stack().borrow_mut() = SigStack::default();
    // Restore the process exit signal to SIGCHLD.
    process.set_exit_signal(SIGCHLD);

    Ok(())
}

fn wait_other_threads_exit(ctx: &Context) -> Result<()> {
    let is_main_thread = ctx.posix_thread.tid() == ctx.process.pid();

    let mut tasks = ctx.process.tasks().lock();
    loop {
        if is_main_thread {
            if tasks.as_slice().len() == 1 {
                return Ok(());
            }
        } else if tasks.as_slice().len() == 2 && tasks.has_exited_main() {
            return Ok(());
        }

        // Wait until any signal comes or any other thread exits.
        let (waiter, waker) = Waiter::new_pair();

        ctx.posix_thread
            .set_signalled_waker(waker.clone(), PauseReason::Sleep);
        if ctx.has_pending_sigkill() {
            ctx.posix_thread.clear_signalled_waker();
            return_errno_with_message!(Errno::EAGAIN, "the current thread has received SIGKILL");
        }

        tasks.set_execve_waker(waker);
        drop(tasks);

        waiter.wait();

        ctx.posix_thread.clear_signalled_waker();

        tasks = ctx.process.tasks().lock();
        tasks.clear_execve_waker();
    }
}

fn set_cpu_context(
    thread_local: &ThreadLocal,
    user_context: &mut UserContext,
    elf_load_info: &ElfLoadInfo,
) {
    // Reset FPU context.
    thread_local.fpu().set_context(FpuContext::new());

    // Reset general-purpose registers.
    *user_context.general_regs_mut() = GeneralRegs::default();
    // Clear the TLS pointer.
    user_context.set_tls_pointer(0);
    // Set the new instruction pointer to the ELF entry point.
    user_context.set_instruction_pointer(elf_load_info.entry_point as _);
    debug!("entry_point: 0x{:x}", elf_load_info.entry_point);
    // Set the new user-space stack pointer.
    user_context.set_stack_pointer(elf_load_info.user_stack_top as _);
    debug!("user stack top: 0x{:x}", elf_load_info.user_stack_top);
}

/// Sets the UID and GID in the credentials according to the ELF inode.
///
/// The capabilities will be updated accordingly.
fn apply_caps_from_exec(
    process: &Process,
    posix_thread: &PosixThread,
    elf_inode: &Arc<dyn Inode>,
) -> Result<()> {
    // FIXME: We need to recalculate the capabilities during execve even the executable inode
    // does not have setuid/setgid bit.
    let credentials = posix_thread.credentials_mut();
    set_uid_from_elf(process, &credentials, elf_inode)?;
    set_gid_from_elf(process, &credentials, elf_inode)?;
    credentials.set_keep_capabilities(false)?;

    Ok(())
}

/// Sets the UID in the credentials according to the ELF inode.
///
/// If the ELF inode has the `set_uid` bit, the effective UID is set to the same value as the ELF
/// inode's UID.
fn set_uid_from_elf(
    current: &Process,
    credentials: &Credentials<WriteOp>,
    elf_inode: &Arc<dyn Inode>,
) -> Result<()> {
    if elf_inode.mode()?.has_set_uid() {
        let uid = elf_inode.owner()?;
        credentials.set_euid(uid);

        current.clear_parent_death_signal();
    }

    // No matter whether the ELF inode has `set_uid` bit, SUID should be reset.
    credentials.reset_suid();
    Ok(())
}

/// Sets the GID in the credentials according to the ELF inode.
///
/// If the ELF inode has the `set_gid` bit, the effective GID is set to the same value as the ELF
/// inode's GID.
fn set_gid_from_elf(
    current: &Process,
    credentials: &Credentials<WriteOp>,
    elf_inode: &Arc<dyn Inode>,
) -> Result<()> {
    if elf_inode.mode()?.has_set_gid() {
        let gid = elf_inode.group()?;
        credentials.set_egid(gid);

        current.clear_parent_death_signal();
    }

    // No matter whether the ELF inode has `set_gid` bit, SGID should be reset.
    credentials.reset_sgid();
    Ok(())
}

fn reset_vfork_child(process: &Process) {
    if process.status().is_vfork_child() {
        // Resumes the parent process.
        process.status().set_vfork_child(false);
        let parent = process.parent().lock().process().upgrade().unwrap();
        parent.children_wait_queue().wake_all();
    }
}

fn unshare_and_close_files(ctx: &Context) {
    ctx.unshare_files();

    ctx.thread_local
        .borrow_file_table()
        .unwrap()
        .write()
        .close_files_on_exec();
}

fn unshare_and_reset_sigdispositions(process: &Process) {
    let mut sig_dispositions = process.sig_dispositions().lock();

    let mut new = *sig_dispositions.lock();
    new.inherit();

    *sig_dispositions = Arc::new(Mutex::new(new));
}
