
#include <inc/lib.h>
#include <inc/elf.h>

#define TMPPAGE		(BY2PG)
#define TMPPAGETOP	(TMPPAGE+BY2PG)

// Set up the initial stack page for the new child process with envid 'child',
// using the arguments array pointed to by 'argv',
// which is a null-terminated array of pointers to null-terminated strings.
//
// On success, returns 0 and sets *init_esp
// to the initial stack pointer with which the child should start.
// Returns < 0 on failure.
//
static int
init_stack(u_int child, char **argv, u_long *init_esp)
{
	int argc, i, r, tot;
	char *strings;
	u_int *args;
        u_long offset = USTACKTOP - TMPPAGETOP;

//        printf("offset=%08lx\n", offset);
        
	// Count the number of arguments (argc)
	// and the total amount of space needed for strings (tot)
	tot = 0;
	for (argc=0; argv[argc]; argc++)
		tot += strlen(argv[argc])+1;

	// Make sure everything will fit in the initial stack page
	if (ROUND(tot, 4)+4*(argc+3) > BY2PG)
		return -E_NO_MEM;

	// Determine where to place the strings and the args array
	strings = (char*)TMPPAGETOP - tot;
	args = (u_int*)(TMPPAGETOP - ROUND(tot, 4) - 4*(argc+1));

	if ((r = sys_mem_alloc(0, TMPPAGE, PTE_P|PTE_U|PTE_W)) < 0)
		return r;

//        printf("strings=%08lx, args=%08lx\n", strings, args);

        
        char *strst = strings;
        
        for (i = 0; i < argc; i++) {
	//	- copy the argument strings into the stack page at 'strings'
	//
	//	- initialize args[0..argc-1] to be pointers to these strings
	//	  that will be valid addresses for the child environment
	//	  (for whom this page will be at USTACKTOP-BY2PG!).
                strcpy(strst, argv[i]);
                args[i] = (u_long)strst + offset;
                strst += strlen(argv[i]) + 1;
        }

        //	- set args[argc] to 0 to null-terminate the args array.
	//
	//	- push two more words onto the child's stack below 'args',
	//	  containing the argc and argv parameters to be passed
	//	  to the child's umain() function.
        args[argc] = 0;
        args[-1] = (u_long)args + offset;
        args[-2] = argc;

        
	//	- set *init_esp to the initial stack pointer for the child
        *init_esp = (u_long) &args[-2] + offset; 
        
	if ((r = sys_mem_map(0, TMPPAGE, child, USTACKTOP-BY2PG, PTE_P|PTE_U|PTE_W)) < 0)
		goto error;
	if ((r = sys_mem_unmap(0, TMPPAGE)) < 0)
		goto error;

	return 0;

error:
	sys_mem_unmap(0, TMPPAGE);
	return r;
}

// Helper function that builds a child process but doesn't set it
// running. There's got to be a better name for this.
static int
quasispawn(char *prog, char **argv)
{
        int r;
        
	//   - Open the program file and read the elf header (see inc/elf.h).
        int fd = open(prog, O_RDONLY);
        if (fd < 0)
                return fd;

        struct Elf elf;
        if ((r = read(fd, &elf, sizeof(struct Elf))) < 0)
                return r;

	//   - Use sys_env_alloc() to create a new environment.
        int childid = sys_env_alloc();
        if (childid <= 0)
                return childid;

	//   - Call the init_stack() function above to set up
	//     the initial stack page for the child environment.
        u_int init_esp;
        
        if ((r = init_stack(childid, argv, &init_esp)) != 0)
                return r;
        
	//   - Map all of the program's segments that are of p_type
	//     ELF_PROG_LOAD into the new environment's address space.
	//     Use the p_flags field in the Proghdr for each segment
	//     to determine how to map the segment:

        // I do not believe your lies.
        struct Proghdr ph;
        int i;
        
        for (i = 0; i < elf.e_phnum; i++) {
//                printf("reading elf prog header %ld\n", i);
                if ((r = seek(fd, elf.e_phoff + i * sizeof(struct Proghdr)))
                    < 0)
                        return r;
                if ((r = read(fd, &ph, sizeof(struct Proghdr))) < 0)
                        return r;

                if (ph.p_type != ELF_PROG_LOAD)
                        continue;
                

//                if (ph.p_flags & ELF_PROG_FLAG_WRITE) {
//                printf("p_va=%08lx, p_memsz=%08lx, p_filesz=%08lx, p_offset=%08lx\n", ph.p_va, ph.p_memsz, ph.p_filesz, ph.p_offset);
                
                u_int va;
                for (va = ROUNDDOWN(ph.p_va, BY2PG);
                     va < ROUND(ph.p_va + ph.p_memsz, BY2PG);va += BY2PG) {
                        if ((r = sys_mem_alloc(0, TMPPAGE, PTE_P|PTE_U|PTE_W))
                            < 0)
                                return r;
                        memset(TMPPAGE, 0, BY2PG);
                        
//                        printf("va=%08lx\n", va);
                        
                        void *buf = (void *) TMPPAGE;
                        
                        if (va < ph.p_va) {
                                // First partial page, start partway
                                // into the buf
                                buf += ph.p_va - va;
                        }

//                        printf("buf=%08lx\n", buf);

                        if (va < ph.p_va + ph.p_filesz) {
                                u_long pos;
                                
                                if (va < ph.p_va)
                                        pos = ph.p_offset;
                                else
                                        pos = va - ph.p_va + ph.p_offset;
                                
                                u_long len =  MIN((u_long) TMPPAGETOP - (u_long) buf,
                                                  ph.p_va + ph.p_filesz - va);

//                                printf("pos=%08lx, len=%08lx\n", pos, len);
                                
                                if ((r = seek(fd, pos)) < 0)
                                        return r;
                                
                                if ((r = read(fd, buf, len)) < 0)
                                        return r;
                        } else {
//                                printf("zeros only\n");
                        }
                        

                        if ((r = sys_mem_map(0, TMPPAGE, childid, va,
                                             PTE_P | PTE_U |
                                             ((ph.p_flags & ELF_PROG_FLAG_WRITE) ? PTE_W : 0)))
                            < 0)
                                return r;

                        if ((r = sys_mem_unmap(0, TMPPAGE)) < 0)
                                return r;
                }
        }

        
	//   - Use the new sys_set_trapframe() call to set up the
	//     correct initial eip and esp register values in the child.
	//     You can use envs[ENVX(child)].env_tf as a template trapframe
	//     in order to get the initial segment registers and such.
        struct Trapframe tf = envs[ENVX(childid)].env_tf;
        tf.tf_eip = elf.e_entry;
        tf.tf_esp = init_esp;
        if ((r = sys_set_trapframe(childid, &tf)) != 0)
                return r;

        return childid;
}

// Spawn a child process from a program image loaded from the file system.
// prog: the pathname of the program to run.
// argv: pointer to null-terminated array of pointers to strings,
// 	 which will be passed to the child as its command-line arguments.
// Returns child envid on success, < 0 on failure.
int
spawn(char *prog, char **argv)
{
        int r;
        if ((r = quasispawn(prog, argv)) <= 0)
                return r;

        int childid = r;
	//   - Start the child process running with sys_set_status().
	if ((r = sys_set_status(childid, ENV_RUNNABLE)) != 0)
                return r;

        return childid;
}

        

// Spawn, taking command-line arguments array directly on the stack.
int
spawnl(char *prog, char *args, ...)
{
	return spawn(prog, &args);
}


// Transform the current environment into a program image loaded from
// the file system.  prog: the pathname of the program to run.  argv:
// pointer to null-terminated array of pointers to strings, which will
// be passed to the child as its command-line arguments.
// Does not return on success, return  < 0 on failure.
int
exec(char *prog, char **argv)
{
        int r;
        if ((r = quasispawn(prog, argv)) <= 0)
                return r;

        int childid = r;
        
	// Eat the child!
	if ((r = sys_env_eat_child(childid)) != 0)
                return r;

        // This point is never reached.
        return -1;
}

        

// Exec, taking command-line arguments array directly on the stack.
int
execl(char *prog, char *args, ...)
{
	return exec(prog, &args);
}

