aspnetcore/src/AspNetCore/Src/serverprocess.cxx

2349 lines
64 KiB
C++

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "precomp.hxx"
#include <IPHlpApi.h>
#include <share.h>
extern BOOL g_fNsiApiNotSupported;
#define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000
HRESULT
SERVER_PROCESS::Initialize(
PROCESS_MANAGER *pProcessManager,
STRU *pszProcessExePath,
STRU *pszArguments,
DWORD dwStartupTimeLimitInMS,
DWORD dwShtudownTimeLimitInMS,
BOOL fWindowsAuthEnabled,
BOOL fBasicAuthEnabled,
BOOL fAnonymousAuthEnabled,
ENVIRONMENT_VAR_HASH *pEnvironmentVariables,
BOOL fStdoutLogEnabled,
STRU *pstruStdoutLogFile
)
{
HRESULT hr = S_OK;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 };
m_pProcessManager = pProcessManager;
m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS;
m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS;
m_fStdoutLogEnabled = fStdoutLogEnabled;
m_fWindowsAuthEnabled = fWindowsAuthEnabled;
m_fBasicAuthEnabled = fBasicAuthEnabled;
m_fAnonymousAuthEnabled = fAnonymousAuthEnabled;
m_pProcessManager->ReferenceProcessManager();
if (FAILED(hr = m_ProcessPath.Copy(*pszProcessExePath)) ||
FAILED(hr = m_struLogFile.Copy(*pstruStdoutLogFile))||
FAILED(hr = m_Arguments.Copy(*pszArguments)))
{
goto Finished;
}
if (m_hJobObject == NULL)
{
m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES
NULL); // LPCTSTR lpName
#pragma warning( disable : 4312)
// 0xdeadbeef is used by Antares
if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef)
{
m_hJobObject = NULL;
// ignore job object creation error.
}
#pragma warning( error : 4312)
if (m_hJobObject != NULL)
{
jobInfo.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(m_hJobObject,
JobObjectExtendedLimitInformation,
&jobInfo,
sizeof jobInfo))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
}
m_pEnvironmentVarTable = pEnvironmentVariables;
}
Finished:
return hr;
}
HRESULT
SERVER_PROCESS::GetRandomPort
(
DWORD* pdwPickedPort,
DWORD dwExcludedPort = 0
)
{
HRESULT hr = S_OK;
BOOL fPortInUse = FALSE;
DWORD dwActualProcessId = 0;
if (g_fNsiApiNotSupported)
{
//
// the default value for optional parameter dwExcludedPort is 0 which is reserved
// a random number between MIN_PORT and MAX_PORT
//
while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort);
}
else
{
DWORD cRetry = 0;
do
{
//
// ignore dwActualProcessId because here we are
// determing whether the randomly generated port is
// in use by any other process.
//
while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort);
hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse);
} while (fPortInUse && ++cRetry < MAX_RETRY);
if (cRetry >= MAX_RETRY)
{
hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET);
}
}
return hr;
}
HRESULT
SERVER_PROCESS::SetupListenPort(
ENVIRONMENT_VAR_HASH *pEnvironmentVarTable
)
{
HRESULT hr = S_OK;
ENVIRONMENT_VAR_ENTRY *pEntry = NULL;
pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry);
if (pEntry != NULL)
{
pEntry->Dereference();
if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0')
{
m_dwPort = (DWORD)_wtoi(pEntry->QueryValue());
if (m_dwPort >MAX_PORT || m_dwPort < MIN_PORT)
{
hr = E_INVALIDARG;
goto Finished;
// need add log for this one
}
hr = m_struPort.Copy(pEntry->QueryValue());
goto Finished;
}
else
{
//
// user set the env variable but did not give value, let's set it up
//
pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR);
}
}
WCHAR buffer[15];
if (FAILED(hr = GetRandomPort(&m_dwPort)))
{
goto Finished;
}
if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0)
{
hr = E_INVALIDARG;
goto Finished;
}
pEntry = new ENVIRONMENT_VAR_ENTRY();
if (pEntry == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) ||
FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) ||
FAILED(hr = m_struPort.Copy(buffer)))
{
goto Finished;
}
Finished:
if (pEntry != NULL)
{
pEntry->Dereference();
pEntry = NULL;
}
return hr;
}
HRESULT
SERVER_PROCESS::SetupAppPath(
IHttpContext* pContext,
ENVIRONMENT_VAR_HASH* pEnvironmentVarTable
)
{
HRESULT hr = S_OK;
DWORD dwCounter = 0;
DWORD dwPosition = 0;
WCHAR* pszPath = NULL;
ENVIRONMENT_VAR_ENTRY* pEntry = NULL;
pEnvironmentVarTable->FindKey(ASPNETCORE_APP_PATH_ENV_STR, &pEntry);
if (pEntry != NULL)
{
// user should not set this environment variable in configuration
pEnvironmentVarTable->DeleteKey(ASPNETCORE_APP_PATH_ENV_STR);
pEntry->Dereference();
pEntry = NULL;
}
if (m_struAppPath.IsEmpty())
{
if (FAILED(hr = m_pszRootApplicationPath.Copy(pContext->GetApplication()->GetApplicationPhysicalPath())) ||
FAILED(hr = m_struAppFullPath.Copy(pContext->GetApplication()->GetAppConfigPath())))
{
goto Finished;
}
}
// let's find the app path. IIS does not support nested sites
// we can seek for the fourth '/' if it exits
// MACHINE/WEBROOT/APPHOST/<site>/<app>.
pszPath = m_struAppFullPath.QueryStr();
while (pszPath[dwPosition] != NULL)
{
if (pszPath[dwPosition] == '/')
{
dwCounter++;
if (dwCounter == 4)
break;
}
dwPosition++;
}
if (dwCounter == 4)
{
hr = m_struAppPath.Copy(pszPath + dwPosition);
}
else
{
hr = m_struAppPath.Copy(L"/");
}
if (FAILED(hr))
{
goto Finished;
}
pEntry = new ENVIRONMENT_VAR_ENTRY();
if (pEntry == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) ||
FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)))
{
goto Finished;
}
Finished:
if (pEntry != NULL)
{
pEntry->Dereference();
pEntry = NULL;
}
return hr;
}
HRESULT
SERVER_PROCESS::SetupAppToken(
ENVIRONMENT_VAR_HASH *pEnvironmentVarTable
)
{
HRESULT hr = S_OK;
UUID logUuid;
PSTR pszLogUuid = NULL;
BOOL fRpcStringAllocd = FALSE;
RPC_STATUS rpcStatus;
STRU strAppToken;
ENVIRONMENT_VAR_ENTRY* pEntry = NULL;
pEnvironmentVarTable->FindKey(ASPNETCORE_APP_TOKEN_ENV_STR, &pEntry);
if (pEntry != NULL)
{
// user sets the environment variable
m_straGuid.Reset();
hr = m_straGuid.CopyW(pEntry->QueryValue());
pEntry->Dereference();
pEntry = NULL;
goto Finished;
}
else
{
if (m_straGuid.IsEmpty())
{
// the GUID has not been set yet
rpcStatus = UuidCreate(&logUuid);
if (rpcStatus != RPC_S_OK)
{
hr = rpcStatus;
goto Finished;
}
rpcStatus = UuidToStringA(&logUuid, (BYTE **)&pszLogUuid);
if (rpcStatus != RPC_S_OK)
{
hr = rpcStatus;
goto Finished;
}
fRpcStringAllocd = TRUE;
if (FAILED(hr = m_straGuid.Copy(pszLogUuid)))
{
goto Finished;
}
}
pEntry = new ENVIRONMENT_VAR_ENTRY();
if (pEntry == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (FAILED(strAppToken.CopyA(m_straGuid.QueryStr())) ||
FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) ||
FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)))
{
goto Finished;
}
}
Finished:
if (fRpcStringAllocd)
{
RpcStringFreeA((BYTE **)&pszLogUuid);
pszLogUuid = NULL;
}
if (pEntry != NULL)
{
pEntry->Dereference();
pEntry = NULL;
}
return hr;
}
HRESULT
SERVER_PROCESS::InitEnvironmentVariablesTable(
ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable
)
{
HRESULT hr = S_OK;
BOOL fFound = FALSE;
DWORD dwResult, dwError;
STRU strIisAuthEnvValue;
STACK_STRU(strStartupAssemblyEnv, 1024);
ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL;
ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL;
ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL;
pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH();
if (pEnvironmentVarTable == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
//
// few environment variables expected, small bucket size for hash table
//
if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/)))
{
goto Finished;
}
// copy the envirable hash table (from configuration) to a temp one as we may need to remove elements
m_pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToTable, pEnvironmentVarTable);
if (pEnvironmentVarTable->Count() != m_pEnvironmentVarTable->Count())
{
// hash table copy failed
hr = E_UNEXPECTED;
goto Finished;
}
pEnvironmentVarTable->FindKey(ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry);
if (pIISAuthEntry != NULL)
{
// user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off
pIISAuthEntry->Dereference();
pEnvironmentVarTable->DeleteKey(ASPNETCORE_IIS_AUTH_ENV_STR);
}
if (m_fWindowsAuthEnabled)
{
strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS);
}
if (m_fBasicAuthEnabled)
{
strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC);
}
if (m_fAnonymousAuthEnabled)
{
strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS);
}
if (strIisAuthEnvValue.IsEmpty())
{
strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE);
}
pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY();
if (pIISAuthEntry == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) ||
FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry)))
{
goto Finished;
}
pEnvironmentVarTable->FindKey(HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry);
if (pHostingEntry != NULL)
{
// user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration
// the value will be used in OutputEnvironmentVariables. Do nothing here
pHostingEntry->Dereference();
pHostingEntry = NULL;
goto Skipped;
}
//check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system
dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR,
strStartupAssemblyEnv.QueryStr(),
strStartupAssemblyEnv.QuerySizeCCH());
if (dwResult == 0)
{
dwError = GetLastError();
// Windows API (e.g., CreateProcess) allows variable with empty string value
// in such case dwResult will be 0 and dwError will also be 0
// As UI and CMD does not allow empty value, ignore this environment var
if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS)
{
hr = HRESULT_FROM_WIN32(dwError);
goto Finished;
}
}
else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH())
{
// have to increase the buffer and try get environment var again
strStartupAssemblyEnv.Reset();
strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1);
dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR,
strStartupAssemblyEnv.QueryStr(),
strStartupAssemblyEnv.QuerySizeCCH());
if (strStartupAssemblyEnv.IsEmpty())
{
hr = E_UNEXPECTED;
goto Finished;
}
fFound = TRUE;
}
else
{
fFound = TRUE;
}
strStartupAssemblyEnv.SyncWithBuffer();
if (fFound)
{
strStartupAssemblyEnv.Append(L";");
}
strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE);
// the environment variable was not defined, create it and add to hashtable
pHostingEntry = new ENVIRONMENT_VAR_ENTRY();
if (pHostingEntry == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) ||
FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry)))
{
goto Finished;
}
Skipped:
*ppEnvironmentVarTable = pEnvironmentVarTable;
pEnvironmentVarTable = NULL;
Finished:
if (pHostingEntry != NULL)
{
pHostingEntry->Dereference();
pHostingEntry = NULL;
}
if (pIISAuthEntry != NULL)
{
pIISAuthEntry->Dereference();
pIISAuthEntry = NULL;
}
if (pEnvironmentVarTable != NULL)
{
pEnvironmentVarTable->Clear();
delete pEnvironmentVarTable;
pEnvironmentVarTable = NULL;
}
return hr;
}
HRESULT
SERVER_PROCESS::OutputEnvironmentVariables
(
MULTISZ* pmszOutput,
ENVIRONMENT_VAR_HASH* pEnvironmentVarTable
)
{
HRESULT hr = S_OK;
LPWSTR pszEnvironmentVariables = NULL;
LPWSTR pszCurrentVariable = NULL;
LPWSTR pszNextVariable = NULL;
LPWSTR pszEqualChar = NULL;
STRU strEnvVar;
ENVIRONMENT_VAR_ENTRY* pEntry = NULL;
DBG_ASSERT(pmszOutput);
DBG_ASSERT(pEnvironmentVarTable); // We added some startup variables
DBG_ASSERT(pEnvironmentVarTable->Count() >0);
pszEnvironmentVariables = GetEnvironmentStringsW();
if (pszEnvironmentVariables == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT);
goto Finished;
}
pszCurrentVariable = pszEnvironmentVariables;
while (*pszCurrentVariable != L'\0')
{
pszNextVariable = pszCurrentVariable + wcslen(pszCurrentVariable) + 1;
pszEqualChar = wcschr(pszCurrentVariable, L'=');
if (pszEqualChar != NULL)
{
if (FAILED(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1)))
{
goto Finished;
}
pEnvironmentVarTable->FindKey(strEnvVar.QueryStr(), &pEntry);
if (pEntry != NULL)
{
// same env variable is defined in configuration, use it
if (FAILED(hr = strEnvVar.Append(pEntry->QueryValue())))
{
goto Finished;
}
pmszOutput->Append(strEnvVar); //should we check the returned bool
// remove the record from hash table as we already output it
pEntry->Dereference();
pEnvironmentVarTable->DeleteKey(pEntry->QueryName());
strEnvVar.Reset();
pEntry = NULL;
}
else
{
pmszOutput->Append(pszCurrentVariable);
}
}
else
{
// env varaible is not well formated
hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT);
goto Finished;
}
// move to next env variable
pszCurrentVariable = pszNextVariable;
}
// append the remaining env variable in hash table
pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput);
Finished:
if (pszEnvironmentVariables != NULL)
{
FreeEnvironmentStringsW(pszEnvironmentVariables);
pszEnvironmentVariables = NULL;
}
return hr;
}
HRESULT
SERVER_PROCESS::SetupCommandLine(
STRU* pstrCommandLine
)
{
HRESULT hr = S_OK;
LPWSTR pszPath = NULL;
LPWSTR pszFullPath = NULL;
STRU strRelativePath;
DWORD dwBufferSize = 0;
FILE *file = NULL;
DBG_ASSERT(pstrCommandLine);
pszPath = m_ProcessPath.QueryStr();
if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL))
{
// let's check whether it is a relative path
if (FAILED(hr = strRelativePath.Copy(m_pszRootApplicationPath.QueryStr())) ||
FAILED(hr = strRelativePath.Append(L"\\")) ||
FAILED(hr = strRelativePath.Append(pszPath)))
{
goto Finished;
}
dwBufferSize = strRelativePath.QueryCCH() + 1;
pszFullPath = new WCHAR[dwBufferSize];
if (pszFullPath == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
if (_wfullpath(pszFullPath,
strRelativePath.QueryStr(),
dwBufferSize) == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Finished;
}
if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL)
{
fclose(file);
pszPath = pszFullPath;
}
}
if (FAILED(hr = pstrCommandLine->Copy(pszPath)) ||
FAILED(hr = pstrCommandLine->Append(L" ")) ||
FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr())))
{
goto Finished;
}
Finished:
if (pszFullPath != NULL)
{
delete pszFullPath;
}
return hr;
}
HRESULT
SERVER_PROCESS::PostStartCheck(
const STRU* const pStruCommandline,
STRU* pStruErrorMessage)
{
HRESULT hr = S_OK;
BOOL fReady = FALSE;
BOOL fProcessMatch = FALSE;
BOOL fDebuggerAttached = FALSE;
DWORD dwTickCount = 0;
DWORD dwTimeDifference = 0;
DWORD dwActualProcessId = 0;
INT iChildProcessIndex = -1;
if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0)
{
// some error occurred - assume debugger is not attached;
fDebuggerAttached = FALSE;
}
dwTickCount = GetTickCount();
do
{
DWORD processStatus;
if (GetExitCodeProcess(m_hProcessHandle, &processStatus))
{
// make sure the process is still running
if (processStatus != STILL_ACTIVE)
{
hr = E_FAIL;
pStruErrorMessage->SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
pStruCommandline->QueryStr(),
hr,
processStatus);
goto Finished;
}
}
//
// dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported
//
hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady);
fDebuggerAttached = IsDebuggerIsAttached();
if (!fReady)
{
Sleep(250);
}
dwTimeDifference = (GetTickCount() - dwTickCount);
} while (fReady == FALSE &&
((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached));
// register call back with the created process
if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle)))
{
goto Finished;
}
//
// check if debugger is attached after startupTimeout.
//
if (!fDebuggerAttached &&
CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0)
{
// some error occurred - assume debugger is not attached;
fDebuggerAttached = FALSE;
}
if (!g_fNsiApiNotSupported)
{
//
// NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche
//
if (dwActualProcessId == m_dwProcessId)
{
m_dwListeningProcessId = m_dwProcessId;
fProcessMatch = TRUE;
}
if (!fProcessMatch)
{
// could be the scenario that backend creates child process
if (FAILED(hr = GetChildProcessHandles()))
{
goto Finished;
}
for (DWORD i = 0; i < m_cChildProcess; ++i)
{
// a child process listen on the assigned port
if (dwActualProcessId == m_dwChildProcessIds[i])
{
m_dwListeningProcessId = m_dwChildProcessIds[i];
fProcessMatch = TRUE;
if (m_hChildProcessHandles[i] != NULL)
{
if (fDebuggerAttached == FALSE &&
CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0)
{
// some error occurred - assume debugger is not attached;
fDebuggerAttached = FALSE;
}
if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i],
m_hChildProcessHandles[i])))
{
goto Finished;
}
iChildProcessIndex = i;
}
break;
}
}
}
if(!fProcessMatch)
{
//
// process that we created is not listening
// on the port we specified.
//
fReady = FALSE;
pStruErrorMessage->SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
pStruCommandline->QueryStr(),
m_dwPort,
hr);
hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
goto Finished;
}
}
if (!fReady)
{
//
// hr is already set by CheckIfServerIsUp
//
if (dwTimeDifference >= m_dwStartupTimeLimitInMS)
{
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
pStruErrorMessage->SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
pStruCommandline->QueryStr(),
m_dwPort,
hr);
}
goto Finished;
}
if (iChildProcessIndex >= 0)
{
//
// final check to make sure child process listening on HTTP is still UP
// This is needed because, the child process might have crashed/exited between
// the previous call to checkIfServerIsUp and RegisterProcessWait
// and we would not know about it.
//
hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady);
if ((FAILED(hr) || fReady == FALSE))
{
pStruErrorMessage->SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
pStruCommandline->QueryStr(),
m_dwPort,
hr);
goto Finished;
}
}
//
// ready to mark the server process ready but before this,
// create and initialize the FORWARDER_CONNECTION
//
if (m_pForwarderConnection != NULL)
{
m_pForwarderConnection->DereferenceForwarderConnection();
m_pForwarderConnection = NULL;
}
if (m_pForwarderConnection == NULL)
{
m_pForwarderConnection = new FORWARDER_CONNECTION();
if (m_pForwarderConnection == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
hr = m_pForwarderConnection->Initialize(m_dwPort);
if (FAILED(hr))
{
goto Finished;
}
}
if (!g_fNsiApiNotSupported)
{
m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
FALSE,
m_dwListeningProcessId);
}
//
// mark server process as Ready
//
m_fReady = TRUE;
Finished:
return hr;
}
HRESULT
SERVER_PROCESS::StartProcess(
IHttpContext *context
)
{
HRESULT hr = S_OK;
PROCESS_INFORMATION processInformation = {0};
STARTUPINFOW startupInfo = {0};
BOOL fDonePrepareCommandLine = FALSE;
DWORD dwCreationFlags = 0;
STACK_STRU( strEventMsg, 256);
STRU strFullProcessPath;
STRU struRelativePath;
STRU struApplicationId;
STRU struCommandLine;
LPCWSTR apsz[1];
MULTISZ mszNewEnvironment;
ENVIRONMENT_VAR_HASH *pHashTable = NULL;
GetStartupInfoW(&startupInfo);
//
// setup stdout and stderr handles to our stdout handle only if
// the handle is valid.
//
SetupStdHandles(context, &startupInfo);
if (FAILED(hr = InitEnvironmentVariablesTable(&pHashTable)))
{
goto Finished;
}
//
// setup the the port that the backend process will listen on
//
if (FAILED(hr = SetupListenPort(pHashTable)))
{
goto Finished;
}
//
// get app path
//
if (FAILED(hr = SetupAppPath(context, pHashTable)))
{
goto Finished;
}
//
// generate new guid for each process
//
if (FAILED(hr = SetupAppToken(pHashTable)))
{
goto Finished;
}
//
// setup environment variables for new process
//
if (FAILED(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable)))
{
goto Finished;
}
//
// generate process command line.
//
if (FAILED(hr = SetupCommandLine(&struCommandLine)))
{
goto Finished;
}
fDonePrepareCommandLine = TRUE;
dwCreationFlags = CREATE_NO_WINDOW |
CREATE_UNICODE_ENVIRONMENT |
CREATE_SUSPENDED |
CREATE_NEW_PROCESS_GROUP;
if (!CreateProcessW(
NULL, // applicationName
struCommandLine.QueryStr(),
NULL, // processAttr
NULL, // threadAttr
TRUE, // inheritHandles
dwCreationFlags,
mszNewEnvironment.QueryStr(),
m_pszRootApplicationPath.QueryStr(), // currentDir
&startupInfo,
&processInformation))
{
hr = HRESULT_FROM_WIN32(GetLastError());
// don't the check return code as we already in error report
strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
struCommandLine.QueryStr(),
hr,
0);
goto Finished;
}
m_hProcessHandle = processInformation.hProcess;
m_dwProcessId = processInformation.dwProcessId;
if (m_hJobObject != NULL)
{
if (!AssignProcessToJobObject(m_hJobObject, m_hProcessHandle))
{
hr = HRESULT_FROM_WIN32(GetLastError());
if (hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED))
{
goto Finished;
}
}
}
if (ResumeThread(processInformation.hThread) == -1)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
//
// need to make sure the server is up and listening on the port specified.
//
if (FAILED(hr = PostStartCheck(&struCommandLine, &strEventMsg)))
{
goto Finished;
}
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG,
m_struAppFullPath.QueryStr(),
m_dwProcessId,
m_dwPort)))
{
apsz[0] = strEventMsg.QueryStr();
//
// not checking return code because if ReportEvent
// fails, we cannot do anything.
//
if (FORWARDING_HANDLER::QueryEventLog() != NULL)
{
ReportEventW(FORWARDING_HANDLER::QueryEventLog(),
EVENTLOG_INFORMATION_TYPE,
0,
ASPNETCORE_EVENT_PROCESS_START_SUCCESS,
NULL,
1,
0,
apsz,
NULL);
}
}
Finished:
if (processInformation.hThread != NULL)
{
CloseHandle(processInformation.hThread);
processInformation.hThread = NULL;
}
if (pHashTable != NULL)
{
pHashTable->Clear();
delete pHashTable;
pHashTable = NULL;
}
if (FAILED(hr))
{
if (strEventMsg.IsEmpty())
{
if (!fDonePrepareCommandLine)
strEventMsg.SafeSnwprintf(
m_struAppFullPath.QueryStr(),
ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG,
hr);
else
strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG,
m_struAppFullPath.QueryStr(),
m_pszRootApplicationPath.QueryStr(),
struCommandLine.QueryStr(),
hr);
}
apsz[0] = strEventMsg.QueryStr();
// not checking return code because if ReportEvent
// fails, we cannot do anything.
//
if (FORWARDING_HANDLER::QueryEventLog() != NULL)
{
ReportEventW(FORWARDING_HANDLER::QueryEventLog(),
EVENTLOG_ERROR_TYPE,
0,
ASPNETCORE_EVENT_PROCESS_START_ERROR,
NULL,
1,
0,
apsz,
NULL);
}
}
if (FAILED(hr) || m_fReady == FALSE)
{
if (m_hStdoutHandle != NULL)
{
if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hStdoutHandle);
}
m_hStdoutHandle = NULL;
}
if (m_fStdoutLogEnabled)
{
m_Timer.CancelTimer();
}
if (m_hListeningProcessHandle != NULL)
{
if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hListeningProcessHandle);
}
m_hListeningProcessHandle = NULL;
}
if (m_hProcessWaitHandle != NULL)
{
UnregisterWait(m_hProcessWaitHandle);
m_hProcessWaitHandle = NULL;
}
StopProcess();
StopAllProcessesInJobObject();
}
return hr;
}
HRESULT
SERVER_PROCESS::SetWindowsAuthToken(
HANDLE hToken,
LPHANDLE pTargetTokenHandle
)
{
HRESULT hr = S_OK;
if (m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE)
{
if (!DuplicateHandle( GetCurrentProcess(),
hToken,
m_hListeningProcessHandle,
pTargetTokenHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS ))
{
hr = HRESULT_FROM_GETLASTERROR();
goto Finished;
}
}
Finished:
return hr;
}
HRESULT
SERVER_PROCESS::SetupStdHandles(
IHttpContext *context,
LPSTARTUPINFOW pStartupInfo
)
{
SECURITY_ATTRIBUTES saAttr = {0};
HRESULT hr = S_OK;
SYSTEMTIME systemTime;
STRU struLogFileName;
BOOL fStdoutLoggingFailed = FALSE;
STRU strEventMsg;
LPCWSTR apsz[1];
STRU struAbsLogFilePath;
DBG_ASSERT(pStartupInfo);
if (m_fStdoutLogEnabled)
{
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (m_hStdoutHandle != NULL)
{
if (!CloseHandle(m_hStdoutHandle))
{
hr = HRESULT_FROM_GETLASTERROR();
goto Finished;
}
m_hStdoutHandle = NULL;
}
hr = PATH::ConvertPathToFullPath(m_struLogFile.QueryStr(),
context->GetApplication()->GetApplicationPhysicalPath(),
&struAbsLogFilePath );
if (FAILED(hr))
{
goto Finished;
}
GetSystemTime(&systemTime);
hr = struLogFileName.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
struAbsLogFilePath.QueryStr(),
systemTime.wYear,
systemTime.wMonth,
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
GetCurrentProcessId() );
if (FAILED(hr))
{
goto Finished;
}
m_hStdoutHandle = CreateFileW(struLogFileName.QueryStr(),
FILE_WRITE_DATA,
FILE_SHARE_READ,
&saAttr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (m_hStdoutHandle == INVALID_HANDLE_VALUE)
{
fStdoutLoggingFailed = TRUE;
m_hStdoutHandle = NULL;
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG,
struLogFileName.QueryStr(),
HRESULT_FROM_GETLASTERROR())))
{
apsz[0] = strEventMsg.QueryStr();
//
// not checking return code because if ReportEvent
// fails, we cannot do anything.
//
if (FORWARDING_HANDLER::QueryEventLog() != NULL)
{
ReportEventW(FORWARDING_HANDLER::QueryEventLog(),
EVENTLOG_WARNING_TYPE,
0,
ASPNETCORE_EVENT_CONFIG_ERROR,
NULL,
1,
0,
apsz,
NULL);
}
}
}
if (!fStdoutLoggingFailed)
{
pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
pStartupInfo->hStdError = m_hStdoutHandle;
pStartupInfo->hStdOutput = m_hStdoutHandle;
m_struFullLogFile.Copy(struLogFileName);
// start timer to open and close handles regularly.
m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000);
}
}
if ((!m_fStdoutLogEnabled || fStdoutLoggingFailed) &&
m_pProcessManager->QueryNULHandle() != NULL &&
m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE)
{
pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
pStartupInfo->hStdError = m_pProcessManager->QueryNULHandle();
pStartupInfo->hStdOutput = m_pProcessManager->QueryNULHandle();
}
Finished:
return hr;
}
VOID
CALLBACK
SERVER_PROCESS::TimerCallback(
IN PTP_CALLBACK_INSTANCE Instance,
IN PVOID Context,
IN PTP_TIMER Timer
)
{
Instance;
Timer;
SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) Context;
HANDLE hStdoutHandle = NULL;
SECURITY_ATTRIBUTES saAttr = {0};
HRESULT hr = S_OK;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
hStdoutHandle = CreateFileW(pServerProcess->QueryFullLogPath(),
FILE_READ_DATA,
FILE_SHARE_WRITE,
&saAttr,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hStdoutHandle == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_GETLASTERROR();
}
CloseHandle(hStdoutHandle);
}
HRESULT
SERVER_PROCESS::CheckIfServerIsUp(
_In_ DWORD dwPort,
_Out_ DWORD * pdwProcessId,
_Out_ BOOL * pfReady
)
{
HRESULT hr = S_OK;
DWORD dwResult = 0;
MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL;
MIB_TCPROW_OWNER_PID *pOwner = NULL;
DWORD dwSize = 0;
int iResult = 0;
SOCKADDR_IN sockAddr;
SOCKET socketCheck = INVALID_SOCKET;
DBG_ASSERT(pfReady);
DBG_ASSERT(pdwProcessId);
*pfReady = FALSE;
//
// it's OK for us to return processID 0 in case we cannot detect the real one
//
*pdwProcessId = 0;
if (!g_fNsiApiNotSupported)
{
dwResult = GetExtendedTcpTable(NULL,
&dwSize,
FALSE,
AF_INET,
TCP_TABLE_OWNER_PID_LISTENER,
0);
if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER)
{
hr = HRESULT_FROM_WIN32(dwResult);
goto Finished;
}
pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize);
if (pTCPInfo == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
dwResult = GetExtendedTcpTable(pTCPInfo,
&dwSize,
FALSE,
AF_INET,
TCP_TABLE_OWNER_PID_LISTENER,
0);
if (dwResult != NO_ERROR)
{
hr = HRESULT_FROM_WIN32(dwResult);
goto Finished;
}
// iterate pTcpInfo struct to find PID/PORT entry
for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++)
{
pOwner = &pTCPInfo->table[dwLoop];
if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort)
{
*pdwProcessId = pOwner->dwOwningPid;
*pfReady = TRUE;
break;
}
}
}
else
{
//
// We have to open socket to ping the service
//
socketCheck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketCheck == INVALID_SOCKET)
{
hr = HRESULT_FROM_WIN32(WSAGetLastError());
goto Finished;
}
sockAddr.sin_family = AF_INET;
if (!inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr)))
{
hr = HRESULT_FROM_WIN32(WSAGetLastError());
goto Finished;
}
//sockAddr.sin_addr.s_addr = inet_addr( LOCALHOST );
sockAddr.sin_port = htons((u_short)dwPort);
//
// Connect to server.
// if connection fails, socket is not closed, we reuse the same socket
// while retrying
//
iResult = connect(socketCheck, (SOCKADDR *)&sockAddr, sizeof(sockAddr));
if (iResult == SOCKET_ERROR)
{
hr = HRESULT_FROM_WIN32(WSAGetLastError());
goto Finished;
}
*pfReady = TRUE;
}
Finished:
if (socketCheck != INVALID_SOCKET)
{
iResult = closesocket(socketCheck);
if (iResult == SOCKET_ERROR)
{
hr = HRESULT_FROM_WIN32(WSAGetLastError());
}
socketCheck = INVALID_SOCKET;
}
if (pTCPInfo != NULL)
{
HeapFree(GetProcessHeap(), 0, pTCPInfo);
pTCPInfo = NULL;
}
return hr;
}
// send signal to the process to let it gracefully shutdown
// if the process cannot shutdown within given time, terminate it
VOID
SERVER_PROCESS::SendSignal(
VOID
)
{
HRESULT hr = S_OK;
HANDLE hThread = NULL;
ReferenceServerProcess();
m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId);
if (m_hShutdownHandle == NULL)
{
// since we cannot open the process. let's terminate the process
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
hThread = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE)SendShutDownSignal,
this, // thread function arguments
0, // default creation flags
NULL); // receive thread identifier
if (hThread == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)
{
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
goto Finished;
}
Finished:
if (hThread != NULL)
{
// if the send shutdown message thread is still running, terminate it
DWORD dwThreadStatus = 0;
if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE)
{
TerminateThread(hThread, STATUS_CONTROL_C_EXIT);
}
CloseHandle(hThread);
hThread = NULL;
}
if (FAILED(hr))
{
TerminateBackendProcess();
}
if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hShutdownHandle);
m_hShutdownHandle = NULL;
}
DereferenceServerProcess();
}
//
// StopProcess is only called if process crashes OR if the process
// creation failed and calling this counts towards RapidFailCounts.
//
VOID
SERVER_PROCESS::StopProcess(
VOID
)
{
m_fReady = FALSE;
m_pProcessManager->IncrementRapidFailCount();
for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
{
if (m_hChildProcessHandles[i] != NULL)
{
if (m_hChildProcessHandles[i] != INVALID_HANDLE_VALUE)
{
TerminateProcess(m_hChildProcessHandles[i], 0);
CloseHandle(m_hChildProcessHandles[i]);
}
m_hChildProcessHandles[i] = NULL;
m_dwChildProcessIds[i] = 0;
}
}
if (m_hProcessHandle != NULL)
{
if (m_hProcessHandle != INVALID_HANDLE_VALUE)
{
TerminateProcess(m_hProcessHandle, 0);
CloseHandle(m_hProcessHandle);
}
m_hProcessHandle = NULL;
}
}
BOOL
SERVER_PROCESS::IsDebuggerIsAttached(
VOID
)
{
HRESULT hr = S_OK;
PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL;
DWORD dwPid = 0;
DWORD dwWorkerProcessPid = 0;
DWORD cbNumBytes = 1024;
DWORD dwRetries = 0;
DWORD dwError = NO_ERROR;
BOOL fDebuggerPresent = FALSE;
dwWorkerProcessPid = GetCurrentProcessId();
do
{
dwError = NO_ERROR;
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
processList = NULL;
// resize
cbNumBytes = cbNumBytes * 2;
}
processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
GetProcessHeap(),
0,
cbNumBytes
);
if (processList == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
RtlZeroMemory(processList, cbNumBytes);
if (!QueryInformationJobObject(
m_hJobObject,
JobObjectBasicProcessIdList,
processList,
cbNumBytes,
NULL))
{
dwError = GetLastError();
if (dwError != ERROR_MORE_DATA)
{
hr = HRESULT_FROM_WIN32(dwError);
goto Finished;
}
}
} while (dwRetries++ < 5 &&
processList != NULL &&
(processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList ||
processList->NumberOfProcessIdsInList == 0));
if (dwError == ERROR_MORE_DATA)
{
hr = E_OUTOFMEMORY;
// some error
goto Finished;
}
if (processList == NULL ||
(processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList ||
processList->NumberOfProcessIdsInList == 0))
{
hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED);
// some error
goto Finished;
}
if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES)
{
hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
goto Finished;
}
for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
{
dwPid = (DWORD)processList->ProcessIdList[i];
if (dwPid != dwWorkerProcessPid)
{
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
FALSE,
dwPid);
BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent);
if (hProcess != NULL)
{
CloseHandle(hProcess);
hProcess = NULL;
}
if (!returnValue)
{
goto Finished;
}
if (fDebuggerPresent)
{
break;
}
}
}
Finished:
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
}
return fDebuggerPresent;
}
HRESULT
SERVER_PROCESS::GetChildProcessHandles(
VOID
)
{
HRESULT hr = S_OK;
PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL;
DWORD dwPid = 0;
DWORD dwWorkerProcessPid = 0;
DWORD cbNumBytes = 1024;
DWORD dwRetries = 0;
DWORD dwError = NO_ERROR;
dwWorkerProcessPid = GetCurrentProcessId();
do
{
dwError = NO_ERROR;
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
processList = NULL;
// resize
cbNumBytes = cbNumBytes * 2;
}
processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
GetProcessHeap(),
0,
cbNumBytes
);
if (processList == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
RtlZeroMemory(processList, cbNumBytes);
if (!QueryInformationJobObject(
m_hJobObject,
JobObjectBasicProcessIdList,
processList,
cbNumBytes,
NULL))
{
dwError = GetLastError();
if (dwError != ERROR_MORE_DATA)
{
hr = HRESULT_FROM_WIN32(dwError);
goto Finished;
}
}
} while (dwRetries++ < 5 &&
processList != NULL &&
(processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0));
if (dwError == ERROR_MORE_DATA)
{
hr = E_OUTOFMEMORY;
// some error
goto Finished;
}
if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0))
{
hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED);
// some error
goto Finished;
}
if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES)
{
hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
goto Finished;
}
for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
{
dwPid = (DWORD)processList->ProcessIdList[i];
if (dwPid != m_dwProcessId &&
dwPid != dwWorkerProcessPid )
{
m_hChildProcessHandles[m_cChildProcess] = OpenProcess(
PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
FALSE,
dwPid
);
m_dwChildProcessIds[m_cChildProcess] = dwPid;
m_cChildProcess ++;
}
}
Finished:
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
}
return hr;
}
HRESULT
SERVER_PROCESS::StopAllProcessesInJobObject(
VOID
)
{
HRESULT hr = S_OK;
PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL;
HANDLE hProcess = NULL;
DWORD dwWorkerProcessPid = 0;
DWORD cbNumBytes = 1024;
DWORD dwRetries = 0;
dwWorkerProcessPid = GetCurrentProcessId();
do
{
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
processList = NULL;
// resize
cbNumBytes = cbNumBytes * 2;
}
processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
GetProcessHeap(),
0,
cbNumBytes
);
if (processList == NULL)
{
hr = E_OUTOFMEMORY;
goto Finished;
}
RtlZeroMemory(processList, cbNumBytes);
if (!QueryInformationJobObject(
m_hJobObject,
JobObjectBasicProcessIdList,
processList,
cbNumBytes,
NULL))
{
DWORD dwError = GetLastError();
if (dwError != ERROR_MORE_DATA)
{
hr = HRESULT_FROM_WIN32(dwError);
goto Finished;
}
}
} while (dwRetries++ < 5 &&
processList != NULL &&
(processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0));
if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0))
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
// some error
goto Finished;
}
for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
{
if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i])
{
hProcess = OpenProcess(PROCESS_TERMINATE,
FALSE,
(DWORD)processList->ProcessIdList[i]);
if (hProcess != NULL)
{
if (!TerminateProcess(hProcess, 1))
{
hr = HRESULT_FROM_GETLASTERROR();
}
else
{
WaitForSingleObject(hProcess, INFINITE);
}
if (hProcess != NULL)
{
CloseHandle(hProcess);
hProcess = NULL;
}
}
}
}
Finished:
if (processList != NULL)
{
HeapFree(GetProcessHeap(), 0, processList);
}
return hr;
}
SERVER_PROCESS::SERVER_PROCESS() :
m_cRefs(1),
m_hProcessHandle(NULL),
m_hProcessWaitHandle(NULL),
m_dwProcessId(0),
m_cChildProcess(0),
m_fReady(FALSE),
m_lStopping(0L),
m_hStdoutHandle(NULL),
m_fStdoutLogEnabled(FALSE),
m_hJobObject(NULL),
m_pForwarderConnection(NULL),
m_dwListeningProcessId(0),
m_hListeningProcessHandle(NULL),
m_hShutdownHandle(NULL)
{
InterlockedIncrement(&g_dwActiveServerProcesses);
srand(GetTickCount());
for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
{
m_dwChildProcessIds[i] = 0;
m_hChildProcessHandles[i] = NULL;
m_hChildProcessWaitHandles[i] = NULL;
}
}
SERVER_PROCESS::~SERVER_PROCESS()
{
if (m_hProcessWaitHandle != NULL)
{
UnregisterWait(m_hProcessWaitHandle);
m_hProcessWaitHandle = NULL;
}
for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
{
if (m_hChildProcessWaitHandles[i] != NULL)
{
UnregisterWait(m_hChildProcessWaitHandles[i]);
m_hChildProcessWaitHandles[i] = NULL;
}
}
if (m_hProcessHandle != NULL)
{
if (m_hProcessHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hProcessHandle);
}
m_hProcessHandle = NULL;
}
if (m_hListeningProcessHandle != NULL)
{
if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hListeningProcessHandle);
}
m_hListeningProcessHandle = NULL;
}
for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
{
if (m_hChildProcessHandles[i] != NULL)
{
if (m_hChildProcessHandles[i] != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hChildProcessHandles[i]);
}
m_hChildProcessHandles[i] = NULL;
m_dwChildProcessIds[i] = 0;
}
}
if (m_hStdoutHandle != NULL)
{
if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hStdoutHandle);
}
m_hStdoutHandle = NULL;
}
if (m_fStdoutLogEnabled)
{
m_Timer.CancelTimer();
}
if (m_hJobObject != NULL)
{
if (m_hJobObject != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hJobObject);
}
m_hJobObject = NULL;
}
if (m_pProcessManager != NULL)
{
m_pProcessManager->DereferenceProcessManager();
m_pProcessManager = NULL;
}
if (m_pForwarderConnection != NULL)
{
m_pForwarderConnection->DereferenceForwarderConnection();
m_pForwarderConnection = NULL;
}
m_pEnvironmentVarTable = NULL;
// no need to free m_pEnvironmentVarTable, as it references to
// the same hash table hold by configuration.
// the hashtable memory will be freed once onfiguration got recycled
InterlockedDecrement(&g_dwActiveServerProcesses);
}
VOID
ProcessHandleCallback(
_In_ PVOID pContext,
_In_ BOOL
)
{
SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext;
pServerProcess->HandleProcessExit();
}
HRESULT
SERVER_PROCESS::RegisterProcessWait(
PHANDLE phWaitHandle,
HANDLE hProcessToWaitOn
)
{
HRESULT hr = S_OK;
NTSTATUS status = 0;
_ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL);
*phWaitHandle = NULL;
// wait thread will dereference.
ReferenceServerProcess();
status = RegisterWaitForSingleObject(
phWaitHandle,
hProcessToWaitOn,
(WAITORTIMERCALLBACK)&ProcessHandleCallback,
this,
INFINITE,
WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD
);
if (status < 0)
{
hr = HRESULT_FROM_NT(status);
goto Finished;
}
Finished:
if (FAILED(hr))
{
*phWaitHandle = NULL;
DereferenceServerProcess();
}
return hr;
}
HRESULT
SERVER_PROCESS::HandleProcessExit()
{
HRESULT hr = S_OK;
BOOL fReady = FALSE;
DWORD dwProcessId = 0;
if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L)
{
CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady);
if (!fReady)
{
m_pProcessManager->ShutdownProcess(this);
}
DereferenceServerProcess();
}
return hr;
}
HRESULT
SERVER_PROCESS::SendShutdownHttpMessage()
{
HRESULT hr = S_OK;
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
STACK_STRU(strHeaders, 256);
STRU strAppToken;
STRU strUrl;
DWORD dwStatusCode = 0;
DWORD dwSize = sizeof(dwStatusCode);
LPCWSTR apsz[1];
STACK_STRU(strEventMsg, 256);
hSession = WinHttpOpen(L"",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
if (hSession == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
hConnect = WinHttpConnect(hSession,
L"127.0.0.1",
(USHORT)m_dwPort,
0);
if (hConnect == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (m_struAppPath.QueryCCH() > 1)
{
// app path size is 1 means site root, i.e., "/"
// we don't want to add duplicated '/' to the request url
// otherwise the request will fail
strUrl.Copy(m_struAppPath);
}
strUrl.Append(L"/iisintegration");
hRequest = WinHttpOpenRequest(hConnect,
L"POST",
strUrl.QueryStr(),
NULL,
WINHTTP_NO_REFERER,
NULL,
0);
if (hRequest == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
// set timeout
if (!WinHttpSetTimeouts(hRequest,
m_dwShutdownTimeLimitInMS, // dwResolveTimeout
m_dwShutdownTimeLimitInMS, // dwConnectTimeout
m_dwShutdownTimeLimitInMS, // dwSendTimeout
m_dwShutdownTimeLimitInMS)) // dwReceiveTimeout
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
// set up the shutdown headers
if (FAILED(hr = strHeaders.Append(L"MS-ASPNETCORE-EVENT:shutdown \r\n")) ||
FAILED(hr = strAppToken.Append(L"MS-ASPNETCORE-TOKEN:")) ||
FAILED(hr = strAppToken.AppendA(m_straGuid.QueryStr())) ||
FAILED(hr = strHeaders.Append(strAppToken.QueryStr())))
{
goto Finished;
}
if (!WinHttpSendRequest(hRequest,
strHeaders.QueryStr(), // pwszHeaders
strHeaders.QueryCCH(), // dwHeadersLength
WINHTTP_NO_REQUEST_DATA,
0, // dwOptionalLength
0, // dwTotalLength
0)) // dwContext
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (!WinHttpReceiveResponse(hRequest , NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&dwStatusCode,
&dwSize,
WINHTTP_NO_HEADER_INDEX))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (dwStatusCode != 202)
{
// not expected http status
hr = E_FAIL;
}
// log
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG,
m_dwProcessId,
dwStatusCode)))
{
apsz[0] = strEventMsg.QueryStr();
if (FORWARDING_HANDLER::QueryEventLog() != NULL)
{
ReportEventW(FORWARDING_HANDLER::QueryEventLog(),
EVENTLOG_INFORMATION_TYPE,
0,
ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST,
NULL,
1,
0,
apsz,
NULL);
}
}
Finished:
if (hRequest)
{
WinHttpCloseHandle(hRequest);
hRequest = NULL;
}
if (hConnect)
{
WinHttpCloseHandle(hConnect);
hConnect = NULL;
}
if (hSession)
{
WinHttpCloseHandle(hSession);
hSession = NULL;
}
return hr;
}
//static
VOID
SERVER_PROCESS::SendShutDownSignal(
LPVOID lpParam
)
{
SERVER_PROCESS* pThis = static_cast<SERVER_PROCESS *>(lpParam);
DBG_ASSERT(pThis);
pThis->SendShutDownSignalInternal();
}
//
// send shutdown message first, if fail then send
// ctrl-c to the backend process to let it gracefully shutdown
//
VOID
SERVER_PROCESS::SendShutDownSignalInternal(
VOID
)
{
ReferenceServerProcess();
if (FAILED(SendShutdownHttpMessage()))
{
//
// failed to send shutdown http message
// try send ctrl signal
//
HWND hCurrentConsole = NULL;
BOOL fFreeConsole = FALSE;
hCurrentConsole = GetConsoleWindow();
if (hCurrentConsole)
{
// free current console first, as we may have one, e.g., hostedwebcore case
fFreeConsole = FreeConsole();
}
if (AttachConsole(m_dwProcessId))
{
// call ctrl-break instead of ctrl-c as child process may ignore ctrl-c
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId))
{
// failed to send the ctrl signal. terminate the backend process immediately instead of waiting for timeout
TerminateBackendProcess();
}
FreeConsole();
if (fFreeConsole)
{
// IISExpress and hostedwebcore w3wp run as background process
// have to attach console back to ensure post app_offline scenario still works
AttachConsole(ATTACH_PARENT_PROCESS);
}
}
else
{
// terminate the backend process immediately instead of waiting for timeout
TerminateBackendProcess();
}
}
DereferenceServerProcess();
}
VOID
SERVER_PROCESS::TerminateBackendProcess(
VOID
)
{
LPCWSTR apsz[1];
STACK_STRU(strEventMsg, 256);
if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L)
{
// backend process will be terminated, remove the waitcallback
if (m_hProcessWaitHandle != NULL)
{
UnregisterWait(m_hProcessWaitHandle);
m_hProcessWaitHandle = NULL;
}
// cannot gracefully shutdown or timeout, terminate the process
if (m_hProcessHandle != NULL && m_hProcessHandle != INVALID_HANDLE_VALUE)
{
TerminateProcess(m_hProcessHandle, 0);
m_hProcessHandle = NULL;
}
// as we skipped process exit callback (ProcessHandleCallback),
// need to dereference the object otherwise memory leak
DereferenceServerProcess();
// log a warning for ungraceful shutdown
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG,
m_dwProcessId)))
{
apsz[0] = strEventMsg.QueryStr();
if (FORWARDING_HANDLER::QueryEventLog() != NULL)
{
ReportEventW(FORWARDING_HANDLER::QueryEventLog(),
EVENTLOG_WARNING_TYPE,
0,
ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
NULL,
1,
0,
apsz,
NULL);
}
}
}
}