Use hostfxr error callback support (#6043)
This commit is contained in:
parent
90511e6039
commit
5299eff616
|
|
@ -6,17 +6,15 @@
|
|||
#include "SRWExclusiveLock.h"
|
||||
#include "applicationinfo.h"
|
||||
#include "EventLog.h"
|
||||
#include "hostfxr_utility.h"
|
||||
#include "GlobalVersionUtility.h"
|
||||
#include "HandleWrapper.h"
|
||||
#include "file_utility.h"
|
||||
#include "LoggingHelpers.h"
|
||||
#include "resources.h"
|
||||
#include "ConfigurationLoadException.h"
|
||||
#include "WebConfigConfigurationSource.h"
|
||||
#include "ModuleHelpers.h"
|
||||
#include "BaseOutputManager.h"
|
||||
#include "Environment.h"
|
||||
#include "HostFxr.h"
|
||||
#include "RedirectionOutput.h"
|
||||
|
||||
const PCWSTR HandlerResolver::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll";
|
||||
const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll";
|
||||
|
|
@ -55,10 +53,9 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
|
|||
{
|
||||
if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
|
||||
{
|
||||
std::unique_ptr<HOSTFXR_OPTIONS> options;
|
||||
std::unique_ptr<BaseOutputManager> outputManager;
|
||||
std::unique_ptr<HostFxrResolutionResult> options;
|
||||
|
||||
RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
||||
RETURN_IF_FAILED(HostFxrResolutionResult::Create(
|
||||
L"",
|
||||
pConfiguration.QueryProcessPath(),
|
||||
pApplication.GetApplicationPhysicalPath(),
|
||||
|
|
@ -67,19 +64,13 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
|
|||
|
||||
location = options->GetDotnetExeLocation();
|
||||
|
||||
RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
|
||||
pConfiguration.QueryStdoutLogEnabled(),
|
||||
!m_pServer.IsCommandLineLaunch(),
|
||||
pConfiguration.QueryStdoutLogFile().c_str(),
|
||||
pApplication.GetApplicationPhysicalPath(),
|
||||
outputManager));
|
||||
auto redirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
|
||||
|
||||
|
||||
hr = FindNativeAssemblyFromHostfxr(*options.get(), pstrHandlerDllName, handlerDllPath, outputManager.get());
|
||||
hr = FindNativeAssemblyFromHostfxr(*options, pstrHandlerDllName, handlerDllPath, pApplication, pConfiguration, redirectionOutput);
|
||||
|
||||
if (FAILED_LOG(hr))
|
||||
{
|
||||
auto output = outputManager->GetStdOutContent();
|
||||
auto output = redirectionOutput->GetOutput();
|
||||
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_GENERAL_ERROR,
|
||||
|
|
@ -208,83 +199,76 @@ HandlerResolver::FindNativeAssemblyFromGlobalLocation(
|
|||
//
|
||||
HRESULT
|
||||
HandlerResolver::FindNativeAssemblyFromHostfxr(
|
||||
const HOSTFXR_OPTIONS& hostfxrOptions,
|
||||
const HostFxrResolutionResult& hostfxrOptions,
|
||||
PCWSTR libraryName,
|
||||
std::wstring& handlerDllPath,
|
||||
BaseOutputManager* outputManager
|
||||
const IHttpApplication &pApplication,
|
||||
const ShimOptions& pConfiguration,
|
||||
std::shared_ptr<RedirectionOutput> stringRedirectionOutput
|
||||
)
|
||||
try
|
||||
{
|
||||
std::wstring struNativeSearchPaths;
|
||||
size_t intIndex = 0;
|
||||
size_t intPrevIndex = 0;
|
||||
DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize;
|
||||
DWORD dwRequiredBufferSize = 0;
|
||||
hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = nullptr;
|
||||
|
||||
RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str()));
|
||||
|
||||
try
|
||||
auto const hostFxr = HostFxr::CreateFromLoadedModule();
|
||||
|
||||
{
|
||||
pFnHostFxrSearchDirectories = ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(m_hHostFxrDll, "hostfxr_get_native_search_directories");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_GENERAL_ERROR,
|
||||
ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
|
||||
hostfxrOptions.GetHostFxrLocation().c_str()
|
||||
);
|
||||
return OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
RETURN_LAST_ERROR_IF_NULL(pFnHostFxrSearchDirectories);
|
||||
struNativeSearchPaths.resize(dwBufferSize);
|
||||
|
||||
outputManager->TryStartRedirection();
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
DWORD hostfxrArgc;
|
||||
std::unique_ptr<PCWSTR[]> hostfxrArgv;
|
||||
|
||||
hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
|
||||
const auto intHostFxrExitCode = pFnHostFxrSearchDirectories(
|
||||
hostfxrArgc,
|
||||
hostfxrArgv.get(),
|
||||
struNativeSearchPaths.data(),
|
||||
dwBufferSize,
|
||||
&dwRequiredBufferSize
|
||||
);
|
||||
|
||||
if (intHostFxrExitCode == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (dwRequiredBufferSize > dwBufferSize)
|
||||
{
|
||||
dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
|
||||
|
||||
struNativeSearchPaths.resize(dwBufferSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop redirecting before logging to event log to avoid logging debug logs
|
||||
// twice.
|
||||
outputManager->TryStopRedirection();
|
||||
|
||||
// If hostfxr didn't set the required buffer size, something in the app is misconfigured
|
||||
// Ex: Framework not found.
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_GENERAL_ERROR,
|
||||
ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
|
||||
auto redirectionOutput = LoggingHelpers::CreateOutputs(
|
||||
pConfiguration.QueryStdoutLogEnabled(),
|
||||
pConfiguration.QueryStdoutLogFile(),
|
||||
pApplication.GetApplicationPhysicalPath(),
|
||||
std::move(stringRedirectionOutput)
|
||||
);
|
||||
|
||||
return E_UNEXPECTED;
|
||||
StandardStreamRedirection stdOutRedirection(*redirectionOutput.get(), m_pServer.IsCommandLineLaunch());
|
||||
auto hostFxrErrorRedirection = hostFxr.RedirectOutput(redirectionOutput.get());
|
||||
|
||||
struNativeSearchPaths.resize(dwBufferSize);
|
||||
while (TRUE)
|
||||
{
|
||||
DWORD hostfxrArgc;
|
||||
std::unique_ptr<PCWSTR[]> hostfxrArgv;
|
||||
|
||||
hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
|
||||
|
||||
const auto intHostFxrExitCode = hostFxr.GetNativeSearchDirectories(
|
||||
hostfxrArgc,
|
||||
hostfxrArgv.get(),
|
||||
struNativeSearchPaths.data(),
|
||||
dwBufferSize,
|
||||
&dwRequiredBufferSize
|
||||
);
|
||||
|
||||
if (intHostFxrExitCode == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (dwRequiredBufferSize > dwBufferSize)
|
||||
{
|
||||
dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
|
||||
|
||||
struNativeSearchPaths.resize(dwBufferSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If hostfxr didn't set the required buffer size, something in the app is misconfigured
|
||||
// Ex: Framework not found.
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_GENERAL_ERROR,
|
||||
ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
|
||||
);
|
||||
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputManager->TryStopRedirection();
|
||||
|
||||
struNativeSearchPaths.resize(struNativeSearchPaths.find(L'\0'));
|
||||
|
||||
auto fFound = FALSE;
|
||||
|
|
@ -323,4 +307,4 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
|
|||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
CATCH_RETURN()
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include "ShimOptions.h"
|
||||
#include "hostfxroptions.h"
|
||||
#include "HostFxrResolutionResult.h"
|
||||
#include "HandleWrapper.h"
|
||||
#include "ApplicationFactory.h"
|
||||
#include "BaseOutputManager.h"
|
||||
#include "RedirectionOutput.h"
|
||||
|
||||
class HandlerResolver
|
||||
{
|
||||
|
|
@ -21,7 +21,13 @@ public:
|
|||
private:
|
||||
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory);
|
||||
HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath);
|
||||
HRESULT FindNativeAssemblyFromHostfxr(const HOSTFXR_OPTIONS& hostfxrOptions, PCWSTR libraryName, std::wstring& handlerDllPath, BaseOutputManager* outputManager);
|
||||
HRESULT FindNativeAssemblyFromHostfxr(
|
||||
const HostFxrResolutionResult& hostfxrOptions,
|
||||
PCWSTR libraryName,
|
||||
std::wstring& handlerDllPath,
|
||||
const IHttpApplication &pApplication,
|
||||
const ShimOptions& pConfiguration,
|
||||
std::shared_ptr<RedirectionOutput> stringRedirectionOutput);
|
||||
|
||||
HMODULE m_hModule;
|
||||
const IHttpServer &m_pServer;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#include "applicationinfo.h"
|
||||
|
||||
#include "proxymodule.h"
|
||||
#include "hostfxr_utility.h"
|
||||
#include "HostFxrResolver.h"
|
||||
#include "debugutil.h"
|
||||
#include "resources.h"
|
||||
#include "SRWExclusiveLock.h"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "hostfxroptions.h"
|
||||
#include "HostFxrResolutionResult.h"
|
||||
#include "iapplication.h"
|
||||
#include "SRWSharedLock.h"
|
||||
#include "HandlerResolver.h"
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
#include "IOutputManager.h"
|
||||
#include "StdWrapper.h"
|
||||
#include "EventLog.h"
|
||||
#include "exceptions.h"
|
||||
#include "StringHelpers.h"
|
||||
#include "debugutil.h"
|
||||
|
||||
class BaseOutputManager :
|
||||
public IOutputManager
|
||||
{
|
||||
public:
|
||||
BaseOutputManager() : BaseOutputManager(/* fEnableNativeLogging */ true) {}
|
||||
BaseOutputManager(bool enableNativeLogging) :
|
||||
m_disposed(false),
|
||||
stdoutWrapper(nullptr),
|
||||
stderrWrapper(nullptr),
|
||||
m_enableNativeRedirection(enableNativeLogging)
|
||||
{
|
||||
InitializeSRWLock(&m_srwLock);
|
||||
}
|
||||
~BaseOutputManager() {}
|
||||
|
||||
void
|
||||
TryStartRedirection()
|
||||
{
|
||||
const auto startLambda = [&]() { this->Start(); };
|
||||
TryOperation(startLambda, L"Could not start stdout redirection in %s. Exception message: %s.");
|
||||
}
|
||||
|
||||
void
|
||||
TryStopRedirection()
|
||||
{
|
||||
const auto stopLambda = [&]() { this->Stop(); };
|
||||
TryOperation(stopLambda, L"Could not stop stdout redirection in %s. Exception message: %s.");
|
||||
}
|
||||
|
||||
protected:
|
||||
std::wstring m_stdOutContent;
|
||||
bool m_disposed;
|
||||
bool m_enableNativeRedirection;
|
||||
SRWLOCK m_srwLock{};
|
||||
std::unique_ptr<StdWrapper> stdoutWrapper;
|
||||
std::unique_ptr<StdWrapper> stderrWrapper;
|
||||
|
||||
template<typename Functor>
|
||||
void
|
||||
TryOperation(Functor func,
|
||||
std::wstring exceptionMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
func();
|
||||
}
|
||||
catch (const std::runtime_error& exception)
|
||||
{
|
||||
EventLog::Warn(ASPNETCORE_EVENT_GENERAL_WARNING, exceptionMessage.c_str(), GetModuleName().c_str(), to_wide_string(exception.what(), GetConsoleOutputCP()).c_str());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -199,7 +199,6 @@
|
|||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="application.h" />
|
||||
<ClInclude Include="baseoutputmanager.h" />
|
||||
<ClInclude Include="BindingInformation.h" />
|
||||
<ClInclude Include="ConfigurationSection.h" />
|
||||
<ClInclude Include="ConfigurationSource.h" />
|
||||
|
|
@ -209,22 +208,21 @@
|
|||
<ClInclude Include="EventTracing.h" />
|
||||
<ClInclude Include="exceptions.h" />
|
||||
<ClInclude Include="file_utility.h" />
|
||||
<ClInclude Include="FileOutputManager.h" />
|
||||
<ClInclude Include="GlobalVersionUtility.h" />
|
||||
<ClInclude Include="fx_ver.h" />
|
||||
<ClInclude Include="HandleWrapper.h" />
|
||||
<ClInclude Include="hostfxroptions.h" />
|
||||
<ClInclude Include="hostfxr_utility.h" />
|
||||
<ClInclude Include="HostFxr.h" />
|
||||
<ClInclude Include="HostFxrResolutionResult.h" />
|
||||
<ClInclude Include="HostFxrResolver.h" />
|
||||
<ClInclude Include="iapplication.h" />
|
||||
<ClInclude Include="debugutil.h" />
|
||||
<ClInclude Include="InvalidOperationException.h" />
|
||||
<ClInclude Include="IOutputManager.h" />
|
||||
<ClInclude Include="RedirectionOutput.h" />
|
||||
<ClInclude Include="irequesthandler.h" />
|
||||
<ClInclude Include="LoggingHelpers.h" />
|
||||
<ClInclude Include="ModuleHelpers.h" />
|
||||
<ClInclude Include="NonCopyable.h" />
|
||||
<ClInclude Include="NullOutputManager.h" />
|
||||
<ClInclude Include="PipeOutputManager.h" />
|
||||
<ClInclude Include="StandardStreamRedirection.h" />
|
||||
<ClInclude Include="RegistryKey.h" />
|
||||
<ClInclude Include="requesthandler.h" />
|
||||
<ClInclude Include="resources.h" />
|
||||
|
|
@ -246,14 +244,15 @@
|
|||
<ClCompile Include="Environment.cpp" />
|
||||
<ClCompile Include="EventLog.cpp" />
|
||||
<ClCompile Include="file_utility.cpp" />
|
||||
<ClCompile Include="FileOutputManager.cpp" />
|
||||
<ClCompile Include="fx_ver.cpp" />
|
||||
<ClCompile Include="GlobalVersionUtility.cpp" />
|
||||
<ClCompile Include="HandleWrapper.cpp" />
|
||||
<ClCompile Include="hostfxr_utility.cpp" />
|
||||
<ClCompile Include="hostfxroptions.cpp" />
|
||||
<ClCompile Include="HostFxr.cpp" />
|
||||
<ClCompile Include="HostFxrResolver.cpp" />
|
||||
<ClCompile Include="HostFxrResolutionResult.cpp" />
|
||||
<ClCompile Include="LoggingHelpers.cpp" />
|
||||
<ClCompile Include="PipeOutputManager.cpp" />
|
||||
<ClCompile Include="StandardStreamRedirection.cpp" />
|
||||
<ClCompile Include="RedirectionOutput.cpp" />
|
||||
<ClCompile Include="RegistryKey.cpp" />
|
||||
<ClCompile Include="StdWrapper.cpp" />
|
||||
<ClCompile Include="SRWExclusiveLock.cpp" />
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "FileOutputManager.h"
|
||||
#include "sttimer.h"
|
||||
#include "exceptions.h"
|
||||
#include "debugutil.h"
|
||||
#include "SRWExclusiveLock.h"
|
||||
#include "file_utility.h"
|
||||
#include "StdWrapper.h"
|
||||
#include "StringHelpers.h"
|
||||
|
||||
FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath) :
|
||||
FileOutputManager(pwzStdOutLogFileName, pwzApplicationPath, /* fEnableNativeLogging */ true) { }
|
||||
|
||||
FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath, bool fEnableNativeLogging) :
|
||||
BaseOutputManager(fEnableNativeLogging),
|
||||
m_hLogFileHandle(INVALID_HANDLE_VALUE),
|
||||
m_applicationPath(pwzApplicationPath),
|
||||
m_stdOutLogFileName(pwzStdOutLogFileName)
|
||||
{
|
||||
InitializeSRWLock(&m_srwLock);
|
||||
}
|
||||
|
||||
FileOutputManager::~FileOutputManager()
|
||||
{
|
||||
FileOutputManager::Stop();
|
||||
}
|
||||
|
||||
// Start redirecting stdout and stderr into the file handle.
|
||||
// Uses sttimer to continuously flush output into the file.
|
||||
void
|
||||
FileOutputManager::Start()
|
||||
{
|
||||
SYSTEMTIME systemTime;
|
||||
SECURITY_ATTRIBUTES saAttr = { 0 };
|
||||
FILETIME processCreationTime;
|
||||
FILETIME dummyFileTime;
|
||||
|
||||
// To make Console.* functions work, allocate a console
|
||||
// in the current process.
|
||||
if (!AllocConsole())
|
||||
{
|
||||
THROW_LAST_ERROR_IF(GetLastError() != ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
// Concatenate the log file name and application path
|
||||
auto logPath = m_applicationPath / m_stdOutLogFileName;
|
||||
create_directories(logPath.parent_path());
|
||||
|
||||
THROW_LAST_ERROR_IF(!GetProcessTimes(
|
||||
GetCurrentProcess(),
|
||||
&processCreationTime,
|
||||
&dummyFileTime,
|
||||
&dummyFileTime,
|
||||
&dummyFileTime));
|
||||
|
||||
THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
|
||||
|
||||
m_logFilePath = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
|
||||
logPath.c_str(),
|
||||
systemTime.wYear,
|
||||
systemTime.wMonth,
|
||||
systemTime.wDay,
|
||||
systemTime.wHour,
|
||||
systemTime.wMinute,
|
||||
systemTime.wSecond,
|
||||
GetCurrentProcessId());
|
||||
|
||||
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
saAttr.bInheritHandle = TRUE;
|
||||
saAttr.lpSecurityDescriptor = NULL;
|
||||
|
||||
// Create the file with both READ and WRITE.
|
||||
m_hLogFileHandle = CreateFileW(m_logFilePath.c_str(),
|
||||
FILE_READ_DATA | FILE_WRITE_DATA,
|
||||
FILE_SHARE_READ,
|
||||
&saAttr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
|
||||
THROW_LAST_ERROR_IF(m_hLogFileHandle == INVALID_HANDLE_VALUE);
|
||||
|
||||
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
|
||||
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
|
||||
|
||||
stdoutWrapper->StartRedirection();
|
||||
stderrWrapper->StartRedirection();
|
||||
}
|
||||
|
||||
void
|
||||
FileOutputManager::Stop()
|
||||
{
|
||||
CHAR pzFileContents[MAX_FILE_READ_SIZE] = { 0 };
|
||||
DWORD dwNumBytesRead;
|
||||
LARGE_INTEGER li = { 0 };
|
||||
DWORD dwFilePointer = 0;
|
||||
HANDLE handle = NULL;
|
||||
WIN32_FIND_DATA fileData;
|
||||
|
||||
if (m_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SRWExclusiveLock lock(m_srwLock);
|
||||
|
||||
if (m_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_disposed = true;
|
||||
|
||||
if (m_hLogFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
|
||||
}
|
||||
|
||||
FlushFileBuffers(m_hLogFileHandle);
|
||||
|
||||
if (stdoutWrapper != nullptr)
|
||||
{
|
||||
THROW_IF_FAILED(stdoutWrapper->StopRedirection());
|
||||
}
|
||||
|
||||
if (stderrWrapper != nullptr)
|
||||
{
|
||||
THROW_IF_FAILED(stderrWrapper->StopRedirection());
|
||||
}
|
||||
|
||||
// delete empty log file
|
||||
handle = FindFirstFile(m_logFilePath.c_str(), &fileData);
|
||||
if (handle != INVALID_HANDLE_VALUE &&
|
||||
handle != NULL &&
|
||||
fileData.nFileSizeHigh == 0 &&
|
||||
fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
|
||||
{
|
||||
FindClose(handle);
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(m_logFilePath.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the first 30Kb from the file and store it in a buffer.
|
||||
// By doing this, we can close the handle to the file and be done with it.
|
||||
THROW_LAST_ERROR_IF(!GetFileSizeEx(m_hLogFileHandle, &li));
|
||||
|
||||
if (li.HighPart > 0)
|
||||
{
|
||||
THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID));
|
||||
}
|
||||
|
||||
dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN);
|
||||
|
||||
THROW_LAST_ERROR_IF(dwFilePointer == INVALID_SET_FILE_POINTER);
|
||||
|
||||
THROW_LAST_ERROR_IF(!ReadFile(m_hLogFileHandle, pzFileContents, MAX_FILE_READ_SIZE, &dwNumBytesRead, NULL));
|
||||
|
||||
m_stdOutContent = to_wide_string(std::string(pzFileContents, dwNumBytesRead), GetConsoleOutputCP());
|
||||
|
||||
auto content = GetStdOutContent();
|
||||
if (!content.empty())
|
||||
{
|
||||
// printf will fail in in full IIS
|
||||
if (wprintf(content.c_str()) != -1)
|
||||
{
|
||||
// Need to flush contents for the new stdout and stderr
|
||||
_flushall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring FileOutputManager::GetStdOutContent()
|
||||
{
|
||||
return m_stdOutContent;
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sttimer.h"
|
||||
#include "HandleWrapper.h"
|
||||
#include "StdWrapper.h"
|
||||
#include "stringa.h"
|
||||
#include "stringu.h"
|
||||
#include "BaseOutputManager.h"
|
||||
|
||||
class FileOutputManager : public BaseOutputManager
|
||||
{
|
||||
#define MAX_FILE_READ_SIZE 30000
|
||||
public:
|
||||
FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName);
|
||||
FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName, bool fEnableNativeLogging);
|
||||
~FileOutputManager();
|
||||
|
||||
virtual std::wstring GetStdOutContent() override;
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
HandleWrapper<InvalidHandleTraits> m_hLogFileHandle;
|
||||
std::wstring m_stdOutLogFileName;
|
||||
std::filesystem::path m_applicationPath;
|
||||
std::filesystem::path m_logFilePath;
|
||||
};
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "HostFxr.h"
|
||||
|
||||
#include "ModuleHelpers.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
HostFxrErrorRedirector::HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept
|
||||
: m_setErrorWriter(setErrorWriterFn)
|
||||
{
|
||||
if (m_setErrorWriter)
|
||||
{
|
||||
m_writeFunction = writeFunction;
|
||||
m_setErrorWriter(HostFxrErrorRedirectorCallback);
|
||||
}
|
||||
}
|
||||
|
||||
HostFxrErrorRedirector::~HostFxrErrorRedirector()
|
||||
{
|
||||
if (m_setErrorWriter)
|
||||
{
|
||||
m_setErrorWriter(nullptr);
|
||||
m_writeFunction = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HostFxrErrorRedirector::HostFxrErrorRedirectorCallback(const WCHAR* message)
|
||||
{
|
||||
m_writeFunction->Append(std::wstring(message) + L"\r\n");
|
||||
}
|
||||
|
||||
int HostFxr::Main(DWORD argc, const PCWSTR* argv) const noexcept(false)
|
||||
{
|
||||
return m_hostfxr_main_fn(argc, argv);
|
||||
}
|
||||
|
||||
int HostFxr::GetNativeSearchDirectories(INT argc, const PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept
|
||||
{
|
||||
return m_hostfxr_get_native_search_directories_fn(argc, argv, buffer, buffer_size, required_buffer_size);
|
||||
}
|
||||
|
||||
HostFxrErrorRedirector HostFxr::RedirectOutput(RedirectionOutput* writer) const noexcept
|
||||
{
|
||||
return HostFxrErrorRedirector(m_corehost_set_error_writer_fn, writer);
|
||||
}
|
||||
|
||||
HostFxr HostFxr::CreateFromLoadedModule()
|
||||
{
|
||||
HMODULE hModule;
|
||||
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
|
||||
|
||||
try
|
||||
{
|
||||
return HostFxr(
|
||||
ModuleHelpers::GetKnownProcAddress<hostfxr_main_fn>(hModule, "hostfxr_main"),
|
||||
ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(hModule, "hostfxr_get_native_search_directories"),
|
||||
ModuleHelpers::GetKnownProcAddress<corehost_set_error_writer_fn>(hModule, "hostfxr_set_error_writer", /* optional */ true));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_GENERAL_ERROR,
|
||||
ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
|
||||
ModuleHelpers::GetModuleFileNameValue(hModule).c_str()
|
||||
);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HostFxrResolver.h"
|
||||
#include "exceptions.h"
|
||||
#include "RedirectionOutput.h"
|
||||
|
||||
typedef INT(*hostfxr_get_native_search_directories_fn) (INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
|
||||
typedef INT(*hostfxr_main_fn) (DWORD argc, CONST PCWSTR argv[]);
|
||||
typedef void(*corehost_error_writer_fn) (const WCHAR* message);
|
||||
typedef corehost_error_writer_fn(*corehost_set_error_writer_fn) (corehost_error_writer_fn error_writer);
|
||||
|
||||
class HostFxrErrorRedirector: NonCopyable
|
||||
{
|
||||
public:
|
||||
HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept;
|
||||
|
||||
~HostFxrErrorRedirector();
|
||||
|
||||
static void HostFxrErrorRedirectorCallback(const WCHAR* message);
|
||||
|
||||
private:
|
||||
corehost_set_error_writer_fn m_setErrorWriter;
|
||||
static inline thread_local RedirectionOutput* m_writeFunction;
|
||||
};
|
||||
|
||||
class HostFxr
|
||||
{
|
||||
public:
|
||||
HostFxr(
|
||||
hostfxr_main_fn hostfxr_main_fn,
|
||||
hostfxr_get_native_search_directories_fn hostfxr_get_native_search_directories_fn,
|
||||
corehost_set_error_writer_fn corehost_set_error_writer_fn) noexcept
|
||||
: m_hostfxr_main_fn(hostfxr_main_fn),
|
||||
m_hostfxr_get_native_search_directories_fn(hostfxr_get_native_search_directories_fn),
|
||||
m_corehost_set_error_writer_fn(corehost_set_error_writer_fn)
|
||||
{
|
||||
}
|
||||
|
||||
~HostFxr() = default;
|
||||
|
||||
int Main(DWORD argc, CONST PCWSTR* argv) const noexcept(false);
|
||||
|
||||
int GetNativeSearchDirectories(INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept;
|
||||
|
||||
HostFxrErrorRedirector RedirectOutput(RedirectionOutput* writer) const noexcept;
|
||||
|
||||
static
|
||||
HostFxr CreateFromLoadedModule();
|
||||
|
||||
private:
|
||||
hostfxr_main_fn m_hostfxr_main_fn;
|
||||
hostfxr_get_native_search_directories_fn m_hostfxr_get_native_search_directories_fn;
|
||||
corehost_set_error_writer_fn m_corehost_set_error_writer_fn;
|
||||
};
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "HostFxrResolutionResult.h"
|
||||
|
||||
#include "HostFxrResolver.h"
|
||||
#include "debugutil.h"
|
||||
#include "exceptions.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
void HostFxrResolutionResult::GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const
|
||||
{
|
||||
hostfxrArgc = static_cast<DWORD>(m_arguments.size());
|
||||
hostfxrArgv = std::make_unique<PCWSTR[]>(hostfxrArgc);
|
||||
for (DWORD i = 0; i < hostfxrArgc; ++i)
|
||||
{
|
||||
hostfxrArgv[i] = m_arguments[i].c_str();
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT HostFxrResolutionResult::Create(
|
||||
_In_ const std::wstring& pcwzDotnetExePath,
|
||||
_In_ const std::wstring& pcwzProcessPath,
|
||||
_In_ const std::wstring& pcwzApplicationPhysicalPath,
|
||||
_In_ const std::wstring& pcwzArguments,
|
||||
_Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper)
|
||||
{
|
||||
std::filesystem::path knownDotnetLocation;
|
||||
|
||||
if (!pcwzDotnetExePath.empty())
|
||||
{
|
||||
knownDotnetLocation = pcwzDotnetExePath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::filesystem::path hostFxrDllPath;
|
||||
std::vector<std::wstring> arguments;
|
||||
HostFxrResolver::GetHostFxrParameters(
|
||||
pcwzProcessPath,
|
||||
pcwzApplicationPhysicalPath,
|
||||
pcwzArguments,
|
||||
hostFxrDllPath,
|
||||
knownDotnetLocation,
|
||||
arguments);
|
||||
|
||||
LOG_INFOF(L"Parsed hostfxr options: dotnet location: '%ls' hostfxr path: '%ls' arguments:", knownDotnetLocation.c_str(), hostFxrDllPath.c_str());
|
||||
for (size_t i = 0; i < arguments.size(); i++)
|
||||
{
|
||||
LOG_INFOF(L"Argument[%d] = '%ls'", i, arguments[i].c_str());
|
||||
}
|
||||
ppWrapper = std::make_unique<HostFxrResolutionResult>(knownDotnetLocation, hostFxrDllPath, arguments);
|
||||
}
|
||||
catch (InvalidOperationException &ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
|
||||
pcwzApplicationPhysicalPath.c_str(),
|
||||
ex.as_wstring().c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
catch (std::runtime_error &ex)
|
||||
{
|
||||
EventLog::Error(
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR,
|
||||
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
|
||||
pcwzApplicationPhysicalPath.c_str(),
|
||||
GetUnexpectedExceptionMessage(ex).c_str());
|
||||
|
||||
RETURN_CAUGHT_EXCEPTION();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class HostFxrResolutionResult
|
||||
{
|
||||
public:
|
||||
HostFxrResolutionResult(
|
||||
std::filesystem::path dotnetExeLocation,
|
||||
std::filesystem::path hostFxrLocation,
|
||||
std::vector<std::wstring> arguments
|
||||
) noexcept
|
||||
: m_dotnetExeLocation(std::move(dotnetExeLocation)),
|
||||
m_hostFxrLocation(std::move(hostFxrLocation)),
|
||||
m_arguments(std::move(arguments))
|
||||
{}
|
||||
|
||||
void
|
||||
GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const;
|
||||
|
||||
const std::filesystem::path&
|
||||
GetHostFxrLocation() const noexcept
|
||||
{
|
||||
return m_hostFxrLocation;
|
||||
}
|
||||
|
||||
const std::filesystem::path&
|
||||
GetDotnetExeLocation() const noexcept
|
||||
{
|
||||
return m_dotnetExeLocation;
|
||||
}
|
||||
|
||||
static
|
||||
HRESULT Create(
|
||||
_In_ const std::wstring& pcwzExeLocation,
|
||||
_In_ const std::wstring& pcwzProcessPath,
|
||||
_In_ const std::wstring& pcwzApplicationPhysicalPath,
|
||||
_In_ const std::wstring& pcwzArguments,
|
||||
_Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper);
|
||||
|
||||
private:
|
||||
const std::filesystem::path m_dotnetExeLocation;
|
||||
const std::filesystem::path m_hostFxrLocation;
|
||||
const std::vector<std::wstring> m_arguments;
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "hostfxr_utility.h"
|
||||
#include "HostFxrResolver.h"
|
||||
|
||||
#include <atlcomcli.h>
|
||||
#include "fx_ver.h"
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
namespace fs = std::filesystem;
|
||||
|
||||
void
|
||||
HOSTFXR_UTILITY::GetHostFxrParameters(
|
||||
HostFxrResolver::GetHostFxrParameters(
|
||||
const fs::path &processPath,
|
||||
const fs::path &applicationPhysicalPath,
|
||||
const std::wstring &applicationArguments,
|
||||
|
|
@ -138,13 +138,13 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
}
|
||||
|
||||
BOOL
|
||||
HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
|
||||
HostFxrResolver::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
|
||||
{
|
||||
return ends_with(dotnetPath, L"dotnet.exe", true);
|
||||
}
|
||||
|
||||
void
|
||||
HOSTFXR_UTILITY::AppendArguments(
|
||||
HostFxrResolver::AppendArguments(
|
||||
const std::wstring &applicationArguments,
|
||||
const fs::path &applicationPhysicalPath,
|
||||
std::vector<std::wstring> &arguments,
|
||||
|
|
@ -216,7 +216,7 @@ HOSTFXR_UTILITY::AppendArguments(
|
|||
// like: C:\Program Files\dotnet\dotnet.exe, C:\Program Files\dotnet\dotnet, dotnet.exe, or dotnet.
|
||||
// Get the absolute path to dotnet. If the path is already an absolute path, it will return that path
|
||||
fs::path
|
||||
HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
||||
HostFxrResolver::GetAbsolutePathToDotnet(
|
||||
const fs::path & applicationPath,
|
||||
const fs::path & requestedPath
|
||||
)
|
||||
|
|
@ -296,7 +296,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
}
|
||||
|
||||
fs::path
|
||||
HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
||||
HostFxrResolver::GetAbsolutePathToHostFxr(
|
||||
const fs::path & dotnetPath
|
||||
)
|
||||
{
|
||||
|
|
@ -336,7 +336,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
|||
// Returns true if a valid dotnet was found, else false.R
|
||||
//
|
||||
std::optional<fs::path>
|
||||
HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
|
||||
HostFxrResolver::InvokeWhereToFindDotnet()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
// Arguments to call where.exe
|
||||
|
|
@ -480,14 +480,14 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
|
|||
}
|
||||
|
||||
std::optional<fs::path>
|
||||
HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles()
|
||||
HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles()
|
||||
{
|
||||
const auto programFilesDotnet = fs::path(Environment::ExpandEnvironmentVariables(L"%ProgramFiles%")) / "dotnet" / "dotnet.exe";
|
||||
return is_regular_file(programFilesDotnet) ? std::make_optional(programFilesDotnet) : std::nullopt;
|
||||
}
|
||||
|
||||
std::wstring
|
||||
HOSTFXR_UTILITY::FindHighestDotNetVersion(
|
||||
HostFxrResolver::FindHighestDotNetVersion(
|
||||
_In_ std::vector<std::wstring> & vFolders
|
||||
)
|
||||
{
|
||||
|
|
@ -505,7 +505,7 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion(
|
|||
}
|
||||
|
||||
VOID
|
||||
HOSTFXR_UTILITY::FindDotNetFolders(
|
||||
HostFxrResolver::FindDotNetFolders(
|
||||
const std::filesystem::path &path,
|
||||
_Out_ std::vector<std::wstring> &pvFolders
|
||||
)
|
||||
|
|
@ -9,12 +9,9 @@
|
|||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
typedef INT(*hostfxr_get_native_search_directories_fn) (CONST INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
|
||||
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]);
|
||||
|
||||
#define READ_BUFFER_SIZE 4096
|
||||
|
||||
class HOSTFXR_UTILITY
|
||||
class HostFxrResolver
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
@ -68,7 +65,6 @@ private:
|
|||
const std::filesystem::path & dotnetPath
|
||||
);
|
||||
|
||||
|
||||
static
|
||||
std::optional<std::filesystem::path>
|
||||
InvokeWhereToFindDotnet();
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "stringa.h"
|
||||
|
||||
class IOutputManager
|
||||
{
|
||||
public:
|
||||
virtual
|
||||
void
|
||||
Start() = 0;
|
||||
|
||||
virtual
|
||||
~IOutputManager() = default;
|
||||
|
||||
virtual
|
||||
std::wstring
|
||||
GetStdOutContent() = 0;
|
||||
|
||||
virtual
|
||||
void
|
||||
Stop() = 0;
|
||||
};
|
||||
|
||||
|
|
@ -3,55 +3,21 @@
|
|||
|
||||
#include "stdafx.h"
|
||||
#include "LoggingHelpers.h"
|
||||
#include "FileOutputManager.h"
|
||||
#include "PipeOutputManager.h"
|
||||
#include "NullOutputManager.h"
|
||||
#include "debugutil.h"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include "ntassert.h"
|
||||
#include "StandardStreamRedirection.h"
|
||||
#include "exceptions.h"
|
||||
#include "EventLog.h"
|
||||
#include "BaseOutputManager.h"
|
||||
|
||||
HRESULT
|
||||
LoggingHelpers::CreateLoggingProvider(
|
||||
bool fIsLoggingEnabled,
|
||||
bool fEnableNativeLogging,
|
||||
PCWSTR pwzStdOutFileName,
|
||||
PCWSTR pwzApplicationPath,
|
||||
std::unique_ptr<BaseOutputManager>& outputManager
|
||||
)
|
||||
std::shared_ptr<RedirectionOutput> LoggingHelpers::CreateOutputs(
|
||||
bool enableFileLogging,
|
||||
std::wstring outputFileName,
|
||||
std::wstring applicationPath,
|
||||
std::shared_ptr<RedirectionOutput> stringStreamOutput)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
DBG_ASSERT(outputManager != NULL);
|
||||
|
||||
try
|
||||
auto stdOutOutput = std::make_shared<StandardOutputRedirectionOutput>();
|
||||
std::shared_ptr<RedirectionOutput> fileOutput;
|
||||
if (enableFileLogging)
|
||||
{
|
||||
// Check if there is an existing active console window before redirecting
|
||||
// Window == IISExpress with active console window, don't redirect to a pipe
|
||||
// if true.
|
||||
CONSOLE_SCREEN_BUFFER_INFO dummy;
|
||||
|
||||
if (fIsLoggingEnabled)
|
||||
{
|
||||
auto manager = std::make_unique<FileOutputManager>(pwzStdOutFileName, pwzApplicationPath, fEnableNativeLogging);
|
||||
outputManager = std::move(manager);
|
||||
}
|
||||
else if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &dummy))
|
||||
{
|
||||
outputManager = std::make_unique<PipeOutputManager>(fEnableNativeLogging);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputManager = std::make_unique<NullOutputManager>();
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc&)
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
fileOutput = std::make_shared<FileRedirectionOutput>(applicationPath, outputFileName);
|
||||
}
|
||||
|
||||
return hr;
|
||||
return std::make_shared<AggregateRedirectionOutput>(std::move(fileOutput), std::move(stdOutOutput), std::move(stringStreamOutput));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,19 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "BaseOutputManager.h"
|
||||
#include "NonCopyable.h"
|
||||
#include "StandardStreamRedirection.h"
|
||||
|
||||
class LoggingHelpers
|
||||
{
|
||||
public:
|
||||
|
||||
static
|
||||
HRESULT
|
||||
CreateLoggingProvider(
|
||||
bool fLoggingEnabled,
|
||||
bool fEnableNativeLogging,
|
||||
PCWSTR pwzStdOutFileName,
|
||||
PCWSTR pwzApplicationPath,
|
||||
std::unique_ptr<BaseOutputManager>& outputManager
|
||||
static std::shared_ptr<RedirectionOutput>
|
||||
CreateOutputs(
|
||||
bool enableFileLogging,
|
||||
std::wstring outputFileName,
|
||||
std::wstring applicationPath,
|
||||
std::shared_ptr<RedirectionOutput> stringStreamOutput
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public:
|
|||
void IncrementCurrentModuleRefCount(HandleWrapper<ModuleHandleTraits> &handle)
|
||||
{
|
||||
WCHAR path[MAX_PATH];
|
||||
|
||||
|
||||
#pragma warning( push )
|
||||
#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
|
||||
|
||||
|
|
@ -26,14 +26,30 @@ public:
|
|||
|
||||
template<typename Func>
|
||||
static
|
||||
Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName) {
|
||||
|
||||
Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName, bool optional = false) {
|
||||
|
||||
#pragma warning( push )
|
||||
#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast
|
||||
#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast
|
||||
auto proc = reinterpret_cast<Func>(GetProcAddress(hModule, lpProcName));
|
||||
#pragma warning( pop )
|
||||
|
||||
THROW_LAST_ERROR_IF (!proc);
|
||||
THROW_LAST_ERROR_IF (!optional && !proc);
|
||||
return proc;
|
||||
}
|
||||
|
||||
static
|
||||
std::wstring
|
||||
GetModuleFileNameValue(HMODULE hModule)
|
||||
{
|
||||
// this method is used for logging purposes for modules known to be under MAX_PATH
|
||||
WCHAR path[MAX_PATH];
|
||||
|
||||
#pragma warning( push )
|
||||
#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
|
||||
|
||||
THROW_LAST_ERROR_IF(!GetModuleFileName(hModule, path, MAX_PATH));
|
||||
|
||||
return path;
|
||||
#pragma warning( pop )
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
class NullOutputManager : public BaseOutputManager
|
||||
{
|
||||
public:
|
||||
|
||||
NullOutputManager() = default;
|
||||
|
||||
~NullOutputManager() = default;
|
||||
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
}
|
||||
|
||||
std::wstring GetStdOutContent()
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StdWrapper.h"
|
||||
#include "stringu.h"
|
||||
#include "BaseOutputManager.h"
|
||||
|
||||
class PipeOutputManager : public BaseOutputManager
|
||||
{
|
||||
// Timeout to be used if a thread never exits
|
||||
#define PIPE_OUTPUT_THREAD_TIMEOUT 2000
|
||||
|
||||
// Max event log message is ~32KB, limit pipe size just below that.
|
||||
#define MAX_PIPE_READ_SIZE 30000
|
||||
public:
|
||||
PipeOutputManager();
|
||||
PipeOutputManager(bool fEnableNativeLogging);
|
||||
~PipeOutputManager();
|
||||
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
std::wstring GetStdOutContent() override;
|
||||
|
||||
// Thread functions
|
||||
void ReadStdErrHandleInternal();
|
||||
|
||||
static void ReadStdErrHandle(LPVOID pContext);
|
||||
|
||||
private:
|
||||
|
||||
HANDLE m_hErrReadPipe;
|
||||
HANDLE m_hErrWritePipe;
|
||||
HANDLE m_hErrThread;
|
||||
CHAR m_pipeContents[MAX_PIPE_READ_SIZE] = { 0 };
|
||||
DWORD m_numBytesReadTotal;
|
||||
};
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "RedirectionOutput.h"
|
||||
#include <filesystem>
|
||||
#include "exceptions.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
AggregateRedirectionOutput::AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true):
|
||||
m_outputA(std::move(outputA)), m_outputB(std::move(outputB)), m_outputC(std::move(outputC))
|
||||
{
|
||||
}
|
||||
|
||||
void AggregateRedirectionOutput::Append(const std::wstring& text)
|
||||
{
|
||||
if (m_outputA != nullptr)
|
||||
{
|
||||
m_outputA->Append(text);
|
||||
}
|
||||
if (m_outputB != nullptr)
|
||||
{
|
||||
m_outputB->Append(text);
|
||||
}
|
||||
if (m_outputC != nullptr)
|
||||
{
|
||||
m_outputC->Append(text);
|
||||
}
|
||||
}
|
||||
|
||||
FileRedirectionOutput::FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
SYSTEMTIME systemTime{};
|
||||
FILETIME processCreationTime;
|
||||
FILETIME dummyFileTime;
|
||||
|
||||
// Concatenate the log file name and application path
|
||||
auto logPath = std::filesystem::path(applicationPath) / fileName;
|
||||
create_directories(logPath.parent_path());
|
||||
|
||||
THROW_LAST_ERROR_IF(!GetProcessTimes(
|
||||
GetCurrentProcess(),
|
||||
&processCreationTime,
|
||||
&dummyFileTime,
|
||||
&dummyFileTime,
|
||||
&dummyFileTime));
|
||||
|
||||
THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
|
||||
|
||||
m_fileName = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
|
||||
logPath.c_str(),
|
||||
systemTime.wYear,
|
||||
systemTime.wMonth,
|
||||
systemTime.wDay,
|
||||
systemTime.wHour,
|
||||
systemTime.wMinute,
|
||||
systemTime.wSecond,
|
||||
GetCurrentProcessId());
|
||||
|
||||
m_file.exceptions(std::ifstream::failbit);
|
||||
m_file.open(m_fileName, std::wofstream::out | std::wofstream::app);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
EventLog::Warn(
|
||||
ASPNETCORE_EVENT_GENERAL_WARNING,
|
||||
L"Could not start stdout file redirection to '%s' with application base '%s'. %s.",
|
||||
fileName.c_str(),
|
||||
applicationPath.c_str(),
|
||||
CaughtExceptionToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void FileRedirectionOutput::Append(const std::wstring& text)
|
||||
{
|
||||
if (m_file.is_open())
|
||||
{
|
||||
const auto multiByte = to_multi_byte_string(text, CP_UTF8);
|
||||
m_file << multiByte;
|
||||
}
|
||||
}
|
||||
|
||||
FileRedirectionOutput::~FileRedirectionOutput()
|
||||
{
|
||||
if (m_file.is_open())
|
||||
{
|
||||
m_file.close();
|
||||
std::error_code ec;
|
||||
if (std::filesystem::file_size(m_fileName, ec) == 0 && SUCCEEDED_LOG(ec))
|
||||
{
|
||||
std::filesystem::remove(m_fileName, ec);
|
||||
LOG_IF_FAILED(ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StandardOutputRedirectionOutput::StandardOutputRedirectionOutput(): m_handle(GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
{
|
||||
HANDLE stdOutHandle;
|
||||
DuplicateHandle(
|
||||
/* hSourceProcessHandle*/ GetCurrentProcess(),
|
||||
/* hSourceHandle */ GetStdHandle(STD_OUTPUT_HANDLE),
|
||||
/* hTargetProcessHandle */ GetCurrentProcess(),
|
||||
/* lpTargetHandle */&stdOutHandle,
|
||||
/* dwDesiredAccess */ 0, // dwDesired is ignored if DUPLICATE_SAME_ACCESS is specified
|
||||
/* bInheritHandle */ FALSE,
|
||||
/* dwOptions */ DUPLICATE_SAME_ACCESS);
|
||||
m_handle = stdOutHandle;
|
||||
}
|
||||
|
||||
void StandardOutputRedirectionOutput::Append(const std::wstring& text)
|
||||
{
|
||||
DWORD nBytesWritten = 0;
|
||||
auto encodedBytes = to_multi_byte_string(text, GetConsoleOutputCP());
|
||||
WriteFile(m_handle, encodedBytes.data(), static_cast<DWORD>(encodedBytes.size()), &nBytesWritten, nullptr);
|
||||
}
|
||||
|
||||
StringStreamRedirectionOutput::StringStreamRedirectionOutput()
|
||||
{
|
||||
InitializeSRWLock(&m_srwLock);
|
||||
}
|
||||
|
||||
void StringStreamRedirectionOutput::Append(const std::wstring& text)
|
||||
{
|
||||
SRWExclusiveLock lock(m_srwLock);
|
||||
auto const writeSize = min(m_charactersLeft, text.size());
|
||||
m_output.write(text.c_str(), writeSize);
|
||||
m_charactersLeft -= writeSize;
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SRWExclusiveLock.h"
|
||||
#include "NonCopyable.h"
|
||||
#include "HandleWrapper.h"
|
||||
#include <fstream>
|
||||
|
||||
class RedirectionOutput
|
||||
{
|
||||
public:
|
||||
virtual ~RedirectionOutput() = default;
|
||||
virtual void Append(const std::wstring& text) = 0;
|
||||
};
|
||||
|
||||
class AggregateRedirectionOutput: NonCopyable, public RedirectionOutput
|
||||
{
|
||||
public:
|
||||
AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true);
|
||||
|
||||
void Append(const std::wstring& text) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<RedirectionOutput> m_outputA;
|
||||
std::shared_ptr<RedirectionOutput> m_outputB;
|
||||
std::shared_ptr<RedirectionOutput> m_outputC;
|
||||
};
|
||||
|
||||
class FileRedirectionOutput: NonCopyable, public RedirectionOutput
|
||||
{
|
||||
public:
|
||||
FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName);
|
||||
|
||||
void Append(const std::wstring& text) override;
|
||||
|
||||
~FileRedirectionOutput() override;
|
||||
|
||||
private:
|
||||
std::wstring m_fileName;
|
||||
std::ofstream m_file;
|
||||
};
|
||||
|
||||
class StandardOutputRedirectionOutput: NonCopyable, public RedirectionOutput
|
||||
{
|
||||
public:
|
||||
StandardOutputRedirectionOutput();
|
||||
|
||||
void Append(const std::wstring& text) override;
|
||||
|
||||
private:
|
||||
HandleWrapper<InvalidHandleTraits> m_handle;
|
||||
};
|
||||
|
||||
class ForwardingRedirectionOutput: NonCopyable, public RedirectionOutput
|
||||
{
|
||||
public:
|
||||
ForwardingRedirectionOutput(RedirectionOutput ** target) noexcept
|
||||
: m_target(target)
|
||||
{
|
||||
}
|
||||
|
||||
void Append(const std::wstring& text) override
|
||||
{
|
||||
auto const target = *m_target;
|
||||
if (target)
|
||||
{
|
||||
target->Append(text);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RedirectionOutput** m_target;
|
||||
};
|
||||
|
||||
class StringStreamRedirectionOutput: NonCopyable, public RedirectionOutput
|
||||
{
|
||||
public:
|
||||
StringStreamRedirectionOutput();
|
||||
|
||||
void Append(const std::wstring& text) override;
|
||||
|
||||
std::wstring GetOutput() const
|
||||
{
|
||||
return m_output.str();
|
||||
}
|
||||
|
||||
private:
|
||||
// Logs collected by this output are mostly used for Event Log messages where size limit is 32K
|
||||
std::size_t m_charactersLeft = 30000;
|
||||
std::wstringstream m_output;
|
||||
SRWLOCK m_srwLock{};
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#include "PipeOutputManager.h"
|
||||
#include "StandardStreamRedirection.h"
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Exceptions.h"
|
||||
|
|
@ -13,29 +13,27 @@
|
|||
#define LOG_IF_DUPFAIL(err) do { if (err == -1) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
|
||||
#define LOG_IF_ERRNO(err) do { if (err != 0) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
|
||||
|
||||
PipeOutputManager::PipeOutputManager()
|
||||
: PipeOutputManager( /* fEnableNativeLogging */ true)
|
||||
{
|
||||
}
|
||||
|
||||
PipeOutputManager::PipeOutputManager(bool fEnableNativeLogging) :
|
||||
BaseOutputManager(fEnableNativeLogging),
|
||||
StandardStreamRedirection::StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch) :
|
||||
m_output(output),
|
||||
m_hErrReadPipe(INVALID_HANDLE_VALUE),
|
||||
m_hErrWritePipe(INVALID_HANDLE_VALUE),
|
||||
m_hErrThread(nullptr),
|
||||
m_numBytesReadTotal(0)
|
||||
m_disposed(false),
|
||||
m_commandLineLaunch(commandLineLaunch)
|
||||
{
|
||||
TryStartRedirection();
|
||||
}
|
||||
|
||||
PipeOutputManager::~PipeOutputManager()
|
||||
StandardStreamRedirection::~StandardStreamRedirection() noexcept(false)
|
||||
{
|
||||
PipeOutputManager::Stop();
|
||||
TryStopRedirection();
|
||||
}
|
||||
|
||||
// Start redirecting stdout and stderr into a pipe
|
||||
// Continuously read the pipe on a background thread
|
||||
// until Stop is called.
|
||||
void PipeOutputManager::Start()
|
||||
void StandardStreamRedirection::Start()
|
||||
{
|
||||
SECURITY_ATTRIBUTES saAttr = { 0 };
|
||||
HANDLE hStdErrReadPipe;
|
||||
|
|
@ -57,8 +55,8 @@ void PipeOutputManager::Start()
|
|||
m_hErrReadPipe = hStdErrReadPipe;
|
||||
m_hErrWritePipe = hStdErrWritePipe;
|
||||
|
||||
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
|
||||
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
|
||||
stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
|
||||
stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
|
||||
|
||||
LOG_IF_FAILED(stdoutWrapper->StartRedirection());
|
||||
LOG_IF_FAILED(stderrWrapper->StartRedirection());
|
||||
|
|
@ -80,7 +78,7 @@ void PipeOutputManager::Start()
|
|||
// and prints any output that was captured in the pipe.
|
||||
// If more than 30Kb was written to the pipe, that output will
|
||||
// be thrown away.
|
||||
void PipeOutputManager::Stop()
|
||||
void StandardStreamRedirection::Stop()
|
||||
{
|
||||
DWORD dwThreadStatus = 0;
|
||||
|
||||
|
|
@ -123,12 +121,13 @@ void PipeOutputManager::Stop()
|
|||
// Don't check return value as IO may or may not be completed already.
|
||||
if (m_hErrThread != nullptr)
|
||||
{
|
||||
LOG_INFO(L"Canceling standard stream pipe reader");
|
||||
CancelSynchronousIo(m_hErrThread);
|
||||
}
|
||||
|
||||
// GetExitCodeThread returns 0 on failure; thread status code is invalid.
|
||||
if (m_hErrThread != nullptr &&
|
||||
!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) &&
|
||||
!LOG_LAST_ERROR_IF(!GetExitCodeThread(m_hErrThread, &dwThreadStatus)) &&
|
||||
dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
// Wait for graceful shutdown, i.e., the exit of the background thread or timeout
|
||||
|
|
@ -155,74 +154,40 @@ void PipeOutputManager::Stop()
|
|||
CloseHandle(m_hErrReadPipe);
|
||||
m_hErrReadPipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// If we captured any output, relog it to the original stdout
|
||||
// Useful for the IIS Express scenario as it is running with stdout and stderr
|
||||
m_stdOutContent = to_wide_string(std::string(m_pipeContents, m_numBytesReadTotal), GetConsoleOutputCP());
|
||||
|
||||
if (!m_stdOutContent.empty())
|
||||
{
|
||||
// printf will fail in in full IIS
|
||||
if (wprintf(m_stdOutContent.c_str()) != -1)
|
||||
{
|
||||
// Need to flush contents for the new stdout and stderr
|
||||
_flushall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring PipeOutputManager::GetStdOutContent()
|
||||
{
|
||||
return m_stdOutContent;
|
||||
}
|
||||
|
||||
void
|
||||
PipeOutputManager::ReadStdErrHandle(
|
||||
StandardStreamRedirection::ReadStdErrHandle(
|
||||
LPVOID pContext
|
||||
)
|
||||
{
|
||||
auto pLoggingProvider = static_cast<PipeOutputManager*>(pContext);
|
||||
auto pLoggingProvider = static_cast<StandardStreamRedirection*>(pContext);
|
||||
DBG_ASSERT(pLoggingProvider != NULL);
|
||||
pLoggingProvider->ReadStdErrHandleInternal();
|
||||
}
|
||||
|
||||
void
|
||||
PipeOutputManager::ReadStdErrHandleInternal()
|
||||
StandardStreamRedirection::ReadStdErrHandleInternal()
|
||||
{
|
||||
std::string tempBuffer;
|
||||
tempBuffer.resize(PIPE_READ_SIZE);
|
||||
|
||||
// If ReadFile ever returns false, exit the thread
|
||||
DWORD dwNumBytesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
// Fill a maximum of MAX_PIPE_READ_SIZE into a buffer.
|
||||
if (ReadFile(m_hErrReadPipe,
|
||||
&m_pipeContents[m_numBytesReadTotal],
|
||||
MAX_PIPE_READ_SIZE - m_numBytesReadTotal,
|
||||
tempBuffer.data(),
|
||||
PIPE_READ_SIZE,
|
||||
&dwNumBytesRead,
|
||||
nullptr))
|
||||
{
|
||||
m_numBytesReadTotal += dwNumBytesRead;
|
||||
if (m_numBytesReadTotal >= MAX_PIPE_READ_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
m_output.Append(to_wide_string(tempBuffer.substr(0, dwNumBytesRead), GetConsoleOutputCP()));
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Using std::string as a wrapper around new char[] so we don't need to call delete
|
||||
// Also don't allocate on stack as stack size is 128KB by default.
|
||||
std::string tempBuffer;
|
||||
tempBuffer.resize(MAX_PIPE_READ_SIZE);
|
||||
|
||||
// After reading the maximum amount of data, keep reading in a loop until Stop is called on the output manager.
|
||||
while (true)
|
||||
{
|
||||
if (!ReadFile(m_hErrReadPipe, tempBuffer.data(), MAX_PIPE_READ_SIZE, &dwNumBytesRead, nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RedirectionOutput.h"
|
||||
#include "StdWrapper.h"
|
||||
#include "ModuleHelpers.h"
|
||||
|
||||
class StandardStreamRedirection : NonCopyable
|
||||
{
|
||||
// Timeout to be used if a thread never exits
|
||||
#define PIPE_OUTPUT_THREAD_TIMEOUT 2000
|
||||
#define PIPE_READ_SIZE 4096
|
||||
|
||||
public:
|
||||
StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch);
|
||||
|
||||
~StandardStreamRedirection() noexcept(false);
|
||||
|
||||
private:
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
void
|
||||
TryStartRedirection()
|
||||
{
|
||||
try
|
||||
{
|
||||
Start();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TryStopRedirection()
|
||||
{
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OBSERVE_CAUGHT_EXCEPTION();
|
||||
}
|
||||
}
|
||||
|
||||
// Thread functions
|
||||
void ReadStdErrHandleInternal();
|
||||
static void ReadStdErrHandle(LPVOID pContext);
|
||||
|
||||
HANDLE m_hErrReadPipe;
|
||||
HANDLE m_hErrWritePipe;
|
||||
HANDLE m_hErrThread;
|
||||
|
||||
bool m_disposed;
|
||||
bool m_commandLineLaunch;
|
||||
SRWLOCK m_srwLock{};
|
||||
std::unique_ptr<StdWrapper> stdoutWrapper;
|
||||
std::unique_ptr<StdWrapper> stderrWrapper;
|
||||
RedirectionOutput& m_output;
|
||||
};
|
||||
|
|
@ -46,3 +46,14 @@ std::wstring to_wide_string(const std::string &source, const unsigned int codePa
|
|||
|
||||
return destination;
|
||||
}
|
||||
|
||||
std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage)
|
||||
{
|
||||
auto const encodedByteCount = WideCharToMultiByte(codePage, 0, text.data(), -1, nullptr, 0, nullptr, nullptr);
|
||||
|
||||
std::string encodedBytes;
|
||||
encodedBytes.resize(encodedByteCount);
|
||||
WideCharToMultiByte(codePage, 0, text.data(), -1, encodedBytes.data(), encodedByteCount, nullptr, nullptr);
|
||||
encodedBytes.resize(encodedByteCount - 1);
|
||||
return encodedBytes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ int compare_ignore_case(const std::wstring& s1, const std::wstring& s2);
|
|||
[[nodiscard]]
|
||||
std::wstring to_wide_string(const std::string &source, const unsigned int codePage);
|
||||
|
||||
[[nodiscard]]
|
||||
std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage);
|
||||
|
||||
template<typename ... Args>
|
||||
[[nodiscard]]
|
||||
std::wstring format(const std::wstring& format, Args ... args)
|
||||
|
|
|
|||
|
|
@ -156,6 +156,17 @@ private:
|
|||
return hr;
|
||||
}
|
||||
|
||||
__declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS const std::error_code& error_code)
|
||||
{
|
||||
if (error_code)
|
||||
{
|
||||
TraceHRESULT(LOCATION_CALL error_code.value());
|
||||
DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Failed error_code returned: 0x%x 0xs at " LOCATION_FORMAT, error_code.value(), error_code.message().c_str(), LOCATION_CALL_ONLY);
|
||||
return E_FAIL;
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ONLY)
|
||||
{
|
||||
try
|
||||
|
|
@ -188,6 +199,30 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON
|
|||
}
|
||||
}
|
||||
|
||||
__declspec(noinline) inline std::wstring CaughtExceptionToString()
|
||||
{
|
||||
try
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (const InvalidOperationException& exception)
|
||||
{
|
||||
return exception.as_wstring();
|
||||
}
|
||||
catch (const std::system_error& exception)
|
||||
{
|
||||
return to_wide_string(exception.what(), CP_ACP);
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
return to_wide_string(exception.what(), CP_ACP);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return L"Unknown exception type";
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
__declspec(noinline) inline void ThrowResultException(LOCATION_ARGUMENTS HRESULT hr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@
|
|||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
|
|
|||
|
|
@ -50,13 +50,12 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ConfigUtilityTests.cpp" />
|
||||
<ClCompile Include="FileOutputManagerTests.cpp" />
|
||||
<ClCompile Include="GlobalVersionTests.cpp" />
|
||||
<ClCompile Include="Helpers.cpp" />
|
||||
<ClCompile Include="hostfxr_utility_tests.cpp" />
|
||||
<ClCompile Include="inprocess_application_tests.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="PipeOutputManagerTests.cpp" />
|
||||
<ClCompile Include="StandardOutputRedirectionTest.cpp" />
|
||||
<ClCompile Include="BindingInformationTest.cpp" />
|
||||
<ClCompile Include="utility_tests.cpp" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "FileOutputManager.h"
|
||||
|
||||
class FileManagerWrapper
|
||||
{
|
||||
public:
|
||||
FileOutputManager* manager;
|
||||
FileManagerWrapper(FileOutputManager* m)
|
||||
: manager(m)
|
||||
{
|
||||
manager->Start();
|
||||
}
|
||||
|
||||
~FileManagerWrapper()
|
||||
{
|
||||
delete manager;
|
||||
}
|
||||
};
|
||||
|
||||
namespace FileOutManagerStartupTests
|
||||
{
|
||||
using ::testing::Test;
|
||||
class FileOutputManagerTest : public Test
|
||||
{
|
||||
protected:
|
||||
void
|
||||
Test(std::wstring fileNamePrefix, FILE* out)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
FileOutputManager* pManager = new FileOutputManager(fileNamePrefix, tempDirectory.path());
|
||||
|
||||
{
|
||||
FileManagerWrapper wrapper(pManager);
|
||||
|
||||
wprintf(expected, out);
|
||||
}
|
||||
|
||||
for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
|
||||
{
|
||||
std::wstring filename(p.path().filename());
|
||||
ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
|
||||
|
||||
std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWritten)
|
||||
{
|
||||
Test(L"", stdout);
|
||||
Test(L"log", stdout);
|
||||
}
|
||||
|
||||
TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWrittenErr)
|
||||
{
|
||||
Test(L"", stderr);
|
||||
Test(L"log", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace FileOutManagerOutputTests
|
||||
{
|
||||
TEST(FileOutManagerOutputTest, StdOut)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
|
||||
{
|
||||
FileManagerWrapper wrapper(pManager);
|
||||
|
||||
fwprintf(stdout, expected);
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FileOutManagerOutputTest, StdErr)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path().c_str());
|
||||
{
|
||||
FileManagerWrapper wrapper(pManager);
|
||||
|
||||
fwprintf(stderr, expected);
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FileOutManagerOutputTest, CapAt30KB)
|
||||
{
|
||||
PCWSTR expected = L"hello world";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
|
||||
{
|
||||
FileManagerWrapper wrapper(pManager);
|
||||
|
||||
for (int i = 0; i < 3000; i++)
|
||||
{
|
||||
wprintf(expected);
|
||||
}
|
||||
pManager->Stop();
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_EQ(output.size(), 30000);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FileOutManagerOutputTest, StartStopRestoresCorrectly)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
|
||||
{
|
||||
FileManagerWrapper wrapper(pManager);
|
||||
|
||||
wprintf(expected);
|
||||
pManager->Stop();
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "PipeOutputManager.h"
|
||||
|
||||
class FileManagerWrapper
|
||||
{
|
||||
public:
|
||||
PipeOutputManager * manager;
|
||||
FileManagerWrapper(PipeOutputManager* m)
|
||||
: manager(m)
|
||||
{
|
||||
manager->Start();
|
||||
}
|
||||
|
||||
~FileManagerWrapper()
|
||||
{
|
||||
delete manager;
|
||||
}
|
||||
};
|
||||
|
||||
namespace PipeOutputManagerTests
|
||||
{
|
||||
TEST(PipeManagerOutputTest, StdOut)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager(true);
|
||||
|
||||
pManager->Start();
|
||||
fwprintf(stdout, expected);
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, StdOutMultiToWide)
|
||||
{
|
||||
PipeOutputManager* pManager = new PipeOutputManager(true);
|
||||
|
||||
pManager->Start();
|
||||
fprintf(stdout, "test");
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_STREQ(output.c_str(), L"test");
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, StdErr)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager();
|
||||
|
||||
pManager->Start();
|
||||
fwprintf(stderr, expected);
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CheckMaxPipeSize)
|
||||
{
|
||||
std::wstring test;
|
||||
for (int i = 0; i < 3000; i++)
|
||||
{
|
||||
test.append(L"hello world");
|
||||
}
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager();
|
||||
|
||||
pManager->Start();
|
||||
wprintf(test.c_str());
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_EQ(output.size(), (DWORD)30000);
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
|
||||
{
|
||||
auto m_fdPreviousStdOut = _dup(_fileno(stdout));
|
||||
auto m_fdPreviousStdErr = _dup(_fileno(stderr));
|
||||
|
||||
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
|
||||
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager();
|
||||
pManager->Start();
|
||||
|
||||
_dup2(m_fdPreviousStdOut, _fileno(stdout));
|
||||
_dup2(m_fdPreviousStdErr, _fileno(stderr));
|
||||
|
||||
// Test will fail if we didn't redirect stdout back to a file descriptor.
|
||||
// This is because gtest relies on console output to know if a test succeeded or failed.
|
||||
// If the output still points to a file/pipe, the test (and all other tests after it) will fail.
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto stdoutBefore = _fileno(stdout);
|
||||
auto stderrBefore = _fileno(stderr);
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager();
|
||||
|
||||
pManager->Start();
|
||||
fwprintf(stdout, expected);
|
||||
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
ASSERT_EQ(stdoutBefore, _fileno(stdout));
|
||||
ASSERT_EQ(stderrBefore, _fileno(stderr));
|
||||
delete pManager;
|
||||
}
|
||||
// When this returns, we get an AV from gtest.
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto stdoutBefore = _fileno(stdout);
|
||||
auto stderrBefore = _fileno(stderr);
|
||||
auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
PipeOutputManager* pManager = new PipeOutputManager();
|
||||
|
||||
pManager->Start();
|
||||
fwprintf(stderr, expected);
|
||||
pManager->Stop();
|
||||
|
||||
auto output = pManager->GetStdOutContent();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
ASSERT_EQ(stdoutBefore, _fileno(stdout));
|
||||
|
||||
ASSERT_EQ(stderrBefore, _fileno(stderr));
|
||||
|
||||
delete pManager;
|
||||
}
|
||||
|
||||
wprintf(L"Hello!");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "StandardStreamRedirection.h"
|
||||
|
||||
namespace FileRedirectionOutputTests
|
||||
{
|
||||
using ::testing::Test;
|
||||
class FileRedirectionOutputTest : public Test
|
||||
{
|
||||
protected:
|
||||
void
|
||||
Test(std::wstring fileNamePrefix, FILE* out)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
{
|
||||
FileRedirectionOutput redirectionOutput(tempDirectory.path(), fileNamePrefix);
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
|
||||
wprintf(expected, out);
|
||||
}
|
||||
|
||||
for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
|
||||
{
|
||||
std::wstring filename(p.path().filename());
|
||||
ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
|
||||
|
||||
std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWritten)
|
||||
{
|
||||
Test(L"", stdout);
|
||||
Test(L"log", stdout);
|
||||
}
|
||||
|
||||
TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWrittenErr)
|
||||
{
|
||||
Test(L"", stderr);
|
||||
Test(L"log", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace PipeOutputManagerTests
|
||||
{
|
||||
TEST(PipeManagerOutputTest, StdOut)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stdout, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, StdOutMultiToWide)
|
||||
{
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fprintf(stdout, "test");
|
||||
}
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_STREQ(output.c_str(), L"test");
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, StdErr)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stderr, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CheckMaxPipeSize)
|
||||
{
|
||||
std::wstring test;
|
||||
for (int i = 0; i < 3000; i++)
|
||||
{
|
||||
test.append(L"hello world");
|
||||
}
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
wprintf(test.c_str());
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_EQ(output.size(), (DWORD)30000);
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
|
||||
{
|
||||
auto m_fdPreviousStdOut = _dup(_fileno(stdout));
|
||||
auto m_fdPreviousStdErr = _dup(_fileno(stderr));
|
||||
|
||||
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
_dup2(m_fdPreviousStdOut, _fileno(stdout));
|
||||
_dup2(m_fdPreviousStdErr, _fileno(stderr));
|
||||
|
||||
// Test will fail if we didn't redirect stdout back to a file descriptor.
|
||||
// This is because gtest relies on console output to know if a test succeeded or failed.
|
||||
// If the output still points to a file/pipe, the test (and all other tests after it) will fail.
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto stdoutBefore = _fileno(stdout);
|
||||
auto stderrBefore = _fileno(stderr);
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stdout, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
ASSERT_EQ(stdoutBefore, _fileno(stdout));
|
||||
ASSERT_EQ(stderrBefore, _fileno(stderr));
|
||||
}
|
||||
// When this returns, we get an AV from gtest.
|
||||
}
|
||||
|
||||
TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
auto stdoutBefore = _fileno(stdout);
|
||||
auto stderrBefore = _fileno(stderr);
|
||||
auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stderr, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
ASSERT_EQ(stdoutBefore, _fileno(stdout));
|
||||
|
||||
ASSERT_EQ(stderrBefore, _fileno(stderr));
|
||||
}
|
||||
|
||||
wprintf(L"Hello!");
|
||||
}
|
||||
|
||||
|
||||
TEST(StringStreamRedirectionOutputTest, StdOut)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
{
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stdout, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringStreamRedirectionOutputTest, StdErr)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
fwprintf(stderr, expected);
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
|
||||
TEST(StringStreamRedirectionOutputTest, CapAt30KB)
|
||||
{
|
||||
PCWSTR expected = L"hello world";
|
||||
|
||||
auto tempDirectory = TempDirectory();
|
||||
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
for (int i = 0; i < 3000; i++)
|
||||
{
|
||||
wprintf(expected);
|
||||
}
|
||||
}
|
||||
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_EQ(output.size(), 30000);
|
||||
}
|
||||
|
||||
TEST(StringStreamRedirectionOutputTest, StartStopRestoresCorrectly)
|
||||
{
|
||||
PCWSTR expected = L"test";
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
StringStreamRedirectionOutput redirectionOutput;
|
||||
{
|
||||
StandardStreamRedirection pManager(redirectionOutput, false);
|
||||
wprintf(expected);
|
||||
}
|
||||
auto output = redirectionOutput.GetOutput();
|
||||
ASSERT_FALSE(output.empty());
|
||||
|
||||
ASSERT_STREQ(output.c_str(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "hostfxr_utility.h"
|
||||
#include "HostFxrResolver.h"
|
||||
#include "Environment.h"
|
||||
|
||||
TEST(ParseHostFxrArguments, BasicHostFxrArguments)
|
||||
{
|
||||
std::vector<std::wstring> bstrArray;
|
||||
|
||||
HOSTFXR_UTILITY::AppendArguments(
|
||||
HostFxrResolver::AppendArguments(
|
||||
L"exec \"test.dll\"", // args
|
||||
L"invalid", // physical path to application
|
||||
bstrArray); // args array.
|
||||
|
|
@ -26,7 +26,7 @@ TEST(ParseHostFxrArguments, NoExecProvided)
|
|||
{
|
||||
std::vector<std::wstring> bstrArray;
|
||||
|
||||
HOSTFXR_UTILITY::AppendArguments(
|
||||
HostFxrResolver::AppendArguments(
|
||||
L"test.dll", // args
|
||||
L"ignored", // physical path to application
|
||||
bstrArray); // args array.
|
||||
|
|
@ -40,7 +40,7 @@ TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath)
|
|||
std::vector<std::wstring> bstrArray;
|
||||
// we need to use existing dll so let's use ntdll that we know exists everywhere
|
||||
auto system32 = Environment::ExpandEnvironmentVariables(L"%WINDIR%\\System32");
|
||||
HOSTFXR_UTILITY::AppendArguments(
|
||||
HostFxrResolver::AppendArguments(
|
||||
L"exec \"ntdll.dll\"", // args
|
||||
system32, // physical path to application
|
||||
bstrArray, // args array.
|
||||
|
|
@ -57,7 +57,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
|
|||
std::filesystem::path struHostFxrDllLocation;
|
||||
std::filesystem::path struExeLocation;
|
||||
|
||||
EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
|
||||
EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
|
||||
L"dotnet", // processPath
|
||||
L"some\\path", // application physical path, ignored.
|
||||
L"", //arguments
|
||||
|
|
@ -94,7 +94,7 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
|
|||
fDotnetInProgramFiles = std::filesystem::is_regular_file(L"C:/Program Files (x86)/dotnet/dotnet.exe");
|
||||
}
|
||||
|
||||
auto dotnetPath = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles();
|
||||
auto dotnetPath = HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles();
|
||||
if (fDotnetInProgramFiles)
|
||||
{
|
||||
EXPECT_TRUE(dotnetPath.has_value());
|
||||
|
|
@ -111,7 +111,7 @@ TEST(GetHostFxrArguments, InvalidParams)
|
|||
std::filesystem::path struHostFxrDllLocation;
|
||||
std::filesystem::path struExeLocation;
|
||||
|
||||
EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
|
||||
EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
|
||||
L"bogus", // processPath
|
||||
L"", // application physical path, ignored.
|
||||
L"ignored", //arguments
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
#include "hashfn.h"
|
||||
|
||||
#include "requesthandler_config.h"
|
||||
#include "hostfxr_utility.h"
|
||||
#include "HostFxrResolver.h"
|
||||
#include "config_utility.h"
|
||||
#include "environmentvariablehash.h"
|
||||
#include "iapplication.h"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include "AppOfflineTrackingApplication.h"
|
||||
|
||||
typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
|
||||
|
||||
class InProcessApplicationBase : public AppOfflineTrackingApplication
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "inprocessapplication.h"
|
||||
#include "inprocesshandler.h"
|
||||
#include "hostfxroptions.h"
|
||||
#include "HostFxrResolutionResult.h"
|
||||
#include "requesthandler_config.h"
|
||||
#include "environmentvariablehelpers.h"
|
||||
#include "exceptions.h"
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
#include "EventLog.h"
|
||||
#include "ModuleHelpers.h"
|
||||
#include "Environment.h"
|
||||
#include "HostFxr.h"
|
||||
|
||||
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
|
||||
|
||||
|
|
@ -34,6 +35,8 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
|||
{
|
||||
m_dotnetExeKnownLocation = knownLocation;
|
||||
}
|
||||
|
||||
m_stringRedirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
|
||||
}
|
||||
|
||||
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
|
||||
|
|
@ -170,45 +173,27 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
|||
{
|
||||
try
|
||||
{
|
||||
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
|
||||
std::unique_ptr<HostFxrResolutionResult> hostFxrResolutionResult;
|
||||
|
||||
auto context = std::make_shared<ExecuteClrContext>();
|
||||
|
||||
auto pProc = s_fMainCallback;
|
||||
if (pProc == nullptr)
|
||||
if (s_fMainCallback == nullptr)
|
||||
{
|
||||
HMODULE hModule;
|
||||
// hostfxr should already be loaded by the shim. If not, then we will need
|
||||
// to load it ourselves by finding hostfxr again.
|
||||
THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
|
||||
|
||||
// Get the entry point for main
|
||||
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
|
||||
THROW_LAST_ERROR_IF_NULL(pProc);
|
||||
|
||||
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
||||
THROW_IF_FAILED(HostFxrResolutionResult::Create(
|
||||
m_dotnetExeKnownLocation,
|
||||
m_pConfig->QueryProcessPath(),
|
||||
QueryApplicationPhysicalPath(),
|
||||
m_pConfig->QueryArguments(),
|
||||
hostFxrOptions
|
||||
hostFxrResolutionResult
|
||||
));
|
||||
|
||||
hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
|
||||
hostFxrResolutionResult->GetArguments(context->m_argc, context->m_argv);
|
||||
THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
|
||||
context->m_hostFxr = HostFxr::CreateFromLoadedModule();
|
||||
}
|
||||
context->m_pProc = pProc;
|
||||
|
||||
if (m_pLoggerProvider == nullptr)
|
||||
else
|
||||
{
|
||||
THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
|
||||
m_pConfig->QueryStdoutLogEnabled(),
|
||||
!m_pHttpServer.IsCommandLineLaunch(),
|
||||
m_pConfig->QueryStdoutLogFile().c_str(),
|
||||
QueryApplicationPhysicalPath().c_str(),
|
||||
m_pLoggerProvider));
|
||||
|
||||
m_pLoggerProvider->TryStartRedirection();
|
||||
context->m_hostFxr = HostFxr(s_fMainCallback, nullptr, nullptr);
|
||||
}
|
||||
|
||||
// There can only ever be a single instance of .NET Core
|
||||
|
|
@ -238,36 +223,52 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
|||
LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str());
|
||||
}
|
||||
|
||||
//Start CLR thread
|
||||
m_clrThread = std::thread(ClrThreadEntryPoint, context);
|
||||
bool clrThreadExited;
|
||||
|
||||
// Wait for thread exit or shutdown event
|
||||
const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
|
||||
|
||||
// Wait for shutdown request
|
||||
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
|
||||
|
||||
bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
|
||||
// shutdown was signaled
|
||||
// only wait for shutdown in case of successful startup
|
||||
if (m_waitForShutdown)
|
||||
{
|
||||
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
|
||||
auto redirectionOutput = LoggingHelpers::CreateOutputs(
|
||||
m_pConfig->QueryStdoutLogEnabled(),
|
||||
m_pConfig->QueryStdoutLogFile(),
|
||||
QueryApplicationPhysicalPath(),
|
||||
m_stringRedirectionOutput
|
||||
);
|
||||
|
||||
StandardStreamRedirection redirection(*redirectionOutput.get(), m_pHttpServer.IsCommandLineLaunch());
|
||||
|
||||
context->m_redirectionOutput = redirectionOutput.get();
|
||||
|
||||
//Start CLR thread
|
||||
m_clrThread = std::thread(ClrThreadEntryPoint, context);
|
||||
|
||||
// Wait for thread exit or shutdown event
|
||||
const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
|
||||
|
||||
// Wait for shutdown request
|
||||
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
|
||||
|
||||
// Disconnect output
|
||||
context->m_redirectionOutput = nullptr;
|
||||
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
|
||||
}
|
||||
LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
|
||||
|
||||
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
|
||||
clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
|
||||
// shutdown was signaled
|
||||
// only wait for shutdown in case of successful startup
|
||||
if (m_waitForShutdown)
|
||||
{
|
||||
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
|
||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||
|
||||
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
|
||||
}
|
||||
LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
|
||||
}
|
||||
|
||||
// At this point CLR thread either finished or timed out, abandon it.
|
||||
m_clrThread.detach();
|
||||
|
||||
m_pLoggerProvider->TryStopRedirection();
|
||||
|
||||
if (m_fStopCalled)
|
||||
{
|
||||
if (clrThreadExited)
|
||||
|
|
@ -390,7 +391,7 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
|
|||
{
|
||||
__try
|
||||
{
|
||||
auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
|
||||
auto const exitCode = context->m_hostFxr.Main(context->m_argc, context->m_argv.get());
|
||||
|
||||
LOG_INFOF(L"Managed application exited with code %d", exitCode);
|
||||
|
||||
|
|
@ -412,13 +413,22 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
|
|||
VOID
|
||||
IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
|
||||
{
|
||||
HandleWrapper<ModuleHandleTraits> moduleHandle;
|
||||
|
||||
// Keep aspnetcorev2_inprocess.dll loaded while this thread is running
|
||||
// this is required because thread might be abandoned
|
||||
HandleWrapper<ModuleHandleTraits> moduleHandle;
|
||||
ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
|
||||
|
||||
ExecuteClr(context);
|
||||
// Nested block is required here because FreeLibraryAndExitThread would prevent destructors from running
|
||||
// so we need to do in in a nested scope
|
||||
{
|
||||
// We use forwarder here instead of context->m_errorWriter itself to be able to
|
||||
// disconnect listener before CLR exits
|
||||
ForwardingRedirectionOutput redirectionForwarder(&context->m_redirectionOutput);
|
||||
const auto redirect = context->m_hostFxr.RedirectOutput(&redirectionForwarder);
|
||||
|
||||
ExecuteClr(context);
|
||||
}
|
||||
FreeLibraryAndExitThread(moduleHandle.release(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -446,7 +456,7 @@ IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
|
|||
VOID
|
||||
IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
|
||||
{
|
||||
auto content = m_pLoggerProvider->GetStdOutContent();
|
||||
auto content = m_stringRedirectionOutput->GetOutput();
|
||||
|
||||
if (context.m_exceptionCode != 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include <thread>
|
||||
#include "InProcessApplicationBase.h"
|
||||
#include "InProcessOptions.h"
|
||||
#include "BaseOutputManager.h"
|
||||
#include "HostFxr.h"
|
||||
|
||||
class IN_PROCESS_HANDLER;
|
||||
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
|
||||
|
|
@ -115,16 +115,22 @@ private:
|
|||
{
|
||||
ExecuteClrContext():
|
||||
m_argc(0),
|
||||
m_pProc(nullptr),
|
||||
m_hostFxr(nullptr, nullptr, nullptr),
|
||||
m_exitCode(0),
|
||||
m_exceptionCode(0)
|
||||
{
|
||||
}
|
||||
|
||||
DWORD m_argc;
|
||||
std::unique_ptr<PCWSTR[]> m_argv;
|
||||
hostfxr_main_fn m_pProc;
|
||||
~ExecuteClrContext()
|
||||
{
|
||||
m_redirectionOutput = nullptr;
|
||||
}
|
||||
|
||||
DWORD m_argc;
|
||||
std::unique_ptr<PCWSTR[]> m_argv;
|
||||
|
||||
HostFxr m_hostFxr;
|
||||
RedirectionOutput* m_redirectionOutput;
|
||||
int m_exitCode;
|
||||
int m_exceptionCode;
|
||||
};
|
||||
|
|
@ -159,7 +165,7 @@ private:
|
|||
|
||||
static IN_PROCESS_APPLICATION* s_Application;
|
||||
|
||||
std::unique_ptr<BaseOutputManager> m_pLoggerProvider;
|
||||
std::shared_ptr<StringStreamRedirectionOutput> m_stringRedirectionOutput;
|
||||
|
||||
inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "inprocesshandler.h"
|
||||
#include "inprocessapplication.h"
|
||||
#include "IOutputManager.h"
|
||||
#include "ShuttingDownApplication.h"
|
||||
#include "ntassert.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
StopServer();
|
||||
if (variant.HostingModel == HostingModel.InProcess)
|
||||
{
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not start stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070003");
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not stop stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070002");
|
||||
// Error is getting logged twice, from shim and handler
|
||||
EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.CouldNotStartStdoutFileRedirection("Q:\\std", deploymentResult), allowMultiple: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
{
|
||||
public class EventLogHelpers
|
||||
{
|
||||
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString)
|
||||
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, bool allowMultiple = false)
|
||||
{
|
||||
Assert.True(deploymentResult.HostProcess.HasExited);
|
||||
|
||||
var entries = GetEntries(deploymentResult);
|
||||
AssertSingleEntry(expectedRegexMatchString, entries);
|
||||
AssertEntry(expectedRegexMatchString, entries, allowMultiple);
|
||||
}
|
||||
|
||||
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, ILogger logger)
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
var entries = GetEntries(deploymentResult);
|
||||
try
|
||||
{
|
||||
AssertSingleEntry(expectedRegexMatchString, entries);
|
||||
AssertEntry(expectedRegexMatchString, entries);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
var entries = GetEntries(deploymentResult).ToList();
|
||||
foreach (var regexString in expectedRegexMatchString)
|
||||
{
|
||||
var matchedEntries = AssertSingleEntry(regexString, entries);
|
||||
var matchedEntries = AssertEntry(regexString, entries);
|
||||
|
||||
foreach (var matchedEntry in matchedEntries)
|
||||
{
|
||||
|
|
@ -60,12 +60,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}");
|
||||
}
|
||||
|
||||
private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
|
||||
private static EventLogEntry[] AssertEntry(string regexString, IEnumerable<EventLogEntry> entries, bool allowMultiple = false)
|
||||
{
|
||||
var expectedRegex = new Regex(regexString, RegexOptions.Singleline);
|
||||
var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
|
||||
Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'");
|
||||
Assert.True(matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
|
||||
Assert.True(allowMultiple || matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
|
||||
return matchedEntries;
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +197,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application.";
|
||||
}
|
||||
|
||||
public static string CouldNotStartStdoutFileRedirection(string file, IISDeploymentResult deploymentResult)
|
||||
{
|
||||
return
|
||||
$"Could not start stdout file redirection to '{Regex.Escape(file)}' with application base '{EscapedContentRoot(deploymentResult)}'.";
|
||||
}
|
||||
|
||||
private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
var contentRoot = deploymentResult.ContentRoot;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDebug]
|
||||
public async Task FrameworkNotFoundExceptionLogged_Pipe()
|
||||
{
|
||||
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true);
|
||||
|
|
@ -44,7 +43,6 @@ namespace IIS.FunctionalTests.Inprocess
|
|||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipIfDebug]
|
||||
public async Task FrameworkNotFoundExceptionLogged_File()
|
||||
{
|
||||
var deploymentParameters =
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ param($Mode)
|
|||
|
||||
Remove-Item "HKLM:\SOFTWARE\Microsoft\IIS Extensions\IIS AspNetCore Module V2\Parameters" -ErrorAction Ignore;
|
||||
|
||||
$DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps"
|
||||
if (!($DumpFolder))
|
||||
{
|
||||
$DumpFolder = "$PSScriptRoot\..\artifacts\dumps"
|
||||
$DumpFolder = "$PSScriptRoot\..\..\..\..\artifacts\dumps"
|
||||
}
|
||||
if (!(Test-Path $DumpFolder))
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue