Как я могу обнаружить только удаленные, измененные и созданные файлы на томе?

Мне нужно знать, есть ли простой способ обнаружения только тех файлов, которые были удалены, изменены или созданы на томе NTFS.

Я написал программу для резервного копирования вне сайта на C++. После первого резервного копирования я проверяю бит архива каждого файла, чтобы увидеть, были ли внесены какие-либо изменения, и создаю резервную копию только тех файлов, которые были изменены. Кроме того, он резервирует из снимка VSS, чтобы предотвратить блокировки файлов.

Кажется, что это работает нормально на большинстве файловых систем, но для некоторых с большим количеством файлов и каталогов этот процесс занимает слишком много времени, и часто резервное копирование занимает больше суток, чтобы завершить резервное копирование.

Я попытался использовать журнал изменений, чтобы легко обнаружить изменения, сделанные на томе NTFS, но журнал изменений показывал бы много записей, большинство из которых касалось небольших временных файлов, созданных и уничтоженных. Кроме того, я мог указать имя файла, номер ссылки на файл и номер ссылки на родительский файл, но не смог получить полный путь к файлу. Ссылочный номер родительского файла как-то должен давать вам путь к родительскому каталогу.

РЕДАКТИРОВАТЬ: это должно выполняться каждый день, поэтому в начале каждого сканирования он должен записывать только изменения, которые произошли с момента последнего сканирования. Или, по крайней мере, должен быть способ сказать об изменениях с того времени и даты.

4 ответа

Решение

Вы можете перечислить все файлы на томе, используя FSCTL_ENUM_USN_DATA. Это быстрый процесс (мои тесты возвращали лучше, чем 6000 записей в секунду даже на очень старой машине, а более 20000+ более типично) и включают только те файлы, которые существуют в настоящее время.

Возвращенные данные включают в себя флаги файлов, а также USN, чтобы вы могли проверять изменения по своему усмотрению.

Вам все еще нужно будет определить полный путь к файлам, сопоставив идентификаторы родителей с идентификаторами файлов каталогов. Один из подходов состоит в том, чтобы использовать буфер, достаточно большой для одновременного хранения всех записей файла, и выполнять поиск в записях, чтобы найти подходящего родителя для каждого файла, для которого требуется создать резервную копию. Для больших томов вам, вероятно, потребуется обработать записи каталога в более эффективную структуру данных, возможно, хеш-таблицу.

Кроме того, вы можете читать / перечитывать записи для родительских каталогов по мере необходимости. Это было бы менее эффективно, но производительность все равно могла бы быть удовлетворительной в зависимости от того, сколько файлов было скопировано. Похоже, что 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;
    }
}

Дополнительные примечания

  • Как указано в комментариях, вам может понадобиться заменить MFT_ENUM_DATA с MFT_ENUM_DATA_V0 в версиях Windows, более поздних, чем Windows 7. (Это также может зависеть от того, какой компилятор и SDK вы используете.)

  • Я печатаю 64-битные номера файлов, как если бы они были 32-битными. Это была просто ошибка с моей стороны. Возможно, в рабочем коде вы их не будете печатать, но, к вашему сведению.

Журнал изменений - ваш лучший выбор. Вы можете использовать ссылочные номера файлов для сопоставления пар создания / удаления файлов и, таким образом, игнорировать временные файлы, не обрабатывая их дальше.

Я думаю, что вы должны отсканировать таблицу основных файлов, чтобы иметь смысл ParentFileReferenceNumber. Конечно, вам нужно только отслеживать каталоги при этом и использовать структуру данных, которая позволит вам быстро искать информацию, поэтому вам нужно только отсканировать MFT один раз.

Вы можете использовать ReadDirectoryChanges и окружающий Windows API.

Я знаю, как добиться этого в Java. Это поможет вам, если вы реализуете код Java внутри C++.

В Java вы можете добиться этого, используя Jnotify API. Он также ищет изменения в подкаталоге.

Другие вопросы по тегам