/* Process handling for Windows.
Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.

GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef _MSC_VER
# include <stddef.h>    /* for intptr_t */
#else
# include <stdint.h>
#endif
#include <process.h>  /* for msvc _beginthreadex, _endthreadex */
#include <signal.h>
#include <windows.h>

#include "sub_proc.h"
#include "proc.h"
#include "w32err.h"
#include "debug.h"

static char *make_command_line(char *shell_name, char *exec_path, char **argv);
extern char *xmalloc (unsigned int);

typedef struct sub_process_t {
	intptr_t sv_stdin[2];
	intptr_t sv_stdout[2];
	intptr_t sv_stderr[2];
	int using_pipes;
	char *inp;
	DWORD incnt;
	char * volatile outp;
	volatile DWORD outcnt;
	char * volatile errp;
	volatile DWORD errcnt;
	pid_t pid;
	int exit_code;
	int signal;
	long last_err;
	long lerrno;
} sub_process;

/* keep track of children so we can implement a waitpid-like routine */
static sub_process *proc_array[MAXIMUM_WAIT_OBJECTS];
static int proc_index = 0;
static int fake_exits_pending = 0;

/*
 * When a process has been waited for, adjust the wait state
 * array so that we don't wait for it again
 */
static void
process_adjust_wait_state(sub_process* pproc)
{
	int i;

	if (!proc_index)
		return;

	for (i = 0; i < proc_index; i++)
		if (proc_array[i]->pid == pproc->pid)
			break;

	if (i < proc_index) {
		proc_index--;
		if (i != proc_index)
			memmove(&proc_array[i], &proc_array[i+1],
				(proc_index-i) * sizeof(sub_process*));
		proc_array[proc_index] = NULL;
	}
}

/*
 * Waits for any of the registered child processes to finish.
 */
static sub_process *
process_wait_for_any_private(void)
{
	HANDLE handles[MAXIMUM_WAIT_OBJECTS];
	DWORD retval, which;
	int i;

	if (!proc_index)
		return NULL;

	/* build array of handles to wait for */
	for (i = 0; i < proc_index; i++) {
		handles[i] = (HANDLE) proc_array[i]->pid;

		if (fake_exits_pending && proc_array[i]->exit_code)
			break;
	}

	/* wait for someone to exit */
	if (!fake_exits_pending) {
		retval = WaitForMultipleObjects(proc_index, handles, FALSE, INFINITE);
		which = retval - WAIT_OBJECT_0;
	} else {
		fake_exits_pending--;
		retval = !WAIT_FAILED;
		which = i;
	}

	/* return pointer to process */
	if (retval != WAIT_FAILED) {
		sub_process* pproc = proc_array[which];
		process_adjust_wait_state(pproc);
		return pproc;
	} else
		return NULL;
}

/*
 * Terminate a process.
 */
BOOL
process_kill(HANDLE proc, int signal)
{
	sub_process* pproc = (sub_process*) proc;
	pproc->signal = signal;
	return (TerminateProcess((HANDLE) pproc->pid, signal));
}

/*
 * Use this function to register processes you wish to wait for by
 * calling process_file_io(NULL) or process_wait_any(). This must be done
 * because it is possible for callers of this library to reuse the same
 * handle for multiple processes launches :-(
 */
void
process_register(HANDLE proc)
{
	if (proc_index < MAXIMUM_WAIT_OBJECTS)
		proc_array[proc_index++] = (sub_process *) proc;
}

/*
 * Return the number of processes that we are still waiting for.
 */
int
process_used_slots(void)
{
	return proc_index;
}

/*
 * Public function which works kind of like waitpid(). Wait for any
 * of the children to die and return results. To call this function,
 * you must do 1 of things:
 *
 * 	x = process_easy(...);
 *
 * or
 *
 *	x = process_init_fd();
 *	process_register(x);
 *
 * or
 *
 *	x = process_init();
 *	process_register(x);
 *
 * You must NOT then call process_pipe_io() because this function is
 * not capable of handling automatic notification of any child
 * death.
 */

HANDLE
process_wait_for_any(void)
{
	sub_process* pproc = process_wait_for_any_private();

	if (!pproc)
		return NULL;
	else {
		/*
		 * Ouch! can't tell caller if this fails directly. Caller
		 * will have to use process_last_err()
                 */
		(void) process_file_io(pproc);
		return ((HANDLE) pproc);
	}
}

long
process_signal(HANDLE proc)
{
        if (proc == INVALID_HANDLE_VALUE) return 0;
        return (((sub_process *)proc)->signal);
}

long
process_last_err(HANDLE proc)
{
        if (proc == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
	return (((sub_process *)proc)->last_err);
}

long
process_exit_code(HANDLE proc)
{
        if (proc == INVALID_HANDLE_VALUE) return EXIT_FAILURE;
	return (((sub_process *)proc)->exit_code);
}

/*
2006-02:
All the following functions are currently unused.
All of them would crash gmake if called with argument INVALID_HANDLE_VALUE.
Hence whoever wants to use one of this functions must invent and implement
a reasonable error handling for this function.

char *
process_outbuf(HANDLE proc)
{
	return (((sub_process *)proc)->outp);
}

char *
process_errbuf(HANDLE proc)
{
	return (((sub_process *)proc)->errp);
}

int
process_outcnt(HANDLE proc)
{
	return (((sub_process *)proc)->outcnt);
}

int
process_errcnt(HANDLE proc)
{
	return (((sub_process *)proc)->errcnt);
}

void
process_pipes(HANDLE proc, int pipes[3])
{
	pipes[0] = ((sub_process *)proc)->sv_stdin[0];
	pipes[1] = ((sub_process *)proc)->sv_stdout[0];
	pipes[2] = ((sub_process *)proc)->sv_stderr[0];
	return;
}
*/

	HANDLE
process_init()
{
	sub_process *pproc;
	/*
	 * open file descriptors for attaching stdin/stdout/sterr
	 */
	HANDLE stdin_pipes[2];
	HANDLE stdout_pipes[2];
	HANDLE stderr_pipes[2];
	SECURITY_ATTRIBUTES inherit;
	BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH];

	pproc = malloc(sizeof(*pproc));
	memset(pproc, 0, sizeof(*pproc));

	/* We can't use NULL for lpSecurityDescriptor because that
	   uses the default security descriptor of the calling process.
	   Instead we use a security descriptor with no DACL.  This
	   allows nonrestricted access to the associated objects. */

	if (!InitializeSecurityDescriptor((PSECURITY_DESCRIPTOR)(&sd),
					  SECURITY_DESCRIPTOR_REVISION)) {
		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
		return((HANDLE)pproc);
	}

	inherit.nLength = sizeof(inherit);
	inherit.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(&sd);
	inherit.bInheritHandle = TRUE;

	// By convention, parent gets pipe[0], and child gets pipe[1]
	// This means the READ side of stdin pipe goes into pipe[1]
	// and the WRITE side of the stdout and stderr pipes go into pipe[1]
	if (CreatePipe( &stdin_pipes[1], &stdin_pipes[0], &inherit, 0) == FALSE ||
	CreatePipe( &stdout_pipes[0], &stdout_pipes[1], &inherit, 0) == FALSE ||
	CreatePipe( &stderr_pipes[0], &stderr_pipes[1], &inherit, 0) == FALSE) {

		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
		return((HANDLE)pproc);
	}

	//
	// Mark the parent sides of the pipes as non-inheritable
	//
	if (SetHandleInformation(stdin_pipes[0],
				HANDLE_FLAG_INHERIT, 0) == FALSE ||
		SetHandleInformation(stdout_pipes[0],
				HANDLE_FLAG_INHERIT, 0) == FALSE ||
		SetHandleInformation(stderr_pipes[0],
				HANDLE_FLAG_INHERIT, 0) == FALSE) {

		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
		return((HANDLE)pproc);
	}
	pproc->sv_stdin[0]  = (intptr_t) stdin_pipes[0];
	pproc->sv_stdin[1]  = (intptr_t) stdin_pipes[1];
	pproc->sv_stdout[0] = (intptr_t) stdout_pipes[0];
	pproc->sv_stdout[1] = (intptr_t) stdout_pipes[1];
	pproc->sv_stderr[0] = (intptr_t) stderr_pipes[0];
	pproc->sv_stderr[1] = (intptr_t) stderr_pipes[1];

	pproc->using_pipes = 1;

	pproc->lerrno = 0;

	return((HANDLE)pproc);
}


	HANDLE
process_init_fd(HANDLE stdinh, HANDLE stdouth, HANDLE stderrh)
{
	sub_process *pproc;

	pproc = malloc(sizeof(*pproc));
	memset(pproc, 0, sizeof(*pproc));

	/*
	 * Just pass the provided file handles to the 'child side' of the
	 * pipe, bypassing pipes altogether.
	 */
	pproc->sv_stdin[1]  = (intptr_t) stdinh;
	pproc->sv_stdout[1] = (intptr_t) stdouth;
	pproc->sv_stderr[1] = (intptr_t) stderrh;

	pproc->last_err = pproc->lerrno = 0;

	return((HANDLE)pproc);
}


static HANDLE
find_file(const char *exec_path, const char *path_var,
	  char *full_fname, DWORD full_len)
{
	HANDLE exec_handle;
	char *fname;
	char *ext;
	DWORD req_len;
	int i;
	static const char *extensions[] =
	  /* Should .com come before no-extension case?  */
	  { ".exe", ".cmd", ".bat", "", ".com", NULL };

	fname = xmalloc(strlen(exec_path) + 5);
	strcpy(fname, exec_path);
	ext = fname + strlen(fname);

	for (i = 0; extensions[i]; i++) {
		strcpy(ext, extensions[i]);
		if (((req_len = SearchPath (path_var, fname, NULL, full_len,
					    full_fname, NULL)) > 0
		     /* For compatibility with previous code, which
			used OpenFile, and with Windows operation in
			general, also look in various default
			locations, such as Windows directory and
			Windows System directory.  Warning: this also
			searches PATH in the Make's environment, which
			might not be what the Makefile wants, but it
			seems to be OK as a fallback, after the
			previous SearchPath failed to find on child's
			PATH.  */
		     || (req_len = SearchPath (NULL, fname, NULL, full_len,
					       full_fname, NULL)) > 0)
		    && req_len <= full_len
		    && (exec_handle =
				CreateFile(full_fname,
					   GENERIC_READ,
					   FILE_SHARE_READ | FILE_SHARE_WRITE,
					   NULL,
					   OPEN_EXISTING,
					   FILE_ATTRIBUTE_NORMAL,
					   NULL)) != INVALID_HANDLE_VALUE) {
			free(fname);
			return(exec_handle);
		}
	}

	free(fname);
	return INVALID_HANDLE_VALUE;
}


/*
 * Description:   Create the child process to be helped
 *
 * Returns: success <=> 0
 *
 * Notes/Dependencies:
 */
long
process_begin(
	HANDLE proc,
	char **argv,
	char **envp,
	char *exec_path,
	char *as_user)
{
	sub_process *pproc = (sub_process *)proc;
	char *shell_name = 0;
	int file_not_found=0;
	HANDLE exec_handle;
	char exec_fname[MAX_PATH];
	const char *path_var = NULL;
	char **ep;
	char buf[256];
	DWORD bytes_returned;
	DWORD flags;
	char *command_line;
	STARTUPINFO startInfo;
	PROCESS_INFORMATION procInfo;
	char *envblk=NULL;

	/*
	 *  Shell script detection...  if the exec_path starts with #! then
	 *  we want to exec shell-script-name exec-path, not just exec-path
	 *  NT doesn't recognize #!/bin/sh or #!/etc/Tivoli/bin/perl.  We do not
	 *  hard-code the path to the shell or perl or whatever:  Instead, we
	 *  assume it's in the path somewhere (generally, the NT tools
	 *  bin directory)
	 */

	/* Use the Makefile's value of PATH to look for the program to
	   execute, because it could be different from Make's PATH
	   (e.g., if the target sets its own value.  */
	if (envp)
		for (ep = envp; *ep; ep++) {
			if (strncmp (*ep, "PATH=", 5) == 0
			    || strncmp (*ep, "Path=", 5) == 0) {
				path_var = *ep + 5;
				break;
			}
		}
	exec_handle = find_file(exec_path, path_var,
				exec_fname, sizeof(exec_fname));

	/*
	 * If we couldn't open the file, just assume that Windows will be
	 * somehow able to find and execute it.
	 */
	if (exec_handle == INVALID_HANDLE_VALUE) {
		file_not_found++;
	}
	else {
		/* Attempt to read the first line of the file */
		if (ReadFile( exec_handle,
				buf, sizeof(buf) - 1, /* leave room for trailing NULL */
				&bytes_returned, 0) == FALSE || bytes_returned < 2) {

			pproc->last_err = GetLastError();
			pproc->lerrno = E_IO;
			CloseHandle(exec_handle);
			return(-1);
		}
		if (buf[0] == '#' && buf[1] == '!') {
			/*
			 *  This is a shell script...  Change the command line from
			 *	exec_path args to shell_name exec_path args
			 */
			char *p;

			/*  Make sure buf is NULL terminated */
			buf[bytes_returned] = 0;
			/*
			 * Depending on the file system type, etc. the first line
			 * of the shell script may end with newline or newline-carriage-return
			 * Whatever it ends with, cut it off.
			 */
			p= strchr(buf, '\n');
			if (p)
				*p = 0;
			p = strchr(buf, '\r');
			if (p)
				*p = 0;

			/*
			 *  Find base name of shell
			 */
			shell_name = strrchr( buf, '/');
			if (shell_name) {
				shell_name++;
			} else {
				shell_name = &buf[2];/* skipping "#!" */
			}

		}
		CloseHandle(exec_handle);
	}

	flags = 0;

	if (file_not_found)
		command_line = make_command_line( shell_name, exec_path, argv);
	else
		command_line = make_command_line( shell_name, exec_fname, argv);

	if ( command_line == NULL ) {
		pproc->last_err = 0;
		pproc->lerrno = E_NO_MEM;
		return(-1);
	}

	if (envp) {
		if (arr2envblk(envp, &envblk) ==FALSE) {
			pproc->last_err = 0;
			pproc->lerrno = E_NO_MEM;
			free( command_line );
			return(-1);
		}
	}

	if ((shell_name) || (file_not_found)) {
		exec_path = 0;	/* Search for the program in %Path% */
	} else {
		exec_path = exec_fname;
	}

	/*
	 *  Set up inherited stdin, stdout, stderr for child
	 */
	GetStartupInfo(&startInfo);
	startInfo.dwFlags = STARTF_USESTDHANDLES;
	startInfo.lpReserved = 0;
	startInfo.cbReserved2 = 0;
	startInfo.lpReserved2 = 0;
	startInfo.lpTitle = shell_name ? shell_name : exec_path;
	startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1];
	startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1];
	startInfo.hStdError = (HANDLE)pproc->sv_stderr[1];

	if (as_user) {
		if (envblk) free(envblk);
		return -1;
	} else {
		DB (DB_JOBS, ("CreateProcess(%s,%s,...)\n",
			exec_path ? exec_path : "NULL",
			command_line ? command_line : "NULL"));
		if (CreateProcess(
			exec_path,
			command_line,
			NULL,
			0, /* default security attributes for thread */
			TRUE, /* inherit handles (e.g. helper pipes, oserv socket) */
			flags,
			envblk,
			0, /* default starting directory */
			&startInfo,
			&procInfo) == FALSE) {

			pproc->last_err = GetLastError();
			pproc->lerrno = E_FORK;
			fprintf(stderr, "process_begin: CreateProcess(%s, %s, ...) failed.\n",
                                exec_path ? exec_path : "NULL", command_line);
			if (envblk) free(envblk);
			free( command_line );
			return(-1);
		}
	}

	pproc->pid = (pid_t)procInfo.hProcess;
	/* Close the thread handle -- we'll just watch the process */
	CloseHandle(procInfo.hThread);

	/* Close the halves of the pipes we don't need */
        CloseHandle((HANDLE)pproc->sv_stdin[1]);
        CloseHandle((HANDLE)pproc->sv_stdout[1]);
        CloseHandle((HANDLE)pproc->sv_stderr[1]);
        pproc->sv_stdin[1] = 0;
        pproc->sv_stdout[1] = 0;
        pproc->sv_stderr[1] = 0;

	free( command_line );
	if (envblk) free(envblk);
	pproc->lerrno=0;
	return 0;
}



static DWORD
proc_stdin_thread(sub_process *pproc)
{
	DWORD in_done;
	for (;;) {
		if (WriteFile( (HANDLE) pproc->sv_stdin[0], pproc->inp, pproc->incnt,
					 &in_done, NULL) == FALSE)
			_endthreadex(0);
		// This if should never be true for anonymous pipes, but gives
		// us a chance to change I/O mechanisms later
		if (in_done < pproc->incnt) {
			pproc->incnt -= in_done;
			pproc->inp += in_done;
		} else {
			_endthreadex(0);
		}
	}
	return 0; // for compiler warnings only.. not reached
}

static DWORD
proc_stdout_thread(sub_process *pproc)
{
	DWORD bufsize = 1024;
	char c;
	DWORD nread;
	pproc->outp = malloc(bufsize);
	if (pproc->outp == NULL)
		_endthreadex(0);
	pproc->outcnt = 0;

	for (;;) {
		if (ReadFile( (HANDLE)pproc->sv_stdout[0], &c, 1, &nread, NULL)
					== FALSE) {
/*			map_windows32_error_to_string(GetLastError());*/
			_endthreadex(0);
		}
		if (nread == 0)
			_endthreadex(0);
		if (pproc->outcnt + nread > bufsize) {
			bufsize += nread + 512;
			pproc->outp = realloc(pproc->outp, bufsize);
			if (pproc->outp == NULL) {
				pproc->outcnt = 0;
				_endthreadex(0);
			}
		}
		pproc->outp[pproc->outcnt++] = c;
	}
	return 0;
}

static DWORD
proc_stderr_thread(sub_process *pproc)
{
	DWORD bufsize = 1024;
	char c;
	DWORD nread;
	pproc->errp = malloc(bufsize);
	if (pproc->errp == NULL)
		_endthreadex(0);
	pproc->errcnt = 0;

	for (;;) {
		if (ReadFile( (HANDLE)pproc->sv_stderr[0], &c, 1, &nread, NULL) == FALSE) {
			map_windows32_error_to_string(GetLastError());
			_endthreadex(0);
		}
		if (nread == 0)
			_endthreadex(0);
		if (pproc->errcnt + nread > bufsize) {
			bufsize += nread + 512;
			pproc->errp = realloc(pproc->errp, bufsize);
			if (pproc->errp == NULL) {
				pproc->errcnt = 0;
				_endthreadex(0);
			}
		}
		pproc->errp[pproc->errcnt++] = c;
	}
	return 0;
}


/*
 * Purpose: collects output from child process and returns results
 *
 * Description:
 *
 * Returns:
 *
 * Notes/Dependencies:
 */
	long
process_pipe_io(
	HANDLE proc,
	char *stdin_data,
	int stdin_data_len)
{
	sub_process *pproc = (sub_process *)proc;
	bool_t stdin_eof = FALSE, stdout_eof = FALSE, stderr_eof = FALSE;
	HANDLE childhand = (HANDLE) pproc->pid;
	HANDLE tStdin = NULL, tStdout = NULL, tStderr = NULL;
	unsigned int dwStdin, dwStdout, dwStderr;
	HANDLE wait_list[4];
	DWORD wait_count;
	DWORD wait_return;
	HANDLE ready_hand;
	bool_t child_dead = FALSE;
	BOOL GetExitCodeResult;

	/*
	 *  Create stdin thread, if needed
	 */
	pproc->inp = stdin_data;
	pproc->incnt = stdin_data_len;
	if (!pproc->inp) {
		stdin_eof = TRUE;
		CloseHandle((HANDLE)pproc->sv_stdin[0]);
		pproc->sv_stdin[0] = 0;
	} else {
		tStdin = (HANDLE) _beginthreadex( 0, 1024,
			(unsigned (__stdcall *) (void *))proc_stdin_thread,
						  pproc, 0, &dwStdin);
		if (tStdin == 0) {
			pproc->last_err = GetLastError();
			pproc->lerrno = E_SCALL;
			goto done;
		}
	}

	/*
	 *   Assume child will produce stdout and stderr
	 */
	tStdout = (HANDLE) _beginthreadex( 0, 1024,
		(unsigned (__stdcall *) (void *))proc_stdout_thread, pproc, 0,
		&dwStdout);
	tStderr = (HANDLE) _beginthreadex( 0, 1024,
		(unsigned (__stdcall *) (void *))proc_stderr_thread, pproc, 0,
		&dwStderr);

	if (tStdout == 0 || tStderr == 0) {

		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
		goto done;
	}


	/*
	 *  Wait for all I/O to finish and for the child process to exit
	 */

	while (!stdin_eof || !stdout_eof || !stderr_eof || !child_dead) {
		wait_count = 0;
		if (!stdin_eof) {
			wait_list[wait_count++] = tStdin;
		}
		if (!stdout_eof) {
			wait_list[wait_count++] = tStdout;
		}
		if (!stderr_eof) {
			wait_list[wait_count++] = tStderr;
		}
		if (!child_dead) {
			wait_list[wait_count++] = childhand;
		}

		wait_return = WaitForMultipleObjects(wait_count, wait_list,
			 FALSE, /* don't wait for all: one ready will do */
			 child_dead? 1000 :INFINITE); /* after the child dies, subthreads have
			 	one second to collect all remaining output */

		if (wait_return == WAIT_FAILED) {
/*			map_windows32_error_to_string(GetLastError());*/
			pproc->last_err = GetLastError();
			pproc->lerrno = E_SCALL;
			goto done;
		}

		ready_hand = wait_list[wait_return - WAIT_OBJECT_0];

		if (ready_hand == tStdin) {
			CloseHandle((HANDLE)pproc->sv_stdin[0]);
			pproc->sv_stdin[0] = 0;
			CloseHandle(tStdin);
			tStdin = 0;
			stdin_eof = TRUE;

		} else if (ready_hand == tStdout) {

		  	CloseHandle((HANDLE)pproc->sv_stdout[0]);
			pproc->sv_stdout[0] = 0;
			CloseHandle(tStdout);
			tStdout = 0;
		  	stdout_eof = TRUE;

		} else if (ready_hand == tStderr) {

			CloseHandle((HANDLE)pproc->sv_stderr[0]);
			pproc->sv_stderr[0] = 0;
			CloseHandle(tStderr);
			tStderr = 0;
			stderr_eof = TRUE;

		} else if (ready_hand == childhand) {

			DWORD ierr;
			GetExitCodeResult = GetExitCodeProcess(childhand, &ierr);
			if (ierr == CONTROL_C_EXIT) {
				pproc->signal = SIGINT;
			} else {
				pproc->exit_code = ierr;
			}
			if (GetExitCodeResult == FALSE) {
				pproc->last_err = GetLastError();
				pproc->lerrno = E_SCALL;
				goto done;
			}
			child_dead = TRUE;

		} else {

			/* ?? Got back a handle we didn't query ?? */
			pproc->last_err = 0;
			pproc->lerrno = E_FAIL;
			goto done;
		}
	}

 done:
	if (tStdin != 0)
		CloseHandle(tStdin);
	if (tStdout != 0)
		CloseHandle(tStdout);
	if (tStderr != 0)
		CloseHandle(tStderr);

	if (pproc->lerrno)
		return(-1);
	else
		return(0);

}

/*
 * Purpose: collects output from child process and returns results
 *
 * Description:
 *
 * Returns:
 *
 * Notes/Dependencies:
 */
	long
process_file_io(
	HANDLE proc)
{
	sub_process *pproc;
	HANDLE childhand;
	DWORD wait_return;
	BOOL GetExitCodeResult;
        DWORD ierr;

	if (proc == NULL)
		pproc = process_wait_for_any_private();
	else
		pproc = (sub_process *)proc;

	/* some sort of internal error */
	if (!pproc)
		return -1;

	childhand = (HANDLE) pproc->pid;

	/*
	 * This function is poorly named, and could also be used just to wait
	 * for child death if you're doing your own pipe I/O.  If that is
	 * the case, close the pipe handles here.
	 */
	if (pproc->sv_stdin[0]) {
		CloseHandle((HANDLE)pproc->sv_stdin[0]);
		pproc->sv_stdin[0] = 0;
	}
	if (pproc->sv_stdout[0]) {
		CloseHandle((HANDLE)pproc->sv_stdout[0]);
		pproc->sv_stdout[0] = 0;
	}
	if (pproc->sv_stderr[0]) {
		CloseHandle((HANDLE)pproc->sv_stderr[0]);
		pproc->sv_stderr[0] = 0;
	}

	/*
	 *  Wait for the child process to exit
	 */

	wait_return = WaitForSingleObject(childhand, INFINITE);

	if (wait_return != WAIT_OBJECT_0) {
/*		map_windows32_error_to_string(GetLastError());*/
		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
		goto done2;
	}

	GetExitCodeResult = GetExitCodeProcess(childhand, &ierr);
	if (ierr == CONTROL_C_EXIT) {
		pproc->signal = SIGINT;
	} else {
		pproc->exit_code = ierr;
	}
	if (GetExitCodeResult == FALSE) {
		pproc->last_err = GetLastError();
		pproc->lerrno = E_SCALL;
	}

done2:
	if (pproc->lerrno)
		return(-1);
	else
		return(0);

}

/*
 * Description:  Clean up any leftover handles, etc.  It is up to the
 * caller to manage and free the input, ouput, and stderr buffers.
 */
	void
process_cleanup(
	HANDLE proc)
{
	sub_process *pproc = (sub_process *)proc;
	int i;

	if (pproc->using_pipes) {
		for (i= 0; i <= 1; i++) {
			if ((HANDLE)pproc->sv_stdin[i])
				CloseHandle((HANDLE)pproc->sv_stdin[i]);
			if ((HANDLE)pproc->sv_stdout[i])
				CloseHandle((HANDLE)pproc->sv_stdout[i]);
			if ((HANDLE)pproc->sv_stderr[i])
				CloseHandle((HANDLE)pproc->sv_stderr[i]);
		}
	}
	if ((HANDLE)pproc->pid)
		CloseHandle((HANDLE)pproc->pid);

	free(pproc);
}


/*
 * Description:
 *	 Create a command line buffer to pass to CreateProcess
 *
 * Returns:  the buffer or NULL for failure
 *	Shell case:  sh_name a:/full/path/to/script argv[1] argv[2] ...
 *  Otherwise:   argv[0] argv[1] argv[2] ...
 *
 * Notes/Dependencies:
 *   CreateProcess does not take an argv, so this command creates a
 *   command line for the executable.
 */

static char *
make_command_line( char *shell_name, char *full_exec_path, char **argv)
{
	int		argc = 0;
	char**		argvi;
	int*		enclose_in_quotes = NULL;
	int*		enclose_in_quotes_i;
	unsigned int	bytes_required = 0;
	char*		command_line;
	char*		command_line_i;
	int  cygwin_mode = 0; /* HAVE_CYGWIN_SHELL */
	int have_sh = 0; /* HAVE_CYGWIN_SHELL */

#ifdef HAVE_CYGWIN_SHELL
	have_sh = (shell_name != NULL || strstr(full_exec_path, "sh.exe"));
	cygwin_mode = 1;
#endif

	if (shell_name && full_exec_path) {
		bytes_required
		  = strlen(shell_name) + 1 + strlen(full_exec_path);
		/*
		 * Skip argv[0] if any, when shell_name is given.
		 */
		if (*argv) argv++;
		/*
		 * Add one for the intervening space.
		 */
		if (*argv) bytes_required++;
	}

	argvi = argv;
	while (*(argvi++)) argc++;

	if (argc) {
		enclose_in_quotes = (int*) calloc(1, argc * sizeof(int));

		if (!enclose_in_quotes) {
			return NULL;
		}
	}

	/* We have to make one pass through each argv[i] to see if we need
	 * to enclose it in ", so we might as well figure out how much
	 * memory we'll need on the same pass.
	 */

	argvi = argv;
	enclose_in_quotes_i = enclose_in_quotes;
	while(*argvi) {
		char* p = *argvi;
		unsigned int backslash_count = 0;

		/*
		 * We have to enclose empty arguments in ".
		 */
		if (!(*p)) *enclose_in_quotes_i = 1;

		while(*p) {
			switch (*p) {
			case '\"':
				/*
				 * We have to insert a backslash for each "
				 * and each \ that precedes the ".
				 */
				bytes_required += (backslash_count + 1);
				backslash_count = 0;
				break;

#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
			case '\\':
				backslash_count++;
				break;
#endif
	/*
	 * At one time we set *enclose_in_quotes_i for '*' or '?' to suppress
	 * wildcard expansion in programs linked with MSVC's SETARGV.OBJ so
	 * that argv in always equals argv out. This was removed.  Say you have
	 * such a program named glob.exe.  You enter
	 * glob '*'
	 * at the sh command prompt.  Obviously the intent is to make glob do the
	 * wildcarding instead of sh.  If we set *enclose_in_quotes_i for '*' or '?',
	 * then the command line that glob would see would be
	 * glob "*"
	 * and the _setargv in SETARGV.OBJ would _not_ expand the *.
	 */
			case ' ':
			case '\t':
				*enclose_in_quotes_i = 1;
				/* fall through */

			default:
				backslash_count = 0;
				break;
			}

			/*
			 * Add one for each character in argv[i].
			 */
			bytes_required++;

			p++;
		}

		if (*enclose_in_quotes_i) {
			/*
			 * Add one for each enclosing ",
			 * and one for each \ that precedes the
			 * closing ".
			 */
			bytes_required += (backslash_count + 2);
		}

		/*
		 * Add one for the intervening space.
		 */
		if (*(++argvi)) bytes_required++;
		enclose_in_quotes_i++;
	}

	/*
	 * Add one for the terminating NULL.
	 */
	bytes_required++;

	command_line = (char*) malloc(bytes_required);

	if (!command_line) {
		if (enclose_in_quotes) free(enclose_in_quotes);
		return NULL;
	}

	command_line_i = command_line;

	if (shell_name && full_exec_path) {
		while(*shell_name) {
			*(command_line_i++) = *(shell_name++);
		}

		*(command_line_i++) = ' ';

		while(*full_exec_path) {
			*(command_line_i++) = *(full_exec_path++);
		}

		if (*argv) {
			*(command_line_i++) = ' ';
		}
	}

	argvi = argv;
	enclose_in_quotes_i = enclose_in_quotes;

	while(*argvi) {
		char* p = *argvi;
		unsigned int backslash_count = 0;

		if (*enclose_in_quotes_i) {
			*(command_line_i++) = '\"';
		}

		while(*p) {
			if (*p == '\"') {
				if (cygwin_mode && have_sh) { /* HAVE_CYGWIN_SHELL */
					/* instead of a \", cygwin likes "" */
					*(command_line_i++) = '\"';
				} else {

				/*
				 * We have to insert a backslash for the "
				 * and each \ that precedes the ".
				 */
				backslash_count++;

				while(backslash_count) {
					*(command_line_i++) = '\\';
					backslash_count--;
				};
				}
#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
			} else if (*p == '\\') {
				backslash_count++;
			} else {
				backslash_count = 0;
#endif
			}

			/*
			 * Copy the character.
			 */
			*(command_line_i++) = *(p++);
		}

		if (*enclose_in_quotes_i) {
#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
			/*
			 * Add one \ for each \ that precedes the
			 * closing ".
			 */
			while(backslash_count--) {
				*(command_line_i++) = '\\';
			};
#endif
			*(command_line_i++) = '\"';
		}

		/*
		 * Append an intervening space.
		 */
		if (*(++argvi)) {
			*(command_line_i++) = ' ';
		}

		enclose_in_quotes_i++;
	}

	/*
	 * Append the terminating NULL.
	 */
	*command_line_i = '\0';

	if (enclose_in_quotes) free(enclose_in_quotes);
	return command_line;
}

/*
 * Description: Given an argv and optional envp, launch the process
 *              using the default stdin, stdout, and stderr handles.
 *              Also, register process so that process_wait_for_any_private()
 *		can be used via process_file_io(NULL) or
 *		process_wait_for_any().
 *
 * Returns:
 *
 * Notes/Dependencies:
 */
HANDLE
process_easy(
	char **argv,
	char **envp)
{
  HANDLE hIn;
  HANDLE hOut;
  HANDLE hErr;
  HANDLE hProcess;

  if (proc_index >= MAXIMUM_WAIT_OBJECTS) {
	DB (DB_JOBS, ("process_easy: All process slots used up\n"));
	return INVALID_HANDLE_VALUE;
  }
  if (DuplicateHandle(GetCurrentProcess(),
                      GetStdHandle(STD_INPUT_HANDLE),
                      GetCurrentProcess(),
                      &hIn,
                      0,
                      TRUE,
                      DUPLICATE_SAME_ACCESS) == FALSE) {
    fprintf(stderr,
            "process_easy: DuplicateHandle(In) failed (e=%ld)\n",
            GetLastError());
    return INVALID_HANDLE_VALUE;
  }
  if (DuplicateHandle(GetCurrentProcess(),
                      GetStdHandle(STD_OUTPUT_HANDLE),
                      GetCurrentProcess(),
                      &hOut,
                      0,
                      TRUE,
                      DUPLICATE_SAME_ACCESS) == FALSE) {
    fprintf(stderr,
           "process_easy: DuplicateHandle(Out) failed (e=%ld)\n",
           GetLastError());
    return INVALID_HANDLE_VALUE;
  }
  if (DuplicateHandle(GetCurrentProcess(),
                      GetStdHandle(STD_ERROR_HANDLE),
                      GetCurrentProcess(),
                      &hErr,
                      0,
                      TRUE,
                      DUPLICATE_SAME_ACCESS) == FALSE) {
    fprintf(stderr,
            "process_easy: DuplicateHandle(Err) failed (e=%ld)\n",
            GetLastError());
    return INVALID_HANDLE_VALUE;
  }

  hProcess = process_init_fd(hIn, hOut, hErr);

  if (process_begin(hProcess, argv, envp, argv[0], NULL)) {
    fake_exits_pending++;
    /* process_begin() failed: make a note of that.  */
    if (!((sub_process*) hProcess)->last_err)
      ((sub_process*) hProcess)->last_err = -1;
    ((sub_process*) hProcess)->exit_code = process_last_err(hProcess);

    /* close up unused handles */
    CloseHandle(hIn);
    CloseHandle(hOut);
    CloseHandle(hErr);
  }

  process_register(hProcess);

  return hProcess;
}
