/*******************************************************************************
 * Copyright (c) 2002, 2007 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     QNX Software Systems - initial API and implementation
 *     Wind River Systems, Inc.  
 * 
 *  Win32ProcessEx.c
 *
 *  This is a JNI implementation of spawner 
 *******************************************************************************/
 
#include "stdafx.h"
#include <string.h>
#include <stdlib.h>
#include <process.h>
#include "Spawner.h"

#include "jni.h"
#include "io.h"


#define PIPE_SIZE 512			// Size of pipe buffer
#define MAX_CMD_SIZE 2049		// Initial size of command line
#define MAX_ENV_SIZE 4096		// Initial size of environment block
#define PIPE_NAME_LENGTH 100	// Size of pipe name buffer
#define PIPE_TIMEOUT 10000		// Default time-out value, in milliseconds.

#define MAX_PROCS (100)			// Maximum number of simultaneiously runnig processes


// Process description block. Should be created for each launched process
typedef struct _procInfo {
	int pid; // Process ID
	int uid; // quasi-unique process ID; we have to create it to avoid duplicated pid 
	         // (actually this impossible from OS point of view but it is still possible
			 // a clash of new created and already finished process with one and the same PID.
	// 3 events connected to this process (see starter)
	HANDLE eventBreak;
	HANDLE eventWait;
	HANDLE eventTerminate;
} procInfo_t, * pProcInfo_t;

static int procCounter = 0; // Number of running processes


// This is a VM helper
void ThrowByName(JNIEnv *env, const char *name, const char *msg);

// Creates _procInfo block for every launched procss
pProcInfo_t createProcInfo(); 

// Find process description for this pid
pProcInfo_t findProcInfo(int pid); 

// We launch separate thread for each project to trap it termination
unsigned int _stdcall waitProcTermination(void* pv) ;

// This is a helper function to prevent losing of quotatin marks
static int copyTo(wchar_t * target, const wchar_t  * source, int cpyLenght, int availSpace);

// Use this function to clean project descriptor and return it to the pool of available blocks.
static void cleanUpProcBlock(pProcInfo_t pCurProcInfo);


// Signal codes
typedef enum {
	SIG_NOOP,
	SIG_HUP,
	SIG_INT,
	SIG_KILL = 9,
	SIG_TERM = 15,
} signals;

extern CRITICAL_SECTION cs;


extern wchar_t path[MAX_PATH]; // Directory where spawner.dll is located

static HMODULE hVM = NULL;   // VM handler


static pProcInfo_t pInfo = NULL;

static int nCounter = 0; // We use it to build unique synchronisation object names

/////////////////////////////////////////////////////////////////////////////////////
// Launcher; launchess process and traps its termination
// Arguments: (see Spawner.java)
//			[in]  cmdarray - array of command line elements 
//			[in]  envp - array of environment variables
//			[in]  dir - working directory
//          [out] channels - streams handlers
/////////////////////////////////////////////////////////////////////////////////////

extern "C"
JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_Spawner_exec2
  (JNIEnv * env, jobject process, jobjectArray cmdarray, jobjectArray envp, jstring dir, jintArray channels, jstring slaveName, jint fdm) 
{
	return -1;
}

void ensureSize(wchar_t** ptr, int* psize, int requiredLength) 
{ 
	int size= *psize;
	if (requiredLength > size) { 
	   size= 2*size;
	   if (size < requiredLength) {
	      size= requiredLength;
	   }
	   *ptr= (wchar_t *)realloc(*ptr, size * sizeof(wchar_t));
	   if (NULL == *ptr) {
	   	  *psize= 0;
	   }
	   else {
	   	  *psize= size;
	   }
    }
}

extern "C"
JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_Spawner_exec0
  (JNIEnv * env, jobject process, jobjectArray cmdarray, jobjectArray envp, jstring dir, jintArray channels) 
{
	HANDLE stdHandles[3];
    PROCESS_INFORMATION pi = {0};
    STARTUPINFOW si;
	DWORD flags = 0;
    const wchar_t  * cwd = NULL;
	LPVOID envBlk = NULL;
    int ret = 0;
    int nCmdLineLength= 0;
	wchar_t * szCmdLine= 0;
	int nBlkSize = MAX_ENV_SIZE; 
	wchar_t * szEnvBlock = NULL;
	jsize nCmdTokens = 0;
	jsize nEnvVars = 0;
	int i;
	DWORD pid = GetCurrentProcessId();
	int nPos;
	pProcInfo_t pCurProcInfo;
	DWORD dwThreadId;
	wchar_t eventBreakName[20];
	wchar_t eventWaitName[20];
	wchar_t eventTerminateName[20];
#ifdef DEBUG_MONITOR
	wchar_t buffer[1000];
#endif
	int nLocalCounter;
	wchar_t inPipeName[PIPE_NAME_LENGTH];
	wchar_t outPipeName[PIPE_NAME_LENGTH];
	wchar_t errPipeName[PIPE_NAME_LENGTH];

    nCmdLineLength= MAX_CMD_SIZE;
    szCmdLine= (wchar_t *)malloc(nCmdLineLength * sizeof(wchar_t));
    szCmdLine[0]= _T('\0');
	if((HIBYTE(LOWORD(GetVersion()))) & 0x80)
		{
		ThrowByName(env, "java/io/IOException", "Does not support Windows 3.1/95/98/Me");
		return 0;
		}

    if (cmdarray == 0) 
		{
		ThrowByName(env, "java/lang/NullPointerException", "No command line specified");
		return 0;
		}

   ZeroMemory(stdHandles, sizeof(stdHandles));

	// Create pipe names
   EnterCriticalSection(&cs);
   swprintf(inPipeName,  L"\\\\.\\pipe\\stdin%08i%010i",  pid, nCounter); 
   swprintf(outPipeName, L"\\\\.\\pipe\\stdout%08i%010i", pid, nCounter); 
   swprintf(errPipeName, L"\\\\.\\pipe\\stderr%08i%010i", pid, nCounter); 
   nLocalCounter = nCounter;
   ++nCounter;
   LeaveCriticalSection(&cs);

   if ((INVALID_HANDLE_VALUE == (stdHandles[0] = CreateNamedPipeW(inPipeName, PIPE_ACCESS_OUTBOUND,
									  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
									  PIPE_UNLIMITED_INSTANCES, PIPE_SIZE, PIPE_SIZE, PIPE_TIMEOUT, NULL))) ||
	   (INVALID_HANDLE_VALUE == (stdHandles[1] = CreateNamedPipeW(outPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
									  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
									  PIPE_UNLIMITED_INSTANCES, PIPE_SIZE, PIPE_SIZE, PIPE_TIMEOUT, NULL))) ||
	   (INVALID_HANDLE_VALUE == (stdHandles[2] = CreateNamedPipeW(errPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
									  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
									  PIPE_UNLIMITED_INSTANCES, PIPE_SIZE, PIPE_SIZE, PIPE_TIMEOUT, NULL))))		{
		CloseHandle(stdHandles[0]);
        CloseHandle(stdHandles[1]);
        CloseHandle(stdHandles[2]);
		ThrowByName(env, "java/io/IOException", "CreatePipe");
		return 0;
   } 

#ifdef DEBUG_MONITOR
	swprintf(buffer, _T("Opened pipes: %s, %s, %s\n"), inPipeName, outPipeName, errPipeName);
	OutputDebugStringW(buffer);
#endif
	

	nCmdTokens = env->GetArrayLength(cmdarray);
    nEnvVars   = env->GetArrayLength(envp);

	pCurProcInfo = createProcInfo();

	if(NULL == pCurProcInfo)
		{
		ThrowByName(env, "java/io/IOException", "Too many processes");
		return 0;
		}

	// Construct starter's command line
	swprintf(eventBreakName, L"SABreak%p", pCurProcInfo);
	swprintf(eventWaitName, L"SAWait%p", pCurProcInfo);
	swprintf(eventTerminateName, L"SATerm%p", pCurProcInfo);
	pCurProcInfo -> eventBreak = CreateEventW(NULL, TRUE, FALSE, eventBreakName);
	ResetEvent(pCurProcInfo -> eventBreak);   
	pCurProcInfo -> eventWait = CreateEventW(NULL, TRUE, FALSE, eventWaitName);
	ResetEvent(pCurProcInfo -> eventWait);   
	pCurProcInfo -> eventTerminate = CreateEventW(NULL, TRUE, FALSE, eventTerminateName);
	ResetEvent(pCurProcInfo -> eventTerminate);   

	swprintf(szCmdLine, L"\"%sstarter.exe\" %i %i %s %s %s ", path, pid, nLocalCounter, eventBreakName, eventWaitName, eventTerminateName);
	nPos = wcslen(szCmdLine);

	// Prepare command line
	for(i = 0; i < nCmdTokens; ++i) 
    	{
		jstring item = (jstring)env->GetObjectArrayElement(cmdarray, i);
		jsize    len = env->GetStringLength(item);
		int nCpyLen;
		const wchar_t *  str = (const wchar_t *)env->GetStringChars(item, 0);	
		if(NULL != str) 
			{
			int requiredSize= nPos+len+2;
			if (requiredSize > 32*1024) {
				ThrowByName(env, "java/io/IOException", "Command line too long");
				return 0;
			}				
			ensureSize(&szCmdLine, &nCmdLineLength, requiredSize);
			if (NULL == szCmdLine) {
				ThrowByName(env, "java/io/IOException", "Not enough memory");
				return 0;
			}
			    
			if(0 > (nCpyLen = copyTo(szCmdLine + nPos, str, len, nCmdLineLength - nPos)))
                {
				ThrowByName(env, "java/io/IOException", "Command line too long");
				return 0;
			}
			nPos += nCpyLen;
			szCmdLine[nPos] = _T(' ');
			++nPos;
			env->ReleaseStringChars(item, (const jchar *)str);
		}
	}
	szCmdLine[nPos] = _T('\0');

#ifdef DEBUG_MONITOR
	swprintf(buffer, _T("There are  %i environment variables \n"), nEnvVars);
	OutputDebugStringW(buffer);
#endif
	// Prepare environment block
    if (nEnvVars > 0) 
		{
		nPos = 0;
		szEnvBlock = (wchar_t *)malloc(nBlkSize * sizeof(wchar_t));
		for(i = 0; i < nEnvVars; ++i) 
			{
			jstring item = (jstring)env->GetObjectArrayElement(envp, i);
			jsize    len = env->GetStringLength(item);
			const wchar_t *  str = (const wchar_t *)env->GetStringChars(item, 0);	
			if(NULL != str)
				{
				while((nBlkSize - nPos) <= (len + 2)) // +2 for two '\0'
					{
					nBlkSize += MAX_ENV_SIZE;
					szEnvBlock = (wchar_t *)realloc(szEnvBlock, nBlkSize * sizeof(wchar_t));
					if(NULL == szEnvBlock) 
						{
						ThrowByName(env, "java/io/IOException", "Not enough memory");
						return 0;
						}
#ifdef DEBUG_MONITOR
					swprintf(buffer, _T("Realloc environment block; new length is  %i \n"), nBlkSize);
					OutputDebugStringW(buffer);
#endif

					}
#ifdef DEBUG_MONITOR
				swprintf(buffer, _T("%s\n"), str);
				OutputDebugStringW(buffer);
#endif
				wcsncpy(szEnvBlock + nPos, str, len);
				nPos += len;
				szEnvBlock[nPos] = _T('\0');
				++nPos;
				env->ReleaseStringChars(item, (const jchar *)str);
				}
			}
			szEnvBlock[nPos] = _T('\0');
		}



    if (dir != 0) 
		{ 
		const wchar_t * str = (const wchar_t *)env->GetStringChars(dir, 0);
		if(NULL != str)
			{
			cwd = wcsdup(str);
			env->ReleaseStringChars(dir, (const jchar *)str);
			}
		}


    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags |= STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;  // Processes in the Process Group are hidden



    SetHandleInformation(stdHandles[0], HANDLE_FLAG_INHERIT, FALSE);
    SetHandleInformation(stdHandles[1],  HANDLE_FLAG_INHERIT, FALSE);
    SetHandleInformation(stdHandles[2],  HANDLE_FLAG_INHERIT, FALSE);

	flags = CREATE_NEW_CONSOLE;
	flags |= CREATE_NO_WINDOW;
	flags |= CREATE_UNICODE_ENVIRONMENT;

#ifdef DEBUG_MONITOR
	OutputDebugStringW(szCmdLine);
#endif
	// launches starter; we need it to create another console group to correctly process 
	// emulation of SYSint signal (Ctrl-C)
    ret = CreateProcessW(0,                /* executable name */
                        szCmdLine,        /* command line */
                        0,                /* process security attribute */
                        0,                /* thread security attribute */
                        FALSE,            /* inherits system handles */
                        flags,            /* normal attached process */
                        szEnvBlock,		  /* environment block */
                        cwd,              /* change to the new current directory */
                        &si,              /* (in)  startup information */
                        &pi);             /* (out) process information */

	if(NULL != cwd)
		free((void *)cwd);

	if(NULL != szEnvBlock)
		free(szEnvBlock);

    if(NULL != szCmdLine) 
        free(szCmdLine);
      
    if (!ret) // Launching error
		{
		char * lpMsgBuf;	    
		CloseHandle(stdHandles[0]);
		CloseHandle(stdHandles[1]);
		CloseHandle(stdHandles[2]); 
		FormatMessageA( 
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(char *)&lpMsgBuf,
			0,
			NULL 
		);
		ThrowByName(env, "java/io/IOException", lpMsgBuf);
		// Free the buffer.
		LocalFree( lpMsgBuf );
		cleanUpProcBlock(pCurProcInfo);
		ret = -1;
		}
    else
		{
    	int file_handles[3];
		HANDLE h[2];
		int what;

		EnterCriticalSection(&cs);

		pCurProcInfo -> pid = pi.dwProcessId;
        h[0] = pCurProcInfo -> eventWait;
		h[1] = (HANDLE)_beginthreadex(NULL, 0, waitProcTermination, 
			(void *) pi.dwProcessId, 0, (UINT*) &dwThreadId);
		
		what = WaitForMultipleObjects(2, h, FALSE, INFINITE); 
		if((what != WAIT_OBJECT_0) && (pCurProcInfo -> pid > 0)) // CreateProcess failed
			{
#ifdef DEBUG_MONITOR
			swprintf(buffer, _T("Process %i failed\n"), pi.dwProcessId);
			OutputDebugStringW(buffer);
#endif
			cleanUpProcBlock(pCurProcInfo);
			ThrowByName(env, "java/io/IOException", "Launching failed");
#ifdef DEBUG_MONITOR
			OutputDebugStringW(_T("Process failed\n"));
#endif
			}
		else 
			{
			ret = (long)(pCurProcInfo -> uid);
			
			// Prepare stream handlers to return to java program
			file_handles[0] = (int)stdHandles[0];
			file_handles[1] = (int)stdHandles[1];
			file_handles[2] = (int)stdHandles[2];
			env->SetIntArrayRegion(channels, 0, 3, (jint *)file_handles);
#ifdef DEBUG_MONITOR
			OutputDebugStringW(_T("Process started\n"));
#endif
			}				
		CloseHandle(h[1]);
		LeaveCriticalSection(&cs);

		}

	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);

    return ret;

}


/////////////////////////////////////////////////////////////////////////////////////
// Launcher; just launches process and don't care about it any more
// Arguments: (see Spawner.java)
//			[in]  cmdarray - array of command line elements 
//			[in]  envp - array of environment variables
//			[in]  dir - working directory
/////////////////////////////////////////////////////////////////////////////////////
extern "C"
JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_Spawner_exec1
  (JNIEnv * env, jobject process, jobjectArray cmdarray, jobjectArray envp, jstring dir) 
{

    SECURITY_ATTRIBUTES sa;
    PROCESS_INFORMATION pi = {0};
    STARTUPINFOW si;
	DWORD flags = 0;
    wchar_t * cwd = NULL;
	wchar_t * envBlk = NULL;
    int ret = 0;
	jsize nCmdTokens = 0;
	jsize nEnvVars = 0;
	int i;
	int nPos;
    int nCmdLineLength= 0;
	wchar_t * szCmdLine= 0;
	int nBlkSize = MAX_ENV_SIZE; 
	wchar_t * szEnvBlock = NULL;

    nCmdLineLength= MAX_CMD_SIZE;
	szCmdLine= (wchar_t *)malloc(nCmdLineLength * sizeof(wchar_t));
    szCmdLine[0]= 0;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = 0;
    sa.bInheritHandle = TRUE;


	nCmdTokens = env->GetArrayLength(cmdarray);
    nEnvVars   = env->GetArrayLength(envp);

	nPos = 0;

	// Prepare command line
	for(i = 0; i < nCmdTokens; ++i) 
		{
		jstring item = (jstring)env->GetObjectArrayElement(cmdarray, i);
		jsize    len = env->GetStringLength(item);
		int nCpyLen;
		const wchar_t *  str = (const wchar_t *)env->GetStringChars(item, 0);	
		if(NULL != str)
			{
			int requiredSize= nPos+len+2;
			if (requiredSize > 32*1024) {
				ThrowByName(env, "java/io/IOException", "Command line too long");
				return 0;
			}				
			ensureSize(&szCmdLine, &nCmdLineLength, requiredSize);
			if (NULL == szCmdLine) {
				ThrowByName(env, "java/io/IOException", "Not enough memory");
				return 0;
			}
			    
			if(0 > (nCpyLen = copyTo(szCmdLine + nPos, str, len, nCmdLineLength - nPos)))
				{
				ThrowByName(env, "java/io/Exception", "Command line too long");
				return 0;
				}
			nPos += nCpyLen;
			szCmdLine[nPos] = _T(' ');
			++nPos;
			env->ReleaseStringChars(item, (const jchar *)str);
			}
		}

	szCmdLine[nPos] = _T('\0');

	// Prepare environment block
    if (nEnvVars > 0) 
		{
		szEnvBlock = (wchar_t *)malloc(nBlkSize * sizeof(wchar_t));
		nPos = 0;
		for(i = 0; i < nEnvVars; ++i) 
			{
			jstring item = (jstring)env->GetObjectArrayElement(envp, i);
			jsize    len = env->GetStringLength(item);
			const wchar_t *  str = (const wchar_t *)env->GetStringChars(item, 0);	
			if(NULL != str)
				{
				while((nBlkSize - nPos) <= (len + 2)) // +2 for two '\0'
					{
					nBlkSize += MAX_ENV_SIZE;
					szEnvBlock = (wchar_t *)realloc(szEnvBlock, nBlkSize * sizeof(wchar_t));
					if(NULL == szEnvBlock) 
						{
						ThrowByName(env, "java/io/Exception", "Not enough memory");
						return 0;
						}
					}
				wcsncpy(szEnvBlock + nPos, str, len);
				nPos += len;
				szEnvBlock[nPos] = _T('\0');
				++nPos;
				env->ReleaseStringChars(item, (const jchar *)str);
				}
			}
    	szEnvBlock[nPos] = _T('\0');
		envBlk = szEnvBlock;
		}



    if (dir != 0) 
		{ 
		const wchar_t * str = (const wchar_t *)env->GetStringChars(dir, 0);
		if(NULL != str) 
			{
			cwd = wcsdup(str);
			env->ReleaseStringChars(dir, (const jchar *)str);
			}
		}


    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);





	flags = CREATE_NEW_CONSOLE;
	flags |= CREATE_UNICODE_ENVIRONMENT;
    ret = CreateProcessW(0,                /* executable name */
                        szCmdLine,              /* command line */
                        0,                /* process security attribute */
                        0,                /* thread security attribute */
                        TRUE,             /* inherits system handles */
                        flags,            /* normal attached process */
                        envBlk,       /* environment block */
                        cwd,              /* change to the new current directory */
                        &si,              /* (in)  startup information */
                        &pi);             /* (out) process information */

    

	if(NULL != cwd)
		free(cwd);
	if(NULL != szEnvBlock)
		free(szEnvBlock);
	if(NULL != szCmdLine)
		free(szCmdLine);

    if (!ret)  // error
		{
		char * lpMsgBuf;

		FormatMessage( 
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
				(wchar_t *)&lpMsgBuf,
				0,
				NULL 
		);
		ThrowByName(env, "java/io/IOException", lpMsgBuf);
		// Free the buffer.
		LocalFree( lpMsgBuf );		
		ret = -1;
		}
    else
		{
		// Clean-up
		CloseHandle(pi.hThread);
		CloseHandle(pi.hProcess);
		ret = (long)pi.dwProcessId; //hProcess;
		}


    return ret;

}


/////////////////////////////////////////////////////////////////////////////////////
// Emulation of the signal raising
// Arguments: (see Spawner.java)
//			[in]  uid - unique process ID
//			[in]  signal - signal to raise
/////////////////////////////////////////////////////////////////////////////////////
extern "C"
JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_Spawner_raise
  (JNIEnv * env, jobject process, jint uid, jint signal) 
{
	jint ret = 0;

	HANDLE hProc;
	pProcInfo_t pCurProcInfo = findProcInfo(uid);
#ifdef DEBUG_MONITOR
	wchar_t buffer[100];
#endif
	
	if(NULL == pCurProcInfo) {
		if(SIG_INT == signal) { // Try another way
			return interruptProcess(uid) ;
		}
		return -1;
	}

#ifdef DEBUG_MONITOR
	swprintf(buffer, _T("Spawner received signal %i for process %i\n"), signal, pCurProcInfo -> pid);
	OutputDebugStringW(buffer);
#endif
	
	hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, pCurProcInfo -> pid);

	if(NULL == hProc)
		return -1;

	switch(signal)
		{
		case SIG_NOOP:
			// Wait 0 msec -just check if the process has been still running
			ret = ((WAIT_TIMEOUT == WaitForSingleObject(hProc, 0)) ? 0 : -1);
			break;
		case SIG_HUP:
			// Temporary do nothing
			ret = 0;
			break;
		case SIG_KILL:
		case SIG_TERM:
#ifdef DEBUG_MONITOR
			swprintf(buffer, _T("Spawner received KILL or TERM signal for process %i\n"), 
				pCurProcInfo -> pid);
			OutputDebugStringW(buffer);
#endif
		    SetEvent(pCurProcInfo -> eventTerminate);
#ifdef DEBUG_MONITOR
			OutputDebugStringW(_T("Spawner signalled KILL event\n"));
#endif
			ret = 0;
			break;
		case SIG_INT:
		    ResetEvent(pCurProcInfo -> eventWait);
			PulseEvent(pCurProcInfo -> eventBreak);
			ret = (WaitForSingleObject(pCurProcInfo -> eventWait, 100) == WAIT_OBJECT_0);
			break;
		default:
			break;
		}

	CloseHandle(hProc);
	return ret;


}



/////////////////////////////////////////////////////////////////////////////////////
// Wait for process termination
// Arguments: (see Spawner.java)
//			[in]  uid - unique process ID
/////////////////////////////////////////////////////////////////////////////////////
extern "C"
JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_Spawner_waitFor
  (JNIEnv * env, jobject process, jint uid) 
{
    DWORD exit_code;
    int what=0;
	HANDLE hProc;
	pProcInfo_t pCurProcInfo = findProcInfo(uid);

	if(NULL == pCurProcInfo)
		return -1;
	
    hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, pCurProcInfo -> pid);
	
	if(NULL == hProc)
		return -1;

    what = WaitForSingleObject(hProc, INFINITE);


    if (what == WAIT_OBJECT_0)
		{
		GetExitCodeProcess(hProc, &exit_code);
		}


	if(hProc)
		CloseHandle(hProc);

    return exit_code;
}





// Utilities

/////////////////////////////////////////////////////////////////////////////////////
// Throws Java exception (will be trapped by VM).
// Arguments: 
//			[in]  name - name of exception class
//			[in]  message to assign thi event
/////////////////////////////////////////////////////////////////////////////////////
void ThrowByName(JNIEnv *env, const char *name, const char *msg)
{
    jclass cls = env->FindClass(name);

    if (cls != 0) /* Otherwise an exception has already been thrown */
        env->ThrowNew(cls, msg);

    /* It's a good practice to clean up the local references. */
    env->DeleteLocalRef(cls);
}




/////////////////////////////////////////////////////////////////////////////////////
// Create process description block.
// Arguments:  no
// Return : pointer to the process descriptor			
/////////////////////////////////////////////////////////////////////////////////////
pProcInfo_t createProcInfo()
{
	int i;
	pProcInfo_t p = NULL;

	EnterCriticalSection(&cs);

	if(NULL == pInfo)
		{
		pInfo = (pProcInfo_t)malloc(sizeof(procInfo_t) * MAX_PROCS);
		ZeroMemory(pInfo, sizeof(procInfo_t) * MAX_PROCS);
		}

	for(i = 0; i < MAX_PROCS; ++i)
		{
		if(pInfo[i].pid == 0)
			{
			pInfo[i].pid = -1;
			pInfo[i].uid = ++procCounter;
			p = pInfo + i;
			break;
			}
		}
	
	LeaveCriticalSection(&cs);
	
	return p;
}

/////////////////////////////////////////////////////////////////////////////////////
// Using unique process ID finds process descriptor
// Arguments:  no
// Return : pointer to the process descriptor			
/////////////////////////////////////////////////////////////////////////////////////
pProcInfo_t findProcInfo(int uid)
{
	int i;
	pProcInfo_t p = NULL;
	if(NULL == pInfo)
		return NULL;

	for(i = 0; i < MAX_PROCS; ++i)
		{
		if(pInfo[i].uid == uid)
			{
			p = pInfo + i;
			break;
			}
		}

	return p;
}

/////////////////////////////////////////////////////////////////////////////////////
// Cleans up vacant process descriptor
// Arguments: 
//				pCurProcInfo - pointer to descriptor to clean up
// Return : no
void cleanUpProcBlock(pProcInfo_t pCurProcInfo)
{
	if(0 != pCurProcInfo -> eventBreak) 
		{
		CloseHandle(pCurProcInfo -> eventBreak);
		pCurProcInfo -> eventBreak = 0;
		}
	if(0 != pCurProcInfo -> eventWait) 
		{
		CloseHandle(pCurProcInfo -> eventWait);
		pCurProcInfo -> eventWait = 0;
		}
	if(0 != pCurProcInfo -> eventTerminate) 
		{
		CloseHandle(pCurProcInfo -> eventTerminate);
		pCurProcInfo -> eventTerminate = 0;
		}

	pCurProcInfo -> pid = 0;
}

/////////////////////////////////////////////////////////////////////////////////////
// Running in separae thread and waiting for the process termination
// Arguments:  
//			pv - (int)pv is a pid
// Return : always 0
/////////////////////////////////////////////////////////////////////////////////////
unsigned int _stdcall waitProcTermination(void* pv) 
{
	int i;
	int pid = (int)pv;
#ifdef DEBUG_MONITOR
	wchar_t buffer[1000];
#endif

	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
	
	if(NULL == hProc) 
		{
#ifdef DEBUG_MONITOR
		swprintf(buffer, _T("waitProcTermination: cannot get handler for PID %i (error %i)\n"), 
			pid, 
			GetLastError());
		OutputDebugStringW(buffer);
#endif
		}
	else
		{
		WaitForSingleObject(hProc, INFINITE);
#ifdef DEBUG_MONITOR
		swprintf(buffer, _T("Process PID %i terminated\n"), pid);
		OutputDebugStringW(buffer);
#endif
		}
	
	for(i = 0; i < MAX_PROCS; ++i)
		{
		if(pInfo[i].pid == pid)
			{
			if(WaitForSingleObject(pInfo[i].eventWait, 1) == WAIT_OBJECT_0)  // Correct finish
				{
#ifdef DEBUG_MONITOR
				swprintf(buffer, _T("waitProcTermination: set PID %i to 0\n"), 
					pid, 
					GetLastError());
				OutputDebugStringW(buffer);
#endif
				cleanUpProcBlock(pInfo + i);
				}
			break;
			} // Otherwise failed because was not started
		}

	CloseHandle(hProc);


	return 0;
}

/////////////////////////////////////////////////////////////////////////////////////
// Use this utility program to process correctly quotation marks in the command line
// Arguments:  
//			target - string to copy to
//			source - string to copy from
//			cpyLength - copy length
//			availSpace - size of the target buffer
// Return :number of bytes used in target, or -1 in case of error
/////////////////////////////////////////////////////////////////////////////////////
int copyTo(wchar_t * target, const wchar_t * source, int cpyLength, int availSpace)
{
	BOOL bSlash = FALSE;
	int i = 0, j = 0;
	int totCpyLength = cpyLength;

#define QUOTATION_DO   0
#define QUOTATION_DONE 1
#define QUOTATION_NONE 2

	int nQuotationMode = 0;



	if(availSpace <= cpyLength) // = to reserve space for final '\0'
		return -1;

	if((_T('\"') == *source) && (_T('\"') == *(source + cpyLength - 1)))
		{
		nQuotationMode = QUOTATION_DONE;
		}
	else
	if(wcschr(source, _T(' ')) == NULL)
		{
		// No reason to quotate term becase it doesn't have embedded spaces
		nQuotationMode = QUOTATION_NONE;
		}
	else
		{
		// Needs to be quotated
		nQuotationMode = QUOTATION_DO;
		*target = _T('\"');
		++j;
		}


	for(; i < cpyLength; ++i, ++j) 
		{
		if(source[i] == _T('\\'))
			bSlash = TRUE;
		else
			{
			// Don't escape embracing quotation marks
			if((source[i] == _T('\"')) && !((nQuotationMode == QUOTATION_DONE) && ((i == 0) || (i == (cpyLength - 1))) ) )
				{
				if(!bSlash) // If still not escaped
					{
					if(j == availSpace)
						return -1;
					target[j] = _T('\\');
					++j;
					}
				}
			bSlash = FALSE;
			}

		if(j == availSpace)
			return -1;
		target[j] = source[i];
		}

	if(nQuotationMode == QUOTATION_DO)
		{
		if(j == availSpace)
			return -1;
		target[j] = _T('\"');
		++j;
		}

	return j;
}
