/*++

    The MIT License (MIT)

    Copyright (c) 2015 Aleksey Kabanov

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

Module Name:

    LazyCopyDriver.c

Abstract:

    This is the main module of the Lazy Copy minifilter driver.

Environment:

    Kernel mode.

--*/

//------------------------------------------------------------------------
//  Includes.
//------------------------------------------------------------------------

#include "LazyCopyDriver.h"
#include "Configuration.h"
#include "Communication.h"
#include "FileLocks.h"
#include "Utilities.h"

// DriverEvents.h was generated by the 'mc.exe -z LazyCopyEtw -n -km LazyCopyEtw.mc' command.
// The file contains a macro per event, and the required code to raise the event.
#include "LazyCopyEtw.h"

//------------------------------------------------------------------------
//  Global variables.
//------------------------------------------------------------------------

// Defined in the "Globals.h".
DRIVER_GLOBAL_DATA Globals         = { 0 };

// TODO: Before releasing the driver, make sure to contact Microsoft to register your reparse point GUID.
// {611F0D07-698B-49F4-9DDB-8446662D3325}
GUID               LC_REPARSE_GUID = { 0x611F0D07, 0x698B, 0x49F4, 0x9D, 0xDB, 0x84, 0x46, 0x66, 0x2D, 0x33, 0x25 };

ULONG              LC_RANDOM_SEED  = 'SeeD';

//------------------------------------------------------------------------
//  Local function prototypes.
//------------------------------------------------------------------------

static
_Check_return_
NTSTATUS
LcInitializeGlobals(
    _In_ PDRIVER_OBJECT DriverObject
    );

static
VOID
LcFreeDriverObjects();

//------------------------------------------------------------------------
//  Text sections.
//------------------------------------------------------------------------

#ifdef ALLOC_PRAGMA
    // Functions that handle driver load/unload and instance setup/cleanup.
    #pragma alloc_text(INIT, DriverEntry)
    #pragma alloc_text(PAGE, DriverInstanceSetup)
    #pragma alloc_text(PAGE, DriverInstanceQueryTeardown)

    // Local functions.
    #pragma alloc_text(PAGE, LcInitializeGlobals)
    #pragma alloc_text(PAGE, LcFreeDriverObjects)
#endif // ALLOC_PRAGMA

//------------------------------------------------------------------------
//  Functions that handle driver load/unload and instance setup/cleanup.
//------------------------------------------------------------------------

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
/*++

Summary:

    This is the initialization function for this minifilter driver.

    It registers the current driver with the Filter Manager and initializes
    all global data structures.

Arguments:

    DriverObject - Pointer to driver object created by the system to
                   represent this driver.

    RegistryPath - Unicode string identifying where the parameters for this
                   driver are located in the registry.

Return value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS status = STATUS_SUCCESS;

    UNREFERENCED_PARAMETER(RegistryPath);

    FLT_ASSERT(DriverObject != NULL);

    LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Initializing driver\n"));

    // Register with the ETW.
    EventRegisterLazyCopyDriver();
    EventWriteDriver_Init_Start(NULL);

    __try
    {
        // Initialize global values and configure driver.
        NT_IF_FAIL_LEAVE(LcInitializeGlobals(DriverObject));
        NT_IF_FAIL_LEAVE(LcInitializeConfiguration(RegistryPath));
        NT_IF_FAIL_LEAVE(LcInitializeFileLocks());

        // Register with the Filter Manager to tell it our callback functions.
        NT_IF_FAIL_LEAVE(FltRegisterFilter(DriverObject, &FilterRegistration, &Globals.Filter));

        // Open the 'server' communication port.
        NT_IF_FAIL_LEAVE(LcCreateCommunicationPort());

        // Start filtering I/O.
        NT_IF_FAIL_LEAVE(FltStartFiltering(Globals.Filter));

        LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Driver has been initialized\n"));
    }
    __finally
    {
        if (!NT_SUCCESS(status))
        {
            LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[LazyCopy] Unable to initialize driver: %08X\n", status));
            LcFreeDriverObjects();
        }
    }

    EventWriteDriver_Init_Stop(NULL);

    return status;
}

//------------------------------------------------------------------------

NTSTATUS
DriverInstanceSetup(
    _In_ PCFLT_RELATED_OBJECTS    FltObjects,
    _In_ FLT_INSTANCE_SETUP_FLAGS Flags,
    _In_ DEVICE_TYPE              VolumeDeviceType,
    _In_ FLT_FILESYSTEM_TYPE      VolumeFilesystemType
    )
/*++

Summary:

    This function is called whenever a new driver instance is created on a volume.
    This gives us a chance to decide if we need to attach to this volume or not.

    If this function is not defined in the registration structure, automatic
    instances are always created.

    In the current implementation we allow attachment to NTFS volumes only.

Arguments:

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

    Flags                - Flags describing the reason for this attach request.

    VolumeDeviceType     - Volume type.

    VolumeFilesystemType - File system type.

Return value:

    STATUS_SUCCESS           - Attach.
    STATUS_FLT_DO_NOT_ATTACH - Do not attach.

--*/
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(Flags);
    UNREFERENCED_PARAMETER(FltObjects);

    FLT_ASSERT(FltObjects != NULL);
    FLT_ASSERT(Globals.Filter == FltObjects->Filter);

    // Allow attachments only on NTFS volumes.
    if (VolumeDeviceType == FILE_DEVICE_DISK_FILE_SYSTEM && VolumeFilesystemType == FLT_FSTYPE_NTFS)
    {
        LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Attached to Volume = %p, Instance = %p\n", FltObjects->Volume, FltObjects->Instance));
        return STATUS_SUCCESS;
    }

    LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, "[LazyCopy] Volume is not eligible for attachment: %p\n", FltObjects->Volume));
    return STATUS_FLT_DO_NOT_ATTACH;
}

//------------------------------------------------------------------------

NTSTATUS
DriverInstanceQueryTeardown(
    _In_ PCFLT_RELATED_OBJECTS             FltObjects,
    _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
    )
/*++

Summary:

    This function is called when an instance is being manually deleted by a
    call to 'FltDetachVolume' or 'FilterDetach' thereby giving us a
    chance to fail that detach request.

    If this function is not defined in the registration structure, explicit
    detach requests via 'FltDetachVolume' or 'FilterDetach' will always fail.

Arguments:

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

    Flags      - Indicating where this detach request came from.

Return value:

    Always returns STATUS_SUCCESS.

--*/
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);

    return STATUS_SUCCESS;
}

//------------------------------------------------------------------------

NTSTATUS
DriverUnload(
    _In_ FLT_FILTER_UNLOAD_FLAGS Flags
    )
/*++

Summary:

    This is the unload function for this minifilter driver.
    It's called when the minifilter is about to be unloaded.

    We can fail this unload request if this is not a mandatory unload
    indicated by the 'Flags' parameter.

Arguments:

    Flags - Indicating if this is a mandatory unload.

Return value:

    Always returns STATUS_SUCCESS.

--*/
{
    UNREFERENCED_PARAMETER(Flags);

    LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Unloading driver\n"));

    // Free global variables, close ports, unregister filter, and etc.
    LcFreeDriverObjects();

    // Unregister from the ETW.
    EventUnregisterLazyCopyDriver();

    LOG((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "[LazyCopy] Driver has been unloaded\n"));

    return STATUS_SUCCESS;
}

//------------------------------------------------------------------------
//  Local functions.
//------------------------------------------------------------------------

static
_Check_return_
NTSTATUS
LcInitializeGlobals(
    _In_ PDRIVER_OBJECT DriverObject
    )
/*++

Summary:

    This function initializes the 'Globals' structure that contains globally shared data.

Arguments:

    DriverObject - Driver object to be stored in the 'Globals' structure.

Return value:

    The return value is the status of the operation.

--*/
{
    NTSTATUS status = STATUS_SUCCESS;

    PAGED_CODE();

    IF_FALSE_RETURN_RESULT(DriverObject != NULL, STATUS_INVALID_PARAMETER_1);

    Globals.DriverObject = DriverObject;
    NT_IF_FAIL_RETURN(LcAllocateResource(&Globals.Lock));

    return status;
}

//------------------------------------------------------------------------

static
VOID
LcFreeDriverObjects()
/*++

Summary:

    This function unregisters all callbacks, closes communication port(s),
    and frees all global resources allocated.

Arguments:

    None.

ReturnvValue:

    None.

--*/
{
    PAGED_CODE();

    // Close the 'server' port.
    LcCloseCommunicationPort();

    if (Globals.Filter != NULL)
    {
        FltUnregisterFilter(Globals.Filter);
        Globals.Filter = NULL;
    }

    LcFreeConfiguration();
    LcFreeFileLocks();

    if (Globals.Lock != NULL)
    {
        LcFreeResource(Globals.Lock);
        Globals.Lock = NULL;
    }
}
