/*
 * sys_exec.c - Kernel function implementing the "exec" syscall
 * 
 * Authors    Mohamed Lamine Karaoui (2015)
 *            Alain Greiner          (2016)
 *
 * 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 <almos_config.h>
#include <hal_types.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 by the sys_exec() function to register the .elf
// pathname in the exec_info  structure, from a string stored in user space.
////////////////////////////////////////////////i////////////////////////////////////// 
// @ exec_info : pointer on the exec_info structure.
// @ pathname  : string containing the path to the .elf file (user space).
// return 0 if success / non-zero if one string too long, or too many strings. 
///////////////////////////////////////////////////////////////////////////////////////
static error_t process_exec_get_path( exec_info_t  * exec_info,
                                      char         * pathname )
{
    uint32_t length;

    // get string length
	hal_strlen_from_uspace( pathname , &length );
	if( length > 255 ) 
    {
        printk(ERROR, "%s: elf file pathname larger than 255 bytes\n", __FUNCTION__ );
        return EINVAL;
    }

    // copy string to exec_info
    hal_copy_from_uspace( &exec_info->path[0] , pathname , length );

    return 0;
}

////////////////////////////////////////////////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.
////////////////////////////////////////////////i////////////////////////////////////// 
// @ exec_info : pointer on the exec_info structure.
// @ is_args   : true if called for (args) / false if called for (envs).
// @ 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
    uint32_t     strings;     // actual number of strings
    kmem_req_t   req;         // kmem request
    page_t     * page;        // page descriptor
    uint32_t     order;       // ln2( number of pages to store strings )
    char      ** k_pointers;  // base of kernel buffer containing array of pointers
    char       * buf_ptr;     // pointer on first empty slot in kernel strings buffer
    char       * buf_base;    // base address of the kernel strings buffer

    // compute ln2( number of pages for kernel strings buffer )
    if( is_args ) order = CONFIG_PROCESS_ARGS_ORDER;
    else          order = CONFIG_PROCESS_ENVS_ORDER;

	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 )
    {
        printk("ERROR in %s : cannot allocate memory for pointers\n", __FUNCTION__ );
        return ENOMEM;
    }
    k_pointers = ppm_page2base( page );
    
    // allocate several physical pages to store the strings themselve
    req.type   = order;
    page       = kmem_alloc( &req );
    if( page == NULL )
    {
        printk("ERROR in %s : cannot allocate memory for strings\n", __FUNCTION__ );
        return ENOMEM;
    }
    buf_base    = ppm_page2base( page );
    
    // copy the array of pointers to kernel buffer 
    hal_copy_from_uspace( k_pointers, 
                          u_pointers,
                          CONFIG_PPM_PAGE_SIZE );

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

        // compute string length
	    hal_strlen_from_uspace( k_pointers[index] , &length );
 
        // copy the user string to kernel buffer
        hal_copy_from_uspace( k_strings,
                              k_pointers[index];
                              length );

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

        // increment pointer on kernel strings buffer
        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  =  buf_base;
        exec_info->args_nr        =  index;
    }
    else if( found_null && !is_args )
    {
        exec_info->envs_pointers  =  k_pointers;
        exec_info->envs_buf_base  =  buf_base;
        exec_info->envs_buf_free  =  buf_ptr;
        exec_info->envs_nr        =  index;
    }
    else 
    {
        printk("ERROR in %s : number of strings larger than 1024\n", __FUNCTION__ );
        return EINVAL;
    }

    return 0;
} // end process_exec_get_strings()

/////////////////////////////////////////////////////////////////////////////////////////
// This function is executed in a "client" cluster by a process whose PID can belong
// to another "server" cluster (defined by the MSB bits of the calling process PID).
// A new process descriptor, and the associated main thread descriptor must be created
// in the "server" cluster, using directly the process_make_exec() function if local,
// or the rpc_process_exec_client() function if server is remote.
/////////////////////////////////////////////////////////////////////////////////////////
// Implementation note:
// This function build an exec_info_t structure containing all informations
// required to create the new process descriptor and the associated thread.
// It calls the static process_exec_get_path() and process_exec_get_strings() functions
// to copy the .elf pathname, the main() arguments and the environment variables from
// user buffers to the exec_info_t, and calls the static process_make_exec() function.
/////////////////////////////////////////////////////////////////////////////////////////
int sys_exec( char  * filename,     // .elf file pathname
              char ** argv,         // process arguments
              char ** envp )        // environment variables
{
    exec_info_t  exec_info;         // structure to pass to process_make_exec()
    thread_t   * thread;            // pointer on thread created in server cluster
	error_t      error   = 0;
	process_t  * process = CURRENT_PROCESS;

    // check arguments
	if((filename == NULL) || (argv == NULL) || (envp == NULL)) 
    {
        printk("\n[ERROR] in %s : missing arguments / file = %x / argv = %x / envp = %x\n",
               __FUNCTION__ , filename , argv , envp );
        return EINVAL;
    }

    // compute client_cxy (local cluster) and server_cxy (target cluster)
    cxy_t     cxy_server = CXY_FROM_PID( process->pid );
    cxy_t     cxy_client = local_cxy;
    bool_t    is_local   = (cxy_server == cxy_client);

    exec_dmsg("\n[INFO] %s starts for process %x on core %d in cluster %x"
                 " / target_cluster = %x / cycle %d\n",
                 __FUNCTION__, process->pid , CURRENT_CORE->lid, 
                 cxy_client, cxy_server, hal_time_stamp());

    // Change process's state to prevent any concurent access TODO ??? [AG]

    // initialize exec_info structure 
    exec_info->pid      = process->pid;
    exec_info->ppid     = process->ppid;
    exec_info->fd_array = &process->fd_array;
    exec_info->vfs_root = &process->vfs_root;
    exec_info->vfs_cwd  = &process->vfs_cwd;
    exec_info->vfs_bin  = &process->vfs_bin;

	// check pathname and store it in exec_info structure
	error = process_exec_get_path( &exec_info , filename );
	if ( error ) return EINVAL;

	// check and store argv in exec_info structure
	error = process_exec_get_strings( &exec_info , true , argv );
	if( error )  return EINVAL;
	
	// check and store envp in exec_info structure
	error = process_exec_get_strings( &exec_info , false , envp );
	if( error )  return EINVAL;
	
    if( is_local )  ////////////// local exec ////////////////////////// 
    {
        exec_dmsg("\n[INFO] %s starts local exec for process %x at cycle %d\n",
                  __FUNCTION__, process->pid, hal_time_stamp());

        // call directly the local process_make_exec() function
        error = process_make_exec( &exec_info , &thread );
        if error
        {
            printk("\n[ERROR] in %s : failed in local exec for process %x\n",
                   __FUNCTION__ , process->pid );
            return EINVAL;
        }

        exec_dmsg("\n[INFO] %s completes local exec for process %x at cycle %d\n",
                  __FUNCTION__, process->pid , hal_time_stamp() );
    }
    else             ///////////// remote exec /////////////////////////
    {
        exec_dmsg("\n[INFO] %s starts remote exec for process %x at cycle %d\n",
                  __FUNCTION__, process->pid, hal_time_stamp() );

        // call the rpc_process_exec_client() function    
        rpc_process_exec_client( cxy_server , &exec_info , &thread , &error );

        if( error )
        {
            printk("\n[ERROR] in %s : failed in remote exec for process %x\n",
                   __FUNCTION__ , process->pid );
            return EINVAL;
        }

        exec_dmsg("\n[INFO] %s completes remote exec for process %x at cycle %d\n",
                  __FUNCTION__, process->pid , hal_time_stamp() );
    }

    // If no error, delete the current thread an process descriptors.
    thread_kill( CURRENT_THREAD );
    process_kill( CURRENT_PROCESS );

    return 0;
} // end sys_exec()

