| /******************************************************************************* |
| * Copyright (c) 2002, 2015 QNX Software Systems and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * QNX Software Systems - initial API and implementation |
| * Wind River Systems, Inc. |
| * |
| * starter.cpp |
| * |
| * This is a small utility for windows spawner |
| *******************************************************************************/ |
| |
| #define STRICT |
| #define _WIN32_WINNT 0x0500 |
| #include <windows.h> |
| #include <process.h> |
| #include <tchar.h> |
| #include <stdio.h> |
| #include <psapi.h> |
| #include <stdbool.h> |
| |
| #include "util.h" |
| |
| #define MAX_CMD_LINE_LENGTH (2049) |
| #define PIPE_NAME_LENGTH 100 |
| |
| int copyTo(wchar_t *target, const wchar_t *source, int cpyLength, int availSpace); |
| void DisplayErrorMessage(); |
| |
| // BOOL KillProcessEx(DWORD dwProcessId); // Handle of the process |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) { // control signal type |
| BOOL ret = TRUE; |
| switch (dwCtrlType) { |
| case CTRL_C_EVENT: |
| break; |
| case CTRL_BREAK_EVENT: |
| break; |
| case CTRL_CLOSE_EVENT: |
| ret = FALSE; |
| break; |
| case CTRL_LOGOFF_EVENT: |
| ret = FALSE; |
| break; |
| case CTRL_SHUTDOWN_EVENT: |
| ret = FALSE; |
| break; |
| default: |
| break; |
| } |
| return ret; |
| } |
| |
| // The default here means we haven't checked yet |
| // i.e. cygwin is true but the bin dir hasn't been captured |
| wchar_t *cygwinBin = NULL; |
| bool _isCygwin = true; |
| |
| bool isCygwin(HANDLE process) { |
| // Have we checked before? |
| if (cygwinBin || !_isCygwin) { |
| return _isCygwin; |
| } |
| |
| // See if this process loaded cygwin, need a different SIGINT for them |
| HMODULE mods[1024]; |
| DWORD needed; |
| if (EnumProcessModules(process, mods, sizeof(mods), &needed)) { |
| int i; |
| needed /= sizeof(HMODULE); |
| for (i = 0; i < needed; ++i) { |
| wchar_t modName[MAX_PATH]; |
| if (GetModuleFileNameEx(process, mods[i], modName, MAX_PATH)) { |
| wchar_t *p = wcsrchr(modName, L'\\'); |
| if (p) { |
| *p = 0; // Null terminate there for future reference |
| if (!wcscmp(++p, L"cygwin1.dll")) { |
| _isCygwin = true; |
| // Store away the bind dir |
| cygwinBin = wcsdup(modName); |
| return _isCygwin; |
| } |
| } |
| } |
| } |
| } |
| |
| _isCygwin = false; |
| return _isCygwin; |
| } |
| |
| bool runCygwinCommand(wchar_t *command) { |
| wchar_t cygcmd[1024]; |
| swprintf(cygcmd, sizeof(cygcmd) / sizeof(cygcmd[0]), L"%s\\%s", cygwinBin, command); |
| |
| STARTUPINFO si; |
| ZeroMemory(&si, sizeof(si)); |
| si.cb = sizeof(si); |
| PROCESS_INFORMATION pi; |
| ZeroMemory(&pi, sizeof(pi)); |
| if (CreateProcess(NULL, cygcmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { |
| WaitForSingleObject(pi.hProcess, INFINITE); |
| CloseHandle(pi.hThread); |
| CloseHandle(pi.hProcess); |
| return true; |
| } else if (CreateProcess(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { |
| WaitForSingleObject(pi.hProcess, INFINITE); |
| CloseHandle(pi.hThread); |
| CloseHandle(pi.hProcess); |
| return true; |
| } |
| return false; |
| } |
| |
| 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 (*ptr) { |
| *psize = size; |
| } else { |
| *psize = 0; |
| } |
| } |
| } |
| |
| int main() { |
| |
| int argc; |
| wchar_t **argv = CommandLineToArgvW(GetCommandLine(), &argc); |
| |
| // Make sure that we've been passed the right number of arguments |
| if (argc < 8) { |
| wprintf(L"Usage: %s (four inheritable event handles) (CommandLineToSpawn)\n", argv[0]); |
| return 0; |
| } |
| |
| // Construct the full command line |
| int nCmdLineLength = MAX_CMD_LINE_LENGTH; |
| wchar_t *szCmdLine = (wchar_t *)malloc(nCmdLineLength * sizeof(wchar_t)); |
| szCmdLine[0] = 0; |
| int nPos = 0; |
| |
| for (int i = 8; i < argc; ++i) { |
| int nCpyLen; |
| int len = wcslen(argv[i]); |
| int requiredSize = nPos + len + 2; |
| if (requiredSize > 32 * 1024) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Command line too long!\n"); |
| } |
| return 0; |
| } |
| ensureSize(&szCmdLine, &nCmdLineLength, requiredSize); |
| if (!szCmdLine) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Not enough memory to build cmd line!\n"); |
| } |
| return 0; |
| } |
| if (0 > (nCpyLen = copyTo(szCmdLine + nPos, argv[i], len, nCmdLineLength - nPos))) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Not enough space to build command line\n"); |
| } |
| return 0; |
| } |
| nPos += nCpyLen; |
| szCmdLine[nPos] = _T(' '); |
| ++nPos; |
| } |
| szCmdLine[nPos] = _T('\0'); |
| |
| STARTUPINFOW si = {sizeof(si)}; |
| PROCESS_INFORMATION pi = {0}; |
| DWORD dwExitCode = 0; |
| |
| BOOL exitProc = FALSE; |
| HANDLE waitEvent = OpenEventW(EVENT_ALL_ACCESS, TRUE, argv[4]); |
| HANDLE h[5]; |
| h[0] = OpenEventW(EVENT_ALL_ACCESS, TRUE, argv[3]); // simulated SIGINT (CTRL-C or Cygwin 'kill -SIGINT') |
| // h[1] we reserve for the process handle |
| h[2] = OpenEventW(EVENT_ALL_ACCESS, TRUE, argv[5]); // simulated SIGTERM |
| h[3] = OpenEventW(EVENT_ALL_ACCESS, TRUE, argv[6]); // simulated SIGKILL |
| h[4] = OpenEventW(EVENT_ALL_ACCESS, TRUE, argv[7]); // CTRL-C, in all cases |
| |
| SetConsoleCtrlHandler(HandlerRoutine, TRUE); |
| |
| int parentPid = wcstol(argv[1], NULL, 10); |
| int nCounter = wcstol(argv[2], NULL, 10); |
| wchar_t inPipeName[PIPE_NAME_LENGTH]; |
| wchar_t outPipeName[PIPE_NAME_LENGTH]; |
| wchar_t errPipeName[PIPE_NAME_LENGTH]; |
| |
| swprintf(inPipeName, sizeof(inPipeName) / sizeof(inPipeName[0]), L"\\\\.\\pipe\\stdin%08i%010i", parentPid, |
| nCounter); |
| swprintf(outPipeName, sizeof(outPipeName) / sizeof(outPipeName[0]), L"\\\\.\\pipe\\stdout%08i%010i", parentPid, |
| nCounter); |
| swprintf(errPipeName, sizeof(errPipeName) / sizeof(errPipeName[0]), L"\\\\.\\pipe\\stderr%08i%010i", parentPid, |
| nCounter); |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Pipes: %s, %s, %s\n", inPipeName, outPipeName, errPipeName); |
| } |
| |
| HANDLE stdHandles[3]; |
| |
| SECURITY_ATTRIBUTES sa; |
| sa.nLength = sizeof(SECURITY_ATTRIBUTES); |
| sa.bInheritHandle = TRUE; |
| sa.lpSecurityDescriptor = NULL; |
| |
| if ((INVALID_HANDLE_VALUE == |
| (stdHandles[0] = CreateFileW(inPipeName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, &sa))) || |
| (INVALID_HANDLE_VALUE == |
| (stdHandles[1] = CreateFileW(outPipeName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, &sa))) || |
| (INVALID_HANDLE_VALUE == |
| (stdHandles[2] = CreateFileW(errPipeName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, &sa)))) { |
| |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Failed to open pipe %i, %i, %i: %i\n", stdHandles[0], stdHandles[1], stdHandles[2], |
| GetLastError()); |
| } |
| CloseHandle(stdHandles[0]); |
| CloseHandle(stdHandles[1]); |
| CloseHandle(stdHandles[2]); |
| return -1; |
| } |
| SetHandleInformation(stdHandles[0], HANDLE_FLAG_INHERIT, TRUE); |
| SetHandleInformation(stdHandles[1], HANDLE_FLAG_INHERIT, TRUE); |
| SetHandleInformation(stdHandles[2], HANDLE_FLAG_INHERIT, TRUE); |
| |
| if (!SetStdHandle(STD_INPUT_HANDLE, stdHandles[0]) || !SetStdHandle(STD_OUTPUT_HANDLE, stdHandles[1]) || |
| !SetStdHandle(STD_ERROR_HANDLE, stdHandles[2])) { |
| |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Failed to reassign standard streams: %i\n", GetLastError()); |
| } |
| CloseHandle(stdHandles[0]); |
| CloseHandle(stdHandles[1]); |
| CloseHandle(stdHandles[2]); |
| return -1; |
| } |
| |
| if (isTraceEnabled(CDT_TRACE_MONITOR_DETAILS)) { |
| wchar_t *lpvEnv = GetEnvironmentStringsW(); |
| |
| if (lpvEnv) { |
| // Variable strings are separated by NULL byte, and the block is |
| // terminated by a NULL byte. |
| |
| cdtTrace(L"Starter: Environment\n"); |
| for (wchar_t *lpszVariable = lpvEnv; *lpszVariable; lpszVariable += wcslen(lpszVariable) + 1) { |
| cdtTrace(L"%s\n", lpszVariable); |
| } |
| |
| FreeEnvironmentStringsW(lpvEnv); |
| } else { |
| // If the returned pointer is NULL, exit. |
| cdtTrace(L"Cannot Read Environment\n"); |
| } |
| } |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Starting: %s\n", szCmdLine); |
| } |
| // Create job object |
| HANDLE hJob = CreateJobObject(NULL, NULL); |
| if (hJob) { |
| // Configure job to |
| // - terminate all associated processes when the last handle to it is closed |
| // - allow child processes to break away from the job. |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo; |
| ZeroMemory(&jobInfo, sizeof(jobInfo)); |
| jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK; |
| if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo))) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Cannot set job information\n"); |
| DisplayErrorMessage(); |
| } |
| } |
| } else if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Cannot create job object\n"); |
| DisplayErrorMessage(); |
| } |
| // Spawn the other processes as part of this Process Group |
| // If this process is already part of a job, the flag CREATE_BREAKAWAY_FROM_JOB |
| // makes the child process detach from the job, such that we can assign it |
| // to our own job object. |
| BOOL f = CreateProcessW(NULL, szCmdLine, NULL, NULL, TRUE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi); |
| // If breaking away from job is not permitted, retry without breakaway flag |
| if (!f) { |
| f = CreateProcessW(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); |
| } |
| |
| // We don't need them any more |
| CloseHandle(stdHandles[0]); |
| CloseHandle(stdHandles[1]); |
| CloseHandle(stdHandles[2]); |
| |
| if (f) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Process %i started\n", pi.dwProcessId); |
| } |
| SetEvent(waitEvent); // Means that process has been spawned |
| CloseHandle(pi.hThread); |
| h[1] = pi.hProcess; |
| |
| if (hJob) { |
| if (!AssignProcessToJobObject(hJob, pi.hProcess)) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Cannot assign process %i to a job\n", pi.dwProcessId); |
| DisplayErrorMessage(); |
| } |
| } |
| } |
| |
| while (!exitProc) { |
| // Wait for the spawned-process to die or for the event |
| // indicating that the processes should be forcibly killed. |
| DWORD event = WaitForMultipleObjects(5, h, FALSE, INFINITE); |
| switch (event) { |
| case WAIT_OBJECT_0 + 0: // SIGINT |
| case WAIT_OBJECT_0 + 4: // CTRL-C |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"starter (PID %i) received CTRL-C event\n", GetCurrentProcessId()); |
| } |
| if ((event == (WAIT_OBJECT_0 + 0)) && isCygwin(h[1])) { |
| // Need to issue a kill command |
| wchar_t kill[1024]; |
| swprintf(kill, sizeof(kill) / sizeof(kill[0]), L"kill -SIGINT %d", pi.dwProcessId); |
| if (!runCygwinCommand(kill)) { |
| // fall back to console event |
| GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); |
| } |
| } else { |
| GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); |
| } |
| |
| SetEvent(waitEvent); |
| break; |
| |
| case WAIT_OBJECT_0 + 1: // App terminated normally |
| // Make it's exit code our exit code |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"starter: launched process has been terminated(PID %i)\n", pi.dwProcessId); |
| } |
| GetExitCodeProcess(pi.hProcess, &dwExitCode); |
| exitProc = TRUE; |
| break; |
| |
| // Terminate and Kill behavior differ only for cygwin processes, where |
| // we use the cygwin 'kill' command. We send a SIGKILL in one case, |
| // SIGTERM in the other. For non-cygwin processes, both requests |
| // are treated exactly the same |
| case WAIT_OBJECT_0 + 2: // TERM |
| case WAIT_OBJECT_0 + 3: // KILL |
| { |
| const wchar_t *signal = (event == WAIT_OBJECT_0 + 2) ? L"TERM" : L"KILL"; |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"starter received %s event (PID %i)\n", signal, GetCurrentProcessId()); |
| } |
| if (isCygwin(h[1])) { |
| // Need to issue a kill command |
| wchar_t kill[1024]; |
| swprintf(kill, sizeof(kill) / sizeof(kill[0]), L"kill -%s %d", signal, pi.dwProcessId); |
| if (!runCygwinCommand(kill)) { |
| // fall back to console event |
| GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); |
| } |
| } else { |
| GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); |
| } |
| |
| SetEvent(waitEvent); |
| |
| if (hJob) { |
| if (!TerminateJobObject(hJob, (DWORD)-1)) { |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Cannot terminate job\n"); |
| DisplayErrorMessage(); |
| } |
| } |
| } |
| |
| // Note that we keep trucking until the child process terminates (case WAIT_OBJECT_0 + 1) |
| break; |
| } |
| |
| default: |
| // Unexpected code |
| if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| DisplayErrorMessage(); |
| } |
| exitProc = TRUE; |
| break; |
| } |
| } |
| } else if (isTraceEnabled(CDT_TRACE_MONITOR)) { |
| cdtTrace(L"Cannot start: %s\n", szCmdLine); |
| |
| DisplayErrorMessage(); |
| } |
| |
| free(szCmdLine); |
| |
| CloseHandle(waitEvent); |
| CloseHandle(h[0]); |
| CloseHandle(h[1]); |
| CloseHandle(h[2]); |
| CloseHandle(h[3]); |
| CloseHandle(h[4]); |
| |
| return dwExitCode; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////////// |
| // 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; |
| |
| enum { QUOTATION_DO, QUOTATION_DONE, QUOTATION_NONE } nQuotationMode = QUOTATION_DO; |
| |
| if (availSpace <= cpyLength) { // = to reserve space for '\0' |
| return -1; |
| } |
| |
| if ((_T('\"') == *source) && (_T('\"') == *(source + cpyLength - 1))) { |
| // Already done |
| nQuotationMode = QUOTATION_DONE; |
| } else if (wcschr(source, _T(' '))) { |
| // Needs to be quotated |
| nQuotationMode = QUOTATION_DO; |
| *target = _T('\"'); |
| ++j; |
| } else { |
| // No reason to quotate term because it doesn't have embedded spaces |
| nQuotationMode = QUOTATION_NONE; |
| } |
| |
| 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 (j == availSpace) { |
| return -1; |
| } |
| target[j] = _T('\\'); |
| ++j; |
| } |
| bSlash = false; |
| } else { |
| 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; |
| } |
| |
| void DisplayErrorMessage() { |
| wchar_t *lpMsgBuf; |
| FormatMessageW(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); |
| OutputDebugStringW(lpMsgBuf); |
| // Free the buffer. |
| LocalFree(lpMsgBuf); |
| } |
| |
| //////////////////////////////// End of File ////////////////////////////////// |