/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */


/* 2.4.04 KeiT: Added Mutex Lock for process start to prevent threads to enter this multiple times */

#include "apr_arch_threadproc.h"
#include "apr_strings.h"
#include "apr_portable.h"
#include "apr_signal.h"
#include <cglob.h>

#define LOCK(i)    io_mutex_lock(i);
#define UNLOCK(i)  io_mutex_unlock(i);

extern int ap_max_child_assigned;

struct send_pipe {
	int in;
	int out;
	int err;
};

/* Memory for storing the duplicated 0, 1, 2 for ever. */
/* We can use this one for restoreing the 0, 1, 2 pathes. */
struct send_pipe sp;

int execvp(const char *prog, const char * const * argv, int orphan)
{
		u_int16 type_lang = mktypelang(MT_PROGRAM, ML_OBJECT);
		error_code rc;
		int pid;
				
		rc = os9kexec(_os_fork, 0, 3, prog, (char **)argv, _environ, 0, &pid, (void *)type_lang, (void *)orphan, (void *)0);


		if (rc)
			pid = -1;
		else
			/* Give the destination process a chance to start */
			_os_yield();

        return pid;
}

int execve(const char *prog, const char * const *argv, const char * const * env, int orphan)
{
		u_int16 type_lang = mktypelang(MT_PROGRAM, ML_OBJECT);
		error_code rc;
		int pid;
		
		rc = os9kexec(_os_fork, 0, 3, prog, (char **)argv, (char **)env, 0, &pid, (void *)type_lang, (void *)orphan, (void *)0);

		if (rc)
			pid = -1;
		else
			/* Give the destination process a chance to start */
			_os_yield();

        return pid;
}

int execv(const char *prog, const char * const * argv, int orphan)
{
	    return execvp(prog, argv, orphan);
}

APR_DECLARE(apr_status_t) apr_procattr_create(apr_procattr_t **new,
                                              apr_pool_t *pool)
{
    (*new) = (apr_procattr_t *)apr_pcalloc(pool, sizeof(apr_procattr_t));

    if ((*new) == NULL) {
        return APR_ENOMEM;
    }
    (*new)->pool = pool;
    (*new)->cmdtype = APR_PROGRAM;
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr,
                                              apr_int32_t in,
                                              apr_int32_t out,
                                              apr_int32_t err)
{
    apr_status_t status;

	LOCK(20);
    if (in != 0) {
        if ((status = apr_file_pipe_create(&attr->child_in, &attr->parent_in,
                                           attr->pool)) != APR_SUCCESS) {
			UNLOCK(20);
            return status;
        }

        switch (in) {
        case APR_FULL_BLOCK:
            break;
        case APR_PARENT_BLOCK:
            apr_file_pipe_timeout_set(attr->child_in, 0);
            break;
        case APR_CHILD_BLOCK:
            apr_file_pipe_timeout_set(attr->parent_in, 0);
            break;
        default:
            apr_file_pipe_timeout_set(attr->child_in, 0);
            apr_file_pipe_timeout_set(attr->parent_in, 0);
        }
    }

    if (out) {
        if ((status = apr_file_pipe_create(&attr->parent_out, &attr->child_out,
                                           attr->pool)) != APR_SUCCESS) {
			UNLOCK(20);
            return status;
        }

        switch (out) {
        case APR_FULL_BLOCK:
            break;
        case APR_PARENT_BLOCK:
            apr_file_pipe_timeout_set(attr->child_out, 0);
            break;
        case APR_CHILD_BLOCK:
            apr_file_pipe_timeout_set(attr->parent_out, 0);
            break;
        default:
            apr_file_pipe_timeout_set(attr->child_out, 0);
            apr_file_pipe_timeout_set(attr->parent_out, 0);
        }
    }

    if (err) {
        if ((status = apr_file_pipe_create(&attr->parent_err, &attr->child_err,
                                           attr->pool)) != APR_SUCCESS) {
			UNLOCK(20);
            return status;
        }

        switch (err) {
        case APR_FULL_BLOCK:
            break;
        case APR_PARENT_BLOCK:
            apr_file_pipe_timeout_set(attr->child_err, 0);
            break;
        case APR_CHILD_BLOCK:
            apr_file_pipe_timeout_set(attr->parent_err, 0);
            break;
        default:
            apr_file_pipe_timeout_set(attr->child_err, 0);
            apr_file_pipe_timeout_set(attr->parent_err, 0);
        }
    }

	UNLOCK(20);
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr,
                                                    apr_file_t *child_in,
                                                    apr_file_t *parent_in)
{
	LOCK(21);

    if (attr->child_in == NULL && attr->parent_in == NULL)
        apr_file_pipe_create(&attr->child_in, &attr->parent_in, attr->pool);

    if (child_in != NULL)
        apr_file_dup2(attr->child_in, child_in, attr->pool);

    if (parent_in != NULL)
        apr_file_dup2(attr->parent_in, parent_in, attr->pool);

	UNLOCK(21);
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_procattr_child_out_set(apr_procattr_t *attr,
                                                     apr_file_t *child_out,
                                                     apr_file_t *parent_out)
{
	LOCK(22);

    if (attr->child_out == NULL && attr->parent_out == NULL)
        apr_file_pipe_create(&attr->child_out, &attr->parent_out, attr->pool);

    if (child_out != NULL)
        apr_file_dup2(attr->child_out, child_out, attr->pool);

    if (parent_out != NULL)
        apr_file_dup2(attr->parent_out, parent_out, attr->pool);

	UNLOCK(22);
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_procattr_child_err_set(apr_procattr_t *attr,
                                                     apr_file_t *child_err,
                                                     apr_file_t *parent_err)
{
	LOCK(23);

    if (attr->child_err == NULL && attr->parent_err == NULL)
        apr_file_pipe_create(&attr->child_err, &attr->parent_err, attr->pool);

    if (child_err != NULL)
        apr_file_dup2(attr->child_err, child_err, attr->pool);

    if (parent_err != NULL)
        apr_file_dup2(attr->parent_err, parent_err, attr->pool);

	UNLOCK(23);
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr,
                                               const char *dir)
{
    attr->currdir = apr_pstrdup(attr->pool, dir);
    if (attr->currdir) {
        return APR_SUCCESS;
    }

    return APR_ENOMEM;
}

APR_DECLARE(apr_status_t) apr_procattr_cmdtype_set(apr_procattr_t *attr,
                                                   apr_cmdtype_e cmd)
{
    attr->cmdtype = cmd;
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_procattr_detach_set(apr_procattr_t *attr,
                                                  apr_int32_t detach)
{
    attr->detached = detach;
    return APR_SUCCESS;
}

static apr_status_t limit_proc(apr_procattr_t *attr)
{
#if APR_HAVE_STRUCT_RLIMIT && APR_HAVE_SETRLIMIT
#ifdef RLIMIT_CPU
    if (attr->limit_cpu != NULL) {
        if ((setrlimit(RLIMIT_CPU, attr->limit_cpu)) != 0) {
            return errno;
        }
    }
#endif
#ifdef RLIMIT_NPROC
    if (attr->limit_nproc != NULL) {
        if ((setrlimit(RLIMIT_NPROC, attr->limit_nproc)) != 0) {
            return errno;
        }
    }
#endif
#ifdef RLIMIT_NOFILE
    if (attr->limit_nofile != NULL) {
        if ((setrlimit(RLIMIT_NOFILE, attr->limit_nofile)) != 0) {
            return errno;
        }
    }
#endif
#if defined(RLIMIT_AS)
    if (attr->limit_mem != NULL) {
        if ((setrlimit(RLIMIT_AS, attr->limit_mem)) != 0) {
            return errno;
        }
    }
#elif defined(RLIMIT_DATA)
    if (attr->limit_mem != NULL) {
        if ((setrlimit(RLIMIT_DATA, attr->limit_mem)) != 0) {
            return errno;
        }
    }
#elif defined(RLIMIT_VMEM)
    if (attr->limit_mem != NULL) {
        if ((setrlimit(RLIMIT_VMEM, attr->limit_mem)) != 0) {
            return errno;
        }
    }
#endif
#else
    /*
     * Maybe make a note in error_log that setrlimit isn't supported??
     */

#endif
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_procattr_child_errfn_set(apr_procattr_t *attr,
                                                       apr_child_errfn_t *errfn)
{
    attr->errfn = errfn;
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_procattr_error_check_set(apr_procattr_t *attr,
                                                       apr_int32_t chk)
{
    attr->errchk = chk;
    return APR_SUCCESS;
}


APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new,
                                          const char *progname,
                                          const char * const *args,
                                          const char * const *env,
                                          apr_procattr_t *attr,
                                          apr_pool_t *pool)
{
    int i;
    const char **newargs;
    int status;
	int orphan = 0;
	char *shell;
	char shell_from_script[32];

	if (sp.in == 0)
	{
		LOCK(24);
		sp.in = dup(STDIN_FILENO);
		sp.out = dup(STDOUT_FILENO);
		sp.err = dup(STDERR_FILENO);
		UNLOCK(24);
	}

	/* Prevent from entering this section more than 1 time */
	LOCK(24);	/* Lock this region */

    new->in = attr->parent_in;
    new->err = attr->parent_err;
    new->out = attr->parent_out;

    if (attr->currdir != NULL) {
		i = strlen(attr->currdir);

		if (attr->currdir[i-1] == '/')
		{
			attr->currdir[i-1] = 0;
		}

        if (chdir(attr->currdir) == -1) {
			UNLOCK(24);	/* Unlock this region */
            return(errno);   /* We have big problems, the child should exit. */
        }
    }

	shell = getenv("SHELL");
	if (shell)
	{
		char *ptr;
		ptr = strchr(shell, ' ');
		if (ptr)
		{
			/* delete shell options (like mshell -el) */
			*ptr = 0;
		}
	} else
	{
		shell = SHELL_PATH;
	}

    if (attr->cmdtype != APR_SHELLCMD) {
		/* Try to detect the file. If it is a module or a script. */
		char data[128];
		int fd, n, shell_end;

		fd = open(progname, S_IREAD);
		if (fd >= 0)
		{
			n = read(fd, data, 128);	/* Read the first 128 byte */
			close(fd);
			if (n >= 0)
			{
				unsigned short sync;
				sync = *(unsigned short *)data;	/* Get the first 2 bytes */
				if (sync != MODSYNC)	/* Check if it is a module, which can be executed directly */
				{
					attr->cmdtype = APR_SHELLCMD;	/* This is not a Module, it must be a script. */

					/* Looks like a script. If #!/bin... is used (a UNIX script start), use our shell */
					if (strncmp(data, "#!/bin", 6) != 0)
					{
						/* We have a "#!mshell" (for example). Use this specified shell. */
						shell_end = FALSE;
						for (i = 0; (i < 31) && (shell_end == FALSE); i++)
						{
							switch(data[i+2]) {	/* Read behind the #! */
								/* Find the end of the #!... script */
								case '\012':	/* LF */
								case '\015':	/* CR */
								case ' ':		/* Space */
								case '\t':		/* Tab */
								case 0:			/* Null */
									shell_from_script[i] = 0;
									shell_end = TRUE;
									break;
								default:
									shell_from_script[i] = data[i+2];
									break;
							}
						}
						shell_from_script[i] = 0;
						shell = shell_from_script;
						attr->cmdtype = APR_SHELLCMD;
					}
				}
			}
		}
	}

    if ((status = limit_proc(attr)) != APR_SUCCESS) {
		UNLOCK(24);	/* Unlock this region */
        return status;
    }

    if (attr->child_in) {
        dup2(attr->child_in->filedes, STDIN_FILENO);
        apr_file_close(attr->child_in);
    }

    if (attr->child_out) {
		dup2(attr->child_out->filedes, STDOUT_FILENO);
        apr_file_close(attr->child_out);
    }

    if (attr->child_err) {
		dup2(attr->child_err->filedes, STDERR_FILENO);
        apr_file_close(attr->child_err);
    }

    if (attr->cmdtype == APR_SHELLCMD) {
        i = 0;
        while (args[i]) {
            i++;
        }

        newargs = (const char **)malloc(sizeof (char *) * (i + 3));
        newargs[0] = shell;

        i = 0;
        while (args[i]) {
            newargs[i + 1] = args[i];
            i++;
        }

        newargs[i + 1] = NULL;

        if (attr->detached) {
            apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
			orphan = 1;
        }

        new->pid = execve(shell, newargs, env, orphan);
		free(newargs);
    }
    else if (attr->cmdtype == APR_PROGRAM) {
        if (attr->detached) {
            apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
			orphan = 1;
        }

        new->pid = execve(progname, args, env, orphan);
    }
    else if (attr->cmdtype == APR_PROGRAM_ENV) {
        if (attr->detached) {
            apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
			orphan = 1;
        }

        new->pid = execv(progname, args, orphan);
    }
    else {
        /* APR_PROGRAM_PATH */
        if (attr->detached) {
            apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
			orphan = 1;
        }

        new->pid = execvp(progname, args, orphan);
    }

    if (attr->child_in)
	{
		dup2(sp.in, STDIN_FILENO);
	}

    if (attr->child_out)
	{
		dup2(sp.out, STDOUT_FILENO);
	}

    if (attr->child_err)
	{
		dup2(sp.err, STDERR_FILENO);
	}

	UNLOCK(24);	/* Unlock this region */

	_os_yield();

	if (new->pid == -1)
	{
		return(errno);
	}

    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_proc_wait_all_procs(apr_proc_t *proc,
                                                  int *exitcode,
                                                  apr_exit_why_e *exitwhy,
                                                  apr_wait_how_e waithow,
                                                  apr_pool_t *p)
{
    proc->pid = -1;
    return apr_proc_wait(proc, exitcode, exitwhy, waithow);
}

APR_DECLARE(apr_status_t) apr_proc_wait(apr_proc_t *proc,
                                        int *exitcode, apr_exit_why_e *exitwhy,
                                        apr_wait_how_e waithow)
{
    pid_t pstatus;
    int waitpid_options = 0;
    int exit_int;
    int ignore;
    apr_exit_why_e ignorewhy;
	apr_status_t stat;

    if (exitcode == NULL) {
        exitcode = &ignore;
    }

    if (exitwhy == NULL) {
        exitwhy = &ignorewhy;
    }

    if (waithow != APR_WAIT) {
        waitpid_options |= WNOHANG;
    }

	pstatus = waitpid(proc->pid, &exit_int, waitpid_options);
	if (((int)pstatus) > 0) {
        proc->pid = pstatus;

        if (WIFEXITED(exit_int)) {
            *exitwhy = APR_PROC_EXIT;
            *exitcode = WEXITSTATUS(exit_int);
        }
        else if (WIFSIGNALED(exit_int)) {
            *exitwhy = APR_PROC_SIGNAL;
            *exitcode = WTERMSIG(exit_int);
        }
        else {
            /* unexpected condition */
            return APR_EGENERAL;
        }
        return APR_CHILD_DONE;
    }

	/* Special Hack. OS-9 waitpid(-1 ...) is not detecting thread childs
	 * Therefore, we test every thread child we have created .
	 */
	stat = APR_CHILD_NOTDONE;

	if (((int)proc->pid) == -1)
	{
		stat = os9_thread_proc_wait(proc, exitcode, exitwhy, waithow);
	} 
	else if (pstatus == -1)
	{
		/* If we have not pid -1 and an error, return it */
		return errno;
	}

    return stat;
}

APR_DECLARE(apr_status_t) apr_procattr_limit_set(apr_procattr_t *attr,
                                                 apr_int32_t what,
                                                 struct rlimit *limit)
{
    switch(what) {
        case APR_LIMIT_CPU:
#ifdef RLIMIT_CPU
            attr->limit_cpu = limit;
            break;
#else
            return APR_ENOTIMPL;
#endif

        case APR_LIMIT_MEM:
#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)
            attr->limit_mem = limit;
            break;
#else
            return APR_ENOTIMPL;
#endif

        case APR_LIMIT_NPROC:
#ifdef RLIMIT_NPROC
            attr->limit_nproc = limit;
            break;
#else
            return APR_ENOTIMPL;
#endif

        case APR_LIMIT_NOFILE:
#ifdef RLIMIT_NOFILE
            attr->limit_nofile = limit;
            break;
#else
            return APR_ENOTIMPL;
#endif

    }

    return APR_SUCCESS;
}
