Skip to content

Commit aa6c1f9

Browse files
Improve error handling for shadow copy
Add logging for when no permissions ot shadow copy directory Add tests for when no permissions and when only write permissions Add error handling to CopyToDirectory
1 parent c4c3760 commit aa6c1f9

File tree

4 files changed

+314
-107
lines changed

4 files changed

+314
-107
lines changed

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp

Lines changed: 126 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)
8484
return S_OK;
8585
}
8686

87+
std::wstring pExceptionMessage;
88+
8789
try
8890
{
8991
const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication);
@@ -138,29 +140,43 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)
138140
}
139141
return S_OK;
140142
}
141-
catch (const ConfigurationLoadException &ex)
142-
{
143-
EventLog::Error(
144-
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
145-
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
146-
ex.get_message().c_str());
147-
}
148143
catch (...)
149144
{
150145
OBSERVE_CAUGHT_EXCEPTION();
146+
pExceptionMessage = CaughtExceptionToString();
151147
EventLog::Error(
152148
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
153149
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
154-
L"");
150+
pExceptionMessage.c_str());
155151
}
156152

153+
ErrorContext errorContext;
154+
errorContext.statusCode = 500i16;
155+
errorContext.subStatusCode = 30i16;
156+
errorContext.generalErrorType = "ASP.NET Core app failed to start - An exception was thrown during startup";
157+
if (GetLastError() == ERROR_ACCESS_DENIED)
158+
{
159+
errorContext.errorReason = "Ensure the application pool process model has write permissions to the shadow copy directory";
160+
}
161+
// TODO: Depend on show detailed errors or nah?
162+
errorContext.detailedErrorContent = to_multi_byte_string(pExceptionMessage, CP_UTF8);
163+
auto page = ANCM_ERROR_PAGE;
164+
165+
166+
auto responseContent = FILE_UTILITY::GetHtml(g_hServerModule,
167+
page,
168+
errorContext.statusCode,
169+
errorContext.subStatusCode,
170+
errorContext.generalErrorType,
171+
errorContext.errorReason,
172+
errorContext.detailedErrorContent);
157173
m_pApplication = make_application<ServerErrorApplication>(
158174
pHttpApplication,
159175
E_FAIL,
160176
false /* disableStartupPage */,
161-
"" /* responseContent */,
162-
500i16 /* statusCode */,
163-
0i16 /* subStatusCode */,
177+
responseContent /* responseContent */,
178+
errorContext.statusCode /* statusCode */,
179+
errorContext.subStatusCode/* subStatusCode */,
164180
"Internal Server Error");
165181

166182
return S_OK;
@@ -193,7 +209,26 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt
193209
}
194210
}
195211

196-
auto shadowCopyPath = HandleShadowCopy(options, pHttpContext);
212+
std::filesystem::path shadowCopyPath;
213+
214+
// Only support shadow copying for IIS.
215+
if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch())
216+
{
217+
try
218+
{
219+
shadowCopyPath = HandleShadowCopy(options, pHttpContext, error);
220+
}
221+
catch (...)
222+
{
223+
OBSERVE_CAUGHT_EXCEPTION();
224+
throw;
225+
}
226+
227+
if (shadowCopyPath.empty())
228+
{
229+
return E_FAIL;
230+
}
231+
}
197232

198233
RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), shadowCopyPath, m_pApplicationFactory, options, error));
199234
LOG_INFO(L"Creating handler application");
@@ -275,74 +310,107 @@ APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated)
275310
* we will start a thread that deletes all other folders in that directory.
276311
*/
277312
std::filesystem::path
278-
APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext)
313+
APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext, ErrorContext& error)
279314
{
280-
std::filesystem::path shadowCopyPath;
315+
std::filesystem::path shadowCopyPath = options.QueryShadowCopyDirectory();
316+
std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath();
281317

282-
// Only support shadow copying for IIS.
283-
if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch())
318+
// Make shadow copy path absolute.
319+
if (!shadowCopyPath.is_absolute())
284320
{
285-
shadowCopyPath = options.QueryShadowCopyDirectory();
286-
std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath();
321+
shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath);
322+
}
287323

288-
// Make shadow copy path absolute.
289-
if (!shadowCopyPath.is_absolute())
290-
{
291-
shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath);
292-
}
324+
// The shadow copy directory itself isn't copied to directly.
325+
// Instead subdirectories with numerically increasing names are created.
326+
// This is because on shutdown, the app itself will still have all dlls loaded,
327+
// meaning we can't copy to the same subdirectory. Therefore, on shutdown,
328+
// we create a directory that is one larger than the previous largest directory number.
329+
auto directoryName = 0;
330+
std::string directoryNameStr = "0";
331+
auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath);
332+
if (!shadowCopyBaseDirectory.exists())
333+
{
334+
LOG_INFOF(L"Attempting to Create Directory");
293335

294-
// The shadow copy directory itself isn't copied to directly.
295-
// Instead subdirectories with numerically increasing names are created.
296-
// This is because on shutdown, the app itself will still have all dlls loaded,
297-
// meaning we can't copy to the same subdirectory. Therefore, on shutdown,
298-
// we create a directory that is one larger than the previous largest directory number.
299-
auto directoryName = 0;
300-
std::string directoryNameStr = "0";
301-
auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath);
302-
if (!shadowCopyBaseDirectory.exists())
336+
auto ret = CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), nullptr);
337+
if (!ret)
303338
{
304-
CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), nullptr);
339+
LOG_ERRORF(L"Failed to create shadow copy base directory %ls. Error: %d",
340+
shadowCopyBaseDirectory.path().c_str(),
341+
GetLastError());
305342
}
343+
}
306344

307-
for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath))
345+
for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath))
346+
{
347+
if (entry.is_directory())
308348
{
309-
if (entry.is_directory())
349+
try
310350
{
311-
try
312-
{
313-
auto tempDirName = entry.path().filename().string();
314-
int intFileName = std::stoi(tempDirName);
315-
if (intFileName > directoryName)
316-
{
317-
directoryName = intFileName;
318-
directoryNameStr = tempDirName;
319-
}
320-
}
321-
catch (...)
351+
auto tempDirName = entry.path().filename().string();
352+
int intFileName = std::stoi(tempDirName);
353+
if (intFileName > directoryName)
322354
{
323-
OBSERVE_CAUGHT_EXCEPTION();
324-
// Ignore any folders that can't be converted to an int.
355+
directoryName = intFileName;
356+
directoryNameStr = tempDirName;
325357
}
326358
}
359+
catch (...)
360+
{
361+
OBSERVE_CAUGHT_EXCEPTION();
362+
// Ignore any folders that can't be converted to an int.
363+
}
327364
}
365+
}
366+
367+
int copiedFileCount = 0;
368+
369+
shadowCopyPath = shadowCopyPath / directoryNameStr;
370+
LOG_INFOF(L"Copying from %ls to shadow copy directory %ls.", physicalPath.c_str(), shadowCopyPath.c_str());
328371

329-
int copiedFileCount = 0;
372+
// Avoid using canonical for shadowCopyBaseDirectory
373+
// It could expand to a network drive, or an expanded link folder path
374+
// We already made it an absolute path relative to the physicalPath above
375+
try {
376+
// CopyToDirectory throws exception on failure, therefore don't need to check return value
377+
Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path(), copiedFileCount);
378+
}
379+
catch (const std::system_error& ex)
380+
{
381+
auto exWideString = to_wide_string(ex.what(), CP_ACP);
382+
383+
std::wstring logMessage = format(L"Failed to copy files from %s to shadow copy directory %s. Error: %s",
384+
physicalPath.c_str(),
385+
shadowCopyPath.c_str(),
386+
exWideString.c_str());
330387

331-
shadowCopyPath = shadowCopyPath / directoryNameStr;
332-
LOG_INFOF(L"Copying to shadow copy directory %ls.", shadowCopyPath.c_str());
388+
LOG_ERRORF(L"%ls", logMessage.c_str());
389+
EventLog::Error(ASPNETCORE_CONFIGURATION_LOAD_ERROR,
390+
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
391+
logMessage.c_str());
333392

334-
// Avoid using canonical for shadowCopyBaseDirectory
335-
// It could expand to a network drive, or an expanded link folder path
336-
// We already made it an absolute path relative to the physicalPath above
337-
HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path(), copiedFileCount);
393+
// TODO: Better substatus code
394+
error.statusCode = 500i16;
395+
error.subStatusCode = 30i16;
338396

339-
LOG_INFOF(L"Finished copying %d files to shadow copy directory %ls.", copiedFileCount, shadowCopyBaseDirectory.path().c_str());
397+
std::string errorMessage = "Failed to copy to shadow copy directory";
398+
auto exceptionCode = ex.code().value();
399+
if (exceptionCode == ERROR_ACCESS_DENIED || exceptionCode == ERROR_PATH_NOT_FOUND)
400+
{
401+
errorMessage = "No permissions to shadow copy directory";
402+
}
340403

341-
if (hr != S_OK)
404+
error.generalErrorType = format("ASP.NET Core app failed to start - %s", errorMessage.c_str());
405+
error.errorReason = format("Ensure the application pool process model has write permissions to the shadow copy directory %ls",
406+
shadowCopyPath.c_str());
407+
if (options.QueryShowDetailedErrors())
342408
{
343-
return std::wstring();
409+
error.detailedErrorContent = ex.what();
344410
}
411+
return std::wstring();
345412
}
346413

414+
LOG_INFOF(L"Finished copying %d files to shadow copy directory %ls.", copiedFileCount, shadowCopyBaseDirectory.path().c_str());
347415
return shadowCopyPath;
348416
}

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class APPLICATION_INFO: NonCopyable
8080
TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options, ErrorContext& error);
8181

8282
std::filesystem::path
83-
HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext);
83+
HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext, ErrorContext& error);
8484

8585
IHttpServer &m_pServer;
8686
HandlerResolver &m_handlerResolver;

src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,23 @@ void Environment::CopyToDirectoryInner(const std::filesystem::path& source, cons
168168
auto destinationDirEntry = std::filesystem::directory_entry(destination);
169169
if (!destinationDirEntry.exists())
170170
{
171-
CreateDirectory(destination.wstring().c_str(), nullptr);
171+
auto ret = CreateDirectory(destination.wstring().c_str(), nullptr);
172+
if (!ret)
173+
{
174+
// TODO: macro this or not?
175+
std::string msg = format("Failed to create destination directory: %s (source: %s)",
176+
destination.string().c_str(),
177+
source.string().c_str());
178+
auto ex = std::system_error(GetLastError(), std::system_category(), msg);
179+
try {
180+
throw ex;
181+
}
182+
catch (...)
183+
{
184+
OBSERVE_CAUGHT_EXCEPTION();
185+
throw;
186+
}
187+
}
172188
}
173189

174190
for (auto& path : std::filesystem::directory_iterator(source))
@@ -187,13 +203,29 @@ void Environment::CopyToDirectoryInner(const std::filesystem::path& source, cons
187203
continue;
188204
}
189205
}
190-
206+
auto sourcePathString = path.path().wstring();
207+
auto destinationPathString = destinationPath.wstring();
208+
auto ret = CopyFile(sourcePathString.c_str(), destinationPathString.c_str(), FALSE);
209+
if (!ret)
210+
{
211+
std::string msg = format("Failed to copy file %s to %s",
212+
sourcePathString.c_str(),
213+
destinationPathString.c_str());
214+
auto ex = std::system_error(GetLastError(), std::system_category(), msg);
215+
try {
216+
throw ex;
217+
}
218+
catch (...)
219+
{
220+
OBSERVE_CAUGHT_EXCEPTION();
221+
throw;
222+
}
223+
}
191224
copiedFileCount++;
192-
CopyFile(path.path().wstring().c_str(), destinationPath.wstring().c_str(), FALSE);
193225
}
194226
else if (path.is_directory())
195227
{
196-
auto sourceInnerDirectory = path.path();
228+
auto& sourceInnerDirectory = path.path();
197229

198230
if (sourceInnerDirectory.wstring().rfind(directoryToIgnore, 0) != 0)
199231
{

0 commit comments

Comments
 (0)