Skip to content

Commit

Permalink
Implements shadow copying for ASP.NET Core + IIS (#28357)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkotalik authored Mar 20, 2021
1 parent 6db1f6e commit fce37c1
Show file tree
Hide file tree
Showing 31 changed files with 894 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class AppOfflineHandler: public REQUEST_HANDLER
public:
AppOfflineHandler(IHttpContext& pContext, const std::string& appOfflineContent)
: REQUEST_HANDLER(pContext),
m_pContext(pContext),
m_strAppOfflineContent(appOfflineContent)
m_pContext(pContext),
m_strAppOfflineContent(appOfflineContent)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ class ApplicationFactory
HRESULT Execute(
_In_ IHttpServer *pServer,
_In_ IHttpContext *pHttpContext,
_In_ std::wstring& shadowCopyDirectory,
_Outptr_ IAPPLICATION **pApplication) const
{
// m_location.data() is const ptr copy to local to get mutable pointer
auto location = m_location;
std::array<APPLICATION_PARAMETER, 3> parameters {
std::array<APPLICATION_PARAMETER, 4> parameters {
{
{"InProcessExeLocation", location.data()},
{"TraceContext", pHttpContext->GetTraceContext()},
{"Site", pHttpContext->GetSite()}
{"Site", pHttpContext->GetSite()},
{"ShadowCopyDirectory", shadowCopyDirectory.data()}
}
};

Expand Down
14 changes: 11 additions & 3 deletions src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer)

HRESULT
HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication,
const std::filesystem::path& shadowCopyPath,
const ShimOptions& pConfiguration,
std::unique_ptr<ApplicationFactory>& pApplicationFactory,
ErrorContext& errorContext)
Expand Down Expand Up @@ -62,7 +63,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
RETURN_IF_FAILED(HostFxrResolutionResult::Create(
L"",
pConfiguration.QueryProcessPath(),
pApplication.GetApplicationPhysicalPath(),
shadowCopyPath.empty() ? pApplication.GetApplicationPhysicalPath() : shadowCopyPath,
pConfiguration.QueryArguments(),
errorContext,
options));
Expand Down Expand Up @@ -125,7 +126,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
}

HRESULT
HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext)
HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext)
{
SRWExclusiveLock lock(m_requestHandlerLoadLock);
if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN)
Expand Down Expand Up @@ -168,7 +169,7 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std

m_loadedApplicationHostingModel = options.QueryHostingModel();
m_loadedApplicationId = pApplication.GetApplicationId();
RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, options, pApplicationFactory, errorContext));
RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, shadowCopyPath, options, pApplicationFactory, errorContext));

return S_OK;
}
Expand All @@ -181,6 +182,13 @@ void HandlerResolver::ResetHostingModel()
m_loadedApplicationId.resize(0);
}

APP_HOSTING_MODEL HandlerResolver::GetHostingModel()
{
SRWExclusiveLock lock(m_requestHandlerLoadLock);

return m_loadedApplicationHostingModel;
}

HRESULT
HandlerResolver::FindNativeAssemblyFromGlobalLocation(
const ShimOptions& pConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ class HandlerResolver
{
public:
HandlerResolver(HMODULE hModule, const IHttpServer &pServer);
HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext);
HRESULT GetApplicationFactory(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext);
void ResetHostingModel();
APP_HOSTING_MODEL GetHostingModel();

private:
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory, ErrorContext& errorContext);
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory, ErrorContext& errorContext);
HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath);
HRESULT FindNativeAssemblyFromHostfxr(
const HostFxrResolutionResult& hostfxrOptions,
Expand Down
15 changes: 14 additions & 1 deletion src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include "Environment.h"

#define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion"
#define CS_ASPNETCORE_SHADOW_COPY L"experimentalEnableShadowCopy"
#define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory"
#define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory"

ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
m_hostingModel(HOSTING_UNKNOWN),
Expand All @@ -31,12 +34,22 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
"or hostingModel=\"outofprocess\" in the web.config file.", hostingModel.c_str()));
}

const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);

if (m_hostingModel == HOSTING_OUT_PROCESS)
{
const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);
m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring());
}

auto experimentalEnableShadowCopyElement = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY).value_or(std::wstring());
m_fexperimentalEnableShadowCopying = equals_ignore_case(L"true", experimentalEnableShadowCopyElement);

auto cleanShadowCopyDirectory = find_element(handlerSettings, CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT).value_or(std::wstring());
m_fCleanShadowCopyDirectory = equals_ignore_case(L"true", cleanShadowCopyDirectory);

m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY)
.value_or(m_fexperimentalEnableShadowCopying ? L"ShadowCopyDirectory" : std::wstring());

m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH);
m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT);
m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED);
Expand Down
21 changes: 21 additions & 0 deletions src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ class ShimOptions: NonCopyable
return m_fShowDetailedErrors;
}

bool
QueryShadowCopyEnabled() const noexcept
{
return m_fexperimentalEnableShadowCopying;
}

bool
QueryCleanShadowCopyDirectory() const noexcept
{
return m_fCleanShadowCopyDirectory;
}

const std::wstring&
QueryShadowCopyDirectory() const noexcept
{
return m_strShadowCopyingDirectory;
}

ShimOptions(const ConfigurationSource &configurationSource);

private:
Expand All @@ -76,4 +94,7 @@ class ShimOptions: NonCopyable
bool m_fStdoutLogEnabled;
bool m_fDisableStartupPage;
bool m_fShowDetailedErrors;
bool m_fexperimentalEnableShadowCopying;
bool m_fCleanShadowCopyDirectory;
std::wstring m_strShadowCopyingDirectory;
};
105 changes: 98 additions & 7 deletions src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "file_utility.h"

extern HINSTANCE g_hServerModule;
extern BOOL g_fInAppOfflineShutdown;

HRESULT
APPLICATION_INFO::CreateHandler(
Expand Down Expand Up @@ -49,7 +50,6 @@ APPLICATION_INFO::CreateHandler(
while (hr != S_OK)
{
// At this point application is either null or shutdown and is returning S_FALSE

if (m_pApplication != nullptr)
{
LOG_INFO(L"Application went offline");
Expand Down Expand Up @@ -80,11 +80,25 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)

return S_OK;
}

try
{
const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication);
ShimOptions options(configurationSource);

if (g_fInAppOfflineShutdown)
{
m_pApplication = make_application<ServerErrorApplication>(
pHttpApplication,
E_FAIL,
options.QueryDisableStartupPage() /* disableStartupPage */,
"" /* responseContent */,
503i16 /* statusCode */,
0i16 /* subStatusCode */,
"Application Shutting Down");
return S_OK;
}

ErrorContext errorContext;
errorContext.statusCode = 500i16;
errorContext.subStatusCode = 0i16;
Expand Down Expand Up @@ -130,6 +144,7 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
EventLog::Error(
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
Expand Down Expand Up @@ -175,13 +190,17 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt
}
}

RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options, error));
auto shadowCopyPath = HandleShadowCopy(options, pHttpContext);

RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), shadowCopyPath, m_pApplicationFactory, options, error));
LOG_INFO(L"Creating handler application");

IAPPLICATION * newApplication;
std::wstring shadowCopyWstring = shadowCopyPath.wstring();
RETURN_IF_FAILED(m_pApplicationFactory->Execute(
&m_pServer,
&pHttpContext,
shadowCopyWstring,
&newApplication));

m_pApplication.reset(newApplication);
Expand All @@ -206,19 +225,91 @@ APPLICATION_INFO::TryCreateHandler(
return S_OK;
}
}

return S_FALSE;
}

VOID
APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated)
{
IAPPLICATION* app = nullptr;
{
SRWExclusiveLock lock(m_applicationLock);
if (!m_pApplication)
{
return;
}
app = m_pApplication.get();
}

LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str());
app->Stop(fServerInitiated);

SRWExclusiveLock lock(m_applicationLock);

if (m_pApplication)
m_pApplication = nullptr;
m_pApplicationFactory = nullptr;
}

std::filesystem::path
APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext)
{
std::filesystem::path shadowCopyPath;

// Only support shadow copying for IIS.
if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch())
{
LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str());
m_pApplication->Stop(fServerInitiated);
m_pApplication = nullptr;
m_pApplicationFactory = nullptr;
shadowCopyPath = options.QueryShadowCopyDirectory();
std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath();

// Make shadow copy path absolute.
if (!shadowCopyPath.is_absolute())
{
shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath);
}

// The shadow copy directory itself isn't copied to directly.
// Instead subdirectories with numerically increasing names are created.
// This is because on shutdown, the app itself will still have all dlls loaded,
// meaning we can't copy to the same subdirectory. Therefore, on shutdown,
// we create a directory that is one larger than the previous largest directory number.
auto directoryName = 0;
std::string directoryNameStr = "0";
auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath);
if (!shadowCopyBaseDirectory.exists())
{
CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), NULL);
}

for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath))
{
if (entry.is_directory())
{
try
{
auto tempDirName = entry.path().filename().string();
int intFileName = std::stoi(tempDirName);
if (intFileName > directoryName)
{
directoryName = intFileName;
directoryNameStr = tempDirName;
}
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
// Ignore any folders that can't be converted to an int.
}
}
}

shadowCopyPath = shadowCopyPath / directoryNameStr;
HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), std::filesystem::canonical(shadowCopyBaseDirectory.path()));
if (hr != S_OK)
{
return std::wstring();
}
}

return shadowCopyPath;
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class APPLICATION_INFO: NonCopyable
HRESULT
TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options, ErrorContext& error);

std::filesystem::path
HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext);

IHttpServer &m_pServer;
HandlerResolver &m_handlerResolver;

Expand Down
Loading

0 comments on commit fce37c1

Please sign in to comment.