/*
 * kern/do_exec.c - excecutes a new user process, load init.
 *
 * Copyright (c) 2008,2009,2010,2011,2012 Ghassan Almaless
 * Copyright (c) 2011,2012,2013,2014,2015 UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-kernel.
 *
 * ALMOS-kernel 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-kernel 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-kernel; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdarg.h>
#include <config.h>
#include <errno.h>
#include <types.h>
#include <libk.h>
#include <bits.h>
#include <kmem.h>
#include <process.h>
#include <cpu.h>
#include <pmm.h>
#include <page.h>
#include <process.h>
#include <thread.h>
#include <pid.h>
#include <list.h>
#include <vfs.h>
#include <sys-vfs.h>
#include <scheduler.h>
#include <spinlock.h>
#include <cluster.h>
#include <ku_transfert.h>
#include <dqdt.h>

#define USR_LIMIT  (CONFIG_USR_LIMIT)

#define DEV_STDIN   CONFIG_DEV_STDIN
#define DEV_STDOUT  CONFIG_DEV_STDOUT
#define DEV_STDERR  CONFIG_DEV_STDERR

#define TASK_DEFAULT_HEAP_SIZE  CONFIG_TASK_HEAP_MIN_SIZE

#define INIT_PATH   "/bin/init"

/* FIXME : ecopy et estrlen attendent une adresse de vecteur dans l'espace utilisateur. Or pour
 * l'instant l'environnement des processus est "forgé" dans do_exec(), c'est donc des buffers
 * noyaux. Solution temporaire : on utilise kexec_copy et kexec_strlen.
 */
//////////////////////////////////////
error_t args_len( char        ** vect, 
                  uint32_t       pages_max, 
		          uint32_t     * pages_nr,
                  uint32_t     * entries_nr,
		          exec_copy_t    ecopy, 
                  exec_strlen_t  estrlen)
{
	uint32_t     cntr;
	uint32_t     pgnr;
	error_t      error;
	uint32_t     count;
	uint32_t     len;
    char       * ptr;
  
	cntr  = 0;
	count = 0;
	pgnr  = 0;

	while(1)
	{
		if((error = ecopy(&ptr, &vect[cntr], sizeof(ptr))))
		{
			printk(INFO, "INFO: %s:%s: EFAULT Has been Catched on vect, cntr %d, strlen &vect[%x] = %x\n",  \
                                        __FUNCTION__, __LINE__, cntr, &vect[cntr], vect[cntr]);
			return err;
		}

		if(ptr == NULL)
                        break;

		if((err=estrlen(ptr, &len)))
		{
                        printk(INFO, "INFO: %s:%s: EFAULT Has been Catched, cntr %d, strlen &vect[%x] = %x\n",          \
                                        __FUNCTION__, __LINE__, cntr, &vect[cntr], vect[cntr]);
			return err;
		}
		cntr  ++;
		len   ++;
		count += (ARROUND_UP(len, 8));
		pgnr   = ARROUND_UP(count, PMM_PAGE_SIZE);
    
		if((pgnr >> PMM_PAGE_SHIFT) >= pages_max)
			return E2BIG;
	}

	count       = (cntr + 1) * sizeof(char*);
	*pages_nr   = (ARROUND_UP(count, PMM_PAGE_SIZE)) / PMM_PAGE_SIZE;
	*entries_nr = cntr;
	return 0;
}

//for now the pointers can be either be from uspace
//or kernel space. Solutions :
//first keep this binaroty and add a flag to
//choose how to copy the arguments
//second make them all user space ptr
//third make them kernel space ptr
//the first solution (although ugly), is the most
//perfroming(compared to u->k) and simple 
//(compared to k->u)

//////////////////////////////////////////////
//////////////////////////////////////////////
error_t compute_args( process_t     * process, 
		              char         ** vect,
		              page_t       ** pgtbl,
		              uint32_t        pages_max, 
		              uint32_t        start,
		              uint32_t      * current, 
		              uint32_t      * pgnr,
		              uint32_t      * pgindex,
		              exec_copy_t     ecopy, 
		              exec_strlen_t   estrlen)
{
	kmem_req_t req;
	uint32_t   count;
	uint32_t   cntr;
	uint32_t   pages_nr;
	char    ** args;
	char     * ptr;
	uint32_t   i;
	uint32_t   len;
	uint32_t   index;
	uint32_t   paddr;
	error_t    error;

	req.type  = KMEM_PAGE;
	req.size  = 0;
	req.flags = AF_USER | AF_ZERO | AF_REMOTE;
	req.ptr   = process->cluster;

	pages_nr  = 0;
	count     = 0;
	cntr      = 0;
	index     = *pgindex;

	error = args_len(vect, pages_max, &pages_nr, &cntr, ecopy, estrlen);
  
	if( error ) return error;

	*pgnr += pages_nr;

	for(i = index; i < (pages_nr + index); i++)
	{
		pgtbl[i] = kmem_alloc(&req);
    
		if(pgtbl[i] == NULL) return ENOMEM;
	}
  
	*current = start - (pages_nr * PMM_PAGE_SIZE);
 
	paddr = (uint32_t) ppm_page2addr(pgtbl[index]);
	count = (cntr + 1) * sizeof(char*);
	args = (char **) paddr;
	pages_nr = 0;

	/* Copy the table of pointer */
	while(count > PMM_PAGE_SIZE)
	{
		error = ecopy((void*)paddr, vect + (pages_nr << PMM_PAGE_SHIFT), PMM_PAGE_SIZE);

		if( error ) return error;
   
		index ++;
		paddr  = (uint32_t) ppm_page2addr(pgtbl[index]);
		count -= PMM_PAGE_SIZE;
		pages_nr ++;
	}

	if(count > 0)
	{
		error = ecopy((void*)paddr, vect + (pages_nr << PMM_PAGE_SHIFT), count);
		if( error ) return error;
	}

	paddr += count;
 
	/* Copy the the table of pointer content */
	for(i=0; i < cntr; i++)
	{
		if((error = ecopy(&ptr, &vect[i], sizeof(ptr))))
		{
			printk(INFO, "INFO: %s: EFAULT Has been Catched on vect\n", __FUNCTION__);
			return err;
		}

		error = estrlen(ptr, &len);
		if(err) return err;
		len++;


		/* Update pointer */
		args[i] = (char*)(*current + (pages_nr << PMM_PAGE_SHIFT) + count);
    
		/* The content could span multiple pages */
		while(len)
		{
			if((PMM_PAGE_SIZE - count) > (ARROUND_UP(len, 8)))
			{
				error = ecopy((void*)paddr, ptr, len);
				if(err) return err;
				paddr += (ARROUND_UP(len, 8));
				count += (ARROUND_UP(len, 8));
				len = 0;
			}
			else
			{
				error = ecopy((void*)paddr, ptr, PMM_PAGE_SIZE - count);
				if(err) return err;
				ptr += (PMM_PAGE_SIZE - count);
				len -= (PMM_PAGE_SIZE - count);
				index ++;
				paddr = (uint32_t) ppm_page2addr(pgtbl[index]);
				pages_nr ++;
				count = 0;
			}
		}
	}

	*pgindex = index + 1;  
	return 0;
}

///////////////////////////////////////
error_t map_args( process_s  * process,
		          page_t    ** pgtbl, 
		          uint32_t     start_index, 
		          uint32_t     end_index,
		          uint32_t     start_addr )
{
	struct pmm_s *pmm;
	pmm_page_info_t info;
	uint32_t current_vma;
	error_t err;
	uint32_t i;

	pmm = &process->vmm.pmm;
	info.attr = PMM_PRESENT | PMM_READ | PMM_WRITE | PMM_CACHED | PMM_USER; 
	info.cluster = process->cluster;
	current_vma = start_addr;

	for(i = start_index; i < end_index; i++)
	{    
		info.ppn = ppm_page2ppn(pgtbl[i]);
    
		if((error = pmm_set_page(pmm, current_vma, &info)))
			return err;

		pgtbl[i] = NULL;
		current_vma += PMM_PAGE_SIZE;
	}
  
	return 0;
}

//////////////////////////////////////
error_t do_exec( process_t  * process,
 	             char       * pathname,
		         page_t     * pgtbl[],
		         uint32_t     env_end,
		         uint32_t     env_index,
		         uint32_t     argv_end,    // user_stack_top
		         uint32_t     argv_index,
                 thread_t  ** new )
{
 	error_t        error;
 	pthread_attr_t attr;
 	uint32_t       usr_stack_top;
 	int32_t        order;
 	thread_t     * main_thread;
 
	attr.arg2     = (void*)env_end;	/* environ */
	attr.arg1     = (void*)argv_end;	/* argv */
	usr_stack_top = argv_end;
 
    /* FIXME:
     * zero should not be hard-coded. Use something like MAIN_KERNEL which represents the
     * kernel with the init and sh processes (assuming that both of them are on the same kernel).
     */

	if( ((process->pid != PID_MIN_GLOBAL+1) && (current_cid == 0)) ||
                ( (process->pid != PID_MIN_GLOBAL) && (current_cid != 0) ) )
 	{
 		vmm_destroy(&process->vmm);
 		pmm_release(&process->vmm.pmm);    
		error = vmm_init(&process->vmm);
		if(err) goto DO_EXEC_ERR;
	}
 
	attr.stack_addr = (void*)(usr_stack_top - CONFIG_PTHREAD_STACK_SIZE);
	attr.stack_size = CONFIG_PTHREAD_STACK_SIZE;

    // create stack vseg
	error = (error_t) vmm_mmap( process,
				                attr.stack_addr,                            // base
				                USR_LIMIT - (uint32_t)attr.stack_addr,      // size
                                VSEG_TYPE_STACK,                            // type
				                VSEG_RD | VSEG_WR,                          // flags
//				                PRIVATE | ANON | STACK | FIXED,  
                                NULL,                                       // file
                                0 );                                        // offset 
  
	if( error == VM_FAILURE )
	{
		error = CURRENT_THREAD->info.errno;
		printk(INFO, "%s: failed to mmap main's stack for process %x / error = %d\n", 
		       __FUNCTION__, process->pid , error );
        return error;
	}

    // TODO ??? AG        
	error = map_args(process, pgtbl, 0, env_index, (uint32_t)attr.arg2);
 	if( error )
    {
        return error;
    }
 
    // TODO ??? AG        
	error = map_args(process, pgtbl, env_index, argv_index, (uint32_t)attr.arg1);
 	if(err)
    {
        return error;
    }

    // register "entry_point" in VMM, register "code" and "data" vsegs in VMM,
    // using informations contained in the elf file identified by the pathname.
	error = elf_load_process( path_name , process );
	if( error )
	{
		printk(INFO, "%s: failed to access elf for process %x\n", 
		       __FUNCTION__, process->pid );
        return error;
	}

    // create "heap" vseg
	error = (error_t) vmm_mmap( process,
				                (void *)process->vmm.heap_start,            // base
				                PROCESS_DEFAULT_HEAP_SIZE,                  // size
                                VSEG_TYPE_HEAP,                             // type
                                VSEG_RD | VSEG_WR,                          // flags
//				                PRIVATE | ANON | HEAP | FIXED, 
                                NULL,                                       // file
                                0 );                                        // offset

	if( error == VM_FAILURE )
	{
		error = CURRENT_THREAD->info.errno;
		printk(INFO, "%s: failed to mmap heap for process %x\n", 
		       __FUNCTION__, process->pid );
		return error;
	}

	process->vmm.heap_current += TASK_DEFAULT_HEAP_SIZE;

    // initialize pthread attributes
    attr.pid          = exec_info->pid;
    attr.entry_func   = (void*)process->vmm.entry_point;
    attr.entry_args   = bloup;               // TODO [AG] 
    attr.flags        = PT_FLAG_DETACH; 
    attr.stack_size   = CONFIG_PTHREAD_STACK_SIZE;
	attr.sched_policy = SCHED_RR;
    attr.cxy          = LOCAL_CLUSTER;
    attr.lid          = bloup;               // TODO [AG]  

//	attr.flags = (CURRENT_THREAD->info.attr.flags | PT_ATTR_DETACH);
//	attr.sched_policy = SCHED_RR;
//	attr.cid = process->cluster->id;
//	attr.cpu_lid = process->cpu->lid;
//	attr.cpu_gid = process->cpu->gid;
//	attr.entry_func = (void*)process->vmm.entry_point;
//	attr.exit_func = NULL;
//	attr.stack_size = attr.stack_size - SIG_DEFAULT_STACK_SIZE;
//	attr.sigstack_addr = (void*)((uint32_t)attr.stack_addr + attr.stack_size);
//	attr.sigstack_size = SIG_DEFAULT_STACK_SIZE;
//	attr.sigreturn_func = 0;

    // create and initialise thread descriptor,
    // register the thread in local process descriptor
	error = thread_user_create( &attr , &main_thread );
	if( error )
	{
		printk(INFO, "%s: failed to create main thread for process %x\n"
		       __FUNCTION__, process->pid );
        return error;
	}
  
	return 0;
}  // end do_exec()

//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////
error_t prepare_args( process_t   * process,
			          char       ** argv,
                      char       ** envp,
                      page_t      * pgtbl[],
    	              uint32_t    * argv_index,
                      uint32_t    * env_index,
                      uint32_t    * argv_end,
                      uint32_t    * env_end,
                      exec_copy_t   ecopy,
                      exec_strlen_t estrlen )
{
	uint32_t usr_stack_top = USR_LIMIT;
	uint32_t pages_nr      = 0;
	uint32_t pages_max     = CONFIG_TASK_ARGS_PAGES_MAX_NR;
	uint32_t index         = 0;
	error_t  error         = 0;

	memset( &pgtbl[0] , 0 , pages_max*sizeof(page_t *) );

    // env_index & env_end
	error = compute_args( process,
			              envp,
			              &pgtbl[0],
			              pages_max,
			              USR_LIMIT,
			              &usr_stack_top,
			              &pages_nr,
			              &index,
			              ecopy,
			              estrlen );

	*env_index = index;
	*env_end   = usr_stack_top;  	/* environ */

	if( error ) return error;

    // argv_index & argv_end
	error = compute_args( process,
			              argv,
			              &pgtbl[0],
			              pages_max - pages_nr,
			              usr_stack_top,
			              &usr_stack_top,
			              &pages_nr,
			              &index,
			              ecopy,
			              estrlen );

	*argv_index = index;
	*argv_end   = usr_stack_top;	/* argv */

	return error;
}

//////////////////////////////////
void free_args( page_t * pgtbl[] )
{
	uint32_t index;

	for(index = 0; index < CONFIG_TASK_ARGS_PAGES_MAX_NR; index ++)
	{
		if(pgtbl[index] != NULL)
			ppm_free_pages(pgtbl[index]);
	}
}

/////////////////////////////////////////////
error_t full_do_exec( process_t    * process,
		              char         * path_name,
		              char        ** argv,
		              char        ** envp,
		              uint32_t     * isFatal,
		              thread_t    ** new,
		              exec_copy_t    ecopy,
		              exec_strlen_t  estrlen )
{
	error_t    err;
	uint32_t   argv_index;
	uint32_t   env_index;
	uint32_t   argv_end;
	uint32_t   env_end;
    page_t   * pgtbl[CONFIG_TASK_ARGS_PAGES_MAX_NR];

 	*isFatal = 0;
	error = prepare_args( process, 
                        argv, 
                        envp,
			            pgtbl, 
                        &argv_index,
			            &env_index,
			            &argv_end,
			            &env_end,
			            ecopy,
			            estrlen );

	if(err)
		goto EXEC_EXIT;

	error = do_exec( process, 
                   path_name, 
                   &pgtbl[0],  
                   env_end, 
			       env_index,
                   argv_end, 
                   argv_index,
                   new );

	if(err)
 	{
		printk(INFO, "%s: do_exec failed",  __FUNCTION__);
		*isFatal = 1;
	}

EXEC_EXIT:
	if(err)
	{
		free_args(pgtbl);
 	}
 
 	return err;
}


///////////////////////////////////
error_t kexec_copy( void     * dst,
                    void     * src,
                    uint32_t   count )
{
	memcpy( dst , src , count );
	return 0;
}

/////////////////////////////////////
error_t kexec_strlen( char     * dst,
                      uint32_t * count)
{
	*count = strlen( dst );
	return 0;
}

////////////////////////////////////////////////
error_t process_load_init( process_t * process )
{
	process_t   * init;
	dqdt_attr_t   attr;
	thread_t    * main_thread;

	struct vfs_file_s stdin;
	struct vfs_file_s stdout;
	struct vfs_file_s stderr;
	struct ku_obj ku_path;

	error_t err;
	error_t err1;
	error_t err2;
	uint32_t isFatal;
	char *environ[] = {"ALMOS_VERSION="CONFIG_ALMOS_VERSION, NULL};
	char *argv[]    = {INIT_PATH, "init", NULL};

	printk(INFO, "INFO: Loading Init Process [ %s ]\n", INIT_PATH);

	error = dqdt_process_placement(dqdt_root, &attr);
        /* Force init to be on current cluster */
        attr.cid_exec = current_cid;

	assert(error == 0);

	if((error = process_create(&init, &attr, CPU_USR_MODE)))
		return err;

	error = vmm_init(&init->vmm);

	if(err) goto INIT_ERR;

	error = pmm_init(&init->vmm.pmm, current_cluster);

	if(err) goto INIT_ERR;
  
	error = pmm_dup(&init->vmm.pmm, &process->vmm.pmm);
  
	if(err) goto INIT_ERR;
  
	vfs_file_up(&process->vfs_root);
	init->vfs_root = process->vfs_root;

	vfs_file_up(&process->vfs_root);
	init->vfs_cwd  = process->vfs_root;

	init->vmm.limit_addr = CONFIG_USR_LIMIT;
	init->uid = 0;
	init->parent = process->pid;
	atomic_init(&init->childs_nr, 0);
	atomic_init(&process->childs_nr, 1);
  
	list_add_last(&process->children, &init->list);

	KK_BUFF(ku_path, ((void*) DEV_STDIN));
	error  = vfs_open(&init->vfs_cwd, &ku_path,  VFS_O_RDONLY, 0, &stdin);
	KK_BUFF(ku_path, ((void*) DEV_STDOUT));
	err1 = vfs_open(&init->vfs_cwd, &ku_path, VFS_O_WRONLY, 0, &stdout);
	KK_BUFF(ku_path, ((void*) DEV_STDERR));
	err2 = vfs_open(&init->vfs_cwd, &ku_path, VFS_O_WRONLY, 0, &stderr);

	if(error || err1 || err2)
	{
		if(!err)  vfs_close(&stdin,  NULL);
		if(!err1) vfs_close(&stdout, NULL);
		if(!err2) vfs_close(&stderr, NULL);
    
		printk(ERROR,"ERROR: do_exec: cannot open fds [%d, %d, %d]\n", err, err1, err2);
		error = ENOMEM;
    
		goto INIT_ERR;
	}

	process_fd_set(init, 0, &stdin);
	process_fd_set(init, 1, &stdout);
	process_fd_set(init, 2, &stderr);

	CURRENT_THREAD->info.attr.flags = PT_ATTR_AUTO_NXTT | PT_ATTR_MEM_PRIO | PT_ATTR_AUTO_MGRT;

	error = full_do_exec(init, INIT_PATH, &argv[0], &environ[0], &isFatal, 
				&main_thread, kexec_copy, kexec_strlen);
  
	if(error == 0)
	{
		assert(main_thread != NULL && (main_thread->signature == THREAD_ID));
		init->state = TASK_READY;
		error = sched_register(main_thread);
		assert(error == 0); 		/* FIXME: ask DQDT for another core */

#if CONFIG_ENABLE_TASK_TRACE
		main_thread->info.isTraced = true;
#endif
		sched_add_created(main_thread);

                printk(INFO, "INFO: Init Process Loaded [ %s ]\n", INIT_PATH);

		return 0;
	}

INIT_ERR:
	process_destroy(init);
	return err;
}
