From b7e830fccc7d9574b58bbf237d6f184a69d621c1 Mon Sep 17 00:00:00 2001 From: Anton Leherbauer Date: Fri, 4 Apr 2008 07:19:10 +0000 Subject: [PATCH] Fix for 225272: Problems with spawner and fast (Dual Core) machines Patch by Johann Draschwandtner (Wind River) --- .../library/Win32ProcessEx.c | 1942 ++++++++--------- .../os/win32/x86/spawner.dll | Bin 40674 -> 40564 bytes 2 files changed, 961 insertions(+), 981 deletions(-) diff --git a/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c b/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c index a387d391f78..b2dae5ab616 100644 --- a/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c +++ b/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c @@ -1,981 +1,961 @@ -/******************************************************************************* - * 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 -#include -#include -#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; -} +/******************************************************************************* + * Copyright (c) 2002, 2008 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 +#include +#include +#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 +void _cdecl 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}, *piCopy; + 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; + 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] = pi.hProcess; + + what = WaitForMultipleObjects(2, h, FALSE, INFINITE); + if(what != WAIT_OBJECT_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); + + // do the cleanup so launch the according thread + // create a copy of the PROCESS_INFORMATION as this might get destroyed + piCopy = (PROCESS_INFORMATION *)malloc(sizeof(PROCESS_INFORMATION)); + memcpy(piCopy, &pi, sizeof(PROCESS_INFORMATION)); + _beginthread(waitProcTermination, 0, (void *)piCopy); + +#ifdef DEBUG_MONITOR + OutputDebugStringW(_T("Process started\n")); +#endif + } + LeaveCriticalSection(&cs); + + } + + CloseHandle(pi.hThread); + + 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 +///////////////////////////////////////////////////////////////////////////////////// +void _cdecl waitProcTermination(void* pv) +{ + PROCESS_INFORMATION *pi = (PROCESS_INFORMATION *)pv; + int i; +#ifdef DEBUG_MONITOR + wchar_t buffer[1000]; +#endif + + // wait for process termination + WaitForSingleObject(pi->hProcess, INFINITE); + + for(i = 0; i < MAX_PROCS; ++i) + { + if(pInfo[i].pid == pi->dwProcessId) + { + cleanUpProcBlock(pInfo + i); +#ifdef DEBUG_MONITOR + swprintf(buffer, _T("waitProcTermination: set PID %i to 0\n"), + pid, + GetLastError()); + OutputDebugStringW(buffer); +#endif + } + } + CloseHandle(pi->hProcess); + + free(pi); +} + +///////////////////////////////////////////////////////////////////////////////////// +// 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; +} diff --git a/core/org.eclipse.cdt.core.win32/os/win32/x86/spawner.dll b/core/org.eclipse.cdt.core.win32/os/win32/x86/spawner.dll index a1cbaec8cd55a466aa07d2d3556fa410905f4264..8887de47cfe2908ff9431ea86eb4fb52fd6b03bf 100644 GIT binary patch literal 40564 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 C=3T-7 literal 40674 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 A-v9sr