USN NFTS 更改通知事件中断

Posted

技术标签:

【中文标题】USN NFTS 更改通知事件中断【英文标题】:USN NFTS change notification event interrupt 【发布时间】:2016-08-10 23:51:05 【问题描述】:

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

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

但是,有这样的中断吗?

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

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

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

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

【问题讨论】:

我不确定,但我相信您可以使用 FSCTL_READ_USN_JOURNAL 和重叠 I/O 来做到这一点。 【参考方案1】:

此处发布的代码会阻塞执行线程,直到在 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++ 项目的一部分。所以更详细的可以参考original code。

【讨论】:

这太棒了!非常感谢!【参考方案2】:

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

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

【讨论】:

谢谢罗伯特,我会阅读这些链接。我计划在具有近一百万个目录的繁忙文件服务器上运行此应用程序。

以上是关于USN NFTS 更改通知事件中断的主要内容,如果未能解决你的问题,请参考以下文章

如何在 IOS 中的联系人更改事件中获得通知? [复制]

JCheckbox 更改侦听器收到鼠标悬停事件的通知

未收到 MS Teams 的任何更改通知

正点原子FreeRTOS(下)

正点原子FreeRTOS(下)

动态更改android通知文本