/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/*
 * exec.c --
 *
 *	Routines for creating and waiting for child processes.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/exec.c,v 1.21 2005/08/23 21:41:36 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

#ifdef _WIN32
#include <process.h>
static char   **Set2Argv(Ns_DString *dsPtr, Ns_Set *set);
static int  	WaitForProcess(int pid, int *statusPtr);
#else
#define ERR_DUP         (-1)
#define ERR_CHDIR	(-2)
#define ERR_EXEC	(-3)

static int ExecProc(char *exec, char *dir, int fdin, int fdout,
		    char **argv, char **envp);
#endif /* _WIN32 */


/*
 *----------------------------------------------------------------------
 * Ns_ExecProcess --
 *
 *	Execute a command in a child process.
 *
 * Results:
 *      Return process id of child process exec'ing the command or
 *	-1 on failure.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ExecProcess(char *exec, char *dir, int fdin, int fdout, char *args,
	       Ns_Set *env)
{
    return Ns_ExecArgblk(exec, dir, fdin, fdout, args, env);
}


/*
 *----------------------------------------------------------------------
 * Ns_ExecProc --
 *
 *      Execute a command in a child process.
 *
 * Results:
 *      Return process id of child process exec'ing the command or
 *	-1 on failure.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ExecProc(char *exec, char **argv)
{
    return Ns_ExecArgv(exec, NULL, 0, 1, argv, NULL);
}


/*
 *----------------------------------------------------------------------
 * Ns_WaitProcess --
 *
 *      Wait for child process
 *
 * Results:
 *      Ruturn NS_OK for success and NS_ERROR for failure.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_WaitProcess(int pid)
{
    return Ns_WaitForProcess(pid, NULL);
}


/*
 *----------------------------------------------------------------------
 * Ns_WaitForProcess --
 *
 *      Wait for child process.
 *
 * Results:
 *      Ruturn NS_OK for success and NS_ERROR for failure.
 *
 * Side effects:
 *      Sets exit code in exitcodePtr if given.
 *
 *----------------------------------------------------------------------
 */

int
Ns_WaitForProcess(int pid, int *exitcodePtr)
{
#ifdef _WIN32
    HANDLE process = (HANDLE) pid;
    int status = NS_OK;
    int exitcode;

    if ((WaitForSingleObject(process, INFINITE) == WAIT_FAILED) ||
        (GetExitCodeProcess(process, &exitcode) != TRUE)) {
        Ns_Log(Error, "exec: failed to get process exit code: %s",
               NsWin32ErrMsg(GetLastError()));
        status = NS_ERROR;
    }
    if (CloseHandle(process) != TRUE) {
        Ns_Log(Warning, "exec: failed to close handle for process %d: %s",
	       pid, NsWin32ErrMsg(GetLastError()));
        status = NS_ERROR;
    }
    if (status == NS_OK) {
        if (exitcodePtr != NULL) {
            *exitcodePtr = exitcode;
        }
        if (nsconf.exec.checkexit && exitcode != 0) {
            Ns_Log(Error, "exec: process %d exited with non-zero status: %d",
                   pid, exitcode);
            status = NS_ERROR;
        }
    }
    return status;

#else
    char *coredump;
    int exitcode, status;
    
    if (waitpid(pid, &status, 0) != pid) {
        Ns_Log(Error, "waitpid(%d) failed: %s", pid, strerror(errno));
	return NS_ERROR;
    }
    if (WIFSIGNALED(status)) {
    	coredump = "";
#ifdef WCOREDUMP
        if (WCOREDUMP(status)) {
	    coredump = " - core dumped";
	}
#endif
        Ns_Log(Error, "process %d killed with signal %d%s", pid,
	    WTERMSIG(status), coredump);
    } else if (!WIFEXITED(status)) {
    	Ns_Log(Error, "waitpid(%d): invalid status: %d", pid, status);
    } else {
    	exitcode = WEXITSTATUS(status);
    	if (exitcode != 0) {
            Ns_Log(Warning, "process %d exited with non-zero exit code: %d",
               pid, exitcode);
    	}
    	if (exitcodePtr != NULL) {
    	    *exitcodePtr = exitcode;
	}
    }
    return NS_OK;
#endif /* _WIN32 */
}


/*
 *----------------------------------------------------------------------
 * Ns_ExecArgblk --
 *
 *      Execute a command in a child process using a null
 *  	byte separated list of args.
 *
 * Results:
 *      Return process id of child process exec'ing the command or
 *	-1 on failure.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ExecArgblk(char *exec, char *dir, int fdin, int fdout,
		char *args, Ns_Set *env)
{
#ifndef _WIN32
    int    pid;
    char **argv;
    Ns_DString vds;

    Ns_DStringInit(&vds);
    if (args == NULL) {
        argv = NULL;
    } else {
	while (*args != '\0') {
            Ns_DStringNAppend(&vds, (char *) &args, sizeof(args));
            args += strlen(args) + 1;
	}
	args = NULL;
	Ns_DStringNAppend(&vds, (char *) &args, sizeof(args));
	argv = (char **) vds.string;
    }
    pid = Ns_ExecArgv(exec, dir, fdin, fdout, argv, env);
    Ns_DStringFree(&vds);
    return pid;
#else
    STARTUPINFO     si;
    PROCESS_INFORMATION pi;
    HANDLE          hCurrentProcess;
    int             pid;
    Ns_DString      cds, xds, eds;
    char           *envp;
    OSVERSIONINFO   oinfo;
    char           *cmd;

    if (exec == NULL) {
        return -1;
    }
    oinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (GetVersionEx(&oinfo) == TRUE && oinfo.dwPlatformId != VER_PLATFORM_WIN32_NT) {
        cmd = "command.com";
    } else {
        cmd = "cmd.exe";
    }

    /*
     * Setup STARTUPINFO with stdin, stdout, and stderr.
     */
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr));
    hCurrentProcess = GetCurrentProcess();
    if (fdout < 0) {
        fdout = 1;
    }
    if (DuplicateHandle(hCurrentProcess, (HANDLE) _get_osfhandle(fdout), hCurrentProcess,
            &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS) != TRUE) {
        Ns_Log(Error, "exec: failed to duplicate handle: %s",
        NsWin32ErrMsg(GetLastError()));
        return -1;
    }
    if (fdin < 0) {
        fdin = 0;
    }
    if (DuplicateHandle(hCurrentProcess, (HANDLE) _get_osfhandle(fdin), hCurrentProcess,
            &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS) != TRUE) {
        Ns_Log(Error, "exec: failed to duplicate handle: %s",
        NsWin32ErrMsg(GetLastError()));
        (void) CloseHandle(si.hStdOutput);
        return -1;
    }

    /*
     * Setup the command line and environment block and create the new
     * subprocess.
     */

    Ns_DStringInit(&cds);
    Ns_DStringInit(&xds);
    Ns_DStringInit(&eds);
    if (args == NULL) {
        /* NB: exec specifies a complete cmd.exe command string. */
        Ns_DStringVarAppend(&cds, cmd, " /c ", exec, NULL);
        exec = NULL;
    } else {
        char           *s;

        s = args;
        while (*s != '\0') {
            int             len;

            len = strlen(s);
            Ns_DStringNAppend(&cds, s, len);
            s += len + 1;
            if (*s != '\0') {
                Ns_DStringNAppend(&cds, " ", 1);
            }
        }
    Ns_NormalizePath(&xds, exec);
    s = xds.string;
    while (*s != '\0') {
        if (*s == '/') {
            *s = '\\';
        }
        ++s;
    }
    exec = xds.string;
    }
    if (env == NULL) {
        envp = NULL;
    } else {
        Set2Argv(&eds, env);
        envp = eds.string;
    }
    if (CreateProcess(exec, cds.string, NULL, NULL, TRUE, 0, envp, dir, &si, &pi) != TRUE) {
        Ns_Log(Error, "exec: failed to create process: %s: %s",
        exec ? exec : cds.string, NsWin32ErrMsg(GetLastError()));
        pid = -1;
    } else {
        CloseHandle(pi.hThread);
        pid = (int) pi.hProcess;
    }
    Ns_DStringFree(&cds);
    Ns_DStringFree(&xds);
    Ns_DStringFree(&eds);
    CloseHandle(si.hStdInput);
    CloseHandle(si.hStdOutput);
    return pid;
#endif
}


/*
 *----------------------------------------------------------------------
 * Ns_ExecArgv --
 *
 *	Execute a program in a new child process.
 *
 * Results:
 *      Return process id of child process exec'ing the command or
 *	-1 on failure.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ExecArgv(char *exec, char *dir, int fdin, int fdout,
	    char **argv, Ns_Set *env)
{
#ifdef _WIN32
    /*
     * Win32 ExecArgv simply calls ExecArgblk.
     */
    int             pid;     
    Ns_DString      ads;
    char	   *args;
    int		    i;

    Ns_DStringInit(&ads);
    if (argv == NULL) {
        args = NULL;
    } else {
        for (i = 0; argv[i] != NULL; ++i) {
            Ns_DStringNAppend(&ads, argv[i], strlen(argv[i]) + 1);
        }
        args = ads.string;
    }
    pid = Ns_ExecArgblk(exec, dir, fdin, fdout, args, env);
    Ns_DStringFree(&ads);
    return pid;
#else
    Ns_DString eds;
    char *argvSh[4], **envp;
    int i, pid;
    
    if (exec == NULL) {
        return -1;
    }
    if (argv == NULL) {
        argv = argvSh;
        argv[0] = "/bin/sh";
        argv[1] = "-c";
        argv[2] = exec;
        argv[3] = NULL;
        exec = argv[0];
    }
    Ns_DStringInit(&eds);
    if (env == NULL) {
	envp = Ns_CopyEnviron(&eds);
    } else {
	for (i = 0; i < Ns_SetSize(env); ++i) {
            Ns_DStringVarAppend(&eds,
		Ns_SetKey(env, i), "=", Ns_SetValue(env, i), NULL);
            Ns_DStringNAppend(&eds, "", 1);
	}
	Ns_DStringNAppend(&eds, "", 1);
	envp = Ns_DStringAppendArgv(&eds);
    }
    if (fdin < 0) {
	fdin = 0;
    }
    if (fdout < 0) {
	fdout = 1;
    }
    pid = ExecProc(exec, dir, fdin, fdout, argv, envp);
    Ns_DStringFree(&eds);
    return pid;
#endif
}

#ifndef _WIN32

/*
 *----------------------------------------------------------------------
 * ExecProc -- 
 *
 *	Execute a new process.  This code is careful to capture the
 *  	full error status from the child on failure.
 *
 * Results:
 *      Valid new child pid or -1 on error.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static int
ExecProc(char *exec, char *dir, int fdin, int fdout, char **argv,
    	 char **envp)
{
    struct iovec iov[2];
    int    pid, nread, errpipe[2], errnum, result;

    /*
     * Create a pipe for child error message.
     */
     
    if (ns_pipe(errpipe) < 0) {
        Ns_Log(Error, "exec: ns_pipe() failed: %s", strerror(errno));
	return -1;
    }

    /*
     * Fork child and read error message (if any).
     */

    pid = ns_fork();
    if (pid < 0) {
        close(errpipe[0]);
        close(errpipe[1]);
        Ns_Log(Error, "exec: ns_fork() failed: %s", strerror(errno));
	return -1;
    }
    iov[0].iov_base = (caddr_t) &result;
    iov[1].iov_base = (caddr_t) &errnum;
    iov[0].iov_len = iov[1].iov_len = sizeof(int);
    if (pid == 0) {

	/*
	 * Setup child and exec the program, writing any error back
	 * to the parent if necessary.
	 */

        close(errpipe[0]);
        if (dir != NULL && chdir(dir) != 0) {
	    result = ERR_CHDIR;
        } else if ((fdin == 1 && (fdin = dup(1)) < 0) ||
    	    	    (fdout == 0 && (fdout = dup(0)) < 0) ||
	    	    (fdin != 0 && dup2(fdin, 0) < 0) ||
    	    	    (fdout != 1 && dup2(fdout, 1) < 0)) {
	    result = ERR_DUP;
	} else {
	    if (fdin > 2) {
		close(fdin);
	    }
	    if (fdout > 2) {
            	close(fdout);
	    }
            NsRestoreSignals();
	    Ns_NoCloseOnExec(0);
	    Ns_NoCloseOnExec(1);
	    Ns_NoCloseOnExec(2);
            execve(exec, argv, envp);
	    /* NB: Not reached on successful execve(). */
	    result = ERR_EXEC;
	}
	errnum = errno;
	(void) writev(errpipe[1], iov, 2);
	_exit(1);
	
    } else {
    
	/*
	 * Read result and errno from the child if any.
	 */

        close(errpipe[1]);
	do {
            nread = readv(errpipe[0], iov, 2);
	} while (nread < 0 && errno == EINTR);
        close(errpipe[0]);
        if (nread == 0) {
	    errnum = 0;
	    result = pid;
	} else {
            if (nread != (sizeof(int) * 2)) {
	    	Ns_Log(Error, "exec: %s: error reading status from child: %s",
			   exec, strerror(errno));
            } else {
		switch (result) {
		    case ERR_CHDIR:
            		Ns_Log(Error, "exec %s: chdir(%s) failed: %s",
				exec, dir, strerror(errnum));
	    		break;
		    case ERR_DUP:
	    		Ns_Log(Error, "exec %s: dup failed: %s",
				exec, strerror(errnum));
	    		break;
		    case ERR_EXEC:
	    		Ns_Log(Error, "exec %s: execve() failed: %s",
				exec, strerror(errnum));
	    		break;
		    default:
	    		Ns_Log(Error, "exec %s: unknown result from child: %d",
				exec, result);
	    		break;
		}
	    }
            (void) waitpid(pid, NULL, 0);
	    errno = errnum;
        }
    }
    return result;
}

#else /* _WIN32 */



/*
 *----------------------------------------------------------------------
 * Set2Argv --
 *
 *      Convert an Ns_Set containing key-value pairs into a character
 *	array containing a sequence of name-value pairs with their 
 *	terminating null bytes.
 *
 * Results:
 *      Returns pointer to a character array containing a sequence of
 *	name-value pairs with their terminating null bytes.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static char **
Set2Argv(Ns_DString *dsPtr, Ns_Set *env)
{
    int        i;

    for (i = 0; i < Ns_SetSize(env); ++i) {
        Ns_DStringVarAppend(dsPtr,
        Ns_SetKey(env, i), "=", Ns_SetValue(env, i), NULL);
        Ns_DStringNAppend(dsPtr, "", 1);
    }
    Ns_DStringNAppend(dsPtr, "", 1);
    return Ns_DStringAppendArgv(dsPtr);
}

#endif /* _WIN32 */
