/*++

Copyright (c) 1999 Microsoft Corporation

Module Name:

    kmcancel.c

Abstract:

    This module contains code to verify handling of IRP cancelation requests.

Author:

    Abolade Gbadegesin (aboladeg)   05-June-2000

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

#define THREAD_COUNT 10
#define REQUEST_COUNT 50
#define DD_TARGET_DEVICE_NAME   DD_IP_DEVICE_NAME
#define TARGET_IO_CONTROL_CODE  IOCTL_IP_RTCHANGE_NOTIFY_REQUEST

//
// Target driver state.
//

PDEVICE_OBJECT TargetDeviceObject = NULL;
PFILE_OBJECT TargetFileObject = NULL;

//
// Thread-management state.
//

ULONG KmcThreadCount;
KEVENT KmcStopEvent;
KSEMAPHORE KmcStopSemaphore;


//
// FUNCTION PROTOTYPES (alphabetically)
//

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    );

NTSTATUS
KmcRequestCompletionRoutine(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    );

VOID
KmcRequestThread(
    PVOID Context
    );

VOID
KmcUnloadDriver(
    IN PDRIVER_OBJECT  DriverObject
    );

VOID
KmcUpdateThread(
    PVOID Context
    );


NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
    ULONG i;
    NTSTATUS Status;
    HANDLE ThreadHandle;
    UNICODE_STRING UnicodeString;

    KdPrint(("DriverEntry\n"));
    DriverObject->DriverUnload = KmcUnloadDriver;
    KmcThreadCount = 0;
    KeInitializeEvent(&KmcStopEvent, NotificationEvent, FALSE);
    KeInitializeSemaphore(&KmcStopSemaphore, 0, MAXLONG);

    //
    // Obtain the target driver's device-object
    //

    RtlInitUnicodeString(&UnicodeString, DD_TARGET_DEVICE_NAME);
    Status =
        IoGetDeviceObjectPointer(
            &UnicodeString,
            SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE,
            &TargetFileObject,
            &TargetDeviceObject
            );
    if (!NT_SUCCESS(Status)) {
        KdPrint(("DriverEntry: error %x getting IP object\n", Status));
        return Status;
    }

    ObReferenceObject(TargetDeviceObject);


    //
    // Start the request/update threads.
    // The request threads are responsible for issuing the I/O control
    // whose cancelation is being verified, and the update threads are
    // responsible for triggering completion of those I/O control requests
    // in order to highlight any potential race-conditions.
    //

    for (i = 0; i < THREAD_COUNT; i++) {
        Status =
            PsCreateSystemThread(
                &ThreadHandle,
                GENERIC_ALL,
                NULL,
                NULL,
                NULL,
                KmcUpdateThread,
                NULL
                );
        if (NT_SUCCESS(Status)) {
            ZwClose(ThreadHandle);
            ++KmcThreadCount;
        }
        Status =
            PsCreateSystemThread(
                &ThreadHandle,
                GENERIC_ALL,
                NULL,
                NULL,
                NULL,
                KmcRequestThread,
                NULL
                );
        if (NT_SUCCESS(Status)) {
            ZwClose(ThreadHandle);
            ++KmcThreadCount;
        }
    }

    return STATUS_SUCCESS;

} // DriverEntry

typedef struct _KMC_REQUEST {
    IO_STATUS_BLOCK IoStatus;
    PIRP Irp;
    ULONG ReferenceCount;
} KMC_REQUEST, *PKMC_REQUEST;


NTSTATUS
KmcRequestCompletionRoutine(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    )
{
    PKMC_REQUEST Request = (PKMC_REQUEST)Context;
    if (InterlockedDecrement(&Request->ReferenceCount) == 0) {
        IoFreeIrp(Request->Irp);
        ExFreePool(Request);
    }
    return STATUS_MORE_PROCESSING_REQUIRED;
} // KmcCompletionRoutine


VOID
KmcRequestThread(
    PVOID Context
    )
{
    ULONG i, Index;
    LARGE_INTEGER Interval;
    PIRP Irp;
    PIO_STACK_LOCATION IrpSp;
    KIRQL OldIrql;
    PKMC_REQUEST Request, RequestArray[REQUEST_COUNT];

    for (; !KeReadStateEvent(&KmcStopEvent); ) {

        //
        // Queue a series of requests to the driver.
        //

        Index = 0;
        RtlZeroMemory(RequestArray, sizeof(RequestArray));
        for (i = 0; i < REQUEST_COUNT; i++) {
            Request = ExAllocatePool(NonPagedPool, sizeof(*Request));
            if (!Request) {
                continue;
            }
            RtlZeroMemory(Request, sizeof(*Request));

            Irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE);
            if (!Irp) {
                continue;
            }
            Request->Irp = Irp;

            Irp->RequestorMode = KernelMode;
            Irp->Tail.Overlay.Thread = PsGetCurrentThread();
            Irp->Tail.Overlay.OriginalFileObject = TargetFileObject;
            IoSetCompletionRoutine(
                Irp,
                KmcRequestCompletionRoutine,
                Request,
                TRUE,
                TRUE,
                TRUE
                );

            IrpSp = IoGetNextIrpStackLocation(Irp);
            IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
            IrpSp->Parameters.DeviceIoControl.IoControlCode =
                TARGET_IO_CONTROL_CODE;
            IrpSp->DeviceObject = TargetDeviceObject;
            IrpSp->FileObject = TargetFileObject;

            Request->ReferenceCount = 2;
            RequestArray[Index++] = Request;
            IoCallDriver(TargetDeviceObject, Request->Irp);
        }

        //
        // Delay execution for a short interval, and cancel the requests.
        //

        Interval.QuadPart = -10 * 1000 * 50;
        KeDelayExecutionThread(KernelMode, FALSE, &Interval);

        for (i = 0; i < REQUEST_COUNT; i++) {
            if (Request = RequestArray[i]) {
                IoCancelIrp(Request->Irp);
                if (InterlockedDecrement(&Request->ReferenceCount) == 0) {
                    IoFreeIrp(Request->Irp);
                    ExFreePool(Request);
                }
            }
        }
    }

    KeReleaseSemaphore(&KmcStopSemaphore, 0, 1, FALSE);

} // KmcRequestThread


VOID
KmcUnloadDriver(
    IN PDRIVER_OBJECT DriverObject
    )
{
    KdPrint(("KmcUnloadDriver\n"));

    //
    // Signal all threads to stop, and wait for them to exit.
    //

    KeSetEvent(&KmcStopEvent, 0, FALSE);
    while (KmcThreadCount--) {
        KeWaitForSingleObject(
            &KmcStopSemaphore, Executive, KernelMode, FALSE, NULL
            );
    }

    //
    // Release references to the IP device object
    //

    ObDereferenceObject(TargetFileObject);
    ObDereferenceObject(TargetDeviceObject);

} // KmcUnloadDriver


extern
VOID
LookupRoute(
    IPRouteLookupData* RouteLookupData,
    IPRouteEntry* RouteEntry
    );

VOID
KmcUpdateThread(
    PVOID Context
    )
{
    KEVENT Event;
    LARGE_INTEGER Interval;
    IO_STATUS_BLOCK IoStatus;
    PIRP Irp;
    IPRouteEntry RouteEntry;
    IPRouteLookupData RouteLookupData;
    NTSTATUS Status;

    //
    // Retrieve information from IP for use in triggering route-changes.
    //

    RtlZeroMemory(&RouteEntry, sizeof(RouteEntry));
    RouteLookupData.Version = 0;
    RouteLookupData.SrcAdd = 0;
    RouteLookupData.DestAdd = 0x100000a; // 10.0.0.1
    LookupRoute(&RouteLookupData, &RouteEntry);

    RouteEntry.ire_dest = 0x100000a; // 10.0.0.1
    RouteEntry.ire_mask = 0xffffffff;
    RouteEntry.ire_proto = IRE_PROTO_NETMGMT;

    //
    // Repeatedly issue changes to the IP routing table, until told to exit.
    //

    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
    for (; !KeReadStateEvent(&KmcStopEvent); ) {

        Interval.QuadPart = -10 * 1000 * 50;
        KeDelayExecutionThread(KernelMode, FALSE, &Interval);

        Irp =
            IoBuildDeviceIoControlRequest(
                IOCTL_IP_SET_ROUTEWITHREF,
                TargetDeviceObject,
                &RouteEntry,
                sizeof(RouteEntry),
                NULL,
                0,
                FALSE,
                &Event,
                &IoStatus
                );
        if (!Irp) { continue; }
        Status = IoCallDriver(TargetDeviceObject, Irp);
        if (Status == STATUS_PENDING) {
            KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
        }
    }

    KeReleaseSemaphore(&KmcStopSemaphore, 0, 1, FALSE);
        
} // KmcUpdateThread