Чтение файла на этапе предварительной очистки в отложенном рабочем элементе

Я пишу драйвер минифильтра для Windows, которому нужно прочитать весь файл (только файлы размером до определенного порога) на IRP_MJ_CLEANUP. Поскольку FltReadFile не может быть вызван из обратного вызова перед операцией, я поставил задание в рабочую очередь и сделал это там. Когда я заканчиваю чтение файла, я вызываю FltCompletePendedPreOperation и вызываю обратный вызов после очистки, который также обрабатывает операцию публикации как отложенную работу. Вот фрагменты моего кода:

static NTSTATUS HandlePreCleanup(_In_ PFLT_CALLBACK_DATA Data,
                                 _Out_ PVOID *Context)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PFLT_INSTANCE Instance;
    PFILE_OBJECT FileObject;
    PVOID Buffer = NULL;
    LARGE_INTEGER FileOffset;

    FileObject = Data->Iopb->TargetFileObject;
    Instance = Data->Iopb->TargetInstance;   

    Buffer = ExAllocatePoolWithTag(PagedPool,
                                   (ULONG) FILE_CHUNK_SIZE,
                                   PPFILTER_FILE_POOLTAG);
    if (Buffer == NULL) {
        PPERROR("Failed allocating file chunk\n");
        Status = STATUS_MEMORY_NOT_ALLOCATED;
        goto out;
    }

    FileOffset.QuadPart = 0;
    for (;;) {
        ULONG BytesRead;

        Status = FltReadFile(
            Instance, FileObject, &FileOffset,
            (ULONG) FILE_CHUNK_SIZE, Buffer,
            FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
            &BytesRead, NULL, NULL
        );
        if (!NT_SUCCESS(Status)) {
            if (Status == STATUS_END_OF_FILE) {
                Status = STATUS_SUCCESS;
                break;
            }

            PPERROR("Failed reading from file %wZ: error %d\n",
                    &FileObject->FileName, Status);
            goto out;
        }

        FileOffset.QuadPart += BytesRead;
    }

out:
    if (Buffer != NULL) {
        ExFreePoolWithTag(Buffer, PPFILTER_FILE_POOLTAG);
    }

    return Status;
}

static VOID DeferredPreCallback(_In_ PFLT_DEFERRED_IO_WORKITEM WorkItem,
                                _In_ PFLT_CALLBACK_DATA Data,
                                _In_opt_ PVOID Context)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PVOID PostContext = NULL;

    UNREFERENCED_PARAMETER(Context);

    switch (Data->Iopb->MajorFunction) {
    case IRP_MJ_CLEANUP:
        Status = HandlePreCleanup(Data, &PostContext);
        break;
    default:
        NT_ASSERTMSG("Unexpected deferred pre callback operation",
                     FALSE);
        break;
    }

    FltCompletePendedPreOperation(Data,
                                  FLT_PREOP_SUCCESS_WITH_CALLBACK,
                                  PostContext);
    FltFreeDeferredIoWorkItem(WorkItem);
}


static NTSTATUS QueueWork(_Inout_ PFLT_CALLBACK_DATA Data,
                          _In_ PFLT_DEFERRED_IO_WORKITEM_ROUTINE WorkRoutine,
                          _In_ PVOID Context)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PFLT_DEFERRED_IO_WORKITEM WorkItem = NULL;

    WorkItem = FltAllocateDeferredIoWorkItem();
    if (WorkItem == NULL) {
        Status = STATUS_MEMORY_NOT_ALLOCATED;
        PPERROR("Failed allocating work item\n");
        goto failed;
    }

    Status = FltQueueDeferredIoWorkItem(WorkItem, Data, WorkRoutine,
                                        CriticalWorkQueue, Context);
    if (!NT_SUCCESS(Status)) {
        PPERROR("Failed queuing work item to queue: error %d\n",
                Status);
        goto failed;
    }

    return STATUS_SUCCESS;

failed:
    if (WorkItem != NULL) {
        FltFreeDeferredIoWorkItem(WorkItem);
    }

    return Status;
}

static FLT_PREOP_CALLBACK_STATUS DeferPreCallback(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _Out_ PVOID *CompletionContext
)
{
    NTSTATUS Status = STATUS_SUCCESS;

    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    Status = QueueWork(Data, DeferredPreCallback, NULL);
    if (!NT_SUCCESS(Status)) {
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    return FLT_PREOP_PENDING;
}

CONST FLT_OPERATION_REGISTRATION OperationRegistrations[] = {
    {
        IRP_MJ_CLEANUP,
        0,
        DeferPreCallback,
        DeferPostCallback,
        NULL
    },
    { IRP_MJ_OPERATION_END },
};

Некоторое время это работает, но через некоторое время система, кажется, зависает (зависает?). Проблема видимо с вызовом FltReadFile так как при удалении этого вызова зависания не происходит. Любые идеи о том, почему это может произойти или как отладить его дальше?


person smichak    schedule 28.03.2016    source источник


Ответы (1)


Что ж, очевидно, проблема заключалась в том, что FltReadFile получил FileOffset, который не был выровнен с размером сектора тома. Очевидно, это проблема, если FileObject был создан для некэшируемого ввода-вывода (см. раздел «Примечания» в https://msdn.microsoft.com/en-us/library/windows/hardware/ff544286(v=vs.85).aspx). Поскольку я не могу контролировать, как был создан рассматриваемый FileObject, могут быть случаи, когда он действительно был создан для некэшированного ввода-вывода. Чтобы исправить это, я добавил следующую проверку в конце цикла for(;;):

if (BytesRead < FILE_CHUNK_SIZE) {
        break;
}

Это должно работать, если FILE_CHUNK_SIZE кратен размеру сектора тома.

person smichak    schedule 01.04.2016