/*
 * sys_exec.c - Kernel function implementing the "exec" system call.
 *
 * Authors   Alain Greiner (2016,2017,2017,2019,2020)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <kernel_config.h>
#include <hal_kernel_types.h>
#include <hal_uspace.h>
#include <errno.h>
#include <printk.h>
#include <core.h>
#include <vfs.h>
#include <cluster.h>
#include <process.h>
#include <thread.h>
#include <vmm.h>
#include <ppm.h>
#include <rpc.h>

#include <syscalls.h>

////////////////////////////////////////////////i////////////////////////////////////////
// This static function is called twice by the sys_exec() function :
// - to register the main() arguments (args) in the process <exec_info> structure.
// - to register the environment variables (envs) in the <exec_info> structure.
// In both cases the input is an array of NULL terminated string pointers in user space,
// identified by the <u_pointers> argument. The strings can be dispatched anywhere in 
// the calling user process space. The max number of envs, and the max number of args  
// are defined by the CONFIG_PROCESS_ARGS_NR and CONFIG_PROCESS_ENVS_MAX_NR parameters. 
////////////////////////////////////////////////i////////////////////////////////////////
// Implementation Note:
// Both the array of pointers and the strings themselve are stored in kernel space in one
// single, dynamically allocated, kernel buffer containing an integer number of pages,
// defined by the CONFIG_VMM_ENVS_SIZE and CONFIG_VMM_STACK_SIZE parameters.
// These two kernel buffers contains :
// - in the first bytes a fixed size kernel array of kernel pointers on the strings.
// - in the following bytes the strings themselves.
// The exec_info_t structure is defined in the <process.h> file.
////////////////////////////////////////////////i////////////////////////////////////////
// @ is_args     : [in]    true if called for (args) / false if called for (envs).
// @ u_pointers  : [in]    array of pointers on the strings (in user space).
// @ exec_info   : [inout] pointer on the exec_info structure.
// @ return 0 if success / non-zero if too many strings or no memory.
////////////////////////////////////////////////i////////////////////////////////////////
static error_t exec_get_strings( bool_t         is_args,
                                 char        ** u_pointers,
                                 exec_info_t  * exec_info )
{
    uint32_t     index;           // slot index in pointers array
    uint32_t     length;          // string length (in bytes)
    uint32_t     pointers_bytes;  // number of bytes to store pointers
    uint32_t     max_index;       // max size of pointers array
    char      ** k_pointers;      // base of kernel array of pointers
    char       * k_buf_ptr;       // pointer on first empty slot in strings buffer
    uint32_t     k_buf_space;     // number of bytes available in string buffer
    char       * k_buf;           // kernel buffer for both pointers & strings 

#if DEBUG_SYS_EXEC 
thread_t * this  = CURRENT_THREAD;
uint32_t   cycle = (uint32_t)hal_get_cycles();
#endif

    // Allocate one block of physical memory for both the pointers and the strings

    if( is_args )
    {
        k_buf = kmem_alloc( bits_log2(CONFIG_VMM_ARGS_SIZE << CONFIG_PPM_PAGE_ORDER), AF_ZERO );

        pointers_bytes = (CONFIG_PROCESS_ARGS_MAX_NR + 1) * sizeof(char *);
        k_pointers     = (char **)k_buf;
        k_buf_ptr      = k_buf + pointers_bytes;
        k_buf_space    = (CONFIG_VMM_ARGS_SIZE << CONFIG_PPM_PAGE_ORDER) - pointers_bytes;
        max_index      = CONFIG_PROCESS_ARGS_MAX_NR + 1;

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
printk("\n[%s] thread[%x,%x] for args / u_buf %x / k_buf %x\n",
__FUNCTION__, this->process->pid, this->trdid, u_pointers, k_buf );
#endif

    }
    else  // envs
    {
        k_buf = kmem_alloc( bits_log2(CONFIG_VMM_ENVS_SIZE << CONFIG_PPM_PAGE_ORDER), AF_ZERO );

        pointers_bytes = (CONFIG_PROCESS_ENVS_MAX_NR + 1) * sizeof(char *);
        k_pointers     = (char **)k_buf;
        k_buf_ptr      = k_buf + pointers_bytes;
        k_buf_space    = (CONFIG_VMM_ENVS_SIZE << CONFIG_PPM_PAGE_ORDER) - pointers_bytes;
        max_index      = CONFIG_PROCESS_ENVS_MAX_NR + 1;

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
printk("\n[%s] thread[%x,%x] for envs / u_buf %x / k_buf %x\n",
__FUNCTION__, this->process->pid, this->trdid, u_pointers, k_buf );
#endif

    }

    // copy the user array of pointers to kernel buffer
    hal_copy_from_uspace( XPTR( local_cxy , k_pointers ),
                          u_pointers,
                          pointers_bytes );

    // WARNING : the pointers copied in the k_pointers[] array are user pointers,
    // after the loop below, the k_pointers[] array contains kernel pointers.

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
printk("\n[%s] thread[%x,%x] moved u_ptr array of pointers to k_ptr array\n",
__FUNCTION__, this->process->pid, this->trdid );
#endif

    // scan kernel array of pointers to copy strings to kernel buffer
    for( index = 0 ; index < max_index ; index++ )
    {
        // exit loop if (k_pointers[index] == NUll)
        if( k_pointers[index] == NULL ) break;

        // compute string length (without the NUL character)
        length = hal_strlen_from_uspace( k_pointers[index] );

        // return error if overflow in kernel buffer
        if( length > k_buf_space ) return -1;

        // copy the string itself to kernel buffer
        hal_copy_from_uspace( XPTR( local_cxy , k_buf_ptr ),
                              k_pointers[index],
                              length + 1 );

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
printk("\n[%s] thread[%x,%x] copied string[%d] <%s> to kernel buffer / length %d\n",
__FUNCTION__, this->process->pid, this->trdid, index, k_buf_ptr, length );
#endif

        // replace the user pointer by a kernel pointer in the k_pointer[] array
        k_pointers[index] = k_buf_ptr;

        // increment loop variables
        k_buf_ptr   += (length + 1);
        k_buf_space -= (length + 1);

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
{
    if( k_pointers[0] != NULL )
    printk("\n[%s] thread[%x,%x] : &arg0 = %x / arg0 = <%s>\n",
    __FUNCTION__, this->process->pid, this->trdid, k_pointers[0], k_pointers[0] );
    else 
    printk("\n[%s] thread[%x,%x] : unexpected NULL value for &arg0\n",
    __FUNCTION__, this->process->pid, this->trdid );
}
#endif

    }  // end loop on index

    // update into exec_info structure
    if( is_args )
    {
        exec_info->args_pointers  =  k_pointers;
        exec_info->args_nr        =  index;
    }
    else
    {
        exec_info->envs_pointers  =  k_pointers;
        exec_info->envs_buf_free  =  k_buf_ptr;
        exec_info->envs_nr        =  index;
    }

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < cycle )
printk("\n[%s] thread[%x,%x] copied %d strings to kernel buffer\n",
__FUNCTION__, this->process->pid, this->trdid, index );
#endif

    return 0;

} // end exec_get_strings()


///////////////////////////////
int sys_exec( char  * pathname,    // .elf file pathname in user space
              char ** user_args,   // pointer on array of process arguments in user space
              char ** user_envs )  // pointer on array of env variables in user space
{
    error_t       error;
    vseg_t      * vseg;

    // get calling thread, process, & pid
    thread_t    * this    = CURRENT_THREAD;
    process_t   * process = this->process;
    pid_t         pid     = process->pid;
    trdid_t       trdid   = this->trdid;

assert( __FUNCTION__, (CXY_FROM_PID( pid ) == local_cxy) ,
"must be called in the owner cluster\n");

assert( __FUNCTION__, (LTID_FROM_TRDID( trdid ) == 0) ,
"must be called by the main thread\n");

assert( __FUNCTION__, (user_envs == NULL) ,
"environment variables not supported yet\n" );

#if DEBUG_SYS_EXEC || DEBUG_SYSCALLS_ERROR 
uint64_t     tm_start = hal_get_cycles();
#endif

    // check "pathname" mapped in user space
    if( vmm_get_vseg( process , (intptr_t)pathname , &vseg ) )
	{

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%] / pathname pointer %x unmapped\n",
__FUNCTION__, pid, trdid, pathname );
#endif
        this->errno = EINVAL;
		return -1;
	}

    // check "pathname" length
    if( hal_strlen_from_uspace( pathname ) >= CONFIG_VFS_MAX_PATH_LENGTH )
    {

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%x] / pathname too long\n",
__FUNCTION__, pid, trdid );
#endif
        this->errno = ENFILE;
        return -1;
    }

    // check "args" mapped in user space if non NULL
    if( (user_args != NULL) && (vmm_get_vseg( process , (intptr_t)user_args , &vseg )) )
	{

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s for thread[%x,%] : user_args pointer %x unmapped\n",
__FUNCTION__, pid, trdid, user_args );
#endif
        this->errno = EINVAL;
		return -1;
	}

    // check "envs" mapped in user space if not NULL
    if( (user_envs != NULL) && (vmm_get_vseg( process , (intptr_t)user_envs , &vseg )) )
	{

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%] / user_envs pointer %x unmapped\n",
__FUNCTION__, pid, trdid, user_envs );
#endif
        this->errno = EINVAL;
		return -1;
	}

    // 1. copy "pathname" in kernel exec_info structure 
    hal_strcpy_from_uspace( XPTR( local_cxy , &process->exec_info.path[0] ),
                            pathname,
                            CONFIG_VFS_MAX_PATH_LENGTH );

#if DEBUG_SYS_EXEC
if( DEBUG_SYS_EXEC < (uint32_t)tm_start )
printk("\n[%s] thread[%x,%x] enter / path <%s> / args %x / envs %x / cycle %d\n",
__FUNCTION__, pid, trdid, &process->exec_info.path[0],
user_args, user_envs, (uint32_t)tm_start );
#endif

    // 2. copy "arguments" pointers & strings in process exec_info if required
    if( user_args != NULL )
    {
        if( exec_get_strings( true , user_args , &process->exec_info ) )
        {

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%] cannot get arguments for <%s>\n",
__FUNCTION__, pid, trdid, pathname );
#endif
            this->errno = EINVAL;
            return -1;
        }

#if DEBUG_SYS_EXEC
if( DEBUG_SYS_EXEC < (uint32_t)tm_start )
printk("\n[%s] thread[%x,%x] set arguments in exec_info / arg[0] = <%s>\n",
__FUNCTION__, pid, trdid, process->exec_info.args_pointers[0] );
#endif

    }

    // 3. copy "environment" pointers & strings in process exec_info if required
    if( user_envs != NULL )
    {
        if( exec_get_strings( false , user_envs , &process->exec_info ) )
        {

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%] cannot get env variables for <%s>\n",
__FUNCTION__, pid, trdid, pathname );
#endif
            this->errno = EINVAL;
            return -1;
        }

#if DEBUG_SYS_EXEC 
if( DEBUG_SYS_EXEC < (uint32_t)tm_start )
printk("\n[%s] thread[%x,%x] set envs in exec_info / env[0] = <%s>\n",
__FUNCTION__, pid, trdid, process->exec_info.envs_pointers[0] );
#endif

    }

    // call relevant kernel function (no return if success)
    error = process_make_exec();

    if( error )
    {

#if DEBUG_SYSCALLS_ERROR
if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start )
printk("\n[ERROR] in %s : thread[%x,%x] cannot create process <%s>\n",
__FUNCTION__, pid, trdid, process->exec_info.path );
#endif
        this->errno = error;
        return -1;
    }

    return 0;  

} // end sys_exec()

