#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <process.h>
#include <math.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

static char *FindExec(char *);
static int Directory(char *);

/*
 * The first argument is the file descriptor that we want to be stdin.
 * The second argument is the file descriptor that we want to be stdout.
 * The third argument is the file descriptor that we want to be stderr.
 * The fourth argument is the command name
 * The 5th - nth arguments are the arguments for the command
 * All are in integer format
 */

void
main(int argc, char **argv)
{
    int fdstdin, fdstdout, fdstderr;
    int i, pid, ret, exit_status, firstArg, len;
    char *command;
    char *term;

    if (argc < 6) {
    Usage:
	fprintf(stderr,
		"%s: Helper program used by Tcl to create subprocesses.\n",
		argv[0]);
	exit(1);
    }

    SetLastError(0);
    fdstdin  = (int) strtol(argv[1], &term, 0);
    if (errno == ERANGE) {
	goto Usage;
    }

    fdstdout = (int) strtol(argv[2], &term, 0);
    if (errno == ERANGE) {
	goto Usage;
    }

    fdstderr = (int) strtol(argv[3], &term, 0);
    if (errno == ERANGE) {
	goto Usage;
    }

    if (fdstdin != 0 && fdstdin != -1) {
	close(0);
	if (dup2(fdstdin, 0) == -1) {
	    fprintf(stderr, "Unable to dup onto stdin\n");
	    exit(1);
	}
    }

    if (fdstdout != 1 && fdstdout != -1) {
	close(1);
	if (dup2(fdstdout, 1) == -1) {
	    fprintf(stderr, "Unable to dup onto stdout\n");
	    exit(1);
	}
    }

    if (fdstderr != 2 && fdstderr != -1) {
	close(2);
	if (dup2(fdstderr, 2) == -1) {
	    /* This message won't necessarily get out */
	    fprintf(stderr, "Unable to dup onto stdout\n");
	    exit(1);
	}
    }

    /* Close all other open file descriptors */
    for (i = 3; i < 1024; i++) {
	close(i);
    }

    /* Create the new argv.  Need to do this because cmd.exe will not
     * execute without a full path
     */
    command = FindExec(argv[4]);
    /* Change all forward slashes to backwards slashes. Since .bat files
     * are executed in Microsoft's shell, they need backslashes instead
     * of forward slashes.  Backslashes should work for any executable
     * type file while forward slashes only work for most.
     */
    len = strlen(command);
    for (i = 0; i < len; i++) {
	if (command[i] == '/') {
	    command[i] = '\\';
	}
    }
    len = strlen(argv[5]);
    for (i = 0; i < len; i++) {
	if (argv[5][i] == '/') {
	    argv[5][i] = '\\';
	}
    }
    firstArg = 5;

    SetLastError(0);
    pid = _spawnvp(_P_NOWAIT, command, &argv[firstArg]);
    if (pid == -1) {
	int error;
	error = GetLastError();

	if (error == E2BIG) {
	    fprintf(stderr, "%s: command line too long\n", command);
	} else if (error == ENOEXEC) {
	    fprintf(stderr, "%s: Not an executable\n", command);
	} else if (error == ENOMEM) {
	    fprintf(stderr, "%s: No memory available\n", command);
	} else {
	    /* The command may be part of cmd.exe.  Might as well
	     * try it.
	     */
	    command = FindExec("cmd.exe");
	    argv[4] = "/c";
	    argv[3] = "cmd.exe";
	    firstArg = 3;
	    SetLastError(0);
	    pid = _spawnvp(_P_NOWAIT, command, &argv[firstArg]);

	    if (pid == -1) {
		error = GetLastError();

		if (error == E2BIG) {
		    fprintf(stderr, "%s: command line too long\n", command);
		} else if (error == ENOEXEC) {
		    fprintf(stderr, "%s: Not an executable\n", command);
		} else if (error == ENOMEM) {
		    fprintf(stderr, "%s: No memory available\n", command);
		} else {

		    fprintf(stderr, "couldn't find \"%.150s\" to execute\n",
			    argv[5]);
		}
	    }
	}
	if (pid == -1) {
	    exit(1);
	}
    }

    close(0);
    close(1);

    ret = _cwait(&exit_status, pid, _WAIT_GRANDCHILD);
    if (ret == -1) {
	if (errno == ECHILD) {
	    fprintf(stderr, "%s: bad process\n", argv[0]);
	    exit(1);
	} else if (errno == EINTR) {
	    /* Try to cause the spawner to exit with the same sort of
	     * error the child exited with.  For now, just exit with
	     * an error code of 2.
	     */
	    exit(2);
	}
    } else {
	exit((exit_status & 0xff00) >> 8);
    }
}

static char *
FindExec(char *command)
{
    /* Run through the PATH environment variable to find the executable */
    char *path, *bp, *start, *dir;
    char save;
    int len, need, cmdlen, dirlen;
    int have = 0;
    static char *exec = NULL;

#define X_OK 0

    len = strlen(command);
    if (len > 2) {
	if (command[1] == ':' ||
	    strchr(command, '/') != NULL || strchr(command, '\\') != NULL)
	{
	    return command;
	}
    }

    path = getenv("PATH");
    if (path == NULL) {
	path = getenv("Path");
	if (path == NULL) {
	    return command;
	}
    }

    path = strdup(path);
    if (path == NULL) {
	return command;
    }

    for (start = path, bp = start; *start != '\0'; start = bp) {
	while(*bp != '\0' && *bp != ';') {
	    bp++;
	}
	save = *bp;
	*bp = '\0';
	dirlen = strlen(start);
	if (dirlen == 0) {
	    dir = "./";
	    dirlen = 2;
	} else {
	    dir = start;
	}
	cmdlen = strlen(command);
	need = cmdlen + dirlen + 6;
	if (need > have) {
	    if (exec) {
		free(exec);
	    }
	    exec = (char *) malloc(need);
	    if (exec == NULL) {
		return command;
	    }
	    have = need;
	}
	strcpy(exec, dir);
	if (dir[dirlen-1] != '/' && dir[dirlen-1] != '\\') {
	    exec[dirlen] = '/';
	    dirlen++;
	}
	strcpy(&exec[dirlen], command);
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	len = dirlen + cmdlen;
	strcpy(&exec[len], ".exe");
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	strcpy(&exec[len], ".bat");
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	*bp = save;
	bp++;
    }
    return command;
}

int
Directory(char *path)
{
    struct stat statBuf;

    if (stat(path, &statBuf) == -1) {
	return 0;
    }
    if (_S_IFDIR & statBuf.st_mode) {
	return 1;
    }
    return 0;
}
