0

I am trying to monitor different directories for changes using ReadDirectoryChangesW and I succeed in monitoring a single directory with it. But when it comes to monitoring multiple directories, I am having problems that the first directory is assigned to the handle and it looks for changes only in that directory and then only goes to watch the next directory if the first directory condition is satisfied. But I want to watch multiple directories simultaneously. Can I help me with this problem?

I am attaching the code below for the reference.

#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <vector>
#include <fstream>
#include <mutex>
#include "sys/stat.h"
#include <dirent.h>

using namespace std;

mutex m1;

int main()
{
    HANDLE hDir[20];
    vector <string> directory_list;
    string f_path;
    string temp1;
    ifstream pfile;

    pfile.open("C:\\Users\\sathish-pt1608\\Desktop\\path_file.txt");

    if (!pfile.is_open())
        cout << "Unable to open the Requested File...";

    while(pfile.good())
    {
        getline(pfile,f_path);
        struct stat path_stat;
        stat(f_path.c_str(),&path_stat);

        if (S_ISREG(path_stat.st_mode))
        {
            int a = f_path.find_last_of('\\');
            directory_list.push_back(f_path.substr(0, a - 1));
        }
        else if(S_ISDIR(path_stat.st_mode))
        {
            DIR *dir;
            struct dirent *ent;

            dir = opendir(f_path.c_str());

            if (dir != NULL)
            {
                directory_list.push_back(f_path);
                while ( (ent = readdir(dir)) != NULL)
                {
                    temp1 = ent->d_name;
                }
                closedir(dir);
            }
            else
            {
                perror("Please Check the Directory Path...");
                EXIT_FAILURE;
            }
        }
    }

    for (int i = 0; i < directory_list.size();)
    {
        LPCTSTR DirName = directory_list[i].c_str();

        hDir[i] = CreateFile(DirName, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

        FILE_NOTIFY_INFORMATION Buffer[1024];
        DWORD BytesReturned;

        if (!ReadDirectoryChangesW(hDir[i], &Buffer, sizeof(Buffer), TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,  &BytesReturned, NULL, NULL))
        {
            cout << DirName << endl;
        }
        else
            cout << "THE DIRECTORY HAS BEEN MODIFIED..." << endl;

        if (i == directory_list.size() - 1)
            i = 0;
        else
            i++;
    }
}
Blacktempel
  • 3,935
  • 3
  • 29
  • 53
  • *But I want to watch multiple directories simultaneously.* -- Learning multithreaded programming is way beyond the scope of a simple answer. – PaulMcKenzie Dec 06 '17 at 05:57
  • Even if i use multithreaded programming I do not want create threads for each directory.. It will decrease the performance of the system for sure.. So i'm creating a constant no. of threads and checking the multiple directories by equalling distributing them among the threads.. – sathish irfan Dec 06 '17 at 07:22
  • @paul why would that require multiple threads? – sashoalm Dec 06 '17 at 07:48
  • 2
    https://stackoverflow.com/a/47510202/6401656 – RbMm Dec 06 '17 at 08:29
  • [Understanding ReadDirectoryChangesW](https://qualapps.blogspot.de/2010/05/understanding-readdirectorychangesw.html) gives a good overview and also has sample code for tracking changes of big number of directories using completion ports. – zett42 Dec 06 '17 at 09:21
  • @zett42, no, that article describes a solution that uses a completion routine and queued APCs with a single worker thread. See the above answer from RbMm for an example that uses a completion port and thread pool via `BindIoCompletionCallback`. – Eryk Sun Dec 06 '17 at 21:03

2 Answers2

1

You can use windows feature called journals to detect deleted/changed/created files/folders on a drive. You need to open the drive using CreateFile() then use DeviceIoControl() to read the changed buffer from time to time. You will get file details like size, name, attributes etc. Files are considered as records and record size is obtained using the buffer returned by DeviceIoControl() and bytecount.

Journals in windows are created to find changes in a volume in a fast/easier way since multiple changes can happen from time to time. You can use the parameter FSCTL_QUERY_USN_JOURNAL to create the journal and FSCTL_ENUM_USN_DATA to find the required records.

To know more about journals you need to refer msdn link https://msdn.microsoft.com/en-us/library/windows/desktop/aa363798(v=vs.85).aspx

Dummy source code,

    ///C drive
    HANDLE drive;
    drive = CreateFile(L"\\\\?\\c:", GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);
    if (INVALID_HANDLE_VALUE == drive)
    {
        printf("CreateFile: %u\n", GetLastError());
        return 0;
    }

    if (!DeviceIoControl(drive, FSCTL_QUERY_USN_JOURNAL, NULL, 0, buffer, BUFFER_SIZE, &bytecount, NULL))
    {
        printf("FSCTL_QUERY_USN_JOURNAL: %u\n", GetLastError());
        return 0;
    }

    USN_JOURNAL_DATA* journal = (USN_JOURNAL_DATA *)buffer;
    MFT_ENUM_DATA mft_enum_data = {0};
    mft_enum_data.StartFileReferenceNumber = 0;
    mft_enum_data.LowUsn = 0;
    mft_enum_data.HighUsn = journal->MaxUsn;

    for(;;)
    {
        if (!DeviceIoControl(drive, FSCTL_ENUM_USN_DATA, &mft_enum_data, sizeof(mft_enum_data), buffer, BUFFER_SIZE, &bytecount, NULL))
        {
            printf("FSCTL_ENUM_USN_DATA: %u\n", GetLastError());
            return 0;
        }

        USN_RECORD* record;
        USN_RECORD* recordend;
        record = (USN_RECORD *)((USN *)buffer + 1);
        recordend = (USN_RECORD *)(((BYTE *)buffer) + bytecount);

        DWORDLONG filecount = 0;
        while (record < recordend)
        {
            filecount++;

            printf("FileAttributes: %x\n", record->FileAttributes);
            printf("FileNameLength: %u\n", (DWORD)record->FileNameLength);
            printf("RecordLength: %u\n", record->RecordLength);

            WCHAR * filename;
            WCHAR * filenameend;
            filename = (WCHAR *)(((BYTE *)record) + record->FileNameOffset);
            filenameend= (WCHAR *)(((BYTE *)record) + record->FileNameOffset + record->FileNameLength);

            printf("FileName: %s\n", filename);

            record = (USN_RECORD *)(((BYTE *)record) + record->RecordLength);
        }

        mft_enum_data.StartFileReferenceNumber = *((DWORDLONG *)buffer);
    }
  • I have gone through the link specified already.. I am unable to understand clearly the working when a particular directory is mentioned in the `createFile()` you have mentioned.. It gives unexpected results.. – sathish irfan Dec 06 '17 at 07:30
  • The sample code gives you details about files in a drive C. You can try for any drive in your pc. –  Dec 06 '17 at 09:20
  • It fails in the `CreateFile()` statement itself and does not return any attributes.. is the platform on which i am working a problem.? – sathish irfan Dec 06 '17 at 09:37
  • Can you check the GetLastError() and let me know the issue? I think it might need higher level privilege to execute CreateFile() –  Dec 06 '17 at 10:33
  • GetLastError() returns `5` – sathish irfan Dec 06 '17 at 10:44
  • You can check the system error codes in internet, 5 means access is denied. Run your application with administrator privilege. If your running from visual studio, then go to property pages/linker/Manifest file, change UAC execution level as highestAvailable. Similarly if you encounter any errors use getlasterror(), check system error codes. –  Dec 06 '17 at 10:48
  • Well the case i mentioned is for visual studio, there will be option in dev c++ IDE to debug the application by raising UAC level. You need to check that. –  Dec 06 '17 at 10:55
  • @Surjith k Varghese I have installed visual studio and run the same but here i get a message `The Project1(Project Name) has stopped Working`.. – sathish irfan Dec 06 '17 at 13:16
  • is there can any other IDE i can use in executing this code? – sathish irfan Dec 11 '17 at 05:51
0

You can but you will need to perform it as an asynchronous operation, either with only the LPOVERLAPPED parameter or with both it and the completion routine. And as the docs say if you go the latter route you will need to be in an alertable wait.

SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23