#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>

#include "toba.h"
#include "runtime.h"
#include "java_lang_UNIXProcess.h"
#include "java_io_FileDescriptor.h"

extern char **environ;

/* create a Process object appropriate for UNIX Systems */
struct in_java_lang_Process *
create_process(Object args, Object env)
{
    struct in_java_lang_Process *process;

    process = (struct in_java_lang_Process *)
                 new(&cl_java_lang_UNIXProcess.C);

    /* UNIXProcess.init(String[],String[]) */
    init_aSaS_ag12q(process, args, env);

    return process;
}

/* java/lang/UNIXProcess exec ([Ljava/lang/String;[Ljava/lang/String;)V */
Void exec_aSaS_VUMLz(Object Harg1, Object Harg2, Object Harg3)
{
    struct in_java_lang_UNIXProcess *this = 
        (struct in_java_lang_UNIXProcess *)Harg1;
    struct aarray *args = (struct aarray *)Harg2;
    struct aarray *env = (struct aarray *)Harg3;
    struct in_java_io_FileDescriptor *syncfd, *infd, *outfd, *errfd;
    struct in_java_lang_String *str;
    char **envp, **argv;
    int i, j;
    char c;

    /* if we get an error we bail, we've already forked */
    syncfd = this->sync_fd;
    infd = this->stdin_fd;
    outfd = this->stdout_fd;
    errfd = this->stderr_fd;
    if(!syncfd || !infd || !outfd || !errfd || !args) {
        /* this should never happen */
        fatal("internal error - bad args to UNIXProcess.exec");
    }

    argv = (char **)allocate((args->length + 1) * sizeof(char *));
    for(i = 0; i < args->length; i++) {
        str = (struct in_java_lang_String *)args->data[i];
        /* turn null string references into empty strings */
        argv[i] = (str) ? cstring(str) : "";
    }
    argv[i] = 0;

    if(env) {
        envp = (char **)allocate((env->length + 1) * sizeof(char *));
        j = 0;
        for(i = 0; i < env->length; i++) {
            str = (struct in_java_lang_String *)env->data[i];
            /* skip null references */
            if(!str)
                continue;
            envp[j++] = cstring(str);
        }
        envp[j] = 0;
    } else {
        envp = environ;
    }
    
    /* wait for the go ahead */
    if(read(unix_fd(syncfd->fd), &c, 1) != 1) {
        /* this should never happen */
        fatal("UNIXProcess.exec error reading sync");
    }

    close(unix_fd(syncfd->fd));

    /* we assume the pipe fds are all greater than 2 */
    dup2(unix_fd(infd->fd), 0);
    close(unix_fd(infd->fd));
    dup2(unix_fd(outfd->fd), 1);
    close(unix_fd(outfd->fd));
    dup2(unix_fd(errfd->fd), 2);
    close(unix_fd(errfd->fd));

    /* we could be nice and close fds > 2 here */

    execve(argv[0], argv, envp);	/* should not return */

    /* send error information on stderr */
    /* (we can't throw an exception; we already forked) */
    perror(argv[0]);
    exit(1);
}

/* java/lang/UNIXProcess fork ()I */
Int fork__Nxh7c(Object Harg1)
{
    struct in_java_lang_UNIXProcess *this = 
        (struct in_java_lang_UNIXProcess *)Harg1;
    struct in_java_io_FileDescriptor *syncfd, *infd, *outfd, *errfd;
    int inpipe[2], outpipe[2], errpipe[2], syncpipe[2];
    int pid, old_errno;
    char *mesg;
    struct mythread *mt;
    Object h = cl_java_lang_UNIXProcess.V.subprocs;
    int monitor_held;

    syncfd = (struct in_java_io_FileDescriptor *)this->sync_fd;
    infd = (struct in_java_io_FileDescriptor *)this->stdin_fd;
    outfd = (struct in_java_io_FileDescriptor *)this->stdout_fd;
    errfd = (struct in_java_io_FileDescriptor *)this->stderr_fd;
    if(!syncfd || !infd || !outfd || !errfd)
        throwNullPointerException("fork");

    /* open pipes then do a fork */
    inpipe[0] = -1; inpipe[1] = -1;
    outpipe[0] = -1; outpipe[1] = -1;
    errpipe[0] = -1; errpipe[1] = -1;
    syncpipe[0] = -1; syncpipe[1] = -1;
    if(pipe(inpipe) == -1 || pipe(outpipe) == -1 ||
       pipe(errpipe) == -1 || pipe(syncpipe) == -1) {
        mesg = "pipe";
        goto err;
    }

    pid = fork();
    switch(pid) {
    case -1:
        mesg = "fork";
        goto err;
    case 0: /* child */
        infd->fd = java_fd(inpipe[0]);		/* our input */
        close(inpipe[1]);
        outfd->fd = java_fd(outpipe[1]);	/* out output */
        close(outpipe[0]);
        errfd->fd = java_fd(errpipe[1]);	/* our error output */
        close(errpipe[0]);
        syncfd->fd = java_fd(syncpipe[0]);	/* synchr input */
        close(syncpipe[1]);
        break;
    default: /* parent */
        infd->fd = java_fd(inpipe[1]);		/* write -> childs input */
        close(inpipe[0]);
        outfd->fd = java_fd(outpipe[0]);	/* read <- childs output */
        close(outpipe[1]);
        errfd->fd = java_fd(errpipe[0]);	/* read <- childs err */
        close(errpipe[1]);
        syncfd->fd = java_fd(syncpipe[1]);	/* write for synchr */
        close(syncpipe[0]);
	
	/* signal on the hashtable to wake up a reaper thread 
	 * if necessary */
	h = cl_java_lang_UNIXProcess.V.subprocs;
	mt = mythread();
	monitorenter(h, mt, 1, &monitor_held);
	monitornotify(h);
	monitorexit(h, mt, 0, &monitor_held);
	break;
    }
    return pid;

err:
    old_errno = errno;
    close(inpipe[0]);
    close(inpipe[1]);
    close(outpipe[0]);
    close(outpipe[1]);
    close(errpipe[0]);
    close(errpipe[1]);
    close(syncpipe[0]);
    close(syncpipe[1]);
    throwIOException(mesg, old_errno);
    /*NOTREACHED*/
}

/* java/lang/UNIXProcess waitForUNIXProcess ()I */
Int waitForUNIXProcess__AqjJ1(Object Harg1)
/*ARGSUSED*/
{
    /* this isn't called from any (non-native) methods */
    unimpl("java/lang/UNIXProcess/waitForUNIXProcess");
    return 0; /*NOTREACHED*/
}

/* java/lang/UNIXProcess destroy ()V */
Void destroy__WbpE3(Object Harg1)
{
    struct in_java_lang_UNIXProcess *this = 
        (struct in_java_lang_UNIXProcess *)Harg1;

    /* we just send the kill - the reaper will clean it up */
    if(this->isalive)
        kill(this->pid, SIGTERM);
}

