我需要知道是否有一个简单的方法来检测只有在NTFS卷上删除,修改或创build的文件。
我用C ++编写了一个非现场备份程序。 在第一次备份之后,我检查每个文件的存档位以查看是否有任何更改,并仅备份已更改的文件。 此外,它从VSS快照备份以防止文件locking。
这似乎在大多数文件系统上都可以正常工作,但对于有很多文件和目录的文件系统来说,这个过程需要很长时间,而且备份通常需要一天以上才能完成备份。
我尝试使用更改日志来轻松检测NTFS卷上所做的更改,但更改日志会显示大量logging,其中大部分与创build和销毁的小临时文件有关。 另外,我可以获得文件名,文件引用号和父文件引用号,但无法获得完整的文件path。 父文件引用号是不知何故给你的父目录path。
编辑:这需要每天运行,所以在每次扫描开始时,它应该只logging自上次扫描以来发生的变化。 或者至less,从某个时间和某个date开始,应该有一种说法。
您可以使用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之后的版本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
它也查找子目录中的更改。