/*
 * sys_exec.c - Kernel function implementing the "exec" system call.
 *
 * Authors   Alain Greiner (2016,2017)
 *
 * 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_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>


////////////////////////////////////////////////i//////////////////////////////////////
// This static function is called twice by the sys_exec() function :
// - to register the main() arguments (args) in the exec_info structure.
// - to register the environment variables (envs) in the exec_info structure.
// In both cases the input is an array of string pointers in user space,
// and a set of strings in user space.
// We allocate one physical page to store a kernel copy of the array of pointers,
// we allocate one or several physical pages to store the strings themselve,
// and register these buffers and the number of strings in the exec_info structure.
// The max number of strings is 1024 (for both args and envs). The numbers of pages
// to store the (args) and (envs) strings are configuration parameters.
///////////////////////////////////////////////////////////////////////////////////////
// @ exec_info   : pointer on the exec_info structure.
// @ is_args     : true if called for (args) / false if called for (envs).
// @ u_pointers  : array of pointers on the strings (in user space).
// @ return 0 if success / non-zero if too many strings or no more memory.
///////////////////////////////////////////////////////////////////////////////////////
static error_t process_exec_get_strings( exec_info_t  * exec_info,
                                         bool_t         is_args,
                                         char        ** u_pointers )
{
    uint32_t     index;       // string index
    uint32_t     found_null;  // NULL pointer found in array of pointers
    uint32_t     length;      // string length
    kmem_req_t   req;         // kmem request
    page_t     * page;        // page descriptor
    xptr_t       base_xp;     // extended pointer on page base
    uint32_t     order;       // ln2( number of pages to store strings )
    char      ** k_pointers;  // base of kernel array of pointers
    char       * k_buf_ptr;   // pointer on first empty slot in kernel strings buffer
    char       * k_buf_base;  // base address of the kernel strings buffer

    // compute ln2( number of pages for kernel strings buffer )
    if( is_args ) order = bits_log2( CONFIG_VMM_ARGS_SIZE );
    else          order = bits_log2( CONFIG_VMM_ENVS_SIZE );

    req.type   = KMEM_PAGE;
    req.flags  = AF_KERNEL | AF_ZERO;

    // allocate one physical page for kernel array of pointers
    req.type   = 0;
    page       = kmem_alloc( &req );

    if( page == NULL ) return ENOMEM;

    base_xp = ppm_page2base( XPTR( local_cxy , page ) );
    k_pointers = (char **)GET_PTR( base_xp );

    // allocate several physical pages to store the strings themselve
    req.type   = order;
    page       = kmem_alloc( &req );

    if( page == NULL ) return ENOMEM;

    base_xp = ppm_page2base( XPTR( local_cxy , page ) );
    k_buf_base = (char *)GET_PTR( base_xp );

    // copy the array of pointers to kernel buffer
    hal_copy_from_uspace( k_pointers,
                          u_pointers,
                          CONFIG_PPM_PAGE_SIZE );

    // scan kernel array of pointers to copy the strings
    found_null = 0;
    k_buf_ptr  = k_buf_base;
    for( index = 0 ; index < 1024 ; index++ )
    {
        if( k_pointers[index] == NULL )
        {
            found_null = 1;
            break;
        }

        // compute string length
        length = hal_strlen_from_uspace( k_pointers[index] );

        // copy the user string to kernel buffer
        hal_copy_from_uspace( k_buf_ptr,
                              k_pointers[index],
                              length );

        // update k_pointer[index] entry
        k_pointers[index] = k_buf_ptr;

        // increment pointer on kernel strings buffer
        k_buf_ptr += (length + 1);
    }

    // update into exec_info structure
    if( found_null && is_args )
    {
        exec_info->args_pointers  =  k_pointers;
        exec_info->args_buf_base  =  k_buf_base;
        exec_info->args_nr        =  index;
    }
    else if( found_null && !is_args )
    {
        exec_info->envs_pointers  =  k_pointers;
        exec_info->envs_buf_base  =  k_buf_base;
        exec_info->envs_buf_free  =  k_buf_ptr;
        exec_info->envs_nr        =  index;
    }
    else
    {
        return EINVAL;
    }

    return 0;
} // end process_exec_get_strings()

/////////////////////////////////////////////////////////////////////////////////////////
// Implementation note:
// This function must be called by the main thread (thread 0 in owner cluster).
// IT build an exec_info_t structure containing all informations
// required to initialize the new process descriptor and the associated thread.
// It includes the process main() arguments, the environment variables, 
// and the pathname to the new process .elf file. 
// It calls the process_exec_get_strings() functions to copy the main() arguments and 
// the environment variables from user buffers to the exec_info_t structure, allocate
// and call the process_make_exec() function.
// As it must destroy all parent process copies, and all parent threads in all clusters,
// the process_make_exec() function must be executed in the parent owner cluster, 
// and this sys_exec() function uses a RPC to access the owner cluster if required. 
// 
// TODO : the args & envs arguments are not supported yet : both must be NULL  [AG]
/////////////////////////////////////////////////////////////////////////////////////////
int sys_exec( char  * pathname,     // .elf file pathname
              char ** args,         // process arguments
              char ** envs )        // environment variables
{
    exec_info_t   exec_info;        // structure to pass to process_make_exec()
    error_t       error;

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

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

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

    assert( (args == NULL) , __FUNCTION__ , 
    "args not supported yet\n" );

    assert( (envs == NULL) , __FUNCTION__ , 
    "args not supported yet\n" );

    // get owner cluster

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

#if CONFIG_DEBUG_SYSCALLS_ERROR
printk("\n[ERROR] in %s : pathname too long\n", __FUNCTION__ );
#endif
        this->errno = ENFILE;
        return -1;
    }

    // copy pathname in exec_info structure (kernel space)
    hal_strcpy_from_uspace( exec_info.path , pathname , CONFIG_VFS_MAX_PATH_LENGTH );

#if CONFIG_DEBUG_SYS_EXEC
uint64_t      tm_start;
uint64_t      tm_end;
tm_start = hal_get_cycles();
if( CONFIG_DEBUG_SYS_EXEC < tm_start )
printk("\n[DBG] %s : thread %x enter / process %x / path %s / cycle = %d\n",
__FUNCTION__, this, pid, exec_info.path, (uint32_t)tm_start );
#endif

    // check and store args in exec_info structure if required
    if( args != NULL )
    {
        if( process_exec_get_strings( &exec_info , true , args ) )
        {

#if CONFIG_DEBUG_SYSCALLS_ERROR
printk("\n[ERROR] in %s : cannot access args\n", __FUNCTION__ );
#endif
            this->errno = EINVAL;
            return -1;
        }
    }

    // check and store envs in exec_info structure if required
    if( envs != NULL )
    {
        if( process_exec_get_strings( &exec_info , false , envs ) )
        {

#if CONFIG_DEBUG_SYCALLS_ERROR
printk("\n[ERROR] in %s : cannot access envs\n", __FUNCTION__ );
#endif
            this->errno = EINVAL;
            return -1;
        }
    }

    // call relevant kernel function
    error = process_make_exec( &exec_info );

    if( error )
    {

#if CONFIG_DEBUG_SYSCALLS_ERROR
printk("\n[ERROR] in %s : cannot create process %x in cluster %x\n",
__FUNCTION__, pid, CXY_FROM_PID(pid) );
#endif
        this->errno = error;
        return -1;
    }

#if CONFIG_DEBUG_SYS_EXEC
tm_end = hal_get_cycles();
if( CONFIG_DEBUG_SYS_EXEC < tm_end )
printk("\n[DBG] %s : thread %x exit / process %x / cost = %d / cycle %d\n",
__FUNCTION__, this, pid, (uint32_t)(tm_end - tm_start), (uint32_t)tm_end );
#endif

    // deschedule <=> old thread suicide because the BLOCKED_GLOBAL
    // and the FLAG_REQ_DELETE have been set by process_make_exec()
    sched_yield( "old process suicide in sys_exec()" );

    assert( false , __FUNCTION__ , "This code should not be executed\n" );

    return 0;  

} // end sys_exec()

