456 lines
10 KiB
C++
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;
|
|
} |