From bd18850a17c37e9cbad8452ed7a9e7c4808cc930 Mon Sep 17 00:00:00 2001 From: Anton Leherbauer Date: Thu, 19 Mar 2009 13:21:44 +0000 Subject: [PATCH] Bug 269223 - Spawner broken pipe problem --- .../library/Makefile | 3 +- .../library/Win32ProcessEx.c | 1922 ++++++++--------- .../library/iostream.c | 13 +- .../os/win32/x86/spawner.dll | Bin 42616 -> 39844 bytes 4 files changed, 975 insertions(+), 963 deletions(-) diff --git a/core/org.eclipse.cdt.core.win32/library/Makefile b/core/org.eclipse.cdt.core.win32/library/Makefile index 176727fc01d..a24c2cd6dff 100644 --- a/core/org.eclipse.cdt.core.win32/library/Makefile +++ b/core/org.eclipse.cdt.core.win32/library/Makefile @@ -12,7 +12,8 @@ JDK_INCLUDES= "$(JAVA_HOME)/include" JDK_OS_INCLUDES= "$(JAVA_HOME)/include/$(OS)" CC=g++ -CFLAGS = -DUNICODE -I$(JDK_INCLUDES) -I$(JDK_OS_INCLUDES) +DEBUG_FLAGS = -D_UNICODE -DDEBUG_MONITOR -DREAD_REPORT +CFLAGS = -DUNICODE -I$(JDK_INCLUDES) -I$(JDK_OS_INCLUDES) CXX=g++ CXXFLAGS=$(CFLAGS) diff --git a/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c b/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c index b2dae5ab616..a069bd5f019 100644 --- a/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c +++ b/core/org.eclipse.cdt.core.win32/library/Win32ProcessEx.c @@ -1,961 +1,961 @@ -/******************************************************************************* - * 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; -} +/******************************************************************************* + * 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"), + pInfo[i].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/library/iostream.c b/core/org.eclipse.cdt.core.win32/library/iostream.c index 92637ec84d3..bbce17fa2db 100644 --- a/core/org.eclipse.cdt.core.win32/library/iostream.c +++ b/core/org.eclipse.cdt.core.win32/library/iostream.c @@ -125,6 +125,17 @@ JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_SpawnerInputStream_rea nBuffOffset = 0; break; } + else + { + // buffer overflow? + // according to msdn this happens in message read mode only +#ifdef DEBUG_MONITOR + _stprintf(buffer, _T("Buffer full - %i, bytes read: %i\n"), fd, nNumberOfBytesRead); + OutputDebugStringW(buffer); +#endif + // nNumberOfBytesRead can be 0 here for unknown reason (bug 269223) + nNumberOfBytesRead = nNumberOfBytesToRead; + } } } if(nNumberOfBytesRead > 0) @@ -138,7 +149,7 @@ JNIEXPORT jint JNICALL Java_org_eclipse_cdt_utils_spawner_SpawnerInputStream_rea CloseHandle(overlapped.hEvent); #ifdef DEBUG_MONITOR #ifdef READ_REPORT - _stprintf(buffer, _T("End read %i\n"), fd); + _stprintf(buffer, _T("End read %i - bytes read: %d\n"), fd, nBuffOffset); OutputDebugStringW(buffer); #endif #endif 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 45571853c5054d81777be8d67645d52989c64264..04d429dcf7e24080890eee4f26eb9cf37112dd15 100644 GIT binary patch literal 39844 zcmeHw4|tT-neRym8Znxv!KN*3M;mRV5R;HUlYn+08AS~^5)jrxzYNKQ1ST`n`2)d) zHaMA<*)cXxS1s$>7L@IBt=qC)Rw

MQy;+UFzB{Zryv?z4+Y0WZkrztEpw1`+Luy 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