У меня есть приложение, которое я использую для отслеживания изменений файлов / каталогов в каталоге. Тем не менее, этот каталог также содержит symlinks
в другие каталоги (которые доступны). Тем не менее, когда я изменяю file
в пределах symlink
каталог, уведомление не срабатывает. Например. когда я контролирую Root
:
--Root
--Dir
--File1 //Trigger works
--SDir(Symlink dir)
--File2 //Trigger won't work
Тем не менее, когда я контролирую Root/SDir
, который является каталогом символической ссылки, чем триггер на File2 работает правильно. Например.:
--SDir
--File2 //Trigger works
Итак, когда symlink directory
не является корнем, он не будет вызывать изменения файлов в этом каталоге. Тем не менее, когда я установил symlink directory
как рут работает нормально. И да, bWatchSubtree
параметр в пределах ReadDirectoryChangesW-function
установлен в true
,
Для хорошего порядка я открываю дескриптор каталога, используя CreateFile
функция:
CreateFile(
Dir, //Root or Root/SDir in this example
FILE_LIST_DIRECTORY,
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
Итак, почему триггер не будет работать с файлами внутри символических ссылок, если эта же символическая ссылка не является корнем ReadDirectoryChangesW
?
Вот один из способов настроить отслеживание всего дерева … Это исключения из моей базы кода, я не могу поместить весь код здесь из-за зависимостей …
Использование:
FolderWatchInfo fwi;
fwi.Create(strRootPath, TRUE,
FolderWatchInfo::flagDirWatch | FolderWatchInfo::flagFileWatch);
WaitForSingleObject(fwi.GetHandle(), INFINITE); // or the usual WaitForMultipleObjects(...)
// with an exit event
//...
//On event signaled:
CStringsVector vFolders;
fwi.GetSubDirChangeList(vFolders);
Sleep(1000); // give a little time for things to settle...
for each (const CIRString& str in vFolders)
{
OnFolderChange(str); // signal my app
}
События сбрасываются автоматически, поэтому никакого обслуживания не требуется.
Вещи, которых там нет:
CIRSingleLock похож на lock_guard
DirScanner является оболочкой для FindFirstFile (), он сообщает о папках в структуру Reader.
включаемый файл:
// *********************************************************************************************************************************
// *********************************************************************************************************************************
#pragma once
#include <DirScanner.h>
#include <shared_ptr.h>
#include <boost/weak_ptr.hpp>
#include <vector>
#include <IRSingleLock.h>
// *********************************************************************************************************************************
// *********************************************************************************************************************************
class FolderWatchInfo
{
// --------------------------------------------------------------------------------------------------------------------------------
public:
enum Flags
{
flagFileCreateDelete = FILE_NOTIFY_CHANGE_FILE_NAME,
flagFileModify = FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE,
flagFileSecurity = FILE_NOTIFY_CHANGE_SECURITY,
flagFileAttributes = FILE_NOTIFY_CHANGE_ATTRIBUTES,
flagDirCreateDetelete = FILE_NOTIFY_CHANGE_DIR_NAME,
flagFileWatch = flagFileCreateDelete | flagFileModify,
flagDirWatch = flagDirCreateDetelete,
};
// --------------------------------------------------------------------------------------------------------------------------------
typedef shared_ptr<FolderWatchInfo> FolderWatchInfoPtr;
typedef std::vector<FolderWatchInfoPtr> FWIVector;
typedef std::vector<CIRString> CStringsVector;
// --------------------------------------------------------------------------------------------------------------------------------
private:
struct Reader : public DirScanner::Reader
{
FolderWatchInfo& fwi;
Reader(FolderWatchInfo& fwi_)
: DirScanner::Reader(fwi_.GetPathName(), _T("__NOFILES__"), 1)
, fwi(fwi_)
{ }
virtual bool OnDirectoryFound(LPCTSTR szPathName, const WIN32_FIND_DATA& findData)
{
FolderWatchInfoPtr p = make_shared<FolderWatchInfo>();
p->Create(fwi.GetPathName() + _T("\\") + szPathName, fwi.GetFlags());
fwi.m_vChildren.push_back(p);
return TRUE;
}
};
friend struct Reader;
private:
CIRCriticalSection m_lock;
weak_ptr<FolderWatchInfo> m_pParent;
CIRString m_strPathName;
HANDLE m_hWatchEvent;
DWORD m_dwFlags;
FWIVector m_vChildren;
// --------------------------------------------------------------------------------------------------------------------------------
private:
inline FolderWatchInfo(const FolderWatchInfo&) {}
inline bool operator = (const FolderWatchInfo&) {}
// --------------------------------------------------------------------------------------------------------------------------------
public:
FolderWatchInfo();
~FolderWatchInfo();
// --------------------------------------------------------------------------------------------------------------------------------
HANDLE Create(const CIRString& strPathName, BOOL bRecursive, DWORD dwFlags);
void CloseHandle();
void ResetEvent();
protected:
HANDLE Create(const CIRString& strPathName, DWORD dwFlags);
void AddToSubDirChangeList(CStringsVector& vSubDirs);
// --------------------------------------------------------------------------------------------------------------------------------
public:
inline HANDLE GetHandle() const
{
return m_hWatchEvent;
}
// --------------------------------------------------------------------------------------------------------------------------------
inline void GetSubDirChangeList(CStringsVector& vSubDirs)
{
CIRSingleLock lock(m_lock);
vSubDirs.clear();
AddToSubDirChangeList(vSubDirs);
}
// --------------------------------------------------------------------------------------------------------------------------------
inline FolderWatchInfoPtr GetParent() const
{
return FolderWatchInfoPtr(m_pParent, boost::detail::sp_nothrow_tag());
}
// --------------------------------------------------------------------------------------------------------------------------------
inline const FWIVector& GetChildren() const
{
return m_vChildren;
}
// --------------------------------------------------------------------------------------------------------------------------------
inline const CIRString& GetPathName() const
{
return m_strPathName;
}
// --------------------------------------------------------------------------------------------------------------------------------
inline DWORD GetFlags() const
{
return m_dwFlags;
}
// --------------------------------------------------------------------------------------------------------------------------------
inline bool operator == (LPCTSTR szPath)
{
return (m_strPathName.CompareNoCase(szPath) == 0);
}
};
// ***************************************************************************************************************************** EOF
каст.
// *********************************************************************************************************************************
// *********************************************************************************************************************************
#include "StdAfx.h"#include "FolderWatchInfo.h"
// *********************************************************************************************************************************
FolderWatchInfo::FolderWatchInfo()
: m_hWatchEvent(INVALID_HANDLE_VALUE)
, m_dwFlags(0)
{ }
// *********************************************************************************************************************************
FolderWatchInfo::~FolderWatchInfo(void)
{
CIRSingleLock lock(m_lock);
if (m_hWatchEvent != INVALID_HANDLE_VALUE)
{
FindCloseChangeNotification(m_hWatchEvent);
}
}
// *********************************************************************************************************************************
HANDLE FolderWatchInfo::Create(const CIRString& strPathName, BOOL bRecursive, DWORD dwFlags)
{
CIRSingleLock lock(m_lock);
ASSERT(m_hWatchEvent == INVALID_HANDLE_VALUE);
m_strPathName = CleanPathName(strPathName);
m_dwFlags = dwFlags;
m_hWatchEvent = FindFirstChangeNotification(m_strPathName, bRecursive, dwFlags);
if (bRecursive)
{
DirScanner().ScanFolder(Reader(*this));
}
return m_hWatchEvent;
}
// *********************************************************************************************************************************
HANDLE FolderWatchInfo::Create(const CIRString& strPathName, DWORD dwFlags)
{
CIRSingleLock lock(m_lock);
ASSERT(m_hWatchEvent == INVALID_HANDLE_VALUE);
m_strPathName = CleanPathName(strPathName);
m_dwFlags = dwFlags;
m_hWatchEvent = FindFirstChangeNotification(m_strPathName, FALSE, dwFlags);
DirScanner().ScanFolder(Reader(*this));
return m_hWatchEvent;
}
// *********************************************************************************************************************************
void FolderWatchInfo::CloseHandle()
{
CIRSingleLock lock(m_lock);
if (m_hWatchEvent != INVALID_HANDLE_VALUE)
{
FindCloseChangeNotification(m_hWatchEvent);
m_hWatchEvent = INVALID_HANDLE_VALUE;
}
}
// *********************************************************************************************************************************
void FolderWatchInfo::ResetEvent()
{
if (m_hWatchEvent != INVALID_HANDLE_VALUE)
{
while (WAIT_OBJECT_0 == WaitForSingleObject(m_hWatchEvent, 0))
{
if (!FindNextChangeNotification(m_hWatchEvent))
{
CloseHandle();
break;
}
}
}
}
// *********************************************************************************************************************************
void FolderWatchInfo::AddToSubDirChangeList(CStringsVector& vSubDirs)
{
CIRSingleLock lock(m_lock);
if (WAIT_OBJECT_0 == WaitForSingleObject(m_hWatchEvent, 0))
{
ResetEvent();
vSubDirs.push_back(m_strPathName);
}
for each (const FolderWatchInfoPtr& p in m_vChildren)
{
p->AddToSubDirChangeList(vSubDirs);
}
}
// ***************************************************************************************************************************** EOF
Файловая система отправляет вам уведомление только тогда, когда файлы изменены внутри папки. символическая ссылка (или точка монтирования) — это только файл внутри папки, который указывает на другой файл / папку. если целевой файл / каталог не находится в нашей исходной папке — мы и не получим уведомление об изменениях внутри этой целевой папки
когда вы открываете прямую символическую ссылку (или точку монтирования) без FILE_FLAG_OPEN_REPARSE_POINT
вы действительно открываете целевой каталог (на который указывает эта символическая ссылка) и получаете уведомление из этого каталога
Для получения уведомления из какой-то папки обычно нужно сделать следующее:
FILE_FLAG_OVERLAPPED
для процессаReadDirectoryChangesW
асинхронныйBindIoCompletionCallback
для открытой ручки — как результатReadDirectoryChangesW
BindIoCompletionCallback
) пока мы не добралисьERROR_NOTIFY_CLEANUP
ERROR_NOTIFY_CLEANUP
с этим классом мы можем иметь любое количество шпионов каталога — нужно просто создать N экземпляров этого класса. ничего не нужно ждать, петли и т. д. просто продолжайте делать свою задачу. когда вам больше не нужно уведомление из каталога — закройте его и обработайте структуру
простейшая реализация:
class SPYDATA : public OVERLAPPED, RUNDOWN_REF
{
HANDLE m_hFile;
DWORD _dwNotifyFilter;
LONG _dwRef;
UCHAR _buf[PAGE_SIZE];
~SPYDATA()
{
Close();
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};
buf = _buf;
for (;;)
{
DbgPrint("[%03x] %x <%.*S>\n", _dwNotifyFilter, pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);
ULONG NextEntryOffset = pfni->NextEntryOffset;
if (!NextEntryOffset)
{
break;
}
pb += NextEntryOffset;
}
}
void IOCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
{
switch (dwErrorCode)
{
case ERROR_NOTIFY_CLEANUP:
DbgPrint("%p>[%x] ---- NOTIFY_CLEANUP -----\n", this, _dwNotifyFilter);
return ;
case NOERROR:
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
DoRead();
return;
}
DbgPrint("%p>[%x] error=%x\n", this, _dwNotifyFilter, dwErrorCode);
}
void IOCompletionRoutineAndRelease(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
{
IOCompletionRoutine(dwErrorCode, dwNumberOfBytesTransfered);
Release();
}
static VOID CALLBACK _IOCompletionRoutine(
__in DWORD status,
__in DWORD dwNumberOfBytesTransfered,
__in LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->IOCompletionRoutineAndRelease(RtlNtStatusToDosError(status), dwNumberOfBytesTransfered);
}
virtual void RundownCompleted()
{
if (m_hFile) CloseHandle(m_hFile);
}
public:
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
SPYDATA(DWORD dwNotifyFilter) : _dwNotifyFilter(dwNotifyFilter)
{
_dwRef = 1;
m_hFile = 0;
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
void DoRead()
{
if (AcquireRundownProtection())
{
AddRef();
ULONG dwErrorCode = ReadDirectoryChangesW(m_hFile, _buf, sizeof(_buf),
TRUE, _dwNotifyFilter, NULL, this, NULL) ? NOERROR : GetLastError();
ReleaseRundownProtection();
switch (dwErrorCode)
{
case NOERROR:
case ERROR_IO_PENDING:
break;
default:
IOCompletionRoutineAndRelease(dwErrorCode, 0);
}
}
}
ULONG Open(PCWSTR lpFileName )
{
HANDLE hFile = CreateFile(lpFileName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
if (BindIoCompletionCallback(hFile, _IOCompletionRoutine, 0))
{
m_hFile = hFile;
return NOERROR;
}
CloseHandle(hFile);
}
return GetLastError();
}
void Close()
{
BeginRundown();
}
};
BOOL CreateSpy(DWORD dwNotifyFilter, PCWSTR lpFileName, SPYDATA** pp)
{
if (SPYDATA* p = new SPYDATA(dwNotifyFilter))
{
ULONG dwError = p->Open(lpFileName);
if (!dwError)
{
*pp = p;
p->DoRead();
return NOERROR;
}
p->Release();
return dwError;
}
return ERROR_NO_SYSTEM_RESOURCES;
}
void DestroySpyData(SPYDATA* p)
{
if (p)
{
p->Close();
p->Release();
}
}
и использовать его как:
SPYDATA* p;
if (!CreateSpy(FILE_NOTIFY_VALID_MASK, L"<some path>", &p))
{
MessageBoxW(0,0,0,0);// really here any code
DestroySpyData(p);
}
моя реализация Защищенная защита (это не реализовано API в пользовательском режиме)
#define RUNDOWN_INIT_VALUE 0x80000000
#define RUNDOWN_COMPLETE_VALUE 0
#define ObpBeginRundown(p) _interlockedbittestandreset(p, 31)
#define ObpUnlock _InterlockedDecrement
__forceinline BOOL ObpLock(PLONG pLock)
{
LONG Value = *pLock, NewValue;
for ( ; Value; Value = NewValue)
{
NewValue = _InterlockedCompareExchange(pLock, Value + 1, Value);
if (NewValue == Value) return TRUE;
}
return FALSE;
}
__forceinline BOOL ObpAcquireRundownProtection(PLONG pLock)
{
LONG Value = *pLock, NewValue;
for ( ; Value < 0; Value = NewValue)
{
NewValue = _InterlockedCompareExchange(pLock, Value + 1, Value);
if (NewValue == Value) return TRUE;
}
return FALSE;
}
class __declspec(novtable) RUNDOWN_REF
{
LONG _LockCount;
protected:
virtual void RundownCompleted() = 0;
public:
BOOL IsRundownBegin()
{
return 0 <= _LockCount;
}
__forceinline void Reinit()
{
if (InterlockedCompareExchange(&_LockCount, RUNDOWN_INIT_VALUE, RUNDOWN_COMPLETE_VALUE) != RUNDOWN_COMPLETE_VALUE)
{
__debugbreak();
}
}
__forceinline RUNDOWN_REF()
{
_LockCount = RUNDOWN_INIT_VALUE;
}
__forceinline BOOL AcquireRundownProtection()
{
return ObpAcquireRundownProtection(&_LockCount);
}
__forceinline void ReleaseRundownProtection()
{
if (!_InterlockedDecrement(&_LockCount))
{
RundownCompleted();
}
}
void BeginRundown()
{
if (AcquireRundownProtection())
{
ObpBeginRundown(&_LockCount);
ReleaseRundownProtection();
}
}
};