From 24e17eadca5ad0c98564ffe941e4d22973d35202 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 6 Dec 2018 15:19:47 -0800 Subject: [PATCH] Add a flag to set current directory in ANCM InProc (#4369) --- .../CommonLib/ConfigurationSection.h | 1 + .../CommonLib/Environment.cpp | 49 +++++++++++++++++++ .../CommonLib/Environment.h | 4 ++ .../InProcessOptions.cpp | 4 ++ .../InProcessOptions.h | 9 +++- .../inprocessapplication.cpp | 22 +++++++++ .../serverprocess.cpp | 1 + .../environmentvariablehash.h | 1 + .../environmentvariablehelpers.h | 22 ++++++++- .../Inprocess/HostingEnvironmentTests.cs | 12 ++--- .../Inprocess/StartupTests.cs | 17 +++++++ .../OutOfProcess/HelloWorldTest.cs | 9 ++-- .../shared/SharedStartup/Startup.shared.cs | 27 +++++++--- 13 files changed, 157 insertions(+), 21 deletions(-) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h index f998478db2..9b6fd2cb49 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -23,6 +23,7 @@ #define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess" #define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" #define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" +#define CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY L"setCurrentDirectory" #define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" #define CS_ENABLED L"enabled" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp index 5e71216ce5..249884a4af 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -60,3 +60,52 @@ Environment::GetEnvironmentVariableValue(const std::wstring & str) return expandedStr; } + +std::wstring Environment::GetCurrentDirectoryValue() +{ + DWORD requestedSize = GetCurrentDirectory(0, nullptr); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "GetCurrentDirectory"); + } + + std::wstring expandedStr; + do + { + expandedStr.resize(requestedSize); + requestedSize = GetCurrentDirectory(requestedSize, expandedStr.data()); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "GetCurrentDirectory"); + } + } while (expandedStr.size() != requestedSize + 1); + + expandedStr.resize(requestedSize); + + return expandedStr; +} + +std::wstring Environment::GetDllDirectoryValue() +{ + DWORD requestedSize = GetDllDirectory(0, nullptr); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "GetDllDirectory"); + } + + std::wstring expandedStr; + do + { + expandedStr.resize(requestedSize); + requestedSize = GetDllDirectory(requestedSize, expandedStr.data()); + // 0 might be returned if GetDllDirectory is empty + if (requestedSize == 0 && GetLastError() != 0) + { + throw std::system_error(GetLastError(), std::system_category(), "GetDllDirectory"); + } + } while (expandedStr.size() != requestedSize + 1); + + expandedStr.resize(requestedSize); + + return expandedStr; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h index 16022e18cf..8886ad428e 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h @@ -16,5 +16,9 @@ public: std::wstring ExpandEnvironmentVariables(const std::wstring & str); static std::optional GetEnvironmentVariableValue(const std::wstring & str); + static + std::wstring GetCurrentDirectoryValue(); + static + std::wstring GetDllDirectoryValue(); }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp index 1b3e22e1dc..346df2c107 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -53,6 +53,10 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES); + + const auto handlerSettings = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS); + m_fSetCurrentDirectory = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY).value_or(L"false"), L"true"); + m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000; m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h index e299c2033e..dbba6408f4 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h @@ -40,6 +40,12 @@ public: return m_fDisableStartUpErrorPage; } + bool + QuerySetCurrentDirectory() const + { + return m_fSetCurrentDirectory; + } + bool QueryWindowsAuthEnabled() const { @@ -87,7 +93,7 @@ public: } InProcessOptions(const ConfigurationSource &configurationSource); - + static HRESULT InProcessOptions::Create( IHttpServer& pServer, @@ -100,6 +106,7 @@ private: std::wstring m_struStdoutLogFile; bool m_fStdoutLogEnabled; bool m_fDisableStartUpErrorPage; + bool m_fSetCurrentDirectory; bool m_fWindowsAuthEnabled; bool m_fBasicAuthEnabled; bool m_fAnonymousAuthEnabled; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index c351dbc4c1..09f4dc3e5e 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -11,6 +11,7 @@ #include "resources.h" #include "EventLog.h" #include "ModuleHelpers.h" +#include "Environment.h" IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; @@ -218,6 +219,25 @@ IN_PROCESS_APPLICATION::ExecuteApplication() // set the callbacks s_Application = this; + if (m_pConfig->QuerySetCurrentDirectory()) + { + auto dllDirectory = Environment::GetDllDirectoryValue(); + auto currentDirectory = Environment::GetCurrentDirectoryValue(); + + LOG_INFOF(L"Initial Dll directory: '%s', current directory: '%s'", dllDirectory.c_str(), currentDirectory.c_str()); + + // If DllDirectory wasn't set change it to previous current directory value + if (dllDirectory.empty()) + { + LOG_LAST_ERROR_IF(!SetDllDirectory(currentDirectory.c_str())); + LOG_INFOF(L"Setting dll directory to %s", currentDirectory.c_str()); + } + + LOG_LAST_ERROR_IF(!SetCurrentDirectory(this->QueryApplicationPhysicalPath().c_str())); + + LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str()); + } + //Start CLR thread m_clrThread = std::thread(ClrThreadEntryPoint, context); @@ -405,6 +425,7 @@ HRESULT IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess() { auto variables = m_pConfig->QueryEnvironmentVariables(); + auto inputTable = std::unique_ptr(new ENVIRONMENT_VAR_HASH()); RETURN_IF_FAILED(inputTable->Initialize(37 /*prime*/)); // Copy environment variables to old style hash table @@ -422,6 +443,7 @@ IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess() m_pConfig->QueryWindowsAuthEnabled(), m_pConfig->QueryBasicAuthEnabled(), m_pConfig->QueryAnonymousAuthEnabled(), + QueryApplicationPhysicalPath().c_str(), &pHashTable)); table.reset(pHashTable); diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp index 494911472a..9b549e7bf6 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp @@ -785,6 +785,7 @@ SERVER_PROCESS::StartProcess( m_fWindowsAuthEnabled, m_fBasicAuthEnabled, m_fAnonymousAuthEnabled, + m_struAppFullPath.QueryStr(), &pHashTable))) { pStrStage = L"InitEnvironmentVariablesTable"; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h index c5f63f6fde..eb7c9ad544 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h @@ -8,6 +8,7 @@ #define HOSTING_STARTUP_ASSEMBLIES_VALUE L"Microsoft.AspNetCore.Server.IISIntegration" #define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH=" #define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED=" +#define ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR L"ASPNETCORE_IIS_PHYSICAL_PATH=" #define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" #define ASPNETCORE_IIS_AUTH_BASIC L"basic;" #define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h index b65c32ee29..17ab7603b4 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h @@ -191,6 +191,7 @@ public: _In_ BOOL fWindowsAuthEnabled, _In_ BOOL fBasicAuthEnabled, _In_ BOOL fAnonymousAuthEnabled, + _In_ PCWSTR pApplicationPhysicalPath, _Out_ ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable ) { @@ -201,6 +202,7 @@ public: STACK_STRU(strStartupAssemblyEnv, 1024); ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; + ENVIRONMENT_VAR_ENTRY* pIISPathEntry = NULL; ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); @@ -213,7 +215,7 @@ public: goto Finished; } - // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements + // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements pInEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HELPERS::CopyToTable, pEnvironmentVarTable); if (pEnvironmentVarTable->Count() != pInEnvironmentVarTable->Count()) { @@ -222,6 +224,22 @@ public: goto Finished; } + pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, &pIISPathEntry); + if (pIISPathEntry != NULL) + { + // user defined ASPNETCORE_IIS_PHYSICAL_PATH in configuration, wipe it off + pIISPathEntry->Dereference(); + pEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR); + } + + pIISPathEntry = new ENVIRONMENT_VAR_ENTRY(); + + if (FAILED(hr = pIISPathEntry->Initialize(ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, pApplicationPhysicalPath)) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISPathEntry))) + { + goto Finished; + } + pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); if (pIISAuthEntry != NULL) { @@ -259,7 +277,7 @@ public: } // Compiler is complaining about conversion between PCWSTR and PWSTR here. - // Explictly casting. + // Explictly casting. pEnvironmentVarTable->FindKey((PWSTR)HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); if (pHostingEntry != NULL) { diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs index 061b828a6c..3f9c61e5e4 100644 --- a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs @@ -20,14 +20,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests } [ConditionalFact] - [RequiresIIS(IISCapability.ShutdownToken)] + [RequiresNewHandler] public async Task HostingEnvironmentIsCorrect() { - Assert.Equal( - $"ContentRootPath {_fixture.DeploymentResult.ContentRoot}" + Environment.NewLine + - $"WebRootPath {_fixture.DeploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + - $"CurrentDirectory {Path.GetDirectoryName(_fixture.DeploymentResult.HostProcess.MainModule.FileName)}", - await _fixture.Client.GetStringAsync("/HostingEnvironment")); + Assert.Equal(_fixture.DeploymentResult.ContentRoot, await _fixture.Client.GetStringAsync("/ContentRootPath")); + Assert.Equal(_fixture.DeploymentResult.ContentRoot + "\\wwwroot", await _fixture.Client.GetStringAsync("/WebRootPath")); + Assert.Equal(Path.GetDirectoryName(_fixture.DeploymentResult.HostProcess.MainModule.FileName), await _fixture.Client.GetStringAsync("/CurrentDirectory")); + Assert.Equal(_fixture.DeploymentResult.ContentRoot + "\\", await _fixture.Client.GetStringAsync("/BaseDirectory")); + Assert.Equal(_fixture.DeploymentResult.ContentRoot + "\\", await _fixture.Client.GetStringAsync("/ASPNETCORE_IIS_PHYSICAL_PATH")); } } } diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs index 458d356eba..d51e6feb25 100644 --- a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -449,6 +449,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests return dictionary; } + [ConditionalFact] + [RequiresNewHandler] + public async Task SetCurrentDirectoryHandlerSettingWorks() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.HandlerSettings["SetCurrentDirectory"] = "true"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + Assert.Equal(deploymentResult.ContentRoot, await deploymentResult.HttpClient.GetStringAsync("/ContentRootPath")); + Assert.Equal(deploymentResult.ContentRoot + "\\wwwroot", await deploymentResult.HttpClient.GetStringAsync("/WebRootPath")); + Assert.Equal(deploymentResult.ContentRoot, await deploymentResult.HttpClient.GetStringAsync("/CurrentDirectory")); + Assert.Equal(deploymentResult.ContentRoot + "\\", await deploymentResult.HttpClient.GetStringAsync("/BaseDirectory")); + Assert.Equal(deploymentResult.ContentRoot + "\\", await deploymentResult.HttpClient.GetStringAsync("/ASPNETCORE_IIS_PHYSICAL_PATH")); + Assert.Equal(Path.GetDirectoryName(deploymentResult.HostProcess.MainModule.FileName), await deploymentResult.HttpClient.GetStringAsync("/DllDirectory")); + } + private static void MoveApplication( IISDeploymentParameters parameters, string subdirectory) diff --git a/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs index f6e36613d7..11219d4406 100644 --- a/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -68,11 +69,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal("null", responseText); - Assert.Equal( - $"ContentRootPath {deploymentResult.ContentRoot}" + Environment.NewLine + - $"WebRootPath {deploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + - $"CurrentDirectory {deploymentResult.ContentRoot}", - await deploymentResult.HttpClient.GetStringAsync("/HostingEnvironment")); + Assert.Equal(deploymentResult.ContentRoot, await deploymentResult.HttpClient.GetStringAsync("/ContentRootPath")); + Assert.Equal(deploymentResult.ContentRoot + "\\wwwroot", await deploymentResult.HttpClient.GetStringAsync("/WebRootPath")); + Assert.Equal(deploymentResult.ContentRoot, await deploymentResult.HttpClient.GetStringAsync("/CurrentDirectory")); var expectedDll = variant.AncmVersion == AncmVersion.AspNetCoreModule ? "aspnetcore.dll" : "aspnetcorev2.dll"; Assert.Contains(deploymentResult.HostProcess.Modules.OfType(), m=> m.FileName.Contains(expectedDll)); diff --git a/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs b/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs index b5d7f0305f..851654966b 100644 --- a/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs +++ b/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -21,14 +23,15 @@ namespace TestSite serviceCollection.AddResponseCompression(); } - private async Task HostingEnvironment(HttpContext ctx) - { - var hostingEnv = ctx.RequestServices.GetService(); + private async Task ContentRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService().ContentRootPath); - await ctx.Response.WriteAsync("ContentRootPath " + hostingEnv.ContentRootPath + Environment.NewLine); - await ctx.Response.WriteAsync("WebRootPath " + hostingEnv.WebRootPath + Environment.NewLine); - await ctx.Response.WriteAsync("CurrentDirectory " + Environment.CurrentDirectory); - } + private async Task WebRootPath(HttpContext ctx) => await ctx.Response.WriteAsync(ctx.RequestServices.GetService().WebRootPath); + + private async Task CurrentDirectory(HttpContext ctx) => await ctx.Response.WriteAsync(Environment.CurrentDirectory); + + private async Task BaseDirectory(HttpContext ctx) => await ctx.Response.WriteAsync(AppContext.BaseDirectory); + + private async Task ASPNETCORE_IIS_PHYSICAL_PATH(HttpContext ctx) => await ctx.Response.WriteAsync(Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH")); private async Task ConsoleWrite(HttpContext ctx) { @@ -113,5 +116,15 @@ namespace TestSite await context.Response.Body.WriteAsync(new byte[100], 0, 100); }); } + + [DllImport("kernel32.dll")] + static extern uint GetDllDirectory(uint nBufferLength, [Out] StringBuilder lpBuffer); + + private async Task DllDirectory(HttpContext context) + { + var builder = new StringBuilder(1024); + GetDllDirectory(1024, builder); + await context.Response.WriteAsync(builder.ToString()); + } } }