コード例 #1
0
ファイル: Registry.c プロジェクト: coding4u/LazyCopy
_Check_return_
NTSTATUS
LcGetRegistryValueDWord(
    _In_  PUNICODE_STRING RegistryPath,
    _In_  PUNICODE_STRING RegistryValueName,
    _Out_ PULONG          Value
    )
/*++

Summary:

    This function reads a REG_DWORD value from the path given.

Arguments:

    RegistryPath      - Path to the registry value to be read.

    RegistryValueName - Registry value name, which value to read.

    Value             - A pointer to a ULONG variable that receives the DWORD value read.

Return value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS                       status      = STATUS_SUCCESS;
    PKEY_VALUE_PARTIAL_INFORMATION valueBuffer = NULL;

    PAGED_CODE();

    // Other parameters will be validated in the 'LcGetRegistryValue' function.
    IF_FALSE_RETURN_RESULT(Value != NULL, STATUS_INVALID_PARAMETER_3);

    __try
    {
        NT_IF_FAIL_LEAVE(LcGetRegistryValue(RegistryPath, RegistryValueName, &valueBuffer));

        // We are expecting a DWORD value.
        NT_IF_FALSE_LEAVE(valueBuffer->Type == REG_DWORD, STATUS_INVALID_PARAMETER);

        *Value = *(PULONG)&valueBuffer->Data;
    }
    __finally
    {
        if (valueBuffer != NULL)
        {
            LcFreeNonPagedBuffer(valueBuffer);
        }
    }

    return status;
}
コード例 #2
0
ファイル: Fetch.c プロジェクト: aleksk/LazyCopy
static
_Check_return_
NTSTATUS
LcFetchFileByChunks (
    _In_  PCFLT_RELATED_OBJECTS FltObjects,
    _In_  HANDLE                SourceFileHandle,
    _In_  PLARGE_INTEGER        SourceFileSize,
    _Out_ PLARGE_INTEGER        BytesCopied
    )
/*++

Summary:

    This function copies the original file from the 'SourceFileHandle' to the currently opened
    file ('FltObjects->FileObject') by chunks.

    It maintains its own list of chunks, and extends it, if there are no chunks available to read into.
    Write operation goes from one chunk to another in a sequential order. If the next chunk is empty,
    write waits for the read to be completed, and proceeds.

    There are simple rules for chunks allocation:
    1. Up to two chunks are initially allocated:
       a) If the file is smaller than the 'ChunkSize', only one chunk is allocated with buffer
          that is equal to the file size.
       b) If the file is larger than the 'ChunkSize', two chunks are allocated, taking the
          file size into account for the second chunk size.
    2. If all chunks currently allocated are full and awaiting to be written to a disk, and
       the current amount of chunks is lesser than 'MaxChunks', an additional chunk is allocated.

    There is a corner case, when the actual file size differs from the reported one. In this case
    one of the chunks in the list will be smaller than the 'ChunkSize'.
    For example, 'MaxChunks' is 3, ChunkSize is '10', file size reported is '12', and actual file
    size is '25'.
    Two chunks will be initially allocated: [1] 10b; [2] (12-10)=2b.
    Later on, when all of them will be filled in with the data, and EOF will not be received,
    because the actual size is 25b, another chunk [3] of size 10b (ChunkSize) will be allocated.
    In total, there will be three chunks: 10b, 2b, and 10b.
    We don't reallocate the 2nd chunk, because this driver is supposed to be used with a proper
    filesystems, but making this modification might be a valuable TODO item.

    Let's look at how chunks work.
    All chunks are stored in the doubly-linked list. [Head] node doesn't contain any buffer to
    store data in. Refer to the 'FILE_CHUNK' structure for details.
    MSDN about lists: http://msdn.microsoft.com/en-us/library/windows/hardware/ff563802(v=vs.85).aspx

    For large files we will have two chunks from the beginning.
    [Head] <-> [1] <-> [2] <-> [Head]

    There are pointers for [R]ead and [W]rite.

    When the first chunk is being read, the list will look like the following:
    [Head] <-> [1] <-> [2] <-> [Head]
      [W]      [R]

    [W] is awaiting for the [1] to be filled in with the data before writing it to the disk.

    When the [1] chunk is filled with the data:
    [Head] <-> [1*] <-> [2] <-> [Head]
                [W]     [R]

    [1] is full and is being written to a disk, and we're reading into chunk [2].
    Let's also assume that the reads are faster then writes. When the [2] chunk is
    full, there are no free chunks available:
    [Head] <-> [1*] <-> [2*] <-> [Head]
                [W]      [R]

    If the current amount of chunks is lesser than the 'MaxChunks' value, a new chunk will be
    allocated before the next [R] node. In this case it will be added before [Head] to the end
    of the list, and read will continue:
    [Head] <-> [1*] <-> [2*] <-> [3] <-> [Head]
                [W]              [R]

    Then [W] and [R] finish, and [W] moves to the [2] chunk.
    [Head] <-> [1] <-> [2*] <-> [3*] <-> [Head]
                        [W]      [R]

    [R] sees that [1] chunk is available, and reads into it:
    [Head] <-> [1] <-> [2*] <-> [3*] <-> [Head]
               [R]      [W]

    After [R] finishes reading, there are no free chunks again:
    [Head] <-> [1*] <-> [2*] <-> [3*] <-> [Head]
               [R]       [W]

    A new chunk can be allocated again before the [2]:
    [Head] <-> [1*] <-> [4] <-> [2*] <-> [3*] <-> [Head]
                        [R]      [W]

    With this approach, [R] will fill chunks [1]->[2]->[3]->[1]->[4], and write will
    write them in the same order.
    I.e. allocating a new chunk before the next filled chunk (if the amount of chunks
    is lesser than the 'MaxChunks') makes sure that the data is written sequentially,
    and there is no need to constantly seek in the file.

Arguments:

    FltObjects       - Pointer to the 'FLT_RELATED_OBJECTS' data structure containing
                       opaque handles to this filter, instance, its associated volume and
                       file object.

    SourceFileHandle - Handle to the source file to copy content from.

    SourceFileSize   - Size of the source file.

    BytesCopied      - Pointer to the LARGE_INTEGER structure that receives the amount
                       of bytes copied.

Return Value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS               status                = STATUS_SUCCESS;

    LIST_ENTRY             chunksListHead        = { 0 };
    ULONG                  chunkListLength       = 0;

    // State of the R/W operations.
    BOOLEAN                readComplete          = FALSE;
    BOOLEAN                writeComplete         = FALSE;
    PFILE_CHUNK            readChunk             = NULL;
    PFILE_CHUNK            writeChunk            = NULL;
    BOOLEAN                eof                   = FALSE;
    BOOLEAN                waitingForRead        = FALSE;
    KEVENT                 writeEvent            = { 0 };

    WRITE_CALLBACK_CONTEXT writeCallbackContext  = { 0 };

    LARGE_INTEGER          waitTimeout           = { 0 };
    LARGE_INTEGER          zeroTimeout           = { 0 };

    IO_STATUS_BLOCK        statusBlock           = { 0 };

    LARGE_INTEGER          remainingBytes        = { 0 };
    LARGE_INTEGER          totalBytesRead        = { 0 };
    LARGE_INTEGER          totalBytesWritten     = { 0 };
    LARGE_INTEGER          sourceFileOffset      = { 0 };
    LARGE_INTEGER          destinationFileOffset = { 0 };

    PAGED_CODE();

    FLT_ASSERT(FltObjects          != NULL);
    FLT_ASSERT(SourceFileHandle    != NULL);
    FLT_ASSERT(SourceFileSize      != NULL);
    FLT_ASSERT(SourceFileSize->QuadPart > 0);
    FLT_ASSERT(BytesCopied         != NULL);
    FLT_ASSERT(KeGetCurrentIrql()  == PASSIVE_LEVEL);

    *BytesCopied = RtlConvertLongToLargeInteger(0);

    __try
    {
        // Set the relative timeout (1 stands for 100 nanoseconds).
        waitTimeout           = RtlConvertLongToLargeInteger(-10000);
        waitTimeout.QuadPart *= TimeoutMilliseconds;

        KeInitializeEvent(&writeEvent, NotificationEvent, TRUE);
        writeCallbackContext.Event = &writeEvent;

        remainingBytes.QuadPart = SourceFileSize->QuadPart;

        NT_IF_FAIL_LEAVE(LcInitializeChunksList(FltObjects->Instance, &chunksListHead, remainingBytes, &chunkListLength));

        for (;;)
        {
            if (waitingForRead)
            {
                // Wait for the read operation to finish.
                NT_IF_FAIL_LEAVE(ZwWaitForSingleObject(SourceFileHandle, FALSE, &waitTimeout));
                readComplete = TRUE;
            }
            else
            {
                readComplete = ZwWaitForSingleObject(SourceFileHandle, FALSE, &zeroTimeout) == STATUS_SUCCESS;
            }

            writeComplete = KeReadStateEvent(&writeEvent) != 0;

            if (!eof && readComplete)
            {
                // If it's not the first read, update status of the current chunk.
                if (readChunk != NULL)
                {
                    status = statusBlock.Status;

                    if (NT_SUCCESS(status) || status == STATUS_END_OF_FILE)
                    {
                        ULONG bytesRead = (ULONG)statusBlock.Information;

                        readChunk->BytesInBuffer   = bytesRead;

                        remainingBytes.QuadPart   -= bytesRead;
                        totalBytesRead.QuadPart   += bytesRead;
                        sourceFileOffset.QuadPart += bytesRead;

                        if (status == STATUS_END_OF_FILE || bytesRead < readChunk->BufferSize)
                        {
                            eof    = TRUE;
                            status = STATUS_SUCCESS;

                            // Will not be used later in this case, only to have the proper data here.
                            remainingBytes.QuadPart = 0;
                        }
                    }

                    NT_IF_FAIL_LEAVE(status);
                }

                // Move to the next available chunk and schedule read.
                if (!eof)
                {
                    // If the remote file system returned an invalid file size, when we started reading it,
                    // this value might be negative. Set it to the default, so the newly allocated chunk
                    // will have the maximum allowed size.
                    if (remainingBytes.QuadPart <= 0)
                    {
                        remainingBytes.QuadPart = ChunkSize;
                    }

                    NT_IF_FAIL_LEAVE(LcGetNextAvailableChunk(
                        FltObjects->Instance,
                        &chunksListHead,
                        &readChunk,
                        &chunkListLength,
                        TRUE,              // Read operation.
                        &remainingBytes,
                        &writeEvent,
                        &waitTimeout));

                    // Schedule read operation for the current chunk.
                    status = ZwReadFile(
                        SourceFileHandle,
                        NULL,
                        NULL,
                        NULL,
                        &statusBlock,
                        readChunk->Buffer,
                        readChunk->BufferSize,
                        &sourceFileOffset,
                        NULL);

                    NT_IF_FALSE_LEAVE(status == STATUS_PENDING || status == STATUS_SUCCESS, status);
                }
            }

            if (writeComplete)
            {
                if (!waitingForRead)
                {
                    // If it's not the first write, update status of the current chunk.
                    if (writeChunk != NULL)
                    {
                        NT_IF_FAIL_LEAVE(writeCallbackContext.Status);

                        writeChunk->BytesInBuffer       = 0;
                        totalBytesWritten.QuadPart     += writeCallbackContext.BytesWritten;
                        destinationFileOffset.QuadPart += writeCallbackContext.BytesWritten;
                    }

                    NT_IF_FAIL_LEAVE(LcGetNextAvailableChunk(
                        FltObjects->Instance,
                        &chunksListHead,
                        &writeChunk,
                        &chunkListLength,
                        FALSE,             // Write operation.
                        NULL,
                        NULL,
                        NULL));
                }

                waitingForRead = FALSE;

                // If we don't have any data in the current chunk, restart from the beginning of the loop.
                if (writeChunk->BytesInBuffer == 0)
                {
                    if (eof)
                    {
                        // We're done!
                        break;
                    }
                    else
                    {
                        // Since we're waiting for the read to complete for the current chunk,
                        // don't change the chunk position on next iteration.
                        waitingForRead = TRUE;

                        continue;
                    }
                }

                KeClearEvent(&writeEvent);

                NT_IF_FAIL_LEAVE(FltWriteFile(
                    FltObjects->Instance,
                    FltObjects->FileObject,
                    &destinationFileOffset,
                    writeChunk->BytesInBuffer,
                    writeChunk->Buffer,
                    FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
                    NULL,
                    (PFLT_COMPLETED_ASYNC_IO_CALLBACK)&LcWriteCallback,
                    &writeCallbackContext));
            }
        }

        *BytesCopied = totalBytesWritten;
    }
    __finally
    {
        LcClearChunksList(FltObjects->Instance, &chunksListHead);
    }

    return status;
}
コード例 #3
0
ファイル: Fetch.c プロジェクト: aleksk/LazyCopy
NTSTATUS
LcFetchRemoteFile (
    _In_  PCFLT_RELATED_OBJECTS FltObjects,
    _In_  PUNICODE_STRING       SourceFile,
    _In_  PUNICODE_STRING       TargetFile,
    _In_  BOOLEAN               UseCustomHandler,
    _Out_ PLARGE_INTEGER        BytesCopied
    )
/*++

Summary:

    This function copies the remote file content to the current file object.

    In order for the remote file to be fetched, make sure that the network redirector
    device is used, i.e. the 'SourceFile' root points to the '\Device\Mup\<path>'.

Arguments:

    FltObjects       - Pointer to the 'FLT_RELATED_OBJECTS' data structure containing
                       opaque handles to this filter, instance, its associated volume and
                       file object.

    SourceFile       - Path to the file to fetch content from.

    TargetFile       - Path to the file to store content to.

    UseCustomHandler - Whether the file should be fetched by the user-mode client.

    BytesCopied      - The amount of bytes copied.

Return Value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS                     status           = STATUS_SUCCESS;
    HANDLE                       sourceFileHandle = NULL;
    IO_STATUS_BLOCK              statusBlock      = { 0 };
    FILE_STANDARD_INFORMATION    standardInfo     = { 0 };
    FILE_END_OF_FILE_INFORMATION eofInfo          = { 0 };

    PAGED_CODE();

    IF_FALSE_RETURN_RESULT(FltObjects  != NULL, STATUS_INVALID_PARAMETER_1);
    IF_FALSE_RETURN_RESULT(SourceFile  != NULL, STATUS_INVALID_PARAMETER_2);
    IF_FALSE_RETURN_RESULT(TargetFile  != NULL, STATUS_INVALID_PARAMETER_3);
    IF_FALSE_RETURN_RESULT(BytesCopied != NULL, STATUS_INVALID_PARAMETER_5);

    FLT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

    *BytesCopied = RtlConvertLongToLargeInteger(0);

    __try
    {
        LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Fetching content from: '%wZ' -> '%wZ'\n", SourceFile, TargetFile));

        if (UseCustomHandler)
        {
            NT_IF_FAIL_LEAVE(LcFetchFileInUserMode(SourceFile, TargetFile, BytesCopied));
        }
        else
        {
            //
            // Open the source file and make sure it's not empty.
            //

            NT_IF_FAIL_LEAVE(LcOpenFile(SourceFile, TargetFile, &sourceFileHandle));

            NT_IF_FAIL_LEAVE(ZwQueryInformationFile(sourceFileHandle, &statusBlock, &standardInfo, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation));
            if (standardInfo.EndOfFile.QuadPart == 0)
            {
                // No need to copy an empty file.
                __leave;
            }

            // Extend the target file, so all readers that wait for the content to be copied will get the actual file size information.
            // Remote file system may return incorrect information, but we are doing it only for the cases, when multiple threads
            // try to access the same file, while we are fetching it.
            eofInfo.EndOfFile.QuadPart = standardInfo.EndOfFile.QuadPart;
            NT_IF_FAIL_LEAVE(FltSetInformationFile(FltObjects->Instance, FltObjects->FileObject, &eofInfo, sizeof(eofInfo), FileEndOfFileInformation));

            //
            // Copy source file contents into the local (target) file.
            //

            NT_IF_FAIL_LEAVE(LcFetchFileByChunks(
                FltObjects,
                sourceFileHandle,
                &standardInfo.EndOfFile,
                BytesCopied));
        }
    }
    __finally
    {
        if (sourceFileHandle != NULL)
        {
            ZwClose(sourceFileHandle);
        }
    }

    return status;
}
コード例 #4
0
ファイル: Registry.c プロジェクト: coding4u/LazyCopy
_Check_return_
NTSTATUS
LcGetRegistryValue(
    _In_     PUNICODE_STRING                 RegistryPath,
    _In_     PUNICODE_STRING                 RegistryValueName,
    _Outptr_ PKEY_VALUE_PARTIAL_INFORMATION* ValueBuffer
    )
/*++

Summary:

    This function retrieves the registry value from the path given.

    Caller should free the 'ValueBuffer' received using the 'LcFreeNonPagedBuffer' function.

Arguments:

    RegistryPath      - Path from where the registry value should be read.

    RegistryValueName - Name of the registry value to read.

    ValueBuffer       - A pointer to a variable that receives the value content.

Return value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS                       status            = STATUS_SUCCESS;

    // Registry key handle.
    HANDLE                         registryKeyHandle = NULL;
    OBJECT_ATTRIBUTES              attributes        = { 0 };

    // Buffer to read the value data into.
    PKEY_VALUE_PARTIAL_INFORMATION valueBuffer       = NULL;
    ULONG                          valueLength       = 0;

    PAGED_CODE();

    IF_FALSE_RETURN_RESULT(NT_SUCCESS(RtlUnicodeStringValidate(RegistryPath)),      STATUS_INVALID_PARAMETER_1);
    IF_FALSE_RETURN_RESULT(RegistryPath->Buffer != NULL,                            STATUS_INVALID_PARAMETER_1);

    IF_FALSE_RETURN_RESULT(NT_SUCCESS(RtlUnicodeStringValidate(RegistryValueName)), STATUS_INVALID_PARAMETER_2);
    IF_FALSE_RETURN_RESULT(RegistryValueName->Buffer != NULL,                       STATUS_INVALID_PARAMETER_2);

    IF_FALSE_RETURN_RESULT(ValueBuffer != NULL,                                     STATUS_INVALID_PARAMETER_3);

    __try
    {
        // Open the registry key given.
        InitializeObjectAttributes(&attributes, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
        NT_IF_FAIL_LEAVE(ZwOpenKey(&registryKeyHandle, KEY_READ, &attributes));

        // Get the length of the value for proper buffer allocation.
        status = ZwQueryValueKey(registryKeyHandle, RegistryValueName, KeyValuePartialInformation, NULL, 0, &valueLength);
        if (status != STATUS_BUFFER_TOO_SMALL && status != STATUS_BUFFER_OVERFLOW)
        {
            status = STATUS_INVALID_PARAMETER;
            __leave;
        }

        // Allocate buffer and read value into it.
        NT_IF_FAIL_LEAVE(LcAllocateNonPagedBuffer((PVOID*)&valueBuffer, valueLength));
        NT_IF_FAIL_LEAVE(ZwQueryValueKey(registryKeyHandle, RegistryValueName, KeyValuePartialInformation, valueBuffer, valueLength, &valueLength));

        // Set the output value and set the 'valueBuffer' to NULL, so it won't be freed in the __finally block.
        *ValueBuffer = valueBuffer;
        valueBuffer = NULL;
    }
    __finally
    {
        if (registryKeyHandle != NULL)
        {
            ZwClose(registryKeyHandle);
        }

        if (valueBuffer != NULL)
        {
            LcFreeNonPagedBuffer(valueBuffer);
        }
    }

    return status;
}
コード例 #5
0
ファイル: Registry.c プロジェクト: coding4u/LazyCopy
_Check_return_
NTSTATUS
LcGetRegistryValueString(
    _In_  PUNICODE_STRING RegistryPath,
    _In_  PUNICODE_STRING RegistryValueName,
    _Out_ PUNICODE_STRING Value
    )
/*++

Summary:

    This function reads REG_MULTI_SZ or REG_SZ value from the path given.

    Caller should manually free the string received using the 'LcFreeUnicodeString' function.

Arguments:

    RegistryPath      - Path to the registry value to be read.

    RegistryValueName - Registry value name, which value to read.

    Value             - A pointer to a unicode string that receives the value.

Return value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS                       status      = STATUS_SUCCESS;
    PKEY_VALUE_PARTIAL_INFORMATION valueBuffer = NULL;
    UNICODE_STRING                 string      = { 0 };

    PAGED_CODE();

    // Other parameters will be validated in the 'LcGetRegistryValue' function.
    IF_FALSE_RETURN_RESULT(Value != NULL, STATUS_INVALID_PARAMETER_3);

    __try
    {
        NT_IF_FAIL_LEAVE(LcGetRegistryValue(RegistryPath, RegistryValueName, &valueBuffer));

        // We are expecting REG_MULTI_SZ or REG_SZ value.
        NT_IF_FALSE_LEAVE(valueBuffer->Type == REG_MULTI_SZ || valueBuffer->Type == REG_SZ, STATUS_INVALID_PARAMETER);

        // Allocate internal string buffer and copy the value there.
        NT_IF_FAIL_LEAVE(LcAllocateUnicodeString(&string, (USHORT)valueBuffer->DataLength));
        __analysis_assume(string.Buffer != NULL);
        RtlCopyMemory(string.Buffer, &valueBuffer->Data, valueBuffer->DataLength);
        string.Length = (USHORT)valueBuffer->DataLength - sizeof(WCHAR);

        // Set the output value and set the 'string.Buffer' to NULL, so it won't be freed in the __finally block.
        *Value        = string;
        string.Buffer = NULL;
    }
    __finally
    {
        if (valueBuffer != NULL)
        {
            LcFreeNonPagedBuffer(valueBuffer);
        }

        if (string.Buffer != NULL)
        {
            LcFreeUnicodeString(&string);
        }
    }

    return status;
}