aspnetcore/src/AspNetCore/Src/filewatcher.cxx

456 lines
10 KiB
C++

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "precomp.hxx"
FILE_WATCHER::FILE_WATCHER() :
m_hCompletionPort(NULL),
m_hChangeNotificationThread(NULL)
{
}
FILE_WATCHER::~FILE_WATCHER()
{
if (m_hChangeNotificationThread != NULL)
{
CloseHandle(m_hChangeNotificationThread);
m_hChangeNotificationThread = NULL;
}
}
HRESULT
FILE_WATCHER::Create(
VOID
)
{
HRESULT hr = S_OK;
m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
0);
if (m_hCompletionPort == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
m_hChangeNotificationThread = CreateThread(NULL,
0,
ChangeNotificationThread,
this,
0,
NULL);
if (m_hChangeNotificationThread == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(m_hCompletionPort);
m_hCompletionPort = NULL;
goto Finished;
}
Finished:
return hr;
}
DWORD
WINAPI
FILE_WATCHER::ChangeNotificationThread(
LPVOID pvArg
)
/*++
Routine Description:
IO completion thread
Arguments:
None
Return Value:
Win32 error
--*/
{
FILE_WATCHER * pFileMonitor;
BOOL fSuccess = FALSE;
DWORD cbCompletion = 0;
OVERLAPPED * pOverlapped = NULL;
DWORD dwErrorStatus;
ULONG_PTR completionKey;
pFileMonitor = (FILE_WATCHER*)pvArg;
DBG_ASSERT(pFileMonitor != NULL);
while (TRUE)
{
fSuccess = GetQueuedCompletionStatus(
pFileMonitor->m_hCompletionPort,
&cbCompletion,
&completionKey,
&pOverlapped,
INFINITE);
DBG_ASSERT(fSuccess);
DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread");
dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError();
if (completionKey == FILE_WATCHER_SHUTDOWN_KEY)
{
continue;
}
DBG_ASSERT(pOverlapped != NULL);
if (pOverlapped != NULL)
{
FileWatcherCompletionRoutine(
dwErrorStatus,
cbCompletion,
pOverlapped);
}
pOverlapped = NULL;
cbCompletion = 0;
}
}
VOID
WINAPI
FILE_WATCHER::FileWatcherCompletionRoutine(
DWORD dwCompletionStatus,
DWORD cbCompletion,
OVERLAPPED * pOverlapped
)
/*++
Routine Description:
Called when ReadDirectoryChangesW() completes
Arguments:
dwCompletionStatus - Error of completion
cbCompletion - Bytes of completion
pOverlapped - State of completion
Return Value:
None
--*/
{
FILE_WATCHER_ENTRY * pMonitorEntry;
pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped);
pMonitorEntry->DereferenceFileWatcherEntry();
DBG_ASSERT(pMonitorEntry != NULL);
pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion);
if (pMonitorEntry->QueryIsValid())
{
//
// Continue monitoring
//
pMonitorEntry->Monitor();
}
else
{
//
// Marked by application distructor
// Deference the entry to delete it
//
pMonitorEntry->DereferenceFileWatcherEntry();
}
}
FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) :
_pFileMonitor(pFileMonitor),
_hDirectory(INVALID_HANDLE_VALUE),
_hImpersonationToken(NULL),
_pApplication(NULL),
_lStopMonitorCalled(0),
_cRefs(1),
_fIsValid(TRUE)
{
_dwSignature = FILE_WATCHER_ENTRY_SIGNATURE;
InitializeSRWLock(&_srwLock);
}
FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY()
{
_dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE;
if (_hDirectory != INVALID_HANDLE_VALUE)
{
CloseHandle(_hDirectory);
_hDirectory = INVALID_HANDLE_VALUE;
}
if (_hImpersonationToken != NULL)
{
CloseHandle(_hImpersonationToken);
_hImpersonationToken = NULL;
}
}
#pragma warning(disable:4100)
HRESULT
FILE_WATCHER_ENTRY::HandleChangeCompletion(
_In_ DWORD dwCompletionStatus,
_In_ DWORD cbCompletion
)
/*++
Routine Description:
Handle change notification (see if any of associated config files
need to be flushed)
Arguments:
dwCompletionStatus - Completion status
cbCompletion - Bytes of completion
Return Value:
HRESULT
--*/
{
HRESULT hr = S_OK;
FILE_NOTIFY_INFORMATION * pNotificationInfo;
BOOL fFileChanged = FALSE;
AcquireSRWLockExclusive(&_srwLock);
if (!_fIsValid)
{
goto Finished;
}
// When directory handle is closed then HandleChangeCompletion
// happens with cbCompletion = 0 and dwCompletionStatus = 0
// From documentation it is not clear if that combination
// of return values is specific to closing handles or whether
// it could also mean an error condition. Hence we will maintain
// explicit flag that will help us determine if entry
// is being shutdown (StopMonitor() called)
//
if (_lStopMonitorCalled)
{
goto Finished;
}
//
// There could be a FCN overflow
// Let assume the file got changed instead of checking files
// Othersie we have to cache the file info
//
if (cbCompletion == 0)
{
fFileChanged = TRUE;
}
else
{
pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr();
DBG_ASSERT(pNotificationInfo != NULL);
while (pNotificationInfo != NULL)
{
//
// check whether the monitored file got changed
//
if (wcsncmp(pNotificationInfo->FileName,
_strFileName.QueryStr(),
pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0)
{
fFileChanged = TRUE;
break;
}
//
// Advance to next notification
//
if (pNotificationInfo->NextEntryOffset == 0)
{
pNotificationInfo = NULL;
}
else
{
pNotificationInfo = (FILE_NOTIFY_INFORMATION*)
((PBYTE)pNotificationInfo +
pNotificationInfo->NextEntryOffset);
}
}
}
if (fFileChanged)
{
//
// so far we only monitoring app_offline
//
_pApplication->UpdateAppOfflineFileHandle();
}
Finished:
ReleaseSRWLockExclusive(&_srwLock);
return hr;
}
#pragma warning( error : 4100 )
HRESULT
FILE_WATCHER_ENTRY::Monitor(VOID)
{
HRESULT hr = S_OK;
DWORD cbRead;
AcquireSRWLockExclusive(&_srwLock);
ReferenceFileWatcherEntry();
ZeroMemory(&_overlapped, sizeof(_overlapped));
if(!ReadDirectoryChangesW(_hDirectory,
_buffDirectoryChanges.QueryPtr(),
_buffDirectoryChanges.QuerySize(),
FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline
FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS & ~FILE_NOTIFY_CHANGE_ATTRIBUTES,
&cbRead,
&_overlapped,
NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
ReleaseSRWLockExclusive(&_srwLock);
return hr;
}
VOID
FILE_WATCHER_ENTRY::StopMonitor(VOID)
{
//
// Flag that monitoring is being stopped so that
// we know that HandleChangeCompletion() call
// can be ignored
//
InterlockedExchange(&_lStopMonitorCalled, 1);
AcquireSRWLockExclusive(&_srwLock);
if (_hDirectory != INVALID_HANDLE_VALUE)
{
CloseHandle(_hDirectory);
_hDirectory = INVALID_HANDLE_VALUE;
}
ReleaseSRWLockExclusive(&_srwLock);
}
HRESULT
FILE_WATCHER_ENTRY::Create(
_In_ PCWSTR pszDirectoryToMonitor,
_In_ PCWSTR pszFileNameToMonitor,
_In_ APPLICATION* pApplication,
_In_ HANDLE hImpersonationToken
)
{
HRESULT hr = S_OK;
BOOL fRet = FALSE;
if (pszDirectoryToMonitor == NULL ||
pszFileNameToMonitor == NULL ||
pApplication == NULL)
{
DBG_ASSERT(FALSE);
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Finished;
}
//
//remember the application
//
_pApplication = pApplication;
if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor)))
{
goto Finished;
}
if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor)))
{
goto Finished;
}
//
// Resize change buffer to something "reasonable"
//
if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE))
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto Finished;
}
if (hImpersonationToken != NULL)
{
fRet = DuplicateHandle(GetCurrentProcess(),
hImpersonationToken,
GetCurrentProcess(),
&_hImpersonationToken,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!fRet)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
}
else
{
if (_hImpersonationToken != NULL)
{
CloseHandle(_hImpersonationToken);
_hImpersonationToken = NULL;
}
}
_hDirectory = CreateFileW(
_strDirectoryName.QueryStr(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (_hDirectory == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
if (CreateIoCompletionPort(
_hDirectory,
_pFileMonitor->QueryCompletionPort(),
NULL,
0) == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
//
// Start monitoring
//
hr = Monitor();
Finished:
return hr;
}