Как я могу обнаружить только удаленные, измененные и созданные файлы на томе? - PullRequest
7 голосов
/ 14 сентября 2011

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

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

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

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

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

Ответы [ 4 ]

19 голосов
/ 18 сентября 2011

Вы можете перечислить все файлы на томе, используя 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-битными. Это была просто ошибка с моей стороны. Возможно, в рабочем коде вы их не будете печатать, но, к вашему сведению,

4 голосов
/ 17 сентября 2011

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

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

1 голос
/ 14 сентября 2011

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

0 голосов
/ 17 сентября 2011

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

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

...