From eebbb6a6022ce609dcf141906148eaf32b1e570f Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 22 Aug 2018 12:04:04 -0700 Subject: [PATCH] Support portable.exe apps and better error reporting (#1287) --- build/testsite.props | 2 +- .../AspNetCore/HandlerResolver.cpp | 14 +- .../CommonLib/CommonLib.vcxproj | 2 + .../CommonLib/HandleWrapper.cpp | 1 + .../CommonLib/HandleWrapper.h | 9 +- .../CommonLib/StringHelpers.cpp | 15 + .../CommonLib/StringHelpers.h | 20 + .../CommonLib/aspnetcore_msg.mc | 44 +- src/AspNetCoreModuleV2/CommonLib/exceptions.h | 3 +- .../CommonLib/hostfxr_utility.cpp | 462 ++++++------------ .../CommonLib/hostfxr_utility.h | 120 +++-- .../CommonLib/hostfxroptions.cpp | 90 ++-- .../CommonLib/hostfxroptions.h | 59 ++- src/AspNetCoreModuleV2/CommonLib/resources.h | 9 +- .../inprocessapplication.cpp | 12 +- .../Utilities/EventLogHelpers.cs | 16 +- .../Utilities/FunctionalTestsBase.cs | 5 - test/CommonLibTests/hostfxr_utility_tests.cpp | 94 ++-- .../InProcess/StartupTests.cs | 33 +- 19 files changed, 415 insertions(+), 595 deletions(-) create mode 100644 src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/StringHelpers.h diff --git a/build/testsite.props b/build/testsite.props index 1588b54226..ad7204aab4 100644 --- a/build/testsite.props +++ b/build/testsite.props @@ -48,7 +48,7 @@ - + False diff --git a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index 83c34a30c0..51b8cb611e 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -60,7 +60,7 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, ASPN pConfiguration.QueryArguments().c_str(), options)); - location = options->GetExeLocation(); + location = options->GetDotnetExeLocation(); RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider( pConfiguration.QueryStdoutLogEnabled(), @@ -225,7 +225,7 @@ HandlerResolver::FindNativeAssemblyFromHostfxr( DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize; DWORD dwRequiredBufferSize = 0; - RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation())); + RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str())); auto pFnHostFxrSearchDirectories = reinterpret_cast(GetProcAddress(m_hHostFxrDll, "hostfxr_get_native_search_directories")); if (pFnHostFxrSearchDirectories == nullptr) @@ -233,7 +233,7 @@ HandlerResolver::FindNativeAssemblyFromHostfxr( EventLog::Error( ASPNETCORE_EVENT_GENERAL_ERROR, ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG, - hostfxrOptions.GetHostFxrLocation() + hostfxrOptions.GetHostFxrLocation().c_str() ); RETURN_IF_FAILED(E_FAIL); } @@ -243,9 +243,13 @@ HandlerResolver::FindNativeAssemblyFromHostfxr( while (TRUE) { + DWORD hostfxrArgc; + std::unique_ptr hostfxrArgv; + + hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv); const auto intHostFxrExitCode = pFnHostFxrSearchDirectories( - hostfxrOptions.GetArgc(), - hostfxrOptions.GetArgv(), + hostfxrArgc, + hostfxrArgv.get(), struNativeSearchPaths.data(), dwBufferSize, &dwRequiredBufferSize diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 47a5373ee1..84b7f360cc 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -219,6 +219,7 @@ + @@ -243,6 +244,7 @@ Create Create + diff --git a/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp b/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp index fde7d2fd1f..4960b06f4e 100644 --- a/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp @@ -5,3 +5,4 @@ // Workaround for VC++ bug https://developercommunity.visualstudio.com/content/problem/33928/constexpr-failing-on-nullptr-v141-compiler-regress.html const HANDLE InvalidHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE; +const HANDLE FindFileHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE; diff --git a/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h b/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h index 1d607210e2..d3afa64551 100644 --- a/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h +++ b/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include "ntassert.h" struct InvalidHandleTraits { @@ -20,6 +20,13 @@ struct NullHandleTraits static void Close(HANDLE handle) { CloseHandle(handle); } }; +struct FindFileHandleTraits +{ + using HandleType = HANDLE; + static const HANDLE DefaultHandle; + static void Close(HANDLE handle) { FindClose(handle); } +}; + struct ModuleHandleTraits { using HandleType = HMODULE; diff --git a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp new file mode 100644 index 0000000000..8e574677cd --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp @@ -0,0 +1,15 @@ +// 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 "StringHelpers.h" + +bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase) +{ + if (source.length() < suffix.length()) + { + return false; + } + + const auto offset = source.length() - suffix.length(); + return CSTR_EQUAL == CompareStringOrdinal(source.c_str() + offset, static_cast(suffix.length()), suffix.c_str(), static_cast(suffix.length()), ignoreCase); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h new file mode 100644 index 0000000000..4999bec7a7 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h @@ -0,0 +1,20 @@ +// 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. + +#pragma once + +#include + +[[nodiscard]] +bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase = false); + +template +[[nodiscard]] +std::wstring format(const std::wstring& format, Args ... args) +{ + const size_t size = swprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra char for '\0' + std::unique_ptr formattedBuffer(new wchar_t[size]); + swprintf(formattedBuffer.get(), size, format.c_str(), args ... ); + return std::wstring(formattedBuffer.get(), formattedBuffer.get() + size - 1); +} + diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index 5b4c244c63..f0e3d66de3 100644 --- a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -38,12 +38,6 @@ Language=English %1 . -Messageid=1002 -SymbolicName=ASPNETCORE_EVENT_PROCESS_CRASH -Language=English -%1 -. - Messageid=1003 SymbolicName=ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED Language=English @@ -110,42 +104,12 @@ Language=English %1 . -Messageid=1014 -SymbolicName=ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP -Language=English -%1 -. - -Messageid=1015 -SymbolicName=ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING -Language=English -%1 -. - -Messageid=1016 -SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND -Language=English -%1 -. - -Messageid=1017 -SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND -Language=English -%1 -. - Messageid=1018 SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION Language=English %1 . -Messageid=1019 -SymbolicName=ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND -Language=English -%1 -. - Messageid=1020 SymbolicName=ASPNETCORE_EVENT_PROCESS_START_FAILURE Language=English @@ -164,12 +128,6 @@ Language=English %1 . -Messageid=1023 -SymbolicName=ASPNETCORE_EVENT_APP_IN_SHUTDOWN -Language=English -%1 -. - Messageid=1024 SymbolicName=ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR Language=English @@ -213,7 +171,7 @@ Language=English . Messageid=1031 -SymbolicName=ASPNETCORE_EVENT_INVALID_PROCESS_PATH +SymbolicName=ASPNETCORE_EVENT_INPROCESS_START_ERROR Language=English %1 . diff --git a/src/AspNetCoreModuleV2/CommonLib/exceptions.h b/src/AspNetCoreModuleV2/CommonLib/exceptions.h index bd6f30af33..31873bc743 100644 --- a/src/AspNetCoreModuleV2/CommonLib/exceptions.h +++ b/src/AspNetCoreModuleV2/CommonLib/exceptions.h @@ -43,6 +43,7 @@ #define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } #define LOG_IF_FAILED(hr) LogHResultFailed(LOCATION_INFO, hr) +#define LOG_LAST_ERROR() LogLastErrorIf(LOCATION_INFO, true) #define LOG_LAST_ERROR_IF(condition) LogLastErrorIf(LOCATION_INFO, condition) #define SUCCEEDED_LOG(hr) SUCCEEDED(LOG_IF_FAILED(hr)) #define FAILED_LOG(hr) FAILED(LOG_IF_FAILED(hr)) @@ -74,7 +75,7 @@ __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS std::exception& exception) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, LOCATION_FORMAT "Unhandled exception: %s", LOCATION_CALL exception.what()); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Exception : %s caught at" LOCATION_FORMAT, exception.what(), LOCATION_CALL_ONLY); } __declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS HRESULT hr) diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index 3d360b324d..c96133596d 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -3,185 +3,78 @@ #include "hostfxr_utility.h" -#include #include -#include "EventLog.h" -#include "ntassert.h" #include "fx_ver.h" #include "debugutil.h" #include "exceptions.h" #include "HandleWrapper.h" #include "Environment.h" -#include "file_utility.h" +#include "StringHelpers.h" namespace fs = std::filesystem; -// -// Runs a standalone appliction. -// The folder structure looks like this: -// Application/ -// hostfxr.dll -// Application.exe -// Application.dll -// etc. -// We get the full path to hostfxr.dll and Application.dll and run hostfxr_main, -// passing in Application.dll. -// Assuming we don't need Application.exe as the dll is the actual application. -// -HRESULT -HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( - PCWSTR pwzExeAbsolutePath, // includes .exe file extension. - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - _Inout_ STRU* pStruHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv -) -{ - LOG_INFOF("Resolving standalone hostfxr parameters for application: %S arguments: %S path: %S", - pwzExeAbsolutePath, - pcwzArguments, - pwzExeAbsolutePath); - - const fs::path exePath(pwzExeAbsolutePath); - - if (!exePath.has_extension()) - { - LOG_INFOF("Exe path has not extension, returning"); - - return false; - } - - const fs::path physicalPath(pcwzApplicationPhysicalPath); - const fs::path hostFxrLocation = physicalPath / "hostfxr.dll"; - - LOG_INFOF("Checking hostfxr.dll at %S", hostFxrLocation.c_str()); - - if (!is_regular_file(hostFxrLocation)) - { - fs::path runtimeConfigLocation = exePath; - runtimeConfigLocation.replace_extension(L".runtimeconfig.json"); - - LOG_INFOF("Checking runtimeconfig.json at %S", runtimeConfigLocation.c_str()); - if (!is_regular_file(runtimeConfigLocation)) - { - EventLog::Error( - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP, - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG, - pcwzApplicationPhysicalPath, - 0); - return E_FAIL; - } - - EventLog::Error( - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND, - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG, - pcwzApplicationPhysicalPath, - 0); - return E_FAIL; - } - - fs::path dllPath = exePath; - dllPath.replace_extension(".dll"); - - if (!is_regular_file(dllPath)) - { - LOG_INFOF("Application dll at %S was not found", dllPath.c_str()); - return E_FAIL; - } - - auto arguments = std::wstring(dllPath) + L" " + pcwzArguments; - - RETURN_IF_FAILED(pStruHostFxrDllLocation->Copy(hostFxrLocation.c_str())); - - RETURN_IF_FAILED(ParseHostfxrArguments( - arguments.c_str(), - pwzExeAbsolutePath, - pcwzApplicationPhysicalPath, - pdwArgCount, - ppwzArgv)); - - return S_OK; -} - -BOOL -HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath) -{ - auto name = dotnetPath.filename(); - name.replace_extension(""); - return _wcsnicmp(name.c_str(), L"dotnet", 6) == 0; -} - -HRESULT +void HOSTFXR_UTILITY::GetHostFxrParameters( - _In_ PCWSTR pcwzProcessPath, - _In_ PCWSTR pcwzApplicationPhysicalPath, - _In_ PCWSTR pcwzArguments, - _Inout_ STRU *pStruHostFxrDllLocation, - _Inout_ STRU *pStruExeAbsolutePath, - _Out_ DWORD *pdwArgCount, - _Out_ BSTR **pbstrArgv + const fs::path &processPath, + const fs::path &applicationPhysicalPath, + const std::wstring &applicationArguments, + fs::path &hostFxrDllPath, + fs::path &dotnetExePath, + std::vector &arguments ) { - HRESULT hr = S_OK; + LOG_INFOF("Resolving hostfxr parameters for application: '%S' arguments: '%S' path: '%S'", + processPath.c_str(), + applicationArguments.c_str(), + applicationPhysicalPath.c_str()); - LOG_INFOF("Resolving hostfxr parameters for application: %S arguments: %S path: %S", - pcwzProcessPath, - pcwzArguments, - pcwzApplicationPhysicalPath); + fs::path expandedProcessPath = Environment::ExpandEnvironmentVariables(processPath); + const auto expandedApplicationArguments = Environment::ExpandEnvironmentVariables(applicationArguments); - const fs::path applicationPhysicalPath = pcwzApplicationPhysicalPath; - fs::path processPath = Environment::ExpandEnvironmentVariables(pcwzProcessPath); - std::wstring arguments = Environment::ExpandEnvironmentVariables(pcwzArguments); + LOG_INFOF("Expanded hostfxr parameters for application: '%S' arguments: '%S'", + expandedProcessPath.c_str(), + expandedApplicationArguments.c_str()); + + LOG_INFOF("Known dotnet.exe location: '%S'", dotnetExePath.c_str()); + + if (!expandedProcessPath.has_extension()) + { + // The only executable extension inprocess supports + expandedProcessPath.replace_extension(".exe"); + } + else if (!ends_with(expandedProcessPath, L".exe", true)) + { + throw StartupParametersResolutionException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str())); + } // Check if the absolute path is to dotnet or not. - if (IsDotnetExecutable(processPath)) + if (IsDotnetExecutable(expandedProcessPath)) { - LOG_INFOF("Process path %S is dotnet, treating application as portable", processPath.c_str()); - // - // The processPath ends with dotnet.exe or dotnet - // 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 - // - // Make sure to append the dotnet.exe path correctly here (pass in regular path)? - auto fullProcessPath = GetAbsolutePathToDotnet(applicationPhysicalPath, processPath); - if (!fullProcessPath.has_value()) + LOG_INFOF("Process path '%S' is dotnet, treating application as portable", expandedProcessPath.c_str()); + + if (dotnetExePath.empty()) { - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - EventLog::Error( - ASPNETCORE_EVENT_INVALID_PROCESS_PATH, - ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, - processPath.c_str(), - hr); - return hr; + dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, expandedProcessPath); } - processPath = fullProcessPath.value(); + hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); - auto hostFxrPath = GetAbsolutePathToHostFxr(processPath); - if (!hostFxrPath.has_value()) - { - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - return hr; - } - - RETURN_IF_FAILED(HOSTFXR_UTILITY::ParseHostfxrArguments( - arguments.c_str(), - processPath.c_str(), - pcwzApplicationPhysicalPath, - pdwArgCount, - pbstrArgv)); - - RETURN_IF_FAILED(pStruHostFxrDllLocation->Copy(hostFxrPath->c_str())); - RETURN_IF_FAILED(pStruExeAbsolutePath->Copy(processPath.c_str())); + ParseHostfxrArguments( + expandedApplicationArguments, + dotnetExePath, + applicationPhysicalPath, + arguments, + true); } else { - LOG_INFOF("Process path %S is not dotnet, treating application as standalone", processPath.c_str()); + LOG_INFOF("Process path '%S' is not dotnet, treating application as standalone or portable with bootstrapper", expandedProcessPath.c_str()); - if (processPath.is_relative()) + auto executablePath = expandedProcessPath; + + if (executablePath.is_relative()) { - processPath = applicationPhysicalPath / processPath; + executablePath = applicationPhysicalPath / expandedProcessPath; } // @@ -189,17 +82,42 @@ HOSTFXR_UTILITY::GetHostFxrParameters( // like: C:\test\MyApp.Exe or MyApp.Exe // Check if the file exists, and if it does, get the parameters for a standalone application // - if (is_regular_file(processPath)) + if (is_regular_file(executablePath)) { - RETURN_IF_FAILED(GetStandaloneHostfxrParameters( - processPath.c_str(), - pcwzApplicationPhysicalPath, - arguments.c_str(), - pStruHostFxrDllLocation, - pdwArgCount, - pbstrArgv)); + auto applicationDllPath = executablePath; + applicationDllPath.replace_extension(".dll"); - RETURN_IF_FAILED(pStruExeAbsolutePath->Copy(processPath.c_str())); + LOG_INFOF("Checking application.dll at %S", applicationDllPath.c_str()); + if (!is_regular_file(applicationDllPath)) + { + throw StartupParametersResolutionException(format(L"Application .dll was not found at %s", applicationDllPath.c_str())); + } + + hostFxrDllPath = executablePath.parent_path() / "hostfxr.dll"; + LOG_INFOF("Checking hostfxr.dll at %S", hostFxrDllPath.c_str()); + if (is_regular_file(hostFxrDllPath)) + { + LOG_INFOF("hostfxr.dll found app local at '%S', treating application as standalone", hostFxrDllPath.c_str()); + } + else + { + LOG_INFOF("hostfxr.dll found app local at '%S', treating application as portable with launcher", hostFxrDllPath.c_str()); + + // passing "dotnet" here because we don't know where dotnet.exe should come from + // so trying all fallbacks is appropriate + if (dotnetExePath.empty()) + { + dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, L"dotnet"); + } + executablePath = dotnetExePath; + hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); + } + + ParseHostfxrArguments( + applicationDllPath.generic_wstring() + L" " + expandedApplicationArguments, + executablePath, + applicationPhysicalPath, + arguments); } else { @@ -207,140 +125,75 @@ HOSTFXR_UTILITY::GetHostFxrParameters( // If the processPath file does not exist and it doesn't include dotnet.exe or dotnet // then it is an invalid argument. // - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - EventLog::Error( - ASPNETCORE_EVENT_INVALID_PROCESS_PATH, - ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, - processPath.c_str(), - hr); - return hr; + throw StartupParametersResolutionException(format(L"Executable was not found at '%s'", executablePath.c_str())); } } - - return S_OK; } -// -// Forms the argument list in HOSTFXR_PARAMETERS. -// Sets the ArgCount and Arguments. -// Arg structure: -// argv[0] = Path to exe activating hostfxr. -// argv[1] = L"exec" -// argv[2] = absolute path to dll. -// -HRESULT +BOOL +HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath) +{ + return ends_with(dotnetPath, L"dotnet.exe", true); +} + +void HOSTFXR_UTILITY::ParseHostfxrArguments( - PCWSTR pwzArgumentsFromConfig, - PCWSTR pwzExePath, - PCWSTR pcwzApplicationPhysicalPath, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** pbstrArgv + const std::wstring &applicationArguments, + const fs::path &applicationExePath, + const fs::path &applicationPhysicalPath, + std::vector &arguments, + bool expandDllPaths ) { - DBG_ASSERT(pdwArgCount != NULL); - DBG_ASSERT(pbstrArgv != NULL); - DBG_ASSERT(pwzExePath != NULL); + LOG_INFOF("Resolving hostfxr_main arguments application: '%S' arguments: '%S' path: %S", applicationExePath.c_str(), applicationArguments.c_str(), applicationPhysicalPath.c_str()); - HRESULT hr = S_OK; - INT argc = 0; - BSTR* argv = NULL; - LPWSTR* pwzArgs = NULL; - STRU struTempPath; - INT intArgsProcessed = 0; + arguments = std::vector(); + arguments.push_back(applicationExePath); - LOG_INFOF("Resolving hostfxr_main arguments application: %S arguments: %S path: %S", - pwzExePath, - pwzArgumentsFromConfig, - pcwzApplicationPhysicalPath); - - // If we call CommandLineToArgvW with an empty string, argc is 5 for some interesting reason. - // Protectively guard against this by check if the string is null or empty. - if (pwzArgumentsFromConfig == NULL || wcscmp(pwzArgumentsFromConfig, L"") == 0) + if (applicationArguments.empty()) { - hr = E_INVALIDARG; - goto Finished; + throw StartupParametersResolutionException(L"Application arguments are empty."); } - pwzArgs = CommandLineToArgvW(pwzArgumentsFromConfig, &argc); - - if (pwzArgs == NULL) + int argc = 0; + auto pwzArgs = std::unique_ptr(CommandLineToArgvW(applicationArguments.c_str(), &argc)); + if (!pwzArgs) { - hr = LOG_IF_FAILED(HRESULT_FROM_WIN32(GetLastError())); - goto Failure; + throw StartupParametersResolutionException(format(L"Unable parse command line argumens '%s' or '%s'", applicationArguments.c_str())); } - argv = new BSTR[argc + 1]; + for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++) + { + std::wstring argument = pwzArgs[intArgsProcessed]; - argv[0] = SysAllocString(pwzExePath); - if (argv[0] == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - // Try to convert the application dll from a relative to an absolute path - // Don't record this failure as pwzArgs[0] may already be an absolute path to the dll. - for (intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++) - { - DBG_ASSERT(pwzArgs[intArgsProcessed] != NULL); - struTempPath.Copy(pwzArgs[intArgsProcessed]); - if (struTempPath.EndsWith(L".dll")) + // Try expanding arguments ending in .dll to a full paths + if (expandDllPaths && ends_with(argument, L".dll", true)) { - if (SUCCEEDED(FILE_UTILITY::ConvertPathToFullPath(pwzArgs[intArgsProcessed], pcwzApplicationPhysicalPath, &struTempPath))) + fs::path argumentAsPath = argument; + if (argumentAsPath.is_relative()) { - argv[intArgsProcessed + 1] = SysAllocString(struTempPath.QueryStr()); - } - else - { - argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]); - } - if (argv[intArgsProcessed + 1] == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - } - else - { - argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]); - if (argv[intArgsProcessed + 1] == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; + argumentAsPath = applicationPhysicalPath / argumentAsPath; + if (exists(argumentAsPath)) + { + LOG_INFOF("Converted argument '%S' to %S", argument.c_str(), argumentAsPath.c_str()); + argument = argumentAsPath; + } } } - LOG_INFOF("Argument[%d] = %S", - intArgsProcessed + 1, - argv[intArgsProcessed + 1]); + arguments.push_back(argument); } - *pbstrArgv = argv; - *pdwArgCount = argc + 1; - - goto Finished; - -Failure: - if (argv != NULL) + for (size_t i = 0; i < arguments.size(); i++) { - // intArgsProcess - 1 here as if we fail to allocated the ith string - // we don't want to free it. - for (INT i = 0; i < intArgsProcessed - 1; i++) - { - SysFreeString(argv[i]); - } + LOG_INFOF("Argument[%d] = %S", i, arguments[i].c_str()); } - - delete[] argv; - -Finished: - if (pwzArgs != NULL) - { - LocalFree(pwzArgs); - } - return hr; } -std::optional +// The processPath ends with dotnet.exe or dotnet +// 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( const fs::path & applicationPath, const fs::path & requestedPath @@ -357,21 +210,11 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( // // If we are given an absolute path to dotnet.exe, we are done // - if (is_regular_file(requestedPath)) + if (is_regular_file(processPath)) { - LOG_INFOF("Found dotnet.exe at %S", requestedPath.c_str()); + LOG_INFOF("Found dotnet.exe at %S", processPath.c_str()); - return std::make_optional(requestedPath); - } - - auto pathWithExe = requestedPath; - pathWithExe.concat(L".exe"); - - if (is_regular_file(pathWithExe)) - { - LOG_INFOF("Found dotnet.exe at %S", pathWithExe.c_str()); - - return std::make_optional(pathWithExe); + return processPath; } // At this point, we are calling where.exe to find dotnet. @@ -382,7 +225,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( { LOG_INFOF("Absolute path to dotnet.exe was not found at %S", requestedPath.c_str()); - return std::nullopt; + throw StartupParametersResolutionException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str())); } const auto dotnetViaWhere = InvokeWhereToFindDotnet(); @@ -390,7 +233,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( { LOG_INFOF("Found dotnet.exe via where.exe invocation at %S", dotnetViaWhere.value().c_str()); - return dotnetViaWhere; + return dotnetViaWhere.value(); } const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles(); @@ -398,14 +241,17 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( { LOG_INFOF("Found dotnet.exe in Program Files at %S", programFilesLocation.value().c_str()); - return programFilesLocation; + return programFilesLocation.value(); } LOG_INFOF("dotnet.exe not found"); - return std::nullopt; + throw StartupParametersResolutionException(format( + L"Could not find dotnet.exe at '%s' or using the system PATH environment variable." + " Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process.", + processPath.c_str())); } -std::optional +fs::path HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( const fs::path & dotnetPath ) @@ -417,23 +263,14 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( if (!is_directory(hostFxrBase)) { - EventLog::Error(ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, hostFxrBase.c_str(), HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT)); - - return std::nullopt; + throw StartupParametersResolutionException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str())); } - auto searchPattern = std::wstring(hostFxrBase) + L"\\*"; - FindDotNetFolders(searchPattern.c_str(), versionFolders); + FindDotNetFolders(hostFxrBase, versionFolders); if (versionFolders.empty()) { - EventLog::Error( - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - hostFxrBase.c_str(), - HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT)); - - return std::nullopt; + throw StartupParametersResolutionException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str())); } const auto highestVersion = FindHighestDotNetVersion(versionFolders); @@ -441,24 +278,18 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( if (!is_regular_file(hostFxrPath)) { - EventLog::Error( - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG, - hostFxrPath.c_str(), - HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); - - return std::nullopt; + throw StartupParametersResolutionException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str())); } LOG_INFOF("hostfxr.dll located at %S", hostFxrPath.c_str()); - return std::make_optional(hostFxrPath); + return hostFxrPath; } // // Tries to call where.exe to find the location of dotnet.exe. // Will check that the bitness of dotnet matches the current // worker process bitness. -// Returns true if a valid dotnet was found, else false. +// Returns true if a valid dotnet was found, else false.R // std::optional HOSTFXR_UTILITY::InvokeWhereToFindDotnet() @@ -638,7 +469,6 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion( fx_ver_t fx_ver(-1, -1, -1); if (fx_ver_t::parse(dir, &fx_ver, false)) { - // TODO using max instead of std::max works max_ver = max(max_ver, fx_ver); } } @@ -648,16 +478,16 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion( VOID HOSTFXR_UTILITY::FindDotNetFolders( - _In_ PCWSTR pszPath, - _Out_ std::vector & pvFolders + const std::filesystem::path &path, + _Out_ std::vector &pvFolders ) { - HANDLE handle = NULL; - WIN32_FIND_DATAW data = { 0 }; - - handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); + WIN32_FIND_DATAW data = {}; + const auto searchPattern = std::wstring(path) + L"\\*"; + HandleWrapper handle = FindFirstFileExW(searchPattern.c_str(), FindExInfoStandard, &data, FindExSearchNameMatch, nullptr, 0); if (handle == INVALID_HANDLE_VALUE) { + LOG_LAST_ERROR(); return; } @@ -665,6 +495,4 @@ HOSTFXR_UTILITY::FindDotNetFolders( { pvFolders.emplace_back(data.cFileName); } while (FindNextFileW(handle, &data)); - - FindClose(handle); } diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index 126867a865..e12153aaf6 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -7,7 +7,7 @@ #include #include #include -#include "stringu.h" +#include 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[]); @@ -19,76 +19,88 @@ class HOSTFXR_UTILITY public: static - HRESULT - GetStandaloneHostfxrParameters( - PCWSTR pwzExeAbsolutePath, // includes .exe file extension. - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - _Inout_ STRU* pStruHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv - ); - - static - HRESULT - ParseHostfxrArguments( - PCWSTR pwzArgumentsFromConfig, - PCWSTR pwzExePath, - PCWSTR pcwzApplicationPhysicalPath, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** pbstrArgv - ); - - static - BOOL - IsDotnetExecutable( - _In_ const std::filesystem::path & dotnetPath - ); - - static - HRESULT + void GetHostFxrParameters( - _In_ PCWSTR pcwzProcessPath, - _In_ PCWSTR pcwzApplicationPhysicalPath, - _In_ PCWSTR pcwzArguments, - _Inout_ STRU *pStruHostFxrDllLocation, - _Inout_ STRU *struExeAbsolutePath, - _Out_ DWORD *pdwArgCount, - _Out_ BSTR **ppwzArgv + const std::filesystem::path &processPath, + const std::filesystem::path &applicationPhysicalPath, + const std::wstring &applicationArguments, + std::filesystem::path &hostFxrDllPath, + std::filesystem::path &dotnetExePath, + std::vector &arguments ); - static - VOID - FindDotNetFolders( - _In_ PCWSTR pszPath, - _Out_ std::vector & pvFolders - ); + class StartupParametersResolutionException: public std::runtime_error + { + public: + StartupParametersResolutionException(std::wstring msg) + : runtime_error("Startup parameter resulution error occured"), message(std::move(msg)) + { + } + + std::wstring get_message() const { return message; } + + private: + std::wstring message; + }; static - std::wstring - FindHighestDotNetVersion( - _In_ std::vector & vFolders - ); - - static - std::optional - GetAbsolutePathToHostFxr( - _In_ const std::filesystem::path & dotnetPath + void + ParseHostfxrArguments( + const std::wstring &arugments, + const std::filesystem::path &exePath, + const std::filesystem::path &applicationPhysicalPath, + std::vector &arguments, + bool expandDllPaths = false ); static std::optional GetAbsolutePathToDotnetFromProgramFiles(); +private: + + static + BOOL + IsDotnetExecutable( + const std::filesystem::path & dotnetPath + ); + + static + VOID + FindDotNetFolders( + const std::filesystem::path& path, + std::vector & pvFolders + ); + + static + std::wstring + FindHighestDotNetVersion( + std::vector & vFolders + ); + + static + std::filesystem::path + GetAbsolutePathToHostFxr( + const std::filesystem::path & dotnetPath + ); + static std::optional InvokeWhereToFindDotnet(); static - std::optional + std::filesystem::path GetAbsolutePathToDotnet( - _In_ const std::filesystem::path & applicationPath, - _In_ const std::filesystem::path & requestedPath + const std::filesystem::path & applicationPath, + const std::filesystem::path & requestedPath ); + + struct LocalFreeDeleter + { + void operator ()(LPWSTR* ptr) const + { + LocalFree(ptr); + } + }; }; diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp index e8f2983028..7445724d98 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp @@ -6,82 +6,48 @@ #include "hostfxr_utility.h" #include "debugutil.h" #include "exceptions.h" +#include "EventLog.h" HRESULT HOSTFXR_OPTIONS::Create( - _In_ PCWSTR pcwzExeLocation, + _In_ PCWSTR pcwzDotnetExePath, _In_ PCWSTR pcwzProcessPath, _In_ PCWSTR pcwzApplicationPhysicalPath, _In_ PCWSTR pcwzArguments, _Out_ std::unique_ptr& ppWrapper) { - STRU struHostFxrDllLocation; - STRU struExeAbsolutePath; - STRU struExeLocation; - BSTR* pwzArgv; - DWORD dwArgCount; + std::filesystem::path knownDotnetLocation; - if (pcwzExeLocation != NULL) + if (pcwzDotnetExePath != nullptr) { - RETURN_IF_FAILED(struExeLocation.Copy(pcwzExeLocation)); + knownDotnetLocation = pcwzDotnetExePath; } + try + { + std::filesystem::path hostFxrDllPath; + std::vector arguments; + HOSTFXR_UTILITY::GetHostFxrParameters( + pcwzProcessPath, + pcwzApplicationPhysicalPath, + pcwzArguments, + hostFxrDllPath, + knownDotnetLocation, + arguments); - // If the exe was not provided by the shim, reobtain the hostfxr parameters (which finds dotnet). - if (struExeLocation.IsEmpty()) - { - RETURN_IF_FAILED(HOSTFXR_UTILITY::GetHostFxrParameters( - pcwzProcessPath, - pcwzApplicationPhysicalPath, - pcwzArguments, - &struHostFxrDllLocation, - &struExeAbsolutePath, - &dwArgCount, - &pwzArgv)); + ppWrapper = std::make_unique(knownDotnetLocation, hostFxrDllPath, arguments); } - else if (HOSTFXR_UTILITY::IsDotnetExecutable(struExeLocation.QueryStr())) + catch (HOSTFXR_UTILITY::StartupParametersResolutionException &resolutionException) { - RETURN_IF_FAILED(HOSTFXR_UTILITY::ParseHostfxrArguments( - pcwzArguments, - pcwzExeLocation, - pcwzApplicationPhysicalPath, - &dwArgCount, - &pwzArgv)); - } - else - { - RETURN_IF_FAILED(HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( - pcwzExeLocation, - pcwzApplicationPhysicalPath, - pcwzArguments, - &struHostFxrDllLocation, - &dwArgCount, - &pwzArgv)); - } + OBSERVE_CAUGHT_EXCEPTION(); - ppWrapper = std::make_unique(); - RETURN_IF_FAILED(ppWrapper->Populate(struHostFxrDllLocation.QueryStr(), struExeAbsolutePath.QueryStr(), dwArgCount, pwzArgv)); + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_START_ERROR, + ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG, + pcwzApplicationPhysicalPath, + resolutionException.get_message().c_str()); + + return E_FAIL; + } + CATCH_RETURN(); return S_OK; } - - -HRESULT HOSTFXR_OPTIONS::Populate(PCWSTR hostFxrLocation, PCWSTR struExeLocation, DWORD argc, BSTR argv[]) -{ - HRESULT hr; - - m_argc = argc; - m_argv = argv; - - if (FAILED(hr = m_hostFxrLocation.Copy(hostFxrLocation))) - { - goto Finished; - } - - if (FAILED(hr = m_exeLocation.Copy(struExeLocation))) - { - goto Finished; - } - - Finished: - - return hr; -} diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h index 99b972cde8..50bdfe1ded 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h @@ -5,41 +5,45 @@ #include #include - -#include "stringu.h" +#include +#include +#include +#include class HOSTFXR_OPTIONS { public: - HOSTFXR_OPTIONS() {} + HOSTFXR_OPTIONS( + std::filesystem::path dotnetExeLocation, + std::filesystem::path hostFxrLocation, + std::vector arguments + ) + : m_dotnetExeLocation(std::move(dotnetExeLocation)), + m_hostFxrLocation(std::move(hostFxrLocation)), + m_arguments(std::move(arguments)) + {} - ~HOSTFXR_OPTIONS() + void + GetArguments(DWORD &hostfxrArgc, std::unique_ptr &hostfxrArgv) const { - delete[] m_argv; + hostfxrArgc = static_cast(m_arguments.size()); + hostfxrArgv = std::unique_ptr(new PCWSTR[hostfxrArgc]); + for (DWORD i = 0; i < hostfxrArgc; ++i) + { + hostfxrArgv[i] = m_arguments[i].c_str(); + } } - DWORD - GetArgc() const - { - return m_argc; - } - - BSTR* - GetArgv() const - { - return m_argv; - } - - PCWSTR + const std::filesystem::path& GetHostFxrLocation() const { - return m_hostFxrLocation.QueryStr(); + return m_hostFxrLocation; } - PCWSTR - GetExeLocation() const + const std::filesystem::path& + GetDotnetExeLocation() const { - return m_exeLocation.QueryStr(); + return m_dotnetExeLocation; } static @@ -51,12 +55,7 @@ public: _Out_ std::unique_ptr& ppWrapper); private: - - HRESULT Populate(PCWSTR hostFxrLocation, PCWSTR struExeLocation, DWORD argc, BSTR argv[]); - - STRU m_exeLocation; - STRU m_hostFxrLocation; - - DWORD m_argc; - BSTR* m_argv; + const std::filesystem::path m_dotnetExeLocation; + const std::filesystem::path m_hostFxrLocation; + const std::vector m_arguments; }; diff --git a/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h index 82f22c52c0..b2ea6f5b85 100644 --- a/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -11,7 +11,6 @@ #define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module V2" #define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module V2" -#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 #define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and process '%d' is listening on port '%d'." #define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." #define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' at stage '%s', ErrorCode = '0x%x', assigned port %d, retryCounter '%d'." @@ -39,15 +38,9 @@ #define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change" #define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process." #define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled" -#define ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG L"Application '%s' was compiled for .NET Framework. Please compile for .NET core to run the inprocess application or change the process mode to out of process. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING_MSG L"Could not find dotnet.exe on the system PATH environment variable for portable application '%s'. Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG L"Could not find the hostfxr directory '%s' in the dotnet directory. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG L"Could not find hostfxr.dll in '%s'. ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'." -#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_LEVEL EVENTLOG_ERROR_TYPE -#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG L"Could not find application executable in '%s'. ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information." -#define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = '%s', ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application. Captured output: %s" #define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress." #define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully." +#define ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG L"Application '%s' wasn't able to start. %s" diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 56eb63577c..182ee53148 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -392,11 +392,10 @@ IN_PROCESS_APPLICATION::ExecuteApplication( { HRESULT hr; HMODULE hModule = nullptr; - DWORD hostfxrArgc = 0; - BSTR *hostfxrArgv = NULL; hostfxr_main_fn pProc; std::unique_ptr hostFxrOptions = NULL; - + DWORD hostfxrArgc = 0; + std::unique_ptr hostfxrArgv; DBG_ASSERT(m_status == MANAGED_APPLICATION_STATUS::STARTING); pProc = s_fMainCallback; @@ -428,10 +427,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication( m_pConfig->QueryArguments()->QueryStr(), hostFxrOptions )); - - hostfxrArgc = hostFxrOptions->GetArgc(); - hostfxrArgv = hostFxrOptions->GetArgv(); - + hostFxrOptions->GetArguments(hostfxrArgc, hostfxrArgv); FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess()); } @@ -457,7 +453,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication( // set the callbacks s_Application = this; - hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv, pProc); + hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv.get(), pProc); Finished: diff --git a/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs b/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs index c449863e4b..c4ca40436a 100644 --- a/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs +++ b/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs @@ -1,7 +1,6 @@ // 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. -using System.Linq; using System.Text; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; @@ -12,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { public class EventLogHelpers { + private static readonly Regex EventLogRegex = new Regex("Event Log: (?.+?)End Event Log Message.", RegexOptions.Singleline | RegexOptions.Compiled); public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString) { Assert.True(deploymentResult.HostProcess.HasExited); @@ -23,9 +23,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests builder.Append(context.Message); } - var eventLogRegex = new Regex($"Event Log: (.*?){expectedRegexMatchString}(.*?)End Event Log Message.", RegexOptions.Singleline); + var count = 0; + var expectedRegex = new Regex(expectedRegexMatchString, RegexOptions.Singleline); + foreach (Match match in EventLogRegex.Matches(builder.ToString())) + { + var eventLogText = match.Groups["EventLogMessage"].Value; + if (expectedRegex.IsMatch(eventLogText)) + { + count++; + } + } - Assert.Matches(eventLogRegex, builder.ToString()); + Assert.True(count > 0, $"'{expectedRegexMatchString}' didn't match any event log messaged"); + Assert.True(count < 2, $"'{expectedRegexMatchString}' matched more then one event log message"); } } } diff --git a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs index b9e80dd27f..e0d49615a5 100644 --- a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs +++ b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs @@ -28,11 +28,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting parameters.EnvironmentVariables[DebugEnvironmentVariable] = "console"; } - if (parameters.ApplicationPublisher == null) - { - throw new InvalidOperationException("All tests should use ApplicationPublisher"); - } - return IISApplicationDeployerFactory.Create(parameters, LoggerFactory); } diff --git a/test/CommonLibTests/hostfxr_utility_tests.cpp b/test/CommonLibTests/hostfxr_utility_tests.cpp index 232f7627b8..7d323868f8 100644 --- a/test/CommonLibTests/hostfxr_utility_tests.cpp +++ b/test/CommonLibTests/hostfxr_utility_tests.cpp @@ -2,80 +2,75 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #include "stdafx.h" -#include "file_utility.h" +#include +#include +#include +#include "hostfxr_utility.h" +#include "Environment.h" TEST(ParseHostFxrArguments, BasicHostFxrArguments) { - DWORD retVal = 0; - BSTR* bstrArray; + std::vector bstrArray; PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( + + HOSTFXR_UTILITY::ParseHostfxrArguments( L"exec \"test.dll\"", // args exeStr, // exe path L"invalid", // physical path to application - &retVal, // arg count - &bstrArray); // args array. + bstrArray); // args array. - EXPECT_EQ(hr, S_OK); - EXPECT_EQ(DWORD(3), retVal); - ASSERT_STREQ(exeStr, bstrArray[0]); - ASSERT_STREQ(L"exec", bstrArray[1]); - ASSERT_STREQ(L"test.dll", bstrArray[2]); + EXPECT_EQ(3, bstrArray.size()); + ASSERT_STREQ(exeStr, bstrArray[0].c_str()); + ASSERT_STREQ(L"exec", bstrArray[1].c_str()); + ASSERT_STREQ(L"test.dll", bstrArray[2].c_str()); } TEST(ParseHostFxrArguments, NoExecProvided) { - DWORD retVal = 0; - BSTR* bstrArray; + std::vector bstrArray; PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( + HOSTFXR_UTILITY::ParseHostfxrArguments( L"test.dll", // args exeStr, // exe path L"ignored", // physical path to application - &retVal, // arg count - &bstrArray); // args array. + bstrArray); // args array. - EXPECT_EQ(hr, S_OK); - EXPECT_EQ(DWORD(2), retVal); - ASSERT_STREQ(exeStr, bstrArray[0]); - ASSERT_STREQ(L"test.dll", bstrArray[1]); + EXPECT_EQ(DWORD(2), bstrArray.size()); + ASSERT_STREQ(exeStr, bstrArray[0].c_str()); + ASSERT_STREQ(L"test.dll", bstrArray[1].c_str()); } TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath) { - DWORD retVal = 0; - BSTR* bstrArray; + std::vector bstrArray; PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - - HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( - L"exec \"test.dll\"", // args + // 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::ParseHostfxrArguments( + L"exec \"ntdll.dll\"", // args exeStr, // exe path - L"C:/test", // physical path to application - &retVal, // arg count - &bstrArray); // args array. + system32, // physical path to application + bstrArray, // args array. + true); // expandDllPaths - EXPECT_EQ(hr, S_OK); - EXPECT_EQ(DWORD(3), retVal); - ASSERT_STREQ(exeStr, bstrArray[0]); - ASSERT_STREQ(L"exec", bstrArray[1]); - ASSERT_STREQ(L"C:\\test\\test.dll", bstrArray[2]); + EXPECT_EQ(DWORD(3), bstrArray.size()); + ASSERT_STREQ(exeStr, bstrArray[0].c_str()); + ASSERT_STREQ(L"exec", bstrArray[1].c_str()); + ASSERT_STREQ((system32 + L"\\ntdll.dll").c_str(), bstrArray[2].c_str()); } TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs) { - DWORD retVal = 0; - BSTR* bstrArray; + std::vector bstrArray; PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( + ASSERT_THROW(HOSTFXR_UTILITY::ParseHostfxrArguments( L"", // args exeStr, // exe path L"ignored", // physical path to application - &retVal, // arg count - &bstrArray); // args array. - - EXPECT_EQ(E_INVALIDARG, hr); + bstrArray), // args array. + HOSTFXR_UTILITY::StartupParametersResolutionException); } TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) @@ -118,19 +113,16 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) TEST(GetHostFxrArguments, InvalidParams) { - DWORD retVal = 0; - BSTR* bstrArray; - STRU struHostFxrDllLocation; - STRU struExeLocation; + std::vector bstrArray; + std::filesystem::path struHostFxrDllLocation; + std::filesystem::path struExeLocation; - HRESULT hr = HOSTFXR_UTILITY::GetHostFxrParameters( + EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters( L"bogus", // processPath L"", // application physical path, ignored. L"ignored", //arguments - NULL, - &struExeLocation, - &retVal, // arg count - &bstrArray); // args array. - - EXPECT_EQ(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), hr); + struHostFxrDllLocation, + struExeLocation, + bstrArray), // args array. + HOSTFXR_UTILITY::StartupParametersResolutionException); } diff --git a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs index e6b48f28bf..73bea61017 100644 --- a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; using System.Net; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; @@ -40,14 +41,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } [ConditionalTheory] - [InlineData("bogus")] - [InlineData("c:\\random files\\dotnet.exe")] - [InlineData(".\\dotnet.exe")] - public async Task InvalidProcessPath_ExpectServerError(string path) + [InlineData("bogus", "", @"Executable was not found at '.*?\\bogus.exe")] + [InlineData("c:\\random files\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\dotnet.exe'")] + [InlineData(".\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\.\\dotnet.exe'")] + [InlineData("dotnet.exe", "", @"Application arguments are empty.")] + [InlineData("dotnet.zip", "", @"Process path 'dotnet.zip' doesn't have '.exe' extension.")] + public async Task InvalidProcessPath_ExpectServerError(string path, string arguments, string subError) { var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); - + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", arguments)); var deploymentResult = await DeployAsync(deploymentParameters); @@ -57,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests StopServer(); - EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, @"Invalid or unknown processPath provided in web\.config: processPath = '.+', ErrorCode = '0x80070002'\."); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}"); } [ConditionalFact] @@ -136,6 +139,24 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal("Hello World", responseText); } + [ConditionalFact] + public async Task StartsWithPortableAndBootstraperExe() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + // rest publisher as it doesn't support additional parameters + deploymentParameters.ApplicationPublisher = null; + // ReferenceTestTasks is workaround for https://github.com/dotnet/sdk/issues/2482 + deploymentParameters.AdditionalPublishParameters = "-p:RuntimeIdentifier=win7-x64 -p:UseAppHost=true -p:SelfContained=false -p:ReferenceTestTasks=false"; + deploymentParameters.RestoreOnPublish = true; + var deploymentResult = await DeployAsync(deploymentParameters); + + Assert.True(File.Exists(Path.Combine(deploymentResult.ContentRoot, "InProcessWebSite.exe"))); + Assert.False(File.Exists(Path.Combine(deploymentResult.ContentRoot, "hostfxr.dll"))); + Assert.Contains("InProcessWebSite.exe", File.ReadAllText(Path.Combine(deploymentResult.ContentRoot, "web.config"))); + + await deploymentResult.AssertStarts(); + } + [ConditionalFact] public async Task DetectsOveriddenServer() {