USN NFTS更改通知事件中断

时间:2016-08-10 23:51:05

标签: windows ntfs usn

我试图找到一种方法让系统告诉我,只要USN Change Journal中有新条目跟踪NTFS上的文件和目录所做的修改{1}}卷(Server 2008/2012)。

通过这种方式,我不必经常轮询日记,并且可以让我的线程一直睡到我收到通知,直到有新的更改事件。

然而,是否有这样的中断?

FSCTL_QUERY_USN_JOURNAL函数并没有特别提及中断(事件,通知),也没有找到另一种方法来实现这一点,而不需要使用较少密集的轮询和比较技术。

我不是一个核心程序员,所以可能有更简单的方法将这些功能与我不知道的中断联系起来。

我是否可以找出USN Change Journal的存储位置,并使用生成的其他进程观察该文件并在更改时中断?

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

2 个答案:

答案 0 :(得分:2)

此处发布的代码会阻止执行线程,直到在Journal中创建新的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;
}

在文件系统的每次更改中,控制台输出应该是这样的:

enter image description here

此代码稍作修改以删除不相关的详细信息,并且是Indexer++项目的一部分。有关详细信息,请参阅original code

答案 1 :(得分:0)

您可以使用Journal,但在这种情况下,我通过调用FindFirstChangeNotification或ReadDirectoryChangesW函数注册目录通知来使用更简单的方法,请参阅https://msdn.microsoft.com/en-us/library/aa364417.aspx

如果您更喜欢使用期刊,我认为这是一篇包含很多示例的最佳介绍性文章。它是为W2K编写的,但这些概念仍然有效:https://www.microsoft.com/msj/0999/journal/journal.aspx