1

Good evening!

I need to get information about new files in certain directory (Windows 10, C++).

To do this:

  1. I call ReadDirectoryChangesW in overlapped mode and point an event to be signaled when ReadDirectoryChangesW will finish
  2. wait until event will be signalled (WaitForMultipleObjects)
  3. call GetOverlappedResult to get ReadDirectoryChangesW info about new files.

All actions above are repeated in a loop.

It works well, but if I create two or more files simultaneously (copy several files and paste them in the Windows Explorer), then in the step (3) I get info about one file only.

But if I rename a file, then I correctly get from ReadDirectoryChangesW two records – about old and new file names.

Buffer (FILE_NOTIFY_INFORMATION) for ReadDirectoryChangesW likely does not overflow – I allocate 10 kb, information about new files is about 50-200 bytes (their paths and names are short enough).

If I do copy/paste 1 file, then 1 file etc, than each copying is reported normally.

It seems ReadDirectoryChangesW/GetOverlappedResult return info about 1 file only if new files appear "fast" (copy/paste several files), but how can I get info about other files ?

Here is my code:

#include <iostream>
 
 
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>
 
#include <assert.h>
 
 
void RefreshDirectory(LPTSTR, HANDLE, DWORD);
void RefreshTree(LPTSTR, HANDLE, DWORD);
void WatchDirectory(LPTSTR);
 
int _tmain(int argc, TCHAR* argv[])
{
 
    if (argc != 2)
    {
        _tprintf(TEXT("Usage: %s <dir>\n"), argv[0]);
        return 0;
    }
 
    WatchDirectory(argv[1]);
}
 
/*#define FILE_ACTION_ADDED                   0x00000001   
#define FILE_ACTION_REMOVED                 0x00000002   
#define FILE_ACTION_MODIFIED                0x00000003   
#define FILE_ACTION_RENAMED_OLD_NAME        0x00000004   
#define FILE_ACTION_RENAMED_NEW_NAME        0x00000005 */ 

const WCHAR * ActionText[] = { L"-", L"file added", L"file removed", L"file modified", L"file ranamed (old)", L"file renamed (new)" };
#define MIN_ACTION_CODE 1
#define MAX_ACTION_CODE 5
 
void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength) {
 
 
    //ActionText[0] = L"-";
 
    FILE_NOTIFY_INFORMATION* fi = (FILE_NOTIFY_INFORMATION*)FileInfoRecords;
 
    if (FileInfoLength == 0) {
        std::wcout << L"No file info!" << std::endl;
        return;
    }
 
    int RecNum = 1;
    DWORD OffsetToNext = 0;
 
    do {
        // next record
        fi = (FILE_NOTIFY_INFORMATION*)(((char*)fi) + OffsetToNext);
 
        std::wstring wfname;
        std::wstring wActionName;
 
        if ((fi->Action < MIN_ACTION_CODE) || (fi->Action > MAX_ACTION_CODE))
            wActionName = L"Unknown code";
        else
            wActionName = ActionText[fi->Action];
 
        int slen = fi->FileNameLength / sizeof(WCHAR);
        wfname.assign(fi->FileName, slen);
 
        // output information about files changes
        std::wcout << L"Rec " << RecNum << L": Action = " << wActionName << L"(" << fi->Action << L")  Offset = " << fi->NextEntryOffset <<
            L"     File = " << wfname << std::endl;
       
 
        OffsetToNext = fi->NextEntryOffset;
 
        assert(RecNum < 50);
 
        RecNum++;
    } while (OffsetToNext > 0);
 
}
 
 
void WatchDirectory(LPTSTR lpDir)
{
    DWORD dwWaitStatus;
    HANDLE dwChangeHandles[2];
    TCHAR lpDrive[4];
    TCHAR lpFile[_MAX_FNAME];
    TCHAR lpExt[_MAX_EXT];
 
    std::wcout << L"Watchng for: " << lpDir << std::endl;
 
    //  split the path from parameters
    _tsplitpath_s(lpDir, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);
 
    lpDrive[2] = (TCHAR)'\\';
    lpDrive[3] = (TCHAR)'\0';
 
    int EventsNumber = 1;
 
    // flags for ReadDirectoryChangesW
    DWORD Flags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME;
 
    // create a handle for a directory to look for
    HANDLE hDir = CreateFile(lpDir, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
 
    if (hDir == INVALID_HANDLE_VALUE) {
        
        DWORD err = GetLastError();
        std::wcout << L"ERROR: CreateFile(folder to trace) function failed = " << err << std::endl;
        ExitProcess(err);
    }
 
 
     // --- initialyze data for ReadDirectoryChangesW ---
    DWORD nBufferLength = 10000;
    LPVOID lpBuffer = malloc(nBufferLength);
    BOOL bWatchSubtree = TRUE;
    DWORD BytesReturned = 0;
 
    // --- create an event for "Overlapped" ---
    HANDLE hEvent = CreateEvent(NULL, FALSE /*manual reset = true*/, FALSE /* initial state*/, NULL);
    if (hEvent == NULL)
    {
        printf("\n Cannot create event.\n");
        ExitProcess(GetLastError());
    }
 
    bool first = true;
 
    while (TRUE)
    {
        // Wait for notification.
        // =============================================================
        OVERLAPPED Overlapped;
        Overlapped.hEvent = hEvent;
        LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine = NULL;
        // request info about changes in overlapped-mode
        BOOL res_rdc = ReadDirectoryChangesW(hDir,
            lpBuffer,
            nBufferLength,
            bWatchSubtree,
            Flags,
            &BytesReturned,
            &Overlapped,
            lpCompletionRoutine);
 
        bool ok_rdc = (res_rdc != 0);
        if (ok_rdc) {
            if (first)
                printf("\nWaiting for notification...\n");
 
            // wait for overlapped-function
            dwChangeHandles[0] = Overlapped.hEvent;
            dwWaitStatus = WaitForMultipleObjects(EventsNumber, dwChangeHandles,
                FALSE, 3000);
 
            switch (dwWaitStatus) {
 
                case WAIT_OBJECT_0: {
                    printf("\n WAIT_OBJECT_0 .\n");
 
                    DWORD NumberOfBytesTransferred = 0;
                    BOOL ok_gor = GetOverlappedResult(hDir, &Overlapped, &NumberOfBytesTransferred, FALSE);
 
                    if (ok_gor == 0) {
                        // 
                        DWORD err = GetLastError();
                        if (err == ERROR_IO_INCOMPLETE)
                            std::wcout << L"Err (GetOverlappedResult) = ERROR_IO_INCOMPLETE" << std::endl;
                        else
                            std::wcout << L"Err (GetOverlappedResult) = " << err << std::endl;
                    }
                    else {
                        // overplapped function(ReadDirectoryChangesW) exits normally
                        std::wcout << L"GetOverlappedResult = OK. Bytes = " << NumberOfBytesTransferred << std::endl;
 
                        // display files changes, received from ReadDirectoryChangesW
                        DisplayFileInfo(lpBuffer, NumberOfBytesTransferred /*FileInfoLength*/);
                    }
 
                    break;
                }   
 
 
                case WAIT_TIMEOUT:
 
                    // 
 
                    if (first)
                        printf("\nNo changes in the timeout period.\n");
                    break;
 
                default:
                    printf("\n ERROR: Unhandled dwWaitStatus.\n");
                    ExitProcess(GetLastError());
                    break;
                }
 
        }
        else {
            // ошибка
            DWORD err = GetLastError();
            std::wcout << L"ReadDirectoryChangesW error = " << err << std::endl;
        }
 
        // =============================================================
 
        first = false;
    }
 
    free(lpBuffer);
}
LUN2
  • 75
  • 5
  • See the comment by Leo Davidson about race conditions with this API in [this thread](https://stackoverflow.com/questions/4179270/how-to-deal-with-windows-readdirectorychangesw-and-its-mixed-long-short-filen). I think you're going to have to walk the directory to look for any changes yourself, once your wait is satisfied, then wait again (in which case you might as well use `FindFirstChangeNotification` / `FindNextChangeNotification` instead) – Paul Sanders Sep 24 '22 at 19:28
  • @Paul Sanders, FindFirstChangeNotification / FindNextChangeNotification do not give me an information about the details - which file is appeared or any other changed were occured, but I need to get them. – LUN2 Sep 24 '22 at 19:47
  • That's why I'm suggesting you walk the directory, so you can detect exactly what has changed yourself. – Paul Sanders Sep 24 '22 at 21:01
  • Additional details should be added to the original question, BTW. Answering your own question should be reserved for when you find a solution and want to tell the world. – Paul Sanders Sep 24 '22 at 21:02
  • 1
    Using events in loop is not good. See here for a better implementation https://stackoverflow.com/a/40356818/403671 Also note you will always observe a discrepancy between end-user actions and file system "change" notifications, for example cut & paste in Explorer is not a rename notification at file system level. Or a folder cut outside watched dir and paste inside watched dir will not generate children created notifications, etc. – Simon Mourier Sep 25 '22 at 06:28
  • @Simon Mourier, thank you for your link, I need a time to learn the materials in it. But I have read in it that using events is a worst way. Why ? It is not reliable ? Using completion routines or i/o completion port will help to not miss records of files changes , – LUN2 Sep 25 '22 at 10:25
  • @Paul Sanders what do you mean when say - "walk the directory". Is it same- to scan files in the folder in my program and search difference before and after scanning ? I thought WinAPI can do part of this work and I will receive from it the end result - a list of changed or appeared files... – LUN2 Sep 25 '22 at 10:29
  • Yes, that's what I mean. It seems that the WIN32 API is not reliable, so... – Paul Sanders Sep 25 '22 at 13:49
  • 1
    I use BindIoCompletionCallback like in the link posted and lose 0 notification (if the buffer is big enough). – Simon Mourier Sep 25 '22 at 17:21

0 Answers0