簡體   English   中英

如何僅檢測卷上已刪除,已更改和已創建的文件?

[英]How can I detect only deleted, changed, and created files on a volume?

我需要知道是否有一種簡單的方法只能檢測在NTFS卷上刪除,修改或創建的文件。

我在C ++中編寫了一個異地備份程序。 在第一次備份之后,我檢查每個文件的存檔位以查看是否進行了任何更改,並僅備份已更改的文件。 此外,它從VSS快照備份以防止文件鎖定。

這似乎在大多數文件系統上都能正常工作,但是對於一些有大量文件和目錄的程序,這個過程需要很長時間,而且備份通常需要一天以上才能完成備份。

我嘗試使用更改日志輕松檢測在NTFS卷上所做的更改,但更改日志將顯示大量記錄,其中大多數與創建和銷毀的小型臨時文件有關。 另外,我可以使用文件名,文件引用號和父文件引用號,但是我無法獲得完整的文件路徑。 父文件引用號以某種方式應該為您提供父目錄路徑。

編輯:這需要每天運行,所以在每次掃描開始時,它應該只記錄自上次掃描以來發生的變化。 或者至少,應該有一種方式來說明由於某某時間和日期的變化。

您可以使用FSCTL_ENUM_USN_DATA枚舉卷上的所有文件。 這是一個快速的過程(即使在非常老的機器上,我的測試每秒返回的記錄優於6000條記錄,20000 +更典型)並且僅包括當前存在的文件。

返回的數據包括文件標志和USN,因此您可以根據自己喜歡的方式檢查更改。

您仍然需要通過將父ID與目錄的文件ID相匹配來計算文件的完整路徑。 一種方法是使用足夠大的緩沖區來同時保存所有文件記錄,並搜索記錄以找到需要備份的每個文件的匹配父級。 對於大型卷,您可能需要將目錄記錄處理為更高效的數據結構,可能是哈希表。

或者,您可以根據需要讀取/重新讀取父目錄的記錄。 這樣效率較低,但性能可能仍然令人滿意,具體取決於備份的文件數量。 Windows確實會緩存FSCTL_ENUM_USN_DATA返回的數據。

此程序在C卷中搜索名為test.txt的文件,並返回有關找到的任何文件的信息,以及有關其父目錄的信息。

#include <Windows.h>

#include <stdio.h>

#define BUFFER_SIZE (1024 * 1024)

HANDLE drive;
USN maxusn;

void show_record (USN_RECORD * record)
{
    void * buffer;
    MFT_ENUM_DATA mft_enum_data;
    DWORD bytecount = 1;
    USN_RECORD * parent_record;

    WCHAR * filename;
    WCHAR * filenameend;

    printf("=================================================================\n");
    printf("RecordLength: %u\n", record->RecordLength);
    printf("MajorVersion: %u\n", (DWORD)record->MajorVersion);
    printf("MinorVersion: %u\n", (DWORD)record->MinorVersion);
    printf("FileReferenceNumber: %lu\n", record->FileReferenceNumber);
    printf("ParentFRN: %lu\n", record->ParentFileReferenceNumber);
    printf("USN: %lu\n", record->Usn);
    printf("Timestamp: %lu\n", record->TimeStamp);
    printf("Reason: %u\n", record->Reason);
    printf("SourceInfo: %u\n", record->SourceInfo);
    printf("SecurityId: %u\n", record->SecurityId);
    printf("FileAttributes: %x\n", record->FileAttributes);
    printf("FileNameLength: %u\n", (DWORD)record->FileNameLength);

    filename = (WCHAR *)(((BYTE *)record) + record->FileNameOffset);
    filenameend= (WCHAR *)(((BYTE *)record) + record->FileNameOffset + record->FileNameLength);

    printf("FileName: %.*ls\n", filenameend - filename, filename);

    buffer = VirtualAlloc(NULL, BUFFER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    if (buffer == NULL)
    {
        printf("VirtualAlloc: %u\n", GetLastError());
        return;
    }

    mft_enum_data.StartFileReferenceNumber = record->ParentFileReferenceNumber;
    mft_enum_data.LowUsn = 0;
    mft_enum_data.HighUsn = maxusn;

    if (!DeviceIoControl(drive, FSCTL_ENUM_USN_DATA, &mft_enum_data, sizeof(mft_enum_data), buffer, BUFFER_SIZE, &bytecount, NULL))
    {
        printf("FSCTL_ENUM_USN_DATA (show_record): %u\n", GetLastError());
        return;
    }

    parent_record = (USN_RECORD *)((USN *)buffer + 1);

    if (parent_record->FileReferenceNumber != record->ParentFileReferenceNumber)
    {
        printf("=================================================================\n");
        printf("Couldn't retrieve FileReferenceNumber %u\n", record->ParentFileReferenceNumber);
        return;
    }

    show_record(parent_record);
}

void check_record(USN_RECORD * record)
{
    WCHAR * filename;
    WCHAR * filenameend;

    filename = (WCHAR *)(((BYTE *)record) + record->FileNameOffset);
    filenameend= (WCHAR *)(((BYTE *)record) + record->FileNameOffset + record->FileNameLength);

    if (filenameend - filename != 8) return;

    if (wcsncmp(filename, L"test.txt", 8) != 0) return;

    show_record(record);
}

int main(int argc, char ** argv)
{
    MFT_ENUM_DATA mft_enum_data;
    DWORD bytecount = 1;
    void * buffer;
    USN_RECORD * record;
    USN_RECORD * recordend;
    USN_JOURNAL_DATA * journal;
    DWORDLONG nextid;
    DWORDLONG filecount = 0;
    DWORD starttick, endtick;

    starttick = GetTickCount();

    printf("Allocating memory.\n");

    buffer = VirtualAlloc(NULL, BUFFER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    if (buffer == NULL)
    {
        printf("VirtualAlloc: %u\n", GetLastError());
        return 0;
    }

    printf("Opening volume.\n");

    drive = CreateFile(L"\\\\?\\c:", GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);

    if (drive == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile: %u\n", GetLastError());
        return 0;
    }

    printf("Calling FSCTL_QUERY_USN_JOURNAL\n");

    if (!DeviceIoControl(drive, FSCTL_QUERY_USN_JOURNAL, NULL, 0, buffer, BUFFER_SIZE, &bytecount, NULL))
    {
        printf("FSCTL_QUERY_USN_JOURNAL: %u\n", GetLastError());
        return 0;
    }

    journal = (USN_JOURNAL_DATA *)buffer;

    printf("UsnJournalID: %lu\n", journal->UsnJournalID);
    printf("FirstUsn: %lu\n", journal->FirstUsn);
    printf("NextUsn: %lu\n", journal->NextUsn);
    printf("LowestValidUsn: %lu\n", journal->LowestValidUsn);
    printf("MaxUsn: %lu\n", journal->MaxUsn);
    printf("MaximumSize: %lu\n", journal->MaximumSize);
    printf("AllocationDelta: %lu\n", journal->AllocationDelta);

    maxusn = journal->MaxUsn;

    mft_enum_data.StartFileReferenceNumber = 0;
    mft_enum_data.LowUsn = 0;
    mft_enum_data.HighUsn = maxusn;

    for (;;)
    {
//      printf("=================================================================\n");
//      printf("Calling FSCTL_ENUM_USN_DATA\n");

        if (!DeviceIoControl(drive, FSCTL_ENUM_USN_DATA, &mft_enum_data, sizeof(mft_enum_data), buffer, BUFFER_SIZE, &bytecount, NULL))
        {
            printf("=================================================================\n");
            printf("FSCTL_ENUM_USN_DATA: %u\n", GetLastError());
            printf("Final ID: %lu\n", nextid);
            printf("File count: %lu\n", filecount);
            endtick = GetTickCount();
            printf("Ticks: %u\n", endtick - starttick);
            return 0;
        }

//      printf("Bytes returned: %u\n", bytecount);

        nextid = *((DWORDLONG *)buffer);
//      printf("Next ID: %lu\n", nextid);

        record = (USN_RECORD *)((USN *)buffer + 1);
        recordend = (USN_RECORD *)(((BYTE *)buffer) + bytecount);

        while (record < recordend)
        {
            filecount++;
            check_record(record);
            record = (USN_RECORD *)(((BYTE *)record) + record->RecordLength);
        }

        mft_enum_data.StartFileReferenceNumber = nextid;
    }
}

補充筆記

  • 如評論中所述,您可能需要在Windows 7之后的Windows版本MFT_ENUM_DATA MFT_ENUM_DATA_V0替換為MFT_ENUM_DATA 。(這可能還取決於您使用的是哪種編譯器和SDK。)

  • 我正在打印64位文件引用號,就像它們是32位一樣。 這對我來說只是一個錯誤。 可能在生產代碼中你無論如何都不會打印它們,但僅供參考。

變更日志是你最好的選擇。 您可以使用文件引用號來匹配文件創建/刪除對,從而忽略臨時文件,而無需進一步處理它們。

我認為你必須掃描主文件表以理解ParentFileReferenceNumber。 當然,您只需要在執行此操作時跟蹤目錄,並使用允許您快速查找信息的數據結構,因此您只需要掃描一次MFT。

您可以使用ReadDirectoryChanges和周圍的Windows API。

我知道如何在java中實現這一點。 如果在C ++中實現Java代碼,它將對您有所幫助。

在Java中,您可以使用Jnotify API實現此Jnotify它還在子目錄中查找更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM