You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
980 lines
26 KiB
980 lines
26 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1995
|
|
//
|
|
// File: process.cxx
|
|
//
|
|
// Contents: Implementation of the CProcessThread class
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "headers.hxx"
|
|
|
|
#include "stdlib.h"
|
|
|
|
DeclareTag(tagProcess, "MTScript", "Monitor Process Creation");
|
|
|
|
char *g_pszExitCodeStr = "PROCESS_EXIT_CODE=";
|
|
|
|
#define PIPEREAD_TIMEOUT 2
|
|
|
|
CProcessParams::CProcessParams()
|
|
{
|
|
pszCommand = 0;
|
|
pszDir = 0;
|
|
pszTitle = 0;
|
|
}
|
|
|
|
CProcessParams::~CProcessParams()
|
|
{
|
|
Free();
|
|
}
|
|
|
|
bool CProcessParams::Copy(const PROCESS_PARAMS *params)
|
|
{
|
|
return Assign(*params);
|
|
}
|
|
|
|
void CProcessParams::Free()
|
|
{
|
|
SysFreeString(pszCommand);
|
|
SysFreeString(pszDir);
|
|
SysFreeString(pszTitle);
|
|
|
|
pszCommand = 0;
|
|
pszDir = 0;
|
|
pszTitle = 0;
|
|
}
|
|
|
|
bool CProcessParams::Assign(const PROCESS_PARAMS ¶ms)
|
|
{
|
|
pszCommand = SysAllocString(params.pszCommand);
|
|
pszDir = SysAllocString(params.pszDir);
|
|
pszTitle = SysAllocString(params.pszTitle);
|
|
fMinimize = params.fMinimize;
|
|
fGetOutput = params.fGetOutput;
|
|
fNoEnviron = params.fNoEnviron;
|
|
fNoCrashPopup = params.fNoCrashPopup;
|
|
|
|
if (params.pszCommand && !pszCommand ||
|
|
params.pszDir && !pszDir ||
|
|
params.pszTitle && !pszTitle)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::CProcessThread, public
|
|
//
|
|
// Synopsis: ctor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CProcessThread::CProcessThread(CScriptHost *pSH)
|
|
: _cstrOutput(CSTR_NOINIT)
|
|
{
|
|
_ulRefs = 1;
|
|
|
|
_pSH = pSH;
|
|
_pSH->AddRef();
|
|
|
|
// This object should be initialized to all zero.
|
|
|
|
Assert(_hPipe == NULL);
|
|
Assert(_hJob == NULL);
|
|
Assert(_hIoPort == NULL);
|
|
Assert(_dwExitCode == 0);
|
|
Assert(_piProc.hProcess == NULL);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::~CProcessThread, public
|
|
//
|
|
// Synopsis: dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CProcessThread::~CProcessThread()
|
|
{
|
|
AssertSz(!_hJob, "NONFATAL: Job handle should be NULL!");
|
|
AssertSz(!_hIoPort, "NONFATAL: Port handle should be NULL!");
|
|
|
|
TraceTag((tagProcess, "Closing process PID=%d", ProcId()));
|
|
if (_hPipe)
|
|
CloseHandle(_hPipe);
|
|
|
|
if (_piProc.hProcess)
|
|
CloseHandle(_piProc.hProcess);
|
|
|
|
ReleaseInterface(_pSH);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::QueryInterface, public
|
|
//
|
|
// Synopsis: Standard implementation. This class implements no meaningful
|
|
// interfaces.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CProcessThread::QueryInterface(REFIID iid, void **ppvObj)
|
|
{
|
|
if (iid == IID_IUnknown)
|
|
{
|
|
*ppvObj = (IUnknown *)this;
|
|
}
|
|
else
|
|
{
|
|
*ppvObj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
((IUnknown *)*ppvObj)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::Init, public
|
|
//
|
|
// Synopsis: Initializes the class
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
CProcessThread::Init()
|
|
{
|
|
//$ FUTURE: For NT4 and Win9x support we will need to dynamically use the
|
|
// job APIs since those platforms do not support them.
|
|
|
|
_hJob = CreateJobObject(NULL, NULL);
|
|
if (!_hJob)
|
|
{
|
|
TraceTag((tagError, "CreateJobObject failed with %d", GetLastError()));
|
|
return FALSE;
|
|
}
|
|
|
|
return CThreadComm::Init();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::GetProcessOutput, public
|
|
//
|
|
// Synopsis: Returns the collected STDOUT/STDERR data from the process
|
|
// up to this point.
|
|
//
|
|
// Arguments: [pbstrOutput] -- Pointer to BSTR where the string should be
|
|
// copied. The BSTR will be allocated.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CProcessThread::GetProcessOutput(BSTR *pbstrOutput)
|
|
{
|
|
LOCK_LOCALS(this);
|
|
|
|
return _cstrOutput.AllocBSTR(pbstrOutput);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::GetExitCode, public
|
|
//
|
|
// Synopsis: Returns the exit code of the process. If the process is still
|
|
// running, it returns STILL_ACTIVE (0x103, dec 259)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DWORD
|
|
CProcessThread::GetExitCode()
|
|
{
|
|
DWORD dwExit;
|
|
|
|
AssertSz(_piProc.hProcess, "FATAL: Bad process handle");
|
|
|
|
GetExitCodeProcess(_piProc.hProcess, &dwExit);
|
|
|
|
if (dwExit == STILL_ACTIVE || !_fUseExitCode)
|
|
{
|
|
return STILL_ACTIVE;
|
|
}
|
|
|
|
return _dwExitCode;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::GetDeadTime, public
|
|
//
|
|
// Synopsis: Returns the number of ms since the process exited. If it
|
|
// hasn't exited yet, it returns 0.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
CProcessThread::GetDeadTime()
|
|
{
|
|
_int64 i64CurrentTime;
|
|
DWORD dwExit;
|
|
|
|
AssertSz(_piProc.hProcess, "FATAL: Bad process handle");
|
|
|
|
GetExitCodeProcess(_piProc.hProcess, &dwExit);
|
|
|
|
// If the process is still running return 0.
|
|
if (dwExit == STILL_ACTIVE || !_i64ExitTime)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
GetSystemTimeAsFileTime((FILETIME*)&i64CurrentTime);
|
|
|
|
// Calculate the difference in milli-seconds. There are 10,000
|
|
// FILETIME intervals in one second (each increment of 1 represents 100
|
|
// nano-seconds)
|
|
|
|
return (i64CurrentTime - _i64ExitTime) / 10000;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::Terminate, public
|
|
//
|
|
// Synopsis: Exits this thread. The process we own is terminated if it's
|
|
// still running.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::Terminate()
|
|
{
|
|
DWORD dwCode;
|
|
CProcessThread * pThis = this;
|
|
BOOL fTerminated = FALSE;
|
|
BOOL fFireEvent = FALSE;
|
|
|
|
VERIFY_THREAD();
|
|
|
|
TraceTag((tagProcess, "Entering Terminate"));
|
|
|
|
//
|
|
// Flush out any data the process may have written to the pipe.
|
|
//
|
|
ReadPipeData();
|
|
|
|
//
|
|
// Make sure we've received all messages from our completion port
|
|
//
|
|
CheckIoPort();
|
|
|
|
if (_piProc.hProcess)
|
|
{
|
|
//
|
|
// Terminate the process(es) if still running.
|
|
//
|
|
|
|
fFireEvent = TRUE;
|
|
|
|
GetExitCodeProcess(_piProc.hProcess, &dwCode);
|
|
|
|
if (dwCode == STILL_ACTIVE)
|
|
{
|
|
TraceTag((tagProcess, "Root process still active!"));
|
|
|
|
if (_hJob)
|
|
{
|
|
TerminateJobObject(_hJob, ERROR_PROCESS_ABORTED);
|
|
}
|
|
else
|
|
{
|
|
TraceTag((tagProcess, "Terminating process, not job!"));
|
|
|
|
TerminateProcess(_piProc.hProcess, ERROR_PROCESS_ABORTED);
|
|
}
|
|
|
|
dwCode = ERROR_PROCESS_ABORTED;
|
|
|
|
//
|
|
// Flush out any data the process may have written to the pipe.
|
|
//
|
|
ReadPipeData();
|
|
|
|
//
|
|
// Make sure we've received all messages from our completion port
|
|
//
|
|
CheckIoPort();
|
|
|
|
fTerminated = TRUE;
|
|
}
|
|
|
|
if (!_fUseExitCode)
|
|
{
|
|
_dwExitCode = dwCode;
|
|
_fUseExitCode = TRUE;
|
|
}
|
|
}
|
|
|
|
if (_hIoPort)
|
|
CloseHandle(_hIoPort);
|
|
|
|
if (_hJob)
|
|
CloseHandle(_hJob);
|
|
|
|
_hIoPort = NULL;
|
|
_hJob = NULL;
|
|
|
|
// We need to hold onto _piProc.hProcess so the system doesn't reuse the
|
|
// process ID.
|
|
|
|
if (fFireEvent)
|
|
{
|
|
if (fTerminated)
|
|
{
|
|
PostToThread(_pSH, MD_PROCESSTERMINATED, &pThis, sizeof(CProcessThread*));
|
|
}
|
|
else
|
|
{
|
|
PostToThread(_pSH, MD_PROCESSEXITED, &pThis, sizeof(CProcessThread*));
|
|
}
|
|
}
|
|
|
|
GetSystemTimeAsFileTime((FILETIME*)&_i64ExitTime);
|
|
|
|
TraceTag((tagProcess, "Exiting process thread!"));
|
|
|
|
Release();
|
|
|
|
ExitThread(0);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::RealThreadRoutine, public
|
|
//
|
|
// Synopsis: Main loop which runs this thread while it is active. The thread
|
|
// will terminate if this method returns. Termination normally
|
|
// happens when the client disconnects by calling Terminate().
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DWORD
|
|
CProcessThread::ThreadMain()
|
|
{
|
|
DWORD dwRet;
|
|
HANDLE ahEvents[3];
|
|
int cEvents = 2;
|
|
HRESULT hr;
|
|
|
|
PROCESS_PARAMS *pParams = (PROCESS_PARAMS *)_pvParams;
|
|
|
|
_ProcParams.Copy(pParams);
|
|
|
|
#if DBG == 1
|
|
{
|
|
char achBuf[10];
|
|
CStr cstrCmd;
|
|
|
|
cstrCmd.Set(pParams->pszCommand);
|
|
cstrCmd.GetMultiByte(achBuf, 10);
|
|
SetName(achBuf);
|
|
}
|
|
#endif
|
|
|
|
AddRef();
|
|
|
|
hr = LaunchProcess(pParams);
|
|
|
|
ThreadStarted(hr); // Release our calling thread
|
|
|
|
if (hr)
|
|
{
|
|
Terminate();
|
|
return 1; // Just in case
|
|
}
|
|
|
|
ahEvents[0] = _hCommEvent;
|
|
ahEvents[1] = _piProc.hProcess;
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// Anonymous pipes don't support asynchronous I/O, and we can't wait
|
|
// on a handle to see if there's data on the completion port. So,
|
|
// what we do is poll for those every 2 seconds instead.
|
|
//
|
|
dwRet = WaitForMultipleObjects(cEvents,
|
|
ahEvents,
|
|
FALSE,
|
|
(_hPipe || _hIoPort)
|
|
? PIPEREAD_TIMEOUT * 1000
|
|
: INFINITE);
|
|
if (dwRet == WAIT_OBJECT_0)
|
|
{
|
|
//
|
|
// Another thread is sending us a message.
|
|
//
|
|
HandleThreadMessage();
|
|
}
|
|
else if (dwRet == WAIT_OBJECT_0 + 1)
|
|
{
|
|
//
|
|
// The process terminated.
|
|
//
|
|
HandleProcessExit();
|
|
}
|
|
else if (dwRet == WAIT_TIMEOUT)
|
|
{
|
|
ReadPipeData();
|
|
|
|
CheckIoPort();
|
|
}
|
|
else if (dwRet == WAIT_FAILED)
|
|
{
|
|
AssertSz(FALSE, "NONFATAL: WaitForMultipleObjectsFailure");
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Make sure we clean everything up OK
|
|
Terminate();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::HandleThreadMessage, public
|
|
//
|
|
// Synopsis: Handles any messages other threads send us.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::HandleThreadMessage()
|
|
{
|
|
THREADMSG tm;
|
|
BYTE bData[MSGDATABUFSIZE];
|
|
DWORD cbData;
|
|
|
|
while (GetNextMsg(&tm, (void **)bData, &cbData))
|
|
{
|
|
switch (tm)
|
|
{
|
|
case MD_PLEASEEXIT:
|
|
//
|
|
// We're being asked to terminate.
|
|
//
|
|
Terminate();
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE, "FATAL: CProcessThread got a message it couldn't handle!");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::HandleProcessExit, public
|
|
//
|
|
// Synopsis: The process we started has exited. Send out the status and
|
|
// exit.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::HandleProcessExit()
|
|
{
|
|
//
|
|
// Flush out any data the process may have written to the pipe.
|
|
//
|
|
ReadPipeData();
|
|
|
|
// Save the exit code
|
|
if (!_fUseExitCode)
|
|
{
|
|
GetExitCodeProcess(_piProc.hProcess, &_dwExitCode);
|
|
|
|
_fUseExitCode = TRUE;
|
|
|
|
AssertSz(_dwExitCode != STILL_ACTIVE, "LEAK: Exited process still active!");
|
|
}
|
|
|
|
Terminate();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::IsDataInPipe, public
|
|
//
|
|
// Synopsis: Is there any data waiting to be read from the anonymous pipe?
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL
|
|
CProcessThread::IsDataInPipe()
|
|
{
|
|
DWORD dwBytesAvail = 0;
|
|
BOOL fSuccess;
|
|
|
|
if (!_hPipe)
|
|
return FALSE;
|
|
|
|
fSuccess = PeekNamedPipe(_hPipe,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&dwBytesAvail,
|
|
NULL);
|
|
|
|
if (!fSuccess)
|
|
{
|
|
//
|
|
// Stop trying to read from the pipe
|
|
//
|
|
CloseHandle(_hPipe);
|
|
_hPipe = NULL;
|
|
}
|
|
|
|
return (dwBytesAvail != 0);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::ReadPipeData, public
|
|
//
|
|
// Synopsis: Read any and all data which is waiting on the pipe. This will
|
|
// be anything that the process writes to STDOUT.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::ReadPipeData()
|
|
{
|
|
BOOL fSuccess = TRUE;
|
|
DWORD cbRead;
|
|
char *pch;
|
|
|
|
while (IsDataInPipe())
|
|
{
|
|
CStr cstrTemp;
|
|
|
|
fSuccess = ReadFile(_hPipe,
|
|
_abBuffer,
|
|
PIPE_BUFFER_SIZE - 1,
|
|
&cbRead,
|
|
NULL);
|
|
|
|
if (!fSuccess || cbRead == 0)
|
|
{
|
|
Terminate();
|
|
|
|
return; // just in case
|
|
}
|
|
|
|
_abBuffer[cbRead] = '\0';
|
|
|
|
//
|
|
// Is the process letting us know that we should pretend the exit code
|
|
// is some value other than the actual exit code?
|
|
// (using PROCESS_EXIT_CODE=x in its STDOUT stream)
|
|
//
|
|
pch = strstr((const char*)_abBuffer, g_pszExitCodeStr);
|
|
if (pch)
|
|
{
|
|
_dwExitCode = atol(pch + strlen(g_pszExitCodeStr));
|
|
_fUseExitCode = TRUE;
|
|
}
|
|
|
|
// The string is now in _abBuffer. Add it to our buffer.
|
|
//
|
|
// Assume the data coming across is MultiByte string data.
|
|
//
|
|
LOCK_LOCALS(this);
|
|
|
|
_cstrOutput.AppendMultiByte((const char *)_abBuffer);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::CheckIoPort, public
|
|
//
|
|
// Synopsis: Check the CompletionPort that we are using to receive messages
|
|
// from the job object. This will tell us if any process we
|
|
// created or that is a child of the one we created crashed.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::CheckIoPort()
|
|
{
|
|
DWORD dwCode = 0;
|
|
DWORD dwKey = 0;
|
|
DWORD dwParam = 0;;
|
|
|
|
if (!_hIoPort)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (GetQueuedCompletionStatus(_hIoPort,
|
|
&dwCode,
|
|
&dwKey,
|
|
(LPOVERLAPPED*)&dwParam,
|
|
0))
|
|
{
|
|
AssertSz(dwKey == (DWORD)this, "NONFATAL: Bogus port value from GetQueuedCompletionStatus");
|
|
|
|
switch (dwCode)
|
|
{
|
|
case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
|
|
|
|
{
|
|
CProcessThread *pThis = this;
|
|
|
|
// There is no reliable way to get the actual exit code, since
|
|
// the process is already gone and we don't have a handle to it.
|
|
|
|
TraceTag((tagProcess, "Process %d crashed!", dwParam));
|
|
|
|
PostToThread(_pSH, MD_PROCESSCRASHED, &pThis, sizeof(CProcessThread*));
|
|
}
|
|
|
|
break;
|
|
|
|
case JOB_OBJECT_MSG_EXIT_PROCESS:
|
|
TraceTag((tagProcess, "Process %d exited.", dwParam));
|
|
break;
|
|
|
|
case JOB_OBJECT_MSG_NEW_PROCESS:
|
|
|
|
TraceTag((tagProcess, "Process %d started.", dwParam));
|
|
// Remember the process id so we can use it later
|
|
_aryProcIds.Append(dwParam);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
DWORD dwError = GetLastError();
|
|
|
|
if (dwError != WAIT_TIMEOUT)
|
|
{
|
|
TraceTag((tagProcess, "GetQueuedCompletionStatus returned %d", dwError));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::LaunchProcess, public
|
|
//
|
|
// Synopsis: Start the child process for the given command.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
CProcessThread::LaunchProcess(const PROCESS_PARAMS *pProcParams)
|
|
{
|
|
BOOL fSuccess;
|
|
TCHAR achCommand[MAX_PATH * 2];
|
|
TCHAR achDir[MAX_PATH];
|
|
BOOL fUseDir;
|
|
HRESULT hr = S_OK;
|
|
CStr cstrEnvironment;
|
|
|
|
STARTUPINFO si = { 0 };
|
|
HANDLE hPipeStdout = NULL;
|
|
HANDLE hPipeStderr = NULL;
|
|
|
|
if (pProcParams->fGetOutput)
|
|
{
|
|
//
|
|
// Setup the anonymous pipe which we will use to get status from the
|
|
// process. Anytime it writes to STDOUT or STDERR it will come across
|
|
// this pipe to us.
|
|
//
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
fSuccess = CreatePipe(&_hPipe, &hPipeStdout, &sa, 0);
|
|
if (!fSuccess)
|
|
{
|
|
AssertSz(FALSE, "FATAL: Could not create anonymous pipe");
|
|
|
|
goto Win32Error;
|
|
}
|
|
|
|
//
|
|
// Change our end of the pipe to non-inheritable so the new process
|
|
// doesn't pick it up.
|
|
//
|
|
fSuccess = DuplicateHandle(GetCurrentProcess(),
|
|
_hPipe,
|
|
GetCurrentProcess(),
|
|
NULL,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS);
|
|
if (!fSuccess)
|
|
{
|
|
AssertSz(FALSE, "FATAL: Error removing inheritance from handle!");
|
|
|
|
goto Win32Error;
|
|
}
|
|
|
|
//
|
|
// Now duplicate the stdout handle for stderr
|
|
//
|
|
fSuccess = DuplicateHandle(GetCurrentProcess(),
|
|
hPipeStdout,
|
|
GetCurrentProcess(),
|
|
&hPipeStderr,
|
|
0,
|
|
TRUE,
|
|
DUPLICATE_SAME_ACCESS);
|
|
if (!fSuccess)
|
|
{
|
|
AssertSz(FALSE, "Error duplicating stdout handle!");
|
|
|
|
goto Win32Error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hPipeStdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
hPipeStderr = GetStdHandle(STD_ERROR_HANDLE);
|
|
}
|
|
|
|
if (_hJob)
|
|
{
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION joLimit = { 0 };
|
|
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joPort = { 0 };
|
|
|
|
if (pProcParams->fNoCrashPopup)
|
|
{
|
|
// Force crashes to terminate the job (and all processes in it).
|
|
|
|
joLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
|
|
|
|
fSuccess = SetInformationJobObject(_hJob,
|
|
JobObjectExtendedLimitInformation,
|
|
&joLimit,
|
|
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
if (!fSuccess) // Ignore failures except to report them.
|
|
{
|
|
TraceTag((tagError,
|
|
"SetInformationJobObject failed with %d",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
// Now we need to setup a completion port so we can find out if one
|
|
// of the processes crashed.
|
|
|
|
_hIoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, (DWORD)this, 0);
|
|
|
|
if (_hIoPort)
|
|
{
|
|
joPort.CompletionKey = this;
|
|
joPort.CompletionPort = _hIoPort;
|
|
|
|
fSuccess = SetInformationJobObject(_hJob,
|
|
JobObjectAssociateCompletionPortInformation,
|
|
&joPort,
|
|
sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT));
|
|
|
|
if (!fSuccess) // Ignore failures except to report them.
|
|
{
|
|
TraceTag((tagError,
|
|
"Failed to set completion port on job: %d",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag((tagError,
|
|
"CreateIoCompletionPort failed with %d!",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
si.cb = sizeof(STARTUPINFO);
|
|
|
|
if (pProcParams->fMinimize)
|
|
{
|
|
si.wShowWindow = SW_SHOWMINNOACTIVE;
|
|
}
|
|
else
|
|
{
|
|
si.wShowWindow = SW_SHOWNORMAL;
|
|
}
|
|
|
|
if (pProcParams->pszTitle != NULL && _tcslen(pProcParams->pszTitle) > 0)
|
|
{
|
|
si.lpTitle = pProcParams->pszTitle;
|
|
}
|
|
|
|
//
|
|
// Setup our inherited standard handles.
|
|
//
|
|
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
si.hStdOutput = hPipeStdout;
|
|
si.hStdError = hPipeStderr;
|
|
|
|
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
|
|
|
if (!pProcParams->fGetOutput)
|
|
{
|
|
si.dwFlags &= ~STARTF_USESTDHANDLES;
|
|
}
|
|
|
|
//
|
|
// Expand environment strings in the command name as well as the working
|
|
// dir.
|
|
//
|
|
|
|
ExpandEnvironmentStrings(pProcParams->pszCommand,
|
|
achCommand,
|
|
MAX_PATH * 2);
|
|
|
|
fUseDir = pProcParams->pszDir && _tcslen(pProcParams->pszDir) > 0;
|
|
|
|
if (fUseDir)
|
|
{
|
|
WCHAR *psz;
|
|
WCHAR achDir2[MAX_PATH];
|
|
|
|
ExpandEnvironmentStrings(pProcParams->pszDir, achDir2, MAX_PATH);
|
|
GetFullPathName(achDir2, MAX_PATH, achDir, &psz);
|
|
}
|
|
|
|
GetProcessEnvironment(&cstrEnvironment, pProcParams->fNoEnviron);
|
|
|
|
TraceTag((tagProcess, "Launching Process: %ls in %ls", achCommand, achDir));
|
|
|
|
//
|
|
// Let's do it!
|
|
//
|
|
fSuccess = CreateProcess(NULL,
|
|
achCommand,
|
|
NULL,
|
|
NULL,
|
|
TRUE,
|
|
BELOW_NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT,
|
|
cstrEnvironment,
|
|
(fUseDir) ? achDir : NULL,
|
|
&si,
|
|
&_piProc);
|
|
if (!fSuccess)
|
|
goto Win32Error;
|
|
|
|
if (_hJob)
|
|
{
|
|
if (!AssignProcessToJobObject(_hJob, _piProc.hProcess))
|
|
{
|
|
TraceTag((tagError, "AssignProcessToJobObject failed with %d", GetLastError()));
|
|
CloseHandle(_hJob);
|
|
_hJob = NULL;
|
|
}
|
|
}
|
|
|
|
TraceTag((tagProcess, "Created process PID=%d %ls", _piProc.dwProcessId, achCommand));
|
|
CloseHandle(_piProc.hThread);
|
|
|
|
Cleanup:
|
|
if (pProcParams->fGetOutput)
|
|
{
|
|
//
|
|
// The write handles have been inherited by the child process, so we
|
|
// let go of them.
|
|
//
|
|
CloseHandle(hPipeStdout);
|
|
CloseHandle(hPipeStderr);
|
|
}
|
|
|
|
return hr;
|
|
|
|
Win32Error:
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
TraceTag((tagProcess, "Error creating process: %x", hr));
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CProcessThread::GetProcessEnvironment, public
|
|
//
|
|
// Synopsis: Builds an environment block for the new process that has
|
|
// our custom id so we can identify them.
|
|
//
|
|
// Arguments: [pcstr] -- Place to put environment block
|
|
// [fNoEnviron] -- If TRUE, don't inherit the environment block
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
CProcessThread::GetProcessEnvironment(CStr *pcstr, BOOL fNoEnviron)
|
|
{
|
|
TCHAR *pszEnviron;
|
|
TCHAR achNewVar[50];
|
|
int cch = 1;
|
|
TCHAR *pch;
|
|
|
|
static long s_lProcID = 0;
|
|
|
|
_lEnvID = InterlockedIncrement(&s_lProcID);
|
|
|
|
wsprintf(achNewVar, _T("__MTSCRIPT_ENV_ID=%d\0"), _lEnvID);
|
|
|
|
if (!fNoEnviron)
|
|
{
|
|
pszEnviron = GetEnvironmentStrings();
|
|
|
|
pch = pszEnviron;
|
|
cch = 2; // Always have two terminating nulls at least
|
|
|
|
while (*pch || *(pch+1))
|
|
{
|
|
pch++;
|
|
cch++;
|
|
}
|
|
|
|
pcstr->Set(NULL, cch + _tcslen(achNewVar));
|
|
|
|
memcpy((LPTSTR)*pcstr, pszEnviron, cch * sizeof(TCHAR));
|
|
|
|
FreeEnvironmentStrings(pszEnviron);
|
|
}
|
|
else
|
|
{
|
|
pcstr->Set(NULL, _tcslen(achNewVar) + 1);
|
|
}
|
|
|
|
memcpy((LPTSTR)*pcstr + cch - 1,
|
|
achNewVar,
|
|
(_tcslen(achNewVar)+1) * sizeof(TCHAR));
|
|
}
|
|
|