1

我正在尝试找到一种方法,让系统在有新条目时告诉我,USN Change Journal以跟踪对NTFS卷上的文件和目录所做的修改(Server 2008/2012)。

这样我就不必不断地轮询日志,并且可以让我的线程休眠,直到有新的更改事件时收到通知。

然而,还有这样的中断吗?

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

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

我是否可以找出 USN 更改日志的存储位置并使用另一个可以生成和中断更改的进程查看该文件?

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

4

2 回答 2

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;
}

现在您可以使用它了(例如在 main 函数中进行测试):

#include "NTFSChangesWatcher.h"

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

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

在此处输入图像描述

此代码经过轻微修改以删除不相关的细节,并且是Indexer++项目的一部分。所以更多细节可以参考原代码

于 2016-08-31T12:01:03.870 回答
1

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

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

于 2016-08-11T11:04:05.460 回答