/*
 * 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>

///////////////////////////////
int sys_exec( char  * pathname,       // .elf file pathname in user space
              char ** user_args,      // pointer on process arguments in user space
              char ** user_envs )     // pointer on 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
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;
	}

#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, cycle );
#endif

    // 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 );

    // 2. copy "arguments" pointers & strings in process exec_info if required
    if( user_args != NULL )
    {
        if( process_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,%] 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] got arguments / 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( process_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,%] 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] got envs / 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()

