blob: 85a9748813d670f811206c5df07af2fe52e1ad4e [file] [log] [blame]
/*******************************************************************************
* 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 //////////////////////////////////