为了账号安全,请及时绑定邮箱和手机立即绑定

如何仅检测卷上已删除,更改和创建的文件?

如何仅检测卷上已删除,更改和创建的文件?

C++
当年话下 2019-11-18 10:42:33
我需要知道是否有一种简单的方法来仅检测在NTFS卷上已删除,修改或创建的文件。我已经编写了一个用C ++进行异地备份的程序。第一次备份后,我检查每个文件的存档位以查看是否进行了任何更改,并仅备份已更改的文件。另外,它从VSS快照备份以防止文件锁定。这似乎在大多数文件系统上都可以正常工作,但是对于某些文件和目录很多的文件系统,此过程将花费很长时间,并且备份通常需要一天以上的时间才能完成备份。我尝试使用更改日志来轻松检测对NTFS卷所做的更改,但是更改日志将显示很多记录,其中大多数与创建和销毁的小型临时文件有关。另外,我可以使用文件名,文件参考号和父文件参考号,但无法获取完整的文件路径。父文件参考号应该以某种方式为您提供父目录路径。编辑:这需要每天运行,因此在每次扫描开始时,它应仅记录自上次扫描以来发生的更改。至少,应该有一种方法可以这样说,因为时间和日期是这样。
查看完整描述

3 回答

?
慕斯709654

TA贡献1840条经验 获得超5个赞

您可以使用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(这也取决于您使用的编译器和SDK。)


我正在打印64位文件参考号,就好像它们是32位一样。那只是我的一个错误。也许在生产代码中您无论如何都不会打印它们,而是供您参考。


查看完整回答
反对 回复 2019-11-18
?
慕村9548890

TA贡献1884条经验 获得超4个赞

零钱日记是您最好的选择。您可以使用文件参考号来匹配文件创建/删除对,从而忽略临时文件,而无需进一步处理它们。

我认为您必须扫描主文件表才能理解ParentFileReferenceNumber。当然,您在执行此操作时只需要跟踪目录,并使用可以快速查找信息的数据结构,因此只需要扫描一次MFT。


查看完整回答
反对 回复 2019-11-18
  • 3 回答
  • 0 关注
  • 581 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信