From 60711bfca1c32c91604634b8128d4533a456bc54 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 19 Jun 2018 11:29:42 -0700 Subject: [PATCH] Use std::filesystem in hostfxr utilities (#926) --- src/AspNetCoreModuleV2/CommonLib/EventLog.h | 6 + .../CommonLib/GlobalVersionUtility.cpp | 5 +- .../CommonLib/GlobalVersionUtility.h | 6 +- .../CommonLib/aspnetcore_msg.mc | 6 + .../CommonLib/hostfxr_utility.cpp | 508 ++++++------------ .../CommonLib/hostfxr_utility.h | 70 ++- .../CommonLib/hostfxroptions.cpp | 2 +- src/AspNetCoreModuleV2/CommonLib/resources.h | 5 + src/AspNetCoreModuleV2/CommonLib/stdafx.h | 1 + src/AspNetCoreModuleV2/CommonLib/utility.cxx | 8 +- .../OutOfProcessRequestHandler/precomp.hxx | 1 - test/CommonLibTests/hostfxr_utility_tests.cpp | 12 +- .../Inprocess/StartupTests.cs | 70 ++- 13 files changed, 276 insertions(+), 424 deletions(-) create mode 100644 src/AspNetCoreModuleV2/CommonLib/EventLog.h diff --git a/src/AspNetCoreModuleV2/CommonLib/EventLog.h b/src/AspNetCoreModuleV2/CommonLib/EventLog.h new file mode 100644 index 0000000000..70bd1a1be6 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/EventLog.h @@ -0,0 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define EVENTLOG(log, name, ...) UTILITY::LogEventF(log, ASPNETCORE_EVENT_ ## name ## _LEVEL, ASPNETCORE_EVENT_ ## name, ASPNETCORE_EVENT_ ## name ## _MSG, __VA_ARGS__) diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp index 09a927104a..a7bd703589 100644 --- a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "stdafx.h" -#include namespace fs = std::experimental::filesystem; @@ -96,7 +95,7 @@ GlobalVersionUtility::RemoveFileNameFromFolderPath(std::wstring fileName) return path.parent_path(); } -std::wstring +std::wstring GlobalVersionUtility::GetModuleName(HMODULE hModuleName) { DWORD dwSize = MAX_PATH; @@ -126,6 +125,6 @@ GlobalVersionUtility::GetModuleName(HMODULE hModuleName) fDone = TRUE; } } - + return retVal; } diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h index fd1a5ee252..4a4e736eb7 100644 --- a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h @@ -8,15 +8,15 @@ class GlobalVersionUtility { public: - static - std::wstring + static + std::wstring GetGlobalRequestHandlerPath(PCWSTR pwzAspNetCoreFolderPath, PCWSTR pwzHandlerVersion, PCWSTR pwzHandlerName); static std::wstring FindHighestGlobalVersion(PCWSTR pwzAspNetCoreFolderPath); - static + static std::wstring RemoveFileNameFromFolderPath(std::wstring fileName); diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index 96cf5fec0c..5ea3c030d2 100644 --- a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -212,6 +212,12 @@ Language=English %1 . +Messageid=1031 +SymbolicName=ASPNETCORE_EVENT_INVALID_PROCESS_PATH +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index 6ea7c46eeb..ad13fd7438 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -2,14 +2,9 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "stdafx.h" +#include -HOSTFXR_UTILITY::HOSTFXR_UTILITY() -{ -} - -HOSTFXR_UTILITY::~HOSTFXR_UTILITY() -{ -} +namespace fs = std::experimental::filesystem; // // Runs a standalone appliction. @@ -25,131 +20,72 @@ HOSTFXR_UTILITY::~HOSTFXR_UTILITY() // HRESULT HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( - PCWSTR pwzExeAbsolutePath, // includes .exe file extension. - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - HANDLE hEventLog, - _Inout_ STRU* pStruHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv + PCWSTR pwzExeAbsolutePath, // includes .exe file extension. + PCWSTR pcwzApplicationPhysicalPath, + PCWSTR pcwzArguments, + HANDLE hEventLog, + _Inout_ STRU* pStruHostFxrDllLocation, + _Out_ DWORD* pdwArgCount, + _Out_ BSTR** ppwzArgv ) { - HRESULT hr = S_OK; - STRU struDllPath; - STRU struArguments; - STRU struHostFxrPath; - STRU struRuntimeConfigLocation; - DWORD dwPosition; + HRESULT hr = S_OK; - // Obtain the app name from the processPath section. - if (FAILED(hr = struDllPath.Copy(pwzExeAbsolutePath))) + const fs::path exePath(pwzExeAbsolutePath); + + if (!exePath.has_extension()) { - goto Finished; + return false; } - dwPosition = struDllPath.LastIndexOf(L'.', 0); - if (dwPosition == -1) - { - hr = E_FAIL; - goto Finished; - } + const fs::path physicalPath(pcwzApplicationPhysicalPath); + const fs::path hostFxrLocation = physicalPath / "hostfxr.dll"; - hr = UTILITY::ConvertPathToFullPath(L".\\hostfxr.dll", pcwzApplicationPhysicalPath, &struHostFxrPath); - if (FAILED(hr)) + if (!is_regular_file(hostFxrLocation)) { - goto Finished; - } + fs::path runtimeConfigLocation = exePath; + runtimeConfigLocation.replace_extension(L".runtimeconfig.json"); - struDllPath.QueryStr()[dwPosition] = L'\0'; - if (FAILED(hr = struDllPath.SyncWithBuffer())) - { - goto Finished; - } - - if (!UTILITY::CheckIfFileExists(struHostFxrPath.QueryStr())) - { - // Most likely a full framework app. - // Check that the runtime config file doesn't exist in the folder as another heuristic. - if (FAILED(hr = struRuntimeConfigLocation.Copy(struDllPath)) || - FAILED(hr = struRuntimeConfigLocation.Append(L".runtimeconfig.json"))) + if (!is_regular_file(runtimeConfigLocation)) { - goto Finished; - } - if (!UTILITY::CheckIfFileExists(struRuntimeConfigLocation.QueryStr())) - { - - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP, - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG, - pcwzApplicationPhysicalPath, - hr); - } - else - { - // If a runtime config file does exist, report a file not found on the app.exe - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND, - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG, - pcwzApplicationPhysicalPath, - hr); + EVENTLOG(hEventLog, INPROCESS_FULL_FRAMEWORK_APP, pcwzApplicationPhysicalPath, 0); + return E_FAIL; } - goto Finished; + EVENTLOG(hEventLog, APPLICATION_EXE_NOT_FOUND, pcwzApplicationPhysicalPath, 0); + return E_FAIL; } - if (FAILED(hr = pStruHostFxrDllLocation->Copy(struHostFxrPath))) + fs::path dllPath = exePath; + dllPath.replace_extension(".dll"); + + if (!is_regular_file(dllPath)) { - goto Finished; + return E_FAIL; } + auto arguments = std::wstring(dllPath) + L" " + pcwzArguments; - if (FAILED(hr = struDllPath.Append(L".dll"))) + if (FAILED(hr = pStruHostFxrDllLocation->Copy(hostFxrLocation.c_str()))) { - goto Finished; + return hr; } - if (!UTILITY::CheckIfFileExists(struDllPath.QueryStr())) - { - // Treat access issue as File not found - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - goto Finished; - } - - if (FAILED(hr = struArguments.Copy(struDllPath)) || - FAILED(hr = struArguments.Append(L" ")) || - FAILED(hr = struArguments.Append(pcwzArguments))) - { - goto Finished; - } - - if (FAILED(hr = ParseHostfxrArguments( - struArguments.QueryStr(), + return ParseHostfxrArguments( + arguments.c_str(), pwzExeAbsolutePath, pcwzApplicationPhysicalPath, hEventLog, pdwArgCount, - ppwzArgv))) - { - goto Finished; - } - -Finished: - - return hr; + ppwzArgv); } BOOL -HOSTFXR_UTILITY::IsDotnetExecutable(STRU *struExecutablePath) +HOSTFXR_UTILITY::IsDotnetExecutable(const std::experimental::filesystem::path & dotnetPath) { - if (struExecutablePath == NULL) - { - return FALSE; - } - return struExecutablePath->EndsWith(L"dotnet.exe") || struExecutablePath->EndsWith(L"dotnet"); + auto name = dotnetPath.filename(); + name.replace_extension(""); + return _wcsnicmp(name.c_str(), L"dotnet", 6) == 0; } HRESULT @@ -165,70 +101,58 @@ HOSTFXR_UTILITY::GetHostFxrParameters( ) { HRESULT hr = S_OK; - STRU struSystemPathVariable; - STRU struAbsolutePathToHostFxr; - STRU struAbsolutePathToDotnet; - STRU struEventMsg; - STACK_STRU(struExpandedProcessPath, MAX_PATH); - STACK_STRU(struExpandedArguments, MAX_PATH); - // Copy and Expand the processPath and Arguments. - if (FAILED(hr = struExpandedProcessPath.CopyAndExpandEnvironmentStrings(pcwzProcessPath)) - || FAILED(hr = struExpandedArguments.CopyAndExpandEnvironmentStrings(pcwzArguments))) + const fs::path applicationPhysicalPath = pcwzApplicationPhysicalPath; + fs::path processPath = ExpandEnvironmentVariables(pcwzProcessPath); + std::wstring arguments = ExpandEnvironmentVariables(pcwzArguments); + + if (processPath.is_relative()) { - goto Finished; - } - - // Convert the process path an absolute path to our current application directory. - // If the path is already an absolute path, it will be unchanged. - hr = UTILITY::ConvertPathToFullPath( - struExpandedProcessPath.QueryStr(), - pcwzApplicationPhysicalPath, - &struAbsolutePathToDotnet - ); - - if (FAILED(hr)) - { - goto Finished; + processPath = applicationPhysicalPath / processPath; } // Check if the absolute path is to dotnet or not. - if (HOSTFXR_UTILITY::IsDotnetExecutable(&struAbsolutePathToDotnet)) + if (IsDotnetExecutable(processPath)) { // // 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 // - if (FAILED(hr = HOSTFXR_UTILITY::GetAbsolutePathToDotnet(&struAbsolutePathToDotnet))) // Make sure to append the dotnet.exe path correctly here (pass in regular path)? + // Make sure to append the dotnet.exe path correctly here (pass in regular path)? + auto fullProcessPath = GetAbsolutePathToDotnet(processPath); + if (!fullProcessPath.has_value()) { - goto Finished; + return E_FAIL; } - if (FAILED(hr = GetAbsolutePathToHostFxr(&struAbsolutePathToDotnet, hEventLog, &struAbsolutePathToHostFxr))) + processPath = fullProcessPath.value(); + + auto hostFxrPath = GetAbsolutePathToHostFxr(processPath, hEventLog); + if (!hostFxrPath.has_value()) { - goto Finished; + return E_FAIL; } if (FAILED(hr = HOSTFXR_UTILITY::ParseHostfxrArguments( - struExpandedArguments.QueryStr(), - struAbsolutePathToDotnet.QueryStr(), + arguments.c_str(), + processPath.c_str(), pcwzApplicationPhysicalPath, hEventLog, pdwArgCount, pbstrArgv))) { - goto Finished; + return hr; } - if (FAILED(hr = pStruHostFxrDllLocation->Copy(struAbsolutePathToHostFxr))) + if (FAILED(hr = pStruHostFxrDllLocation->Copy(hostFxrPath->c_str()))) { - goto Finished; + return hr; } - if (FAILED(hr = pStruExeAbsolutePath->Copy(struAbsolutePathToDotnet))) + if (FAILED(hr = pStruExeAbsolutePath->Copy(processPath.c_str()))) { - goto Finished; + return hr; } } else @@ -238,25 +162,25 @@ 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 (UTILITY::CheckIfFileExists(struAbsolutePathToDotnet.QueryStr())) + if (is_regular_file(processPath)) { - hr = HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( - struAbsolutePathToDotnet.QueryStr(), + if (FAILED(hr = GetStandaloneHostfxrParameters( + processPath.c_str(), pcwzApplicationPhysicalPath, - struExpandedArguments.QueryStr(), + arguments.c_str(), hEventLog, pStruHostFxrDllLocation, pdwArgCount, - pbstrArgv); - if (FAILED(hr)) + pbstrArgv))) { - goto Finished; + return hr; } - if (FAILED(hr = pStruExeAbsolutePath->Copy(struAbsolutePathToDotnet))) + if (FAILED(hr = pStruExeAbsolutePath->Copy(processPath.c_str()))) { - goto Finished; + return hr; } + } else { @@ -264,19 +188,13 @@ 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);; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_GENERAL_ERROR_MSG, - ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, - struExpandedProcessPath.QueryStr(), - hr); + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + UTILITY::LogEventF(hEventLog, ASPNETCORE_EVENT_INVALID_PROCESS_PATH_LEVEL, ASPNETCORE_EVENT_INVALID_PROCESS_PATH, ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, processPath.c_str(), hr); + return hr; } } -Finished: - - return hr; + return S_OK; } // @@ -394,163 +312,82 @@ Finished: return hr; } -HRESULT +std::optional HOSTFXR_UTILITY::GetAbsolutePathToDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet + const fs::path & requestedPath ) { - HRESULT hr = S_OK; - // // If we are given an absolute path to dotnet.exe, we are done // - if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) + if (is_regular_file(requestedPath)) { - goto Finished; + return std::make_optional(requestedPath); } - // - // If the path was C:\Program Files\dotnet\dotnet - // We need to try appending .exe and check if the file exists too. - // - if (FAILED(hr = pStruAbsolutePathToDotnet->Append(L".exe"))) - { - goto Finished; - } + auto pathWithExe = requestedPath; + pathWithExe.concat(L".exe"); - if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) + if (is_regular_file(pathWithExe)) { - goto Finished; + return std::make_optional(pathWithExe); } // At this point, we are calling where.exe to find dotnet. // If we encounter any failures, try getting dotnet.exe from the // backup location. - if (!InvokeWhereToFindDotnet(pStruAbsolutePathToDotnet)) + // Only do it if no path is specified + if (!requestedPath.has_parent_path()) { - hr = GetAbsolutePathToDotnetFromProgramFiles(pStruAbsolutePathToDotnet); + return std::nullopt; } -Finished: + const auto dotnetViaWhere = InvokeWhereToFindDotnet(); + if (dotnetViaWhere.has_value()) + { + return dotnetViaWhere; + } - return hr; + return GetAbsolutePathToDotnetFromProgramFiles(); } -HRESULT +std::optional HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( - STRU* pStruAbsolutePathToDotnet, - HANDLE hEventLog, - STRU* pStruAbsolutePathToHostfxr + const fs::path & dotnetPath, + HANDLE hEventLog ) { - HRESULT hr = S_OK; - STRU struHostFxrPath; - STRU struHostFxrSearchExpression; - STRU struHighestDotnetVersion; - STRU struEventMsg; - std::vector vVersionFolders; - DWORD dwPosition = 0; + std::vector versionFolders; + const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr"; - if (FAILED(hr = struHostFxrPath.Copy(pStruAbsolutePathToDotnet))) + if (!is_directory(hostFxrBase)) { - goto Finished; + EVENTLOG(hEventLog, HOSTFXR_DIRECTORY_NOT_FOUND, hostFxrBase.c_str(), HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT)); + + return std::nullopt; } - dwPosition = struHostFxrPath.LastIndexOf(L'\\', 0); - if (dwPosition == -1) + auto searchPattern = std::wstring(hostFxrBase) + L"\\*"; + FindDotNetFolders(searchPattern.c_str(), versionFolders); + + if (versionFolders.empty()) { - hr = E_FAIL; - goto Finished; + EVENTLOG(hEventLog, HOSTFXR_DIRECTORY_NOT_FOUND, hostFxrBase.c_str(), HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT)); + + return std::nullopt; } - struHostFxrPath.QueryStr()[dwPosition] = L'\0'; + const auto highestVersion = FindHighestDotNetVersion(versionFolders); + const auto hostFxrPath = hostFxrBase / highestVersion / "hostfxr.dll"; - if (FAILED(hr = struHostFxrPath.SyncWithBuffer()) || - FAILED(hr = struHostFxrPath.Append(L"\\"))) + if (!is_regular_file(hostFxrPath)) { - goto Finished; + EVENTLOG(hEventLog, HOSTFXR_DLL_NOT_FOUND, hostFxrPath.c_str(), HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + + return std::nullopt; } - hr = struHostFxrPath.Append(L"host\\fxr"); - if (FAILED(hr)) - { - goto Finished; - } - - if (!UTILITY::DirectoryExists(&struHostFxrPath)) - { - hr = ERROR_BAD_ENVIRONMENT; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - struEventMsg.QueryStr(), - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - // Find all folders under host\\fxr\\ for version numbers. - hr = struHostFxrSearchExpression.Copy(struHostFxrPath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = struHostFxrSearchExpression.Append(L"\\*"); - if (FAILED(hr)) - { - goto Finished; - } - - // As we use the logic from core-setup, we are opting to use std here. - HOSTFXR_UTILITY::FindDotNetFolders(struHostFxrSearchExpression.QueryStr(), &vVersionFolders); - - if (vVersionFolders.size() == 0) - { - hr = HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - hr = FindHighestDotNetVersion(vVersionFolders, &struHighestDotnetVersion); - if (FAILED(hr)) - { - goto Finished; - } - - if (FAILED(hr = struHostFxrPath.Append(L"\\")) - || FAILED(hr = struHostFxrPath.Append(struHighestDotnetVersion.QueryStr())) - || FAILED(hr = struHostFxrPath.Append(L"\\hostfxr.dll"))) - { - goto Finished; - } - - if (!UTILITY::CheckIfFileExists(struHostFxrPath.QueryStr())) - { - // ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG - hr = HRESULT_FROM_WIN32(ERROR_FILE_INVALID); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - if (FAILED(hr = pStruAbsolutePathToHostfxr->Copy(struHostFxrPath))) - { - goto Finished; - } - -Finished: - return hr; + return std::make_optional(hostFxrPath); } // @@ -559,10 +396,8 @@ Finished: // worker process bitness. // Returns true if a valid dotnet was found, else false. // -BOOL -HOSTFXR_UTILITY::InvokeWhereToFindDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet -) +std::optional +HOSTFXR_UTILITY::InvokeWhereToFindDotnet() { HRESULT hr = S_OK; // Arguments to call where.exe @@ -586,16 +421,13 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( INT prevIndex = 0; BOOL fProcessCreationResult = FALSE; BOOL fResult = FALSE; + std::optional result; // Set the security attributes for the read/write pipe securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.bInheritHandle = TRUE; - // Reset the path to dotnet as we will be using whether the string is - // empty or not as state - pStruAbsolutePathToDotnet->Reset(); - // Create a read/write pipe that will be used for reading the result of where.exe if (!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)) { @@ -736,10 +568,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) { // The bitness of dotnet matched with the current worker process bitness. - if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) - { - goto Finished; - } + result = std::make_optional(struDotnetSubstring.QueryStr()); fResult = TRUE; break; } @@ -768,71 +597,21 @@ Finished: SysFreeString(pwzDotnetName); } - return fResult; + return result; } - -HRESULT -HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles( - _Inout_ STRU* pStruAbsolutePathToDotnet -) +std::optional +HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles() { - HRESULT hr = S_OK; - BOOL fFound = FALSE; - DWORD dwNumBytesRead = 0; - DWORD dwPathSize = MAX_PATH; - STRU struDotnetSubstring; - - while (!fFound) - { - if (FAILED(hr = struDotnetSubstring.Resize(dwPathSize))) - { - goto Finished; - } - - dwNumBytesRead = GetEnvironmentVariable(L"ProgramFiles", struDotnetSubstring.QueryStr(), dwPathSize); - if (dwNumBytesRead == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - else if (dwNumBytesRead >= dwPathSize) - { - // - // The path to ProgramFiles should never be this long, but resize and try again. - dwPathSize *= 2 + 30; // for dotnet substring - } - else - { - if (FAILED(hr = struDotnetSubstring.SyncWithBuffer()) || - FAILED(hr = struDotnetSubstring.Append(L"\\dotnet\\dotnet.exe"))) - { - goto Finished; - } - if (!UTILITY::CheckIfFileExists(struDotnetSubstring.QueryStr())) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) - { - goto Finished; - } - fFound = TRUE; - } - } - -Finished: - return hr; + const auto programFilesDotnet = fs::path(ExpandEnvironmentVariables(L"%ProgramFiles%")) / "dotnet" / "dotnet.exe"; + return is_regular_file(programFilesDotnet) ? std::make_optional(programFilesDotnet) : std::nullopt; } -HRESULT +std::wstring HOSTFXR_UTILITY::FindHighestDotNetVersion( - _In_ std::vector vFolders, - _Out_ STRU *pstrResult + _In_ std::vector & vFolders ) { - HRESULT hr = S_OK; fx_ver_t max_ver(-1, -1, -1); for (const auto& dir : vFolders) { @@ -844,16 +623,13 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion( } } - hr = pstrResult->Copy(max_ver.as_str().c_str()); - - // we check FAILED(hr) outside of function - return hr; + return max_ver.as_str(); } VOID HOSTFXR_UTILITY::FindDotNetFolders( _In_ PCWSTR pszPath, - _Out_ std::vector *pvFolders + _Out_ std::vector & pvFolders ) { HANDLE handle = NULL; @@ -868,8 +644,34 @@ HOSTFXR_UTILITY::FindDotNetFolders( do { std::wstring folder(data.cFileName); - pvFolders->push_back(folder); + pvFolders.push_back(folder); } while (FindNextFileW(handle, &data)); FindClose(handle); } + +std::wstring +HOSTFXR_UTILITY::ExpandEnvironmentVariables(const std::wstring & str) +{ + DWORD requestedSize = ExpandEnvironmentStringsW(str.c_str(), nullptr, 0); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables"); + } + + std::wstring expandedStr; + do + { + expandedStr.resize(requestedSize); + requestedSize = ExpandEnvironmentStringsW(str.c_str(), &expandedStr[0], requestedSize); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables"); + } + } while (expandedStr.size() != requestedSize); + + // trim null character as ExpandEnvironmentStringsW returns size including null character + expandedStr.resize(requestedSize - 1); + + return expandedStr; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index 57f77260f4..5e35cc4a6d 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -6,6 +6,8 @@ #include "precomp.h" #include +#include +#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[]); @@ -42,7 +44,7 @@ public: static BOOL IsDotnetExecutable( - STRU* struExecutablePath + _In_ const std::experimental::filesystem::path & dotnetPath ); static @@ -58,47 +60,41 @@ public: _Out_ BSTR **ppwzArgv ); - static - HRESULT - GetAbsolutePathToDotnet( - STRU* pStruAbsolutePathToDotnet - ); - - static - HRESULT - GetAbsolutePathToHostFxr( - _In_ STRU* pStruAbsolutePathToDotnet, - _In_ HANDLE hEventLog, - _Out_ STRU* pStruAbsolutePathToHostfxr - ); - - static - BOOL - InvokeWhereToFindDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet - ); - - static - HRESULT - GetAbsolutePathToDotnetFromProgramFiles( - _Inout_ STRU* pStruAbsolutePathToDotnet - ); - - static - HRESULT - FindHighestDotNetVersion( - _In_ std::vector vFolders, - _Out_ STRU *pstrResult - ); - static VOID FindDotNetFolders( _In_ PCWSTR pszPath, - _Out_ std::vector *pvFolders + _Out_ std::vector & pvFolders ); - HOSTFXR_UTILITY(); - ~HOSTFXR_UTILITY(); + static + std::wstring + FindHighestDotNetVersion( + _In_ std::vector & vFolders + ); + + static + std::optional + GetAbsolutePathToHostFxr( + _In_ const std::experimental::filesystem::path & dotnetPath, + _In_ HANDLE hEventLog + ); + + static + std::optional + GetAbsolutePathToDotnetFromProgramFiles(); + + static + std::optional + InvokeWhereToFindDotnet(); + + static + std::optional + GetAbsolutePathToDotnet( + _In_ const std::experimental::filesystem::path & requestedPath + ); + + static + std::wstring ExpandEnvironmentVariables(const std::wstring & str); }; diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp index 73493ddc1d..feeaa0bf6c 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp @@ -39,7 +39,7 @@ HRESULT HOSTFXR_OPTIONS::Create( goto Finished; } } - else if (HOSTFXR_UTILITY::IsDotnetExecutable(&struExeLocation)) + else if (HOSTFXR_UTILITY::IsDotnetExecutable(struExeLocation.QueryStr())) { if (FAILED(hr = HOSTFXR_UTILITY::ParseHostfxrArguments( pcwzArguments, diff --git a/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h index 773021784a..aaf4f1d50b 100644 --- a/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -36,12 +36,17 @@ #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_LEVEL EVENTLOG_ERROR_TYPE #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_LEVEL EVENTLOG_ERROR_TYPE #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_LEVEL EVENTLOG_ERROR_TYPE #define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG L"Could not find hostfxr.dll in '%s'. ErrorCode = '0x%x'." +#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_LEVEL EVENTLOG_ERROR_TYPE #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_MISSING_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." #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." diff --git a/src/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/AspNetCoreModuleV2/CommonLib/stdafx.h index 129de31a5c..43c8a1b2bf 100644 --- a/src/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -38,5 +38,6 @@ #include "resources.h" #include "aspnetcore_msg.h" #include "hostfxr_utility.h" +#include "EventLog.h" #include "hostfxroptions.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/utility.cxx b/src/AspNetCoreModuleV2/CommonLib/utility.cxx index 965b13eb58..a4d7ed46bd 100644 --- a/src/AspNetCoreModuleV2/CommonLib/utility.cxx +++ b/src/AspNetCoreModuleV2/CommonLib/utility.cxx @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "stdafx.h" +#include "debugutil.h" // static HRESULT @@ -569,9 +570,12 @@ UTILITY::LogEvent( ); } - if (dwEventInfoType == EVENTLOG_ERROR_TYPE) + STACK_STRA(converted, 256); + if (converted.CopyW(pstrMsg)) { - fwprintf(stderr, L"ERROR: %s\n", pstrMsg); + DebugPrintf( + dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, + "Event Log: %s", converted.QueryStr()); } } diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/precomp.hxx b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/precomp.hxx index 1cf69ec0dd..e77b603820 100644 --- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/precomp.hxx +++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/precomp.hxx @@ -54,7 +54,6 @@ #include "debugutil.h" // Common lib -#include "hostfxr_utility.h" #include "requesthandler.h" #include "utility.h" #include "application.h" diff --git a/test/CommonLibTests/hostfxr_utility_tests.cpp b/test/CommonLibTests/hostfxr_utility_tests.cpp index 2aace524e0..a8cd554ec0 100644 --- a/test/CommonLibTests/hostfxr_utility_tests.cpp +++ b/test/CommonLibTests/hostfxr_utility_tests.cpp @@ -84,7 +84,6 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs) TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) { STRU struAbsolutePathToDotnet; - HRESULT hr = S_OK; BOOL fDotnetInProgramFiles; BOOL is64Bit; BOOL fIsWow64 = FALSE; @@ -109,15 +108,14 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) fDotnetInProgramFiles = UTILITY::CheckIfFileExists(L"C:/Program Files (x86)/dotnet/dotnet.exe"); } - hr = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles(&struAbsolutePathToDotnet); + auto dotnetPath = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles(); if (fDotnetInProgramFiles) { - EXPECT_EQ(hr, S_OK); + EXPECT_TRUE(dotnetPath.has_value()); } else { - EXPECT_NE(hr, S_OK); - EXPECT_TRUE(struAbsolutePathToDotnet.IsEmpty()); + EXPECT_FALSE(dotnetPath.has_value()); } } @@ -138,5 +136,5 @@ TEST(GetHostFxrArguments, InvalidParams) &retVal, // arg count &bstrArray); // args array. - EXPECT_EQ(E_INVALIDARG, hr); -} \ No newline at end of file + EXPECT_EQ(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), hr); +} diff --git a/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs index 19c59a9161..c470e12360 100644 --- a/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs @@ -2,10 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; +using System.IO; using System.Net; using System.Threading.Tasks; -using System.Xml.Linq; using IISIntegration.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Testing.xunit; @@ -18,30 +17,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [SkipIfIISExpressSchemaMissingInProcess] public class StartupTests : IISFunctionalTestBase { + private readonly string _dotnetLocation = DotNetMuxer.MuxerPathOrDefault(); + public StartupTests(ITestOutputHelper output) : base(output) { } - [Fact] + [ConditionalFact] public async Task ExpandEnvironmentVariableInWebConfig() { - var dotnetLocation = DotNetMuxer.MuxerPathOrDefault(); - var deploymentParameters = GetBaseDeploymentParameters(); - // Point to dotnet installed in user profile. - deploymentParameters.EnvironmentVariables["DotnetPath"] = dotnetLocation; - - var deploymentResult = await DeployAsync(deploymentParameters); - - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); - - var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); - - var responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal("Hello World", responseText); + await AssertStarts( + deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"), + deploymentParameters => deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation); } - [Fact] + [ConditionalFact] public async Task InvalidProcessPath_ExpectServerError() { var dotnetLocation = "bogus"; @@ -60,6 +51,51 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } + [ConditionalFact] + public async Task StartsWithDotnetLocationWithoutExe() + { + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")); + + await AssertStarts( + deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", dotnetLocationWithoutExtension)); + } + + [ConditionalFact] + public async Task StartsWithDotnetLocationUppercase() + { + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")).ToUpperInvariant(); + + await AssertStarts( + deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", dotnetLocationWithoutExtension)); + } + + [ConditionalTheory] + [InlineData("dotnet")] + [InlineData("dotnet.EXE")] + public async Task StartsWithDotnetOnThePath(string path) + { + await AssertStarts( + deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path), + deploymentParameters => deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation)); + } + + private async Task AssertStarts(Action postDeploy, Action preDeploy = null) + { + var deploymentParameters = GetBaseDeploymentParameters(); + + preDeploy?.Invoke(deploymentParameters); + + var deploymentResult = await DeployAsync(deploymentParameters); + + postDeploy?.Invoke(deploymentResult); + + var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World", responseText); + } + + public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress) .WithTfms(Tfm.NetCoreApp22)