USN NFTS прервать событие уведомления об изменении

Я пытаюсь найти способ, чтобы система сообщала мне о появлении новой записи в USNChange Journal отслеживать изменения, сделанные в файлах и каталогах на NTFS том (Сервер 2008/2012).

Таким образом, мне не нужно постоянно опрашивать журнал, и я могу просто позволить моей теме спать, пока я не получу уведомление о появлении нового события изменения.

Однако есть ли такое прерывание?

FSCTL_QUERY_USN_JOURNAL Функция специально не упоминает прерывания (события, уведомления), и я не смог найти другого способа добиться этого с помощью менее интенсивных методов опроса и сравнения.

Я не заядлый программист, поэтому могут быть более простые способы связать эти функции с прерываниями, о которых я не знаю.

Могу ли я узнать, где хранится журнал изменений USN, и посмотреть этот файл с помощью другого процесса, который может генерировать и прерывать изменения?

https://msdn.microsoft.com/en-us/library/aa365729(v=vs.85).aspx

2 ответа

Размещенный здесь код блокирует поток выполнения до тех пор, пока в журнале не будет создана новая запись USN. Когда поступают новые записи, поток пробуждается, и вы можете обработать изменения и / или уведомить слушателей с помощью обратного вызова, что файловая система изменилась (в примере это просто выводит сообщение на консоль). Затем поток снова блокируется. В этом примере используется один поток на каждый том (поэтому для каждого тома необходим отдельный экземпляр класса NTFSChangesWatcher).

Не указано, какие инструменты или язык вы используете, поэтому я напишу, как я это сделал. Для запуска этого кода создайте консольное приложение Visual Studio C++ Win32. Создайте класс NTFSChangesWatcher. Вставьте этот код в файл NTFSChangesWatcher.h (заменив автоматически сгенерированный):

#pragma once

#include <windows.h>
#include <memory>

class NTFSChangesWatcher
{
public:
    NTFSChangesWatcher(char drive_letter);
    ~NTFSChangesWatcher() = default;

    // Method which runs an infinite loop and waits for new update sequence number in a journal.
    // The thread is blocked till the new USN record created in the journal.
    void WatchChanges();

private:
    HANDLE OpenVolume(char drive_letter);

    bool CreateJournal(HANDLE volume);

    bool LoadJournal(HANDLE volume, USN_JOURNAL_DATA* journal_data);

    bool NTFSChangesWatcher::WaitForNextUsn(PREAD_USN_JOURNAL_DATA read_journal_data) const;

    std::unique_ptr<READ_USN_JOURNAL_DATA> GetWaitForNextUsnQuery(USN start_usn);

    bool NTFSChangesWatcher::ReadJournalRecords(PREAD_USN_JOURNAL_DATA journal_query, LPVOID buffer,
        DWORD& byte_count) const;

    std::unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetReadJournalQuery(USN low_usn);


    char drive_letter_;

    HANDLE volume_;

    std::unique_ptr<USN_JOURNAL_DATA> journal_;

    DWORDLONG journal_id_;

    USN last_usn_;

    // Flags, which indicate which types of changes you want to listen.
    static const int FILE_CHANGE_BITMASK;

    static const int kBufferSize;
};

и этот код в файле NTFSChangesWatcher.cpp:

#include "NTFSChangesWatcher.h"

#include <iostream>

using namespace std;

const int NTFSChangesWatcher::kBufferSize = 1024 * 1024 / 2;

const int NTFSChangesWatcher::FILE_CHANGE_BITMASK =
   USN_REASON_RENAME_NEW_NAME | USN_REASON_SECURITY_CHANGE | USN_REASON_BASIC_INFO_CHANGE | USN_REASON_DATA_OVERWRITE |
   USN_REASON_DATA_TRUNCATION | USN_REASON_DATA_EXTEND | USN_REASON_CLOSE;


NTFSChangesWatcher::NTFSChangesWatcher(char drive_letter) :
    drive_letter_(drive_letter)
{
    volume_ = OpenVolume(drive_letter_);

    journal_ = make_unique<USN_JOURNAL_DATA>();

    bool res = LoadJournal(volume_, journal_.get());

    if (!res) {
        cout << "Failed to load journal" << endl;
        return;
    }

    journal_id_ = journal_->UsnJournalID;
    last_usn_ = journal_->NextUsn;
}

HANDLE NTFSChangesWatcher::OpenVolume(char drive_letter) {

wchar_t pattern[10] = L"\\\\?\\a:";

pattern[4] = static_cast<wchar_t>(drive_letter);
HANDLE volume = nullptr;

volume = CreateFile(
    pattern,  // lpFileName
    // also could be | FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE
    GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,              // dwDesiredAccess
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,  // share mode
    NULL,                                                    // default security attributes
    OPEN_EXISTING,                                           // disposition
    // It is always set, no matter whether you explicitly specify it or not. This means, that access
    // must be aligned with sector size so we can only read a number of bytes that is a multiple of the sector size.
    FILE_FLAG_NO_BUFFERING,  // file attributes
    NULL                     // do not copy file attributes
    );

    if (volume == INVALID_HANDLE_VALUE) {
        // An error occurred!
        cout << "Failed to open volume" << endl;
        return nullptr;
    }

    return volume;
}


bool NTFSChangesWatcher::CreateJournal(HANDLE volume) {

    DWORD byte_count;
    CREATE_USN_JOURNAL_DATA create_journal_data;

    bool ok = DeviceIoControl(volume, // handle to volume
        FSCTL_CREATE_USN_JOURNAL,     // dwIoControlCode
        &create_journal_data,         // input buffer
        sizeof(create_journal_data),  // size of input buffer
        NULL,                         // lpOutBuffer
        0,                            // nOutBufferSize
        &byte_count,                  // number of bytes returned
        NULL) != 0;                   // OVERLAPPED structure

    if (!ok) {
        // An error occurred!
    }

    return ok;
}


bool NTFSChangesWatcher::LoadJournal(HANDLE volume, USN_JOURNAL_DATA* journal_data) {

    DWORD byte_count;

    // Try to open journal.
    if (!DeviceIoControl(volume, FSCTL_QUERY_USN_JOURNAL, NULL, 0, journal_data, sizeof(*journal_data), &byte_count,
        NULL)) {

        // If failed (for example, in case journaling is disabled), create journal and retry.

        if (CreateJournal(volume)) {
            return LoadJournal(volume, journal_data);
        }

        return false;
    }

    return true;
}

void NTFSChangesWatcher::WatchChanges() {

    auto u_buffer = make_unique<char[]>(kBufferSize);

    auto read_journal_query = GetWaitForNextUsnQuery(last_usn_);

    while (true) {

        // This function does not return until new USN record created.
        WaitForNextUsn(read_journal_query.get());

        cout << "New entry created in the journal!" << endl;

        auto journal_query = GetReadJournalQuery(read_journal_query->StartUsn);

        DWORD byte_count;
        if (!ReadJournalRecords(journal_query.get(), u_buffer.get(), byte_count)) {
            // An error occurred.
            cout << "Failed to read journal records" << endl;
        }

        last_usn_ = *(USN*)u_buffer.get();
        read_journal_query->StartUsn = last_usn_;

        // If you need here you can:
        // Read and parse Journal records from the buffer.
        // Notify an NTFSChangeObservers about journal changes.
    }
}

bool NTFSChangesWatcher::WaitForNextUsn(PREAD_USN_JOURNAL_DATA read_journal_data) const {

    DWORD bytes_read;
    bool ok = true;

    // This function does not return until new USN record created.
    ok = DeviceIoControl(volume_, FSCTL_READ_USN_JOURNAL, read_journal_data, sizeof(*read_journal_data),
        &read_journal_data->StartUsn, sizeof(read_journal_data->StartUsn), &bytes_read,
        nullptr) != 0;

    return ok;
   }

   unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetWaitForNextUsnQuery(USN start_usn) {

    auto query = make_unique<READ_USN_JOURNAL_DATA>();

    query->StartUsn = start_usn;
    query->ReasonMask = 0xFFFFFFFF;     // All bits.
    query->ReturnOnlyOnClose = FALSE;   // All entries.
    query->Timeout = 0;                 // No timeout.
    query->BytesToWaitFor = 1;          // Wait for this.
    query->UsnJournalID = journal_id_;  // The journal.
    query->MinMajorVersion = 2;
    query->MaxMajorVersion = 2;

    return query;
}


bool NTFSChangesWatcher::ReadJournalRecords(PREAD_USN_JOURNAL_DATA journal_query, LPVOID buffer,
    DWORD& byte_count) const {

    return DeviceIoControl(volume_, FSCTL_READ_USN_JOURNAL, journal_query, sizeof(*journal_query), buffer, kBufferSize,
        &byte_count, nullptr) != 0;
}

unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetReadJournalQuery(USN low_usn) {

    auto query = make_unique<READ_USN_JOURNAL_DATA>();

    query->StartUsn = low_usn;
    query->ReasonMask = 0xFFFFFFFF;  // All bits.
    query->ReturnOnlyOnClose = FALSE;
    query->Timeout = 0;  // No timeout.
    query->BytesToWaitFor = 0;
    query->UsnJournalID = journal_id_;
    query->MinMajorVersion = 2;
    query->MaxMajorVersion = 2;

    return query;
}

Теперь вы можете использовать его (например, в основной функции для тестирования):

#include "NTFSChangesWatcher.h"

int _tmain(int argc, _TCHAR* argv[])
{
    auto watcher = new NTFSChangesWatcher('z');
    watcher->WatchChanges();
    return 0;
}

И вывод консоли должен быть таким при каждом изменении в файловой системе:

Этот код был немного переработан для удаления несвязанных деталей и является частью проекта Indexer++. Так что для более подробной информации вы можете обратиться к оригинальному коду.

Вы можете использовать журнал, но в этом случае я бы использовал более простой метод, зарегистрировав уведомление каталога, вызвав функции FindFirstChangeNotification или ReadDirectoryChangesW, см. https://msdn.microsoft.com/en-us/library/aa364417.aspx

Если вы предпочитаете использовать журнал, это - я думаю - лучшая вводная статья с множеством примеров. Он написан для W2K, но эти концепции все еще актуальны: https://www.microsoft.com/msj/0999/journal/journal.aspx

Другие вопросы по тегам