// 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 #include 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, MULTISZ *pszEnvironment, 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_pProcessManager->ReferenceProcessManager(); hr = m_ProcessPath.Copy(*pszProcessExePath); if (FAILED(hr)) { goto Finished; } hr = m_struLogFile.Copy(*pstruStdoutLogFile); if (FAILED(hr)) { goto Finished; } hr = m_Arguments.Copy(*pszArguments); if (FAILED(hr)) { goto Finished; } hr = m_Environment.Copy(*pszEnvironment); if (FAILED(hr)) { 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; } } } Finished: return hr; } HRESULT SERVER_PROCESS::StartProcess( IHttpContext *context ) { HRESULT hr = S_OK; PROCESS_INFORMATION processInformation = { 0 }; STARTUPINFOW startupInfo = { 0 }; WCHAR *pszCommandLine = NULL; DWORD dwCommandLineLen = 0; DWORD dwNumDigitsInPort = 0; DWORD dwNumDigitsInDebugPort = 0; LPWSTR pszCurrentEnvironment = NULL; DWORD dwCurrentEnvSize = 0; DWORD dwCreationFlags = 0; BOOL fReady = FALSE; DWORD dwTickCount = 0; STACK_STRU(strAspNetCorePortEnvVar, 32); STACK_STRU(strAspNetCoreDebugPortEnvVar, 32); MULTISZ mszNewEnvironment; MULTISZ mszEnvCopy; DWORD cChildProcess = 0; DWORD dwTimeDifference = 0; STACK_STRU(strEventMsg, 256); BOOL fDebugPortEnvSet = FALSE; BOOL fReplacedEnv = FALSE; BOOL fPortInUse = FALSE; LPCWSTR pszRootApplicationPath = NULL; BOOL fDebuggerAttachedToChildProcess = FALSE; STRU strFullProcessPath; STRU struApplicationId; RPC_STATUS rpcStatus; UUID logUuid; PSTR pszLogUuid = NULL; BOOL fRpcStringAllocd = FALSE; STRU struGuidEnv; STRU finalCommandLine; BOOL fDonePrepareCommandLine = FALSE; // // process id of the process listening on port we randomly generated. // DWORD dwActualProcessId = 0; WCHAR* pszPath = NULL; WCHAR pszFullPath[_MAX_PATH]; LPCWSTR apsz[1]; PCWSTR pszAppPath = NULL; GetStartupInfoW(&startupInfo); // // setup stdout and stderr handles to our stdout handle only if // the handle is valid. // SetupStdHandles(context, &startupInfo); // // generate new guid for each process // 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; hr = m_straGuid.Copy(pszLogUuid); if (FAILED(hr)) { goto Finished; } // // Generate random port that the new process will listen on. // if (g_fNsiApiNotSupported) { m_dwPort = GenerateRandomPort(); } else { DWORD cRetry = 0; do { // // ignore dwActualProcessId because here we are // determing whether the randomly generated port is // in use by any other process. // m_dwPort = GenerateRandomPort(); hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fPortInUse); } while (fPortInUse && ++cRetry < MAX_RETRY); if (cRetry > MAX_RETRY) { hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); goto Finished; } } dwNumDigitsInPort = GetNumberOfDigits(m_dwPort); hr = strAspNetCorePortEnvVar.SafeSnwprintf(L"%s=%u", ASPNETCORE_PORT_STR, m_dwPort); if (FAILED(hr)) { goto Finished; } // // Generate random debug port that the new process will listen on. // this will only be used if ASPNETCORE_DEBUG_PORT placeholder is found // in the aspNetCore config. // if (g_fNsiApiNotSupported) { while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); } else { DWORD cRetry = 0; do { while ((m_dwDebugPort = GenerateRandomPort()) == m_dwPort); hr = CheckIfServerIsUp(m_dwDebugPort, &dwActualProcessId, &fPortInUse); } while (fPortInUse && ++cRetry < MAX_RETRY); if (cRetry > MAX_RETRY) { hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); goto Finished; } } dwNumDigitsInDebugPort = GetNumberOfDigits(m_dwDebugPort); hr = strAspNetCoreDebugPortEnvVar.SafeSnwprintf(L"%s=%u", ASPNETCORE_DEBUG_PORT_STR, m_dwDebugPort); if (FAILED(hr)) { goto Finished; } // // Create environment for new process // struApplicationId.Copy(L"ASPNETCORE_APPL_PATH="); // let's find the app path. IIS does not support nested sites // we can seek for the fourth '/' if it exits // MACHINE/WEBROOT/APPHOST//. pszAppPath = context->GetApplication()->GetAppConfigPath(); DWORD dwCounter = 0; DWORD dwPosition = 0; while (pszAppPath[dwPosition] != NULL) { if (pszAppPath[dwPosition] == '/') { dwCounter++; if (dwCounter == 4) break; } dwPosition++; } if (dwCounter == 4) { struApplicationId.Append(pszAppPath + dwPosition); } else { struApplicationId.Append(L"/"); } mszNewEnvironment.Append(struApplicationId); struGuidEnv.Copy(L"ASPNETCORE_TOKEN="); struGuidEnv.AppendA(m_straGuid.QueryStr(), m_straGuid.QueryCCH()); mszNewEnvironment.Append(struGuidEnv); pszRootApplicationPath = context->GetApplication()->GetApplicationPhysicalPath(); // // generate process command line. // dwCommandLineLen = (DWORD)wcslen(pszRootApplicationPath) + m_ProcessPath.QueryCCH() + m_Arguments.QueryCCH() + 4; pszCommandLine = new WCHAR[dwCommandLineLen]; if (pszCommandLine == NULL) { hr = E_OUTOFMEMORY; goto Finished; } pszPath = m_ProcessPath.QueryStr(); if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) { // let's check whether it is a relative path WCHAR pszRelativePath[_MAX_PATH]; if (swprintf_s(pszRelativePath, _MAX_PATH, L"%s\\%s", pszRootApplicationPath, pszPath) == -1) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Finished; } if (_wfullpath(pszFullPath, pszRelativePath, _MAX_PATH) == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); goto Finished; } FILE *file = NULL; if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) { fclose(file); pszPath = pszFullPath; } } if (swprintf_s(pszCommandLine, dwCommandLineLen, L"\"%s\" %s", pszPath, m_Arguments.QueryStr()) == -1) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Finished; } // // replace %ASPNETCORE_PORT% with port number // hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( pszCommandLine, ASPNETCORE_PORT_PLACEHOLDER, ASPNETCORE_PORT_PLACEHOLDER_CCH, m_dwPort, dwNumDigitsInPort, &fReplacedEnv); if (FAILED(hr)) { goto Finished; } // // append AspNetCorePort to env variables. // mszNewEnvironment.Append(strAspNetCorePortEnvVar); hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( pszCommandLine, ASPNETCORE_DEBUG_PORT_PLACEHOLDER, ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, m_dwDebugPort, dwNumDigitsInDebugPort, &fReplacedEnv); if (FAILED(hr)) { goto Finished; } if (fReplacedEnv) { // // append debug port to environment only if // ASPNETCORE_DEBUG_PORT placeholder is present. // mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); fDebugPortEnvSet = TRUE; } // // append the environment variables from web.config/aspNetCore section. // append takes in length of string without the last null char // this allows user to override current environment variables // if (!mszEnvCopy.Copy(m_Environment)) { hr = E_OUTOFMEMORY; goto Finished; } LPWSTR multisz = mszEnvCopy.QueryStr(); while (*multisz != '\0') { // // replace %ASPNETCORE_PORT% placeholder if present. // hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( multisz, ASPNETCORE_PORT_PLACEHOLDER, ASPNETCORE_PORT_PLACEHOLDER_CCH, m_dwPort, dwNumDigitsInPort, &fReplacedEnv); if (FAILED(hr)) { goto Finished; } // // replace %ASPNETCORE_DEBUG_PORT% placeholder if present. // if this placeholder is present, add this placeholder=value as // an environment variable as well. // hr = ASPNETCORE_UTILS::ReplacePlaceHolderWithValue( multisz, ASPNETCORE_DEBUG_PORT_PLACEHOLDER, ASPNETCORE_DEBUG_PORT_PLACEHOLDER_CCH, m_dwDebugPort, dwNumDigitsInDebugPort, &fReplacedEnv); if (FAILED(hr)) { goto Finished; } if (fReplacedEnv && !fDebugPortEnvSet) { mszNewEnvironment.Append(strAspNetCoreDebugPortEnvVar); } mszNewEnvironment.Append(multisz); multisz += wcslen(multisz) + 1; } // // Append the current env. // copy takes in number of bytes including the double null terminator // pszCurrentEnvironment = GetEnvironmentStringsW(); if (pszCurrentEnvironment == NULL) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); goto Finished; } // // determine length of current environment block // do { while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); } while (*(pszCurrentEnvironment + dwCurrentEnvSize++) != 0); DBG_ASSERT(dwCurrentEnvSize > 0); // // environment block ends with \0\0, we don't want include the last \0 for appending // mszNewEnvironment.Append(pszCurrentEnvironment, dwCurrentEnvSize - 1); dwCreationFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP; finalCommandLine.Copy(pszCommandLine); fDonePrepareCommandLine = TRUE; if (!CreateProcessW( NULL, // applicationName finalCommandLine.QueryStr(), NULL, // processAttr NULL, // threadAttr TRUE, // inheritHandles dwCreationFlags, mszNewEnvironment.QueryStr(), pszRootApplicationPath, // currentDir &startupInfo, &processInformation)) { hr = HRESULT_FROM_WIN32(GetLastError()); // don't check return code as we already in error report strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.QueryStr(), hr, 0); goto Finished; } m_hProcessHandle = processInformation.hProcess; m_dwProcessId = processInformation.dwProcessId; if (m_hJobObject != NULL) { if (!AssignProcessToJobObject(m_hJobObject, processInformation.hProcess)) { 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; } if (CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; } // // since servers like tomcat would startup even if there was a port // collision, need to make sure the server is up and listening on // the port specified. // dwTickCount = GetTickCount(); do { DWORD processStatus; if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) { if (processStatus != STILL_ACTIVE) { hr = E_FAIL; strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.QueryStr(), hr, processStatus); goto Finished; } } if (g_fNsiApiNotSupported) { hr = CheckIfServerIsUp(m_dwPort, &fReady); } else { hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); } fDebuggerAttachedToChildProcess = IsDebuggerIsAttached(); if (!fReady) { Sleep(250); } dwTimeDifference = (GetTickCount() - dwTickCount); } while (fReady == FALSE && ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttachedToChildProcess)); hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle); if (FAILED(hr)) { goto Finished; } // // check if debugger is attached after startupTimeout. // if (!fDebuggerAttachedToChildProcess && CheckRemoteDebuggerPresent(processInformation.hProcess, &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; } hr = GetChildProcessHandles(); if (FAILED(hr)) { goto Finished; } BOOL processMatch = FALSE; if (dwActualProcessId == m_dwProcessId) { m_dwListeningProcessId = m_dwProcessId; processMatch = TRUE; } for (DWORD i = 0; i < m_cChildProcess; ++i) { if (!processMatch && dwActualProcessId == m_dwChildProcessIds[i]) { m_dwListeningProcessId = m_dwChildProcessIds[i]; processMatch = TRUE; } if (m_hChildProcessHandles[i] != NULL) { if (fDebuggerAttachedToChildProcess == FALSE && CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttachedToChildProcess) == 0) { // some error occurred - assume debugger is not attached; fDebuggerAttachedToChildProcess = FALSE; } hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], m_hChildProcessHandles[i]); if (FAILED(hr)) { goto Finished; } cChildProcess++; } } if (fReady == FALSE) { // // hr is already set by CheckIfServerIsUp // if (dwTimeDifference >= m_dwStartupTimeLimitInMS) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.QueryStr(), m_dwPort, hr); } goto Finished; } if (!g_fNsiApiNotSupported && !processMatch) { // // process that we created is not listening // on the port we specified. // fReady = FALSE; strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.QueryStr(), m_dwPort, hr); hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); goto Finished; } if (cChildProcess > 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. // if (g_fNsiApiNotSupported) { hr = CheckIfServerIsUp(m_dwPort, &fReady); } else { hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); } if ((FAILED(hr) || fReady == FALSE)) { strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.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; if (SUCCEEDED(strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, pszAppPath, 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 (FAILED(hr)) { if (strEventMsg.IsEmpty()) { if (!fDonePrepareCommandLine) strEventMsg.SafeSnwprintf( pszAppPath, ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, hr); else strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, pszAppPath, pszRootApplicationPath, finalCommandLine.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 (fRpcStringAllocd) { RpcStringFreeA((BYTE **)&pszLogUuid); pszLogUuid = NULL; } if (processInformation.hThread != NULL) { CloseHandle(processInformation.hThread); processInformation.hThread = NULL; } if (pszCurrentEnvironment != NULL) { FreeEnvironmentStringsW(pszCurrentEnvironment); pszCurrentEnvironment = NULL; } if (pszCommandLine != NULL) { delete[] pszCommandLine; pszCommandLine = 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; } for (DWORD i = 0; iGetApplication()->GetApplicationPhysicalPath(), &struAbsLogFilePath); if (FAILED(hr)) { goto Finished; } GetSystemTime(&systemTime); hr = struLogFileName.SafeSnwprintf(L"%s_%d_%d%d%d%d%d%d.log", struAbsLogFilePath.QueryStr(), GetCurrentProcessId(), systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond); 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_ BOOL *fReady ) { HRESULT hr = S_OK; int iResult = 0; SOCKADDR_IN sockAddr; BOOL fLocked = FALSE; _ASSERT(fReady != NULL); *fReady = FALSE; EnterCriticalSection(&m_csLock); fLocked = TRUE; if (m_socket == INVALID_SOCKET || m_socket == NULL) { m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (m_socket == 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(m_socket, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); if (iResult == SOCKET_ERROR) { hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; } // // Connected successfully, close socket. // iResult = closesocket(m_socket); if (iResult == SOCKET_ERROR) { hr = HRESULT_FROM_WIN32(WSAGetLastError()); goto Finished; } m_socket = NULL; *fReady = TRUE; Finished: if (fLocked) { LeaveCriticalSection(&m_csLock); fLocked = FALSE; } return hr; } 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; DBG_ASSERT(pfReady); DBG_ASSERT(pdwProcessId); *pfReady = FALSE; *pdwProcessId = 0; 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; } } Finished: if (pTCPInfo != NULL) { HeapFree(GetProcessHeap(), 0, pTCPInfo); pTCPInfo = NULL; } return hr; } // send ctrl-c signnal to the process to let it gracefully shutdown // if the process cannot shutdown within given time, terminate it // todo: allow user to config this shutdown timeout VOID SERVER_PROCESS::SendSignal( VOID ) { HANDLE hProc = INVALID_HANDLE_VALUE; BOOL fIsSuccess = FALSE; BOOL fFreeConsole = FALSE; LPCWSTR apsz[1]; STACK_STRU(strEventMsg, 256); ReferenceServerProcess(); hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); if (hProc != INVALID_HANDLE_VALUE) { // free current console first, as we migh 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 fIsSuccess = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId); FreeConsole(); } if (fFreeConsole) { // IISExpress and hostedwebcore w3wp run as background process // have to attach console back to ensure post app_offline scenario still work AttachConsole(ATTACH_PARENT_PROCESS); } } if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) { 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 TerminateProcess(m_hProcessHandle, 0); // 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); } } } } if (hProc != INVALID_HANDLE_VALUE) { CloseHandle(hProc); hProc = INVALID_HANDLE_VALUE; } if (m_hProcessHandle != INVALID_HANDLE_VALUE) { CloseHandle(m_hProcessHandle); m_hProcessHandle = INVALID_HANDLE_VALUE; } 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; iNumberOfAssignedProcesses > 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; iNumberOfProcessIdsInList; 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 (!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; iNumberOfProcessIdsInList; 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; iNumberOfProcessIdsInList; 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_socket(INVALID_SOCKET), m_fReady(FALSE), m_lStopping(0L), m_hStdoutHandle(NULL), m_fStdoutLogEnabled(FALSE), m_hJobObject(NULL), m_pForwarderConnection(NULL), m_dwListeningProcessId(0), m_hListeningProcessHandle(NULL) { InterlockedIncrement(&g_dwActiveServerProcesses); srand((unsigned int)time(NULL)); InitializeCriticalSection(&m_csLock); for (INT i = 0; iDereferenceProcessManager(); m_pProcessManager = NULL; } if (m_pForwarderConnection != NULL) { m_pForwarderConnection->DereferenceForwarderConnection(); m_pForwarderConnection = NULL; } DeleteCriticalSection(&m_csLock); 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; if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { CheckIfServerIsUp(m_dwPort, &fReady); if (!fReady) { m_pProcessManager->ShutdownProcess(this); } DereferenceServerProcess(); } return hr; }