Asynchronous File System Monitoring

.
Asynchronous file monitoring


Introduction

There are 3 main methods for monitoring the file system on a Windows machine. Kernel mode filesystem drivers, the FindFirstChangeNotification API and the ReadDirectoryChangesW API. If the main purpose of your application is not monitoring the file system or bypassing/overriding file system IO operations then Kernel mode file system drivers would be too much unnecessary work. FindFirstChangeNotification is a good alternative but it tells you that a change took place but not which file was changed or how (added, modified, removed..etc.) ReadDirectoryChangesW on the other hand, when called asynchronously, is reasonably efficient and provides detailed info on which file changed and how.

There are also 3 approaches to using ReadDirectoryChangesW asynchronously:

  • GetOverlappedResult: this requires an event object for each directory you'd like to monitor.
    .
  • Using a completion routine: this requires that you provide a completion function (completion routine) that would be called by the system whenever a change occurs.
    .
  • Using IO completion ports: in this method we associate handles of directories to be monitored with a completion port much like we associate it with sockets. This I think is more scalable/efficient than the two other methods and that's what we are implementing here.

Using the Code


#include "FileSysMon.h"

.....

	CFileSysMon myFileSysMon;
	if (!myFileSysMon.Init())
	{
		MessageBox(0, _T("Initialization Error"), NULL, MB_ICONERROR);
		return 0;
	}


	if (myFileSysMon.AddPath(_T("c:\\FolderToMonitor")) != E_FILESYSMON_SUCCESS)
		MessageBox(hWnd, _T("Error adding file to monitoring list"), NULL, MB_ICONERROR);


	VECCHANGES vecChanges;
	while (myFileSysMon.GetQueuedStatus(vecChanges, INFINITE) == E_FILESYSMON_SUCCESS) //INFINITE or a timeout value in milliseconds
	{
		//vecChanges.at(i).strFilePath
		//if ((vecChanges.at(i).dwAction & FILE_ACTION_ADDED) == FILE_ACTION_ADDED)
		//if ((vecChanges.at(i).dwAction & FILE_ACTION_REMOVED) == FILE_ACTION_REMOVED)
		//if ((vecChanges.at(i).dwAction & FILE_ACTION_MODIFIED) == FILE_ACTION_MODIFIED)
		//if ((vecChanges.at(i).dwAction & FILE_ACTION_RENAMED_OLD_NAME) == FILE_ACTION_RENAMED_OLD_NAME)
		//if ((vecChanges.at(i).dwAction & FILE_ACTION_RENAMED_NEW_NAME) == FILE_ACTION_RENAMED_NEW_NAME)

		.....
	}



How Does It Work

First we create a completion port using CreateIOCompletionPort:
	if (!(m_hIOCP = CreateIoCompletionPort(
							(HANDLE)INVALID_HANDLE_VALUE,
							NULL,
							0,
							nThreads)))
	{
		m_nLastError = GetLastError();
		return false;
	}



Then we associate each directory we would like to monitor with that completion port also using CreateIOCompletionPort:
	if (CreateIoCompletionPort(pDir->hFile, m_hIOCP, (ULONG_PTR) pDir->hFile, 0) == NULL)
	{
		m_nLastError = GetLastError();
		CloseHandle(pDir->hFile);
		delete pDir;

		return E_FILESYSMON_ERRORADDTOIOCP;
	}



And we call ReadDirectoryChangesW on the directory handle:
	if (!ReadDirectoryChangesW(pDir->hFile,
					pDir->pBuff,
					MAX_BUFF_SIZE * sizeof(FILE_NOTIFY_INFORMATION),
					bSubTree,
					dwNotifyFilters, 
					&dwBytesReturned,
					&pDir->ol,
					NULL))
	{
		m_nLastError = GetLastError();
		CloseHandle(pDir->hFile);
		delete pDir;
	
		return E_FILESYSMON_ERRORREADDIR;
	}




Then we query the completion port for status messages:
	if (!GetQueuedCompletionStatus(m_hIOCP, &dwBytesXFered, &ulKey, &pOl, dwTimeOut))
	{
		if ((m_nLastError = GetLastError()) == WAIT_TIMEOUT)
			return E_FILESYSMON_NOCHANGE;
		return E_FILESYSMON_ERRORDEQUE;
	}


History

13 JAN 11: first release.

.
.

Comments are closed on this post.