/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    misc.c

Abstract:

    This module contains the code to implement the NtFlushBuffersFile,
    NtSetNewSizeFile, IoQueueWorkItem, and NtCancelIoFile system services
    for the NT I/O system.

--*/

#include "iomgr.h"

//
// Local function prototypes follow
//

VOID
IopProcessWorkItem(
    IN PVOID Parameter
    );

#pragma alloc_text(PAGE, NtCancelIoFile)
#pragma alloc_text(PAGE, NtDeleteFile)
#pragma alloc_text(PAGE, NtFlushBuffersFile)
#pragma alloc_text(PAGE, NtQueryAttributesFile)
#pragma alloc_text(PAGE, NtQueryFullAttributesFile)
#pragma alloc_text(PAGE, IopProcessWorkItem)

NTSTATUS
NtCancelIoFile (
    __in HANDLE FileHandle,
    __out PIO_STATUS_BLOCK IoStatusBlock
    )

/*++

Routine Description:

    This service causes all pending I/O operations for the specified file to be
    marked as canceled.  Most types of operations can be canceled immediately,
    while others may continue toward completion before they are actually
    canceled and the caller is notified.

    Only those pending operations that were issued by the current thread using
    the specified handle are canceled.  Any operations issued for the file by
    any other thread or any other process continues normally.

Arguments:

    FileHandle - Supplies a handle to the file whose operations are to be
        canceled.

    IoStatusBlock - Address of the caller's I/O status block.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    KPROCESSOR_MODE requestorMode;
    PETHREAD thread;
    BOOLEAN found = FALSE;
    PLIST_ENTRY header;
    PLIST_ENTRY entry;
    KIRQL irql;

    PAGED_CODE();


    //
    // Get the address of the current thread.  The thread contains a list of
    // the pending operations for this file.
    //

    thread = PsGetCurrentThread();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    requestorMode = KeGetPreviousModeByThread(&thread->Tcb);

    if (requestorMode != KernelMode) {

        //
        // The caller's access mode is user, so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus( IoStatusBlock );

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred attempting to probe the caller'
            // I/O status block.  Simply return an appropriate error status
            // code.
            //

            return GetExceptionCode();
        }
    }

    //
    // There were no blatant errors so far, so reference the file object so
    // the target device object can be found.  Note that if the handle does
    // not refer to a file object, or if the caller does not have the required
    // access to the file, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        0,
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        NULL );
    if (!NT_SUCCESS( status )) {
        return(status);
    }

    //
    // Note that here the I/O system would normally make a check to determine
    // whether or not the file was opened for synchronous I/O.  If it was, then
    // it would attempt to exclusively acquire the file object lock.  However,
    // since this service is attempting to cancel all of the I/O for the file,
    // it does not make much sense to wait until it has all completed before
    // attempting to cancel it.
    //


    //
    // Update the operation count statistic for the current process for
    // operations other than read and write.
    //

    IopUpdateOtherOperationCount();

    //
    // Walk the list of IRPs on the thread's pending I/O queue looking for IRPs
    // which specify the same file as the FileHandle refers to.  For each IRP
    // found, set its cancel flag.  If no IRPs are found, simply complete the
    // I/O here.  The only synchronization needed here is to block out all APCs
    // for this thread so that no I/O can complete and remove packets from the
    // queue.  No considerations need be made for multi-processing since this
    // thread can only be running on one processor at a time and this routine
    // has control of the thread for now.
    //

    KeRaiseIrql( APC_LEVEL, &irql );

    header = &thread->IrpList;
    entry = thread->IrpList.Flink;

    while (header != entry) {

        //
        // An IRP has been found for this thread.  If the IRP refers to the
        // appropriate file object, set its cancel flag and remember that it
        // was found;  otherwise, simply continue the loop.
        //

        irp = CONTAINING_RECORD( entry, IRP, ThreadListEntry );
        if (irp->Tail.Overlay.OriginalFileObject == fileObject) {
            found = TRUE;
            IoCancelIrp( irp );
        }

        entry = entry->Flink;
    }

    //
    // Lower the IRQL back down to what it was on entry to this procedure.
    //

    KeLowerIrql( irql );

    if (found) {

        LARGE_INTEGER interval;

        //
        // Delay execution for a time and let the request
        // finish.  The delay time is 10ms.
        //

        interval.QuadPart = -10 * 1000 * 10;

        //
        // Wait for a while so the canceled requests can complete.
        //

        while (found) {

            (VOID) KeDelayExecutionThread( KernelMode, FALSE, &interval );

            found = FALSE;

            //
            // Raise the IRQL to prevent modification to the IRP list by the
            // thread's APC routine.
            //

            KeRaiseIrql( APC_LEVEL, &irql );

            //
            // Check the IRP list for requests which refer to the specified
            // file object.
            //

            entry = thread->IrpList.Flink;

            while (header != entry) {

                //
                // An IRP has been found for this thread.  If the IRP refers
                // to the appropriate file object,  remember that it
                // was found;  otherwise, simply continue the loop.
                //

                irp = CONTAINING_RECORD( entry, IRP, ThreadListEntry );
                if (irp->Tail.Overlay.OriginalFileObject == fileObject) {
                    found = TRUE;
                    break;
                }

                entry = entry->Flink;
            }

            //
            // Lower the IRQL back down to what it was on entry to this procedure.
            //

            KeLowerIrql( irql );

        }
    }

    try {

        //
        // Write the status back to the user.
        //

        IoStatusBlock->Status = STATUS_SUCCESS;
        IoStatusBlock->Information = 0L;

    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred attempting to write the caller's
        // I/O status block; however, the service completed successfully so
        // just return success.
        //

    }

    //
    // Dereference the file object.
    //

    ObDereferenceObject( fileObject );

    return STATUS_SUCCESS;
}

NTSTATUS
NtDeleteFile (
    __in POBJECT_ATTRIBUTES ObjectAttributes
    )

/*++

Routine Description:

    This service deletes the specified file.

Arguments:

    ObjectAttributes - Supplies the attributes to be used for file object (name,
        SECURITY_DESCRIPTOR, etc.)

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    KPROCESSOR_MODE requestorMode;
    NTSTATUS status;
    OPEN_PACKET openPacket;
    DUMMY_FILE_OBJECT localFileObject;
    HANDLE handle;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    requestorMode = KeGetPreviousMode();

    //
    // Build a parse open packet that tells the parse method to open the file
    // for open for delete access w/the delete bit set, and then close it.
    //

    RtlZeroMemory( &openPacket, sizeof( OPEN_PACKET ) );

    openPacket.Type = IO_TYPE_OPEN_PACKET;
    openPacket.Size = sizeof( OPEN_PACKET );
    openPacket.CreateOptions = FILE_DELETE_ON_CLOSE;
    openPacket.ShareAccess = (USHORT) FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    openPacket.Disposition = FILE_OPEN;
    openPacket.DeleteOnly = TRUE;
    openPacket.TraversedMountPoint = FALSE;
    openPacket.LocalFileObject = &localFileObject;

    //
    // Update the open count for this process.
    //

    IopUpdateOtherOperationCount();

    //
    // Open the object by its name.  Because of the special DeleteOnly flag
    // set in the open packet, the parse routine will open the file, and
    // then realize that it is only deleting the file, and will therefore
    // immediately dereference the file.  This will cause the cleanup and
    // the close to be sent to the file system, thus causing the file to
    // be deleted.
    //

    status = ObOpenObjectByName( ObjectAttributes,
                                 (POBJECT_TYPE) NULL,
                                 requestorMode,
                                 NULL,
                                 DELETE,
                                 &openPacket,
                                 &handle );

    //
    // The operation is successful if the parse check field of the open packet
    // indicates that the parse routine was actually invoked, and the final
    // status field of the packet is set to success.
    //

    if (openPacket.ParseCheck != OPEN_PACKET_PATTERN) {
        return status;
    } else {
        return openPacket.FinalStatus;
    }
}

NTSTATUS
NtFlushBuffersFile (
    __in HANDLE FileHandle,
    __out PIO_STATUS_BLOCK IoStatusBlock
    )

/*++

Routine Description:

    This service causes all buffered data to the file to be written.

Arguments:

    FileHandle - Supplies a handle to the file whose buffers should be flushed.

    IoStatusBlock - Address of the caller's I/O status block.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PKEVENT event;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    IO_STATUS_BLOCK localIoStatus;
    OBJECT_HANDLE_INFORMATION objectHandleInformation;
    BOOLEAN synchronousIo;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    if (requestorMode != KernelMode) {

        //
        // The caller's access mode is not kernel so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus( IoStatusBlock );

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred attempting to probe the caller's
            // I/O status block.  Simply return an appropriate error status
            // code.
            //

            return GetExceptionCode();

        }
    }

    //
    // There were no blatant errors so far, so reference the file object so
    // the target device object can be found.  Note that if the handle does
    // not refer to a file object, or if the caller does not have the required
    // access to the file, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        0,
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        &objectHandleInformation );
    if (!NT_SUCCESS( status )) {
        return status;
    }

    //
    // Ensure that the caller has either WRITE or APPEND access to the file
    // before allowing this call to continue.  This is especially important
    // if the caller opened a volume, where a flush operation may flush more
    // than what this opener has written to buffers.  Note however that if
    // this is a pipe, then the APPEND access cannot be made since this
    // access code is overlaid with the CREATE_PIPE_INSTANCE access.
    //

    if (SeComputeGrantedAccesses( objectHandleInformation.GrantedAccess,
                                  (!(fileObject->Flags & FO_NAMED_PIPE) ?
                                  FILE_APPEND_DATA : 0) |
                                  FILE_WRITE_DATA ) == 0) {
        ObDereferenceObject( fileObject );
        return STATUS_ACCESS_DENIED;
    }

    //
    // Make a special check here to determine whether this is a synchronous
    // I/O operation.  If it is, then wait here until the file is owned by
    // the current thread.  If this is not a (serialized) synchronous I/O
    // operation, then allocate and initialize the local event.
    //

    if (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                ObDereferenceObject( fileObject );
                return status;
            }
        }
        synchronousIo = TRUE;
        event = NULL;
    } else {

        //
        // This is a synchronous API being invoked for a file that is opened
        // for asynchronous I/O.  This means that this system service is
        // to synchronize the completion of the operation before returning
        // to the caller.  A local event is used to do this.
        //

        event = ExAllocatePool( NonPagedPool, sizeof( KEVENT ) );
        if (event == NULL) {
            ObDereferenceObject( fileObject );
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        KeInitializeEvent( event, SynchronizationEvent, FALSE );
        synchronousIo = FALSE;
    }

    //
    // Set the file object to the Not-Signaled state.
    //

    KeClearEvent( &fileObject->Event );

    //
    // Get the address of the target device object.
    //

    deviceObject = IoGetRelatedDeviceObject( fileObject );

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    //

    irp = IoAllocateIrp( deviceObject->StackSize, FALSE );
    if (!irp) {

        //
        // An exception was incurred while attempting to allocate the IRP.
        // Cleanup and return an appropriate error status code.
        //

        if (event) {
            ExFreePool( event );
        }

        IopAllocateIrpCleanup( fileObject, (PKEVENT) NULL );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->RequestorMode = requestorMode;

    //
    // Fill in the service independent parameters in the IRP.
    //

    if (synchronousIo) {
        irp->UserEvent = (PKEVENT) NULL;
        irp->UserIosb = IoStatusBlock;
    } else {
        irp->UserEvent = event;
        irp->UserIosb = &localIoStatus;
        irp->Flags = IRP_SYNCHRONOUS_API;
    }
    irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE) NULL;

    //
    // Get a pointer to the stack location for the first driver.  This is used
    // to pass the original function codes and parameters.
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    irpSp->MajorFunction = IRP_MJ_FLUSH_BUFFERS;
    irpSp->FileObject = fileObject;

    //
    // Queue the packet, call the driver, and synchronize appropriately with
    // I/O completion.
    //

    status = IopSynchronousServiceTail( deviceObject,
                                        irp,
                                        fileObject,
                                        FALSE,
                                        requestorMode,
                                        synchronousIo,
                                        OtherTransfer );

    //
    // If the file for this operation was not opened for synchronous I/O, then
    // synchronization of completion of the I/O operation has not yet occurred
    // since the allocated event must be used for synchronous APIs on files
    // opened for asynchronous I/O.  Synchronize the completion of the I/O
    // operation now.
    //

    if (!synchronousIo) {

        status = IopSynchronousApiServiceTail( status,
                                               event,
                                               irp,
                                               requestorMode,
                                               &localIoStatus,
                                               IoStatusBlock );
    }

    return status;
}

NTSTATUS
NtQueryAttributesFile (
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __out PFILE_BASIC_INFORMATION FileInformation
    )

/*++

Routine Description:

    This service queries the basic attributes information for a specified file.

Arguments:

    ObjectAttributes - Supplies the attributes to be used for file object (name,
        SECURITY_DESCRIPTOR, etc.)

    FileInformation - Supplies an output buffer to receive the returned file
        attributes information.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    KPROCESSOR_MODE requestorMode;
    NTSTATUS status;
    OPEN_PACKET openPacket;
    DUMMY_FILE_OBJECT localFileObject;
    FILE_NETWORK_OPEN_INFORMATION networkInformation;
    HANDLE handle;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    requestorMode = KeGetPreviousMode();

    if (requestorMode != KernelMode) {

        try {

            //
            // The caller's mode is not kernel, so probe the output buffer.
            //

            ProbeForWriteSmallStructure( FileInformation,
                                         sizeof( FILE_BASIC_INFORMATION ),
                                         sizeof( ULONG_PTR ));

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while probing the caller's parameters.
            // Simply return an appropriate error status code.
            //

            return GetExceptionCode();
        }
    }

    //
    // Build a parse open packet that tells the parse method to open the file,
    // query the file's basic attributes, and close the file.
    //

    RtlZeroMemory( &openPacket, sizeof( OPEN_PACKET ) );

    openPacket.Type = IO_TYPE_OPEN_PACKET;
    openPacket.Size = sizeof( OPEN_PACKET );
    openPacket.ShareAccess = (USHORT) FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    openPacket.Disposition = FILE_OPEN;
    openPacket.CreateOptions = FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT;
    openPacket.BasicInformation = FileInformation;
    openPacket.NetworkInformation = &networkInformation;
    openPacket.QueryOnly = TRUE;
    openPacket.TraversedMountPoint = FALSE;
    openPacket.LocalFileObject = &localFileObject;

    //
    // Update the open count for this process.
    //

    IopUpdateOtherOperationCount();

    //
    // Open the object by its name.  Because of the special QueryOnly flag set
    // in the open packet, the parse routine will open the file, and then
    // realize that it is only performing a query.  It will therefore perform
    // the query, and immediately close the file.
    //

    status = ObOpenObjectByName( ObjectAttributes,
                                 (POBJECT_TYPE) NULL,
                                 requestorMode,
                                 NULL,
                                 FILE_READ_ATTRIBUTES,
                                 &openPacket,
                                 &handle );

    //
    // The operation is successful if the parse check field of the open packet
    // indicates that the parse routine was actually invoked, and the final
    // status field of the packet is set to success.
    //

    if (openPacket.ParseCheck != OPEN_PACKET_PATTERN) {
        if (NT_SUCCESS(status)) {
            ZwClose(handle);
            status = STATUS_OBJECT_TYPE_MISMATCH;
        }
        return status;
    } else {
        return openPacket.FinalStatus;
    }
}

#pragma warning(push)
#pragma warning(disable:4701)

NTSTATUS
NtQueryFullAttributesFile (
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __out PFILE_NETWORK_OPEN_INFORMATION FileInformation
    )

/*++

Routine Description:

    This service queries the network attributes information for a specified
    file.

Arguments:

    ObjectAttributes - Supplies the attributes to be used for file object (name,
        SECURITY_DESCRIPTOR, etc.)

    FileInformation - Supplies an output buffer to receive the returned file
        attributes information.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    KPROCESSOR_MODE requestorMode;
    NTSTATUS status;
    OPEN_PACKET openPacket;
    DUMMY_FILE_OBJECT localFileObject;
    FILE_NETWORK_OPEN_INFORMATION networkInformation;
    HANDLE handle;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    requestorMode = KeGetPreviousMode();

    if (requestorMode != KernelMode) {

        try {

            //
            // The caller's mode is not kernel, so probe the output buffer.
            //

            ProbeForWriteSmallStructure( FileInformation,
                                         sizeof( FILE_NETWORK_OPEN_INFORMATION ),
#if defined(_X86_)
                                         sizeof( LONG ));
#else
                                         sizeof( LONGLONG ));
#endif // defined(_X86_)

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while probing the caller's parameters.
            // Simply return an appropriate error status code.
            //

            return GetExceptionCode();
        }
    }

    //
    // Build a parse open packet that tells the parse method to open the file,
    // query the file's full attributes, and close the file.
    //

    RtlZeroMemory( &openPacket, sizeof( OPEN_PACKET ) );

    openPacket.Type = IO_TYPE_OPEN_PACKET;
    openPacket.Size = sizeof( OPEN_PACKET );
    openPacket.ShareAccess = (USHORT) FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    openPacket.Disposition = FILE_OPEN;
    openPacket.CreateOptions = FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT;
    openPacket.QueryOnly = TRUE;
    openPacket.FullAttributes = TRUE;
    openPacket.TraversedMountPoint = FALSE;
    openPacket.LocalFileObject = &localFileObject;
    if (requestorMode != KernelMode) {
        openPacket.NetworkInformation = &networkInformation;
    } else {
        openPacket.NetworkInformation = FileInformation;
    }

    //
    // Update the open count for this process.
    //

    IopUpdateOtherOperationCount();

    //
    // Open the object by its name.  Because of the special QueryOnly flag set
    // in the open packet, the parse routine will open the file, and then
    // realize that it is only performing a query.  It will therefore perform
    // the query, and immediately close the file.
    //

    status = ObOpenObjectByName( ObjectAttributes,
                                 (POBJECT_TYPE) NULL,
                                 requestorMode,
                                 NULL,
                                 FILE_READ_ATTRIBUTES,
                                 &openPacket,
                                 &handle );

    //
    // The operation is successful if the parse check field of the open packet
    // indicates that the parse routine was actually invoked, and the final
    // status field of the packet is set to success.
    //

    if (openPacket.ParseCheck != OPEN_PACKET_PATTERN) {
        if (NT_SUCCESS(status)) {
            ZwClose(handle);
            status = STATUS_OBJECT_TYPE_MISMATCH;
        }
        return status;
    } else {
        status = openPacket.FinalStatus;
    }

    if (NT_SUCCESS( status )) {
        if (requestorMode != KernelMode) {
            try {

                //
                // The query worked, so copy the returned information to the
                // caller's output buffer.
                //

                RtlCopyMemory( FileInformation,
                               &networkInformation,
                               sizeof( FILE_NETWORK_OPEN_INFORMATION ) );

            } except(EXCEPTION_EXECUTE_HANDLER) {
                status = GetExceptionCode();
            }
        }
    }

    return status;
}

#pragma warning(pop)

PIO_WORKITEM
IoAllocateWorkItem(
    PDEVICE_OBJECT DeviceObject
    )
{
    PIO_WORKITEM ioWorkItem;
    PWORK_QUEUE_ITEM exWorkItem;

    //
    // Allocate a new workitem structure.
    // 

    ioWorkItem = ExAllocatePool( NonPagedPool, sizeof( IO_WORKITEM ));
    if (ioWorkItem != NULL) {

        //
        // Initialize the invariant portions of both ioWorkItem and
        // exWorkItem.
        //

#if DBG
        ioWorkItem->Size = sizeof( IO_WORKITEM );
#endif

        ioWorkItem->DeviceObject = DeviceObject;

        exWorkItem = &ioWorkItem->WorkItem;
        ExInitializeWorkItem( exWorkItem, IopProcessWorkItem, ioWorkItem );
    }

    return ioWorkItem;
}

VOID
IoFreeWorkItem(
    PIO_WORKITEM IoWorkItem
    )

/*++

Routine Description:

    This function is the "wrapper" routine for IoQueueWorkItem.  It calls
    the original worker function, then dereferences the device object to
    (possibly) allow the driver object to go away.

Arguments:

    Parameter - Supplies a pointer to an IO_WORKITEM for us to process.

Return Value:

    None

--*/

{
    ASSERT( IoWorkItem->Size == sizeof( IO_WORKITEM ));

    ExFreePool( IoWorkItem );
}

VOID
IoQueueWorkItem(
    IN PIO_WORKITEM IoWorkItem,
    IN PIO_WORKITEM_ROUTINE WorkerRoutine,
    IN WORK_QUEUE_TYPE QueueType,
    IN PVOID Context
    )
/*++

Routine Description:

    This function inserts a work item into a work queue that is processed
    by a worker thread of the corresponding type.  It effectively
    "wraps" ExQueueWorkItem, ensuring that the device object is referenced
    for the duration of the call.

Arguments:

    IoWorkItem - Supplies a pointer to the work item to add the the queue.
        This structure must have been allocated via IoAllocateWorkItem().

    WorkerRoutine - Supplies a pointer to the routine that is to be called
        in system thread context.

    QueueType - Specifies the type of work queue that the work item
        should be placed in.

    Context - Supplies the context parameter for the callback routine.

Return Value:

    None

--*/

{
    PWORK_QUEUE_ITEM exWorkItem;

    ASSERT( KeGetCurrentIrql() <= DISPATCH_LEVEL );
    ASSERT( IoWorkItem->Size == sizeof( IO_WORKITEM ));

    //
    // Keep a reference on the device object so it doesn't go away.
    //

    ObReferenceObject( IoWorkItem->DeviceObject );

    //
    // Initialize the fields in IoWorkItem
    //

    IoWorkItem->Routine = WorkerRoutine;
    IoWorkItem->Context = Context;

    //
    // Get a pointer to the ExWorkItem, queue it, and return.
    // IopProcessWorkItem() will perform the dereference.
    // 

    exWorkItem = &IoWorkItem->WorkItem;
    ExQueueWorkItem( exWorkItem, QueueType );
}

VOID
IopProcessWorkItem(
    IN PVOID Parameter
    )

/*++

Routine Description:

    This function is the "wrapper" routine for IoQueueWorkItem.  It calls
    the original worker function, then dereferences the device object to
    (possibly) allow the driver object to go away.

Arguments:

    Parameter - Supplies a pointer to an IO_WORKITEM for us to process.

Return Value:

    None

--*/

{
    PIO_WORKITEM ioWorkItem;
    PDEVICE_OBJECT deviceObject;

    PAGED_CODE();

    //
    // Get a pointer to the ioWorkItem and store a copy of DeviceObject
    // locally.  This allow us to function properly if the worker routine
    // elects to free the work item.
    //

    ioWorkItem = (PIO_WORKITEM)Parameter;
    deviceObject = ioWorkItem->DeviceObject;

    //
    // Call the original worker.
    //

    ioWorkItem->Routine( deviceObject,
                         ioWorkItem->Context );

    //
    // Now we can dereference the device object, since its code is no longer
    // being executed for this work item.
    //

    ObDereferenceObject( deviceObject );
}

