Directory Notification

I have had a number of situations over the last five years where I have had to write code that detects changes made in a directory. Some has been for testing my own directory notification code at the file-system level, and others have been for real implementation reasons up in usermode code.

The documentation can be a lot to wade through as there are a number of different ways that such notification can be accomplished. I won’t go into the reasons for using each (check MSDN documentation for some information on that), but I wanted to post some simple samples of using each for anyone who might be interested.

There are five different methods, using Win32 APIs, that are used in this sample. They don’t do anything special except print out basic information about the changes that are detected.

VOID
_processBuffer(
    __in PFILE_NOTIFY_INFORMATION pNotifyInformation
    )
{
    wchar_t filename[ MAX_PATH + 1 ];

    while ( pNotifyInformation->NextEntryOffset != 0 )
    {
        StringCbCopyNW( filename, sizeof( filename ), pNotifyInformation->FileName, pNotifyInformation->FileNameLength );
        wprintf( L"   Action %d [%s]n", pNotifyInformation->Action, filename );
        pNotifyInformation = (PFILE_NOTIFY_INFORMATION) Add2Ptr( pNotifyInformation, pNotifyInformation->NextEntryOffset );
    }
}

DWORD
_changeDir1(
    __in LPCWSTR path
    )
{
    DWORD  status = ERROR_SUCCESS;

    //    For unwind
    HANDLE hFind = INVALID_HANDLE_VALUE;

    hFind =
        FindFirstChangeNotificationW(
            path,
            FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE
            );
    if ( INVALID_HANDLE_VALUE == hFind )
    {
        fn_return( status = GetLastError() );
    }

    while ( WAIT_OBJECT_0 == WaitForSingleObject( hFind, INFINITE ) )
    {
        wprintf( L"Dir Notify: No informationn" );

        if ( !FindNextChangeNotification( hFind ) )
        {
            fn_return( status = GetLastError() );
        }
    }

    fn_return( NOTHING );

fn_exit:

    if ( hFind != INVALID_HANDLE_VALUE )
    {
        FindCloseChangeNotification( hFind );
    }

    return status;
}

DWORD
_changeDir2(
    __in LPCWSTR path
    )
{
    DWORD                    status = ERROR_SUCCESS;
    DWORD                    cbNotifyBuffer;
    DWORD                    cbReturned;

    //    For unwind
    HANDLE                   hFile = INVALID_HANDLE_VALUE;
    PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL;

    cbNotifyBuffer = 4096;
    pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ];
    if ( !pNotifyBuffer )
    {
        fn_return( status = ERROR_NOT_ENOUGH_MEMORY );
    }

    hFile =
        CreateFileW(
            path,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS,
            NULL
            );
    if ( INVALID_HANDLE_VALUE == hFile )
    {
        fn_return( status = GetLastError() );
    }

    while (
        ReadDirectoryChangesW(
            hFile,
            pNotifyBuffer,
            cbNotifyBuffer,
            FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
            &cbReturned,
            NULL,
            NULL
            )
        )
    {
        if ( cbReturned > 0 )
        {
            wprintf( L"Dir Notify:n" );
            _processBuffer( pNotifyBuffer );
        }
        else
        {
            wprintf( L"Dir Notify: No informationn" );
        }
    }

fn_exit:

    if ( INVALID_HANDLE_VALUE != hFile )
    {
        CloseHandle( hFile );
    }

    if ( pNotifyBuffer )
    {
        delete [] pNotifyBuffer;
    }

    return status;
}

DWORD
_changeDir3(
    __in LPCWSTR path
    )
{
    DWORD                    status = ERROR_SUCCESS;
    DWORD                    cbNotifyBuffer;
    DWORD                    cbReturned;
    PFILE_NOTIFY_INFORMATION pNotifyInformation;
    wchar_t                  filename[ MAX_PATH + 1 ];
    OVERLAPPED               overlapped;

    //    For unwind
    HANDLE                   hFile = INVALID_HANDLE_VALUE;
    PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL;

    ZeroMemory( &overlapped, sizeof( overlapped ) );

    cbNotifyBuffer = 4096;
    pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ];
    if ( !pNotifyBuffer )
    {
        fn_return( status = ERROR_NOT_ENOUGH_MEMORY );
    }

    hFile =
        CreateFileW(
            path,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL
            );
    if ( INVALID_HANDLE_VALUE == hFile )
    {
        fn_return( status = GetLastError() );
    }

    overlapped.hEvent = CreateEventW( NULL, TRUE, FALSE, NULL );

    while (
        ReadDirectoryChangesW(
            hFile,
            pNotifyBuffer,
            cbNotifyBuffer,
            FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
            NULL,
            &overlapped,
            NULL
            )
        &&
        GetOverlappedResult( hFile, &overlapped, &cbReturned, TRUE )
        )
    {
        if ( cbReturned > 0 )
        {
            wprintf( L"Dir Notify:n" );
            _processBuffer( pNotifyBuffer );
        }
        else
        {
            wprintf( L"Dir Notify: No informationn" );
        }
    }

fn_exit:

    if ( overlapped.hEvent )
    {
        CloseHandle( overlapped.hEvent );
    }

    if ( INVALID_HANDLE_VALUE != hFile )
    {
        CloseHandle( hFile );
    }

    if ( pNotifyBuffer )
    {
        delete [] pNotifyBuffer;
    }

    return status;
}

DWORD
_changeDir4(
    __in LPCWSTR path
    )
{
    DWORD                    status = ERROR_SUCCESS;
    DWORD                    cbNotifyBuffer;
    DWORD                    cbReturned;
    PFILE_NOTIFY_INFORMATION pNotifyInformation;
    wchar_t                  filename[ MAX_PATH + 1 ];
    OVERLAPPED               overlapped;
    ULONG_PTR                completionKey;
    LPOVERLAPPED             pOverlapped;

    //    For unwind
    HANDLE                   hFile = INVALID_HANDLE_VALUE;
    PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL;
    HANDLE                   hCompletionPort = NULL;

    ZeroMemory( &overlapped, sizeof( overlapped ) );

    cbNotifyBuffer = 4096;
    pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ];
    if ( !pNotifyBuffer )
    {
        fn_return( status = ERROR_NOT_ENOUGH_MEMORY );
    }

    hFile =
        CreateFileW(
            path,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL
            );
    if ( INVALID_HANDLE_VALUE == hFile )
    {
        fn_return( status = GetLastError() );
    }

    hCompletionPort = CreateIoCompletionPort( hFile, NULL, 0xF00DF00D, 0 );

    while (
        ReadDirectoryChangesW(
            hFile,
            pNotifyBuffer,
            cbNotifyBuffer,
            FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
            NULL,
            &overlapped,
            NULL
            )
        &&
        GetQueuedCompletionStatus( hCompletionPort, &cbReturned, &completionKey, &pOverlapped, INFINITE )
        )
    {
        if ( cbReturned > 0 )
        {
            wprintf( L"Dir Notify:n" );
            _processBuffer( pNotifyBuffer );
        }
        else
        {
            wprintf( L"Dir Notify: No informationn" );
        }
    }

fn_exit:

    if ( hCompletionPort )
    {
        CloseHandle( hCompletionPort );
    }

    if ( INVALID_HANDLE_VALUE != hFile )
    {
        CloseHandle( hFile );
    }

    if ( pNotifyBuffer )
    {
        delete [] pNotifyBuffer;
    }

    return status;
}

DWORD _cbReturned;

VOID CALLBACK
_changeDir5CompletionRoutine(
    __in DWORD        errorCode,
    __in DWORD        cbReturned,
    __in LPOVERLAPPED pOverlapped
    )
{
    _cbReturned = cbReturned;
    SetEvent( pOverlapped->hEvent );
}

DWORD
_changeDir5(
    __in LPCWSTR path
    )
{
    DWORD                    status = ERROR_SUCCESS;
    DWORD                    cbNotifyBuffer;
    PFILE_NOTIFY_INFORMATION pNotifyInformation;
    wchar_t                  filename[ MAX_PATH + 1 ];
    OVERLAPPED               overlapped;

    //    For unwind
    HANDLE                   hFile = INVALID_HANDLE_VALUE;
    PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL;

    ZeroMemory( &overlapped, sizeof( overlapped ) );

    cbNotifyBuffer = 4096;
    pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ];
    if ( !pNotifyBuffer )
    {
        fn_return( status = ERROR_NOT_ENOUGH_MEMORY );
    }

    hFile =
        CreateFileW(
            path,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL
            );
    if ( INVALID_HANDLE_VALUE == hFile )
    {
        fn_return( status = GetLastError() );
    }

    overlapped.hEvent = CreateEventW( NULL, TRUE, FALSE, NULL );

    while (
        ReadDirectoryChangesW(
            hFile,
            pNotifyBuffer,
            cbNotifyBuffer,
            FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
            NULL,
            &overlapped,
            _changeDir5CompletionRoutine
            )
        &&
        WaitForSingleObject( overlapped.hEvent, INFINITE )
        )
    {
        if ( _cbReturned > 0 )
        {
            wprintf( L"Dir Notify:n" );
            _processBuffer( pNotifyBuffer );
        }
        else
        {
            wprintf( L"Dir Notify: No informationn" );
        }

        ResetEvent( overlapped.hEvent );
    }

fn_exit:

    if ( overlapped.hEvent )
    {
        CloseHandle( overlapped.hEvent );
    }

    if ( INVALID_HANDLE_VALUE != hFile )
    {
        CloseHandle( hFile );
    }

    if ( pNotifyBuffer )
    {
        delete [] pNotifyBuffer;
    }

    return status;
}

int
__cdecl
wmain(
    __in int      argc,
    __in wchar_t* argv[]
    )
{
    DWORD               status = ERROR_SUCCESS;

    if ( argc < 3 )
    {
        fn_return( NOTHING );
    }

    if ( !wcscmp( argv[ 1 ], L"1" ) )
    {
        wprintf( L"Type 1: FindFirstChangeNotificationn" );
        status = _changeDir1( argv[ 2 ] );
    }
    else if ( !wcscmp( argv[ 1 ], L"2" ) )
    {
        wprintf( L"Type 2: ReadDirectoryChanges (Synch)n" );
        status = _changeDir2( argv[ 2 ] );
    }
    else if ( !wcscmp( argv[ 1 ], L"3" ) )
    {
        wprintf( L"Type 3: ReadDirectoryChanges (Asynch w/OVERLAPPED)n" );
        status = _changeDir3( argv[ 2 ] );
    }
    else if ( !wcscmp( argv[ 1 ], L"4" ) )
    {
        wprintf( L"Type 4: ReadDirectoryChanges (Asynch w/IoCompletionPort)n" );
        status = _changeDir4( argv[ 2 ] );
    }
    else if ( !wcscmp( argv[ 1 ], L"5" ) )
    {
        wprintf( L"Type 5: ReadDirectoryChanges (Asynch w/completion routine)n" );
        status = _changeDir4( argv[ 2 ] );
    }

    fn_return( NOTHING );

fn_exit:

    wprintf( L"Status = %dn", status );

    return status;
}

1 thought on “Directory Notification

  1. There are countless examples that show one or two notification methods… It’s nice to see them all together. I am particularly interested in the IoCompletionPort method. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

Complete the following to verify your humanity: * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.