/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    complete.c

Abstract:

   This module implements the executive I/O completion object. Functions are
   provided to create, open, query, and wait for I/O completion objects.

Author:

    David N. Cutler (davec) 25-Feb-1994

Environment:

    Kernel mode only.

Revision History:

--*/

#include "iop.h"

#define IopFreeMiniPacket(MiniPacket) ExFreePool(MiniPacket)

NTSTATUS
NtCreateIoCompletion (
    IN PHANDLE IoCompletionHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN ULONG Count OPTIONAL
    )

/*++

Routine Description:

    This function creates an I/O completion object, sets the maximum
    target concurrent thread count to the specified value, and opens
    a handle to the object with the specified desired access.

Arguments:

    IoCompletionHandle - Supplies a pointer to a variable that receives
        the I/O completion object handle.

    DesiredAccess - Supplies the desired types of access for the I/O
        completion object.

    ObjectAttributes - Supplies a pointer to an object attributes structure.

    Count - Supplies the target maximum  number of threads that should
        be concurrently active. If this parameter is not specified, then
        the number of processors is used.

Return Value:

    STATUS_SUCCESS is returned if the function is success. Otherwise, an
    error status is returned.

--*/

{

    HANDLE Handle;
    PVOID IoCompletion;
    NTSTATUS Status;

    //
    // Allocate I/O completion object.
    //

    Status = ObCreateObject(&IoCompletionObjectType,
                            ObjectAttributes,
                            sizeof(KQUEUE),
                            (PVOID *)&IoCompletion);

    //
    // If the I/O completion object was successfully allocated, then
    // initialize the object and attempt to insert it in the handle
    // table of the current process.
    //

    if (NT_SUCCESS(Status)) {
        KeInitializeQueue((PKQUEUE)IoCompletion, Count);
        Status = ObInsertObject(IoCompletion,
                                ObjectAttributes,
                                0,
                                &Handle);

        //
        // If the I/O completion object was successfully inserted in
        // the handle table of the current process, then attempt to
        // write the handle value. If the write attempt fails, then
        // do not report an error. When the caller attempts to access
        // the handle value, an access violation will occur.
        //

        if (NT_SUCCESS(Status)) {
            *IoCompletionHandle = Handle;
        }
    }

    //
    // Return service status.
    //

    return Status;
}

NTSTATUS
NtQueryIoCompletion (
    IN HANDLE IoCompletionHandle,
    OUT PIO_COMPLETION_BASIC_INFORMATION IoCompletionInformation
    )

/*++

Routine Description:

    This function queries the state of an I/O completion object and returns
    the requested information in the specified record structure.

Arguments:

    IoCompletionHandle - Supplies a handle to an I/O completion object.

    IoCompletionInformation - Supplies a pointer to a record that receives
        the requested information.

Return Value:

    STATUS_SUCCESS is returned if the function is success. Otherwise, an
    error status is returned.

--*/

{

    PVOID IoCompletion;
    LONG Depth;
    NTSTATUS Status;

    //
    // Reference the I/O completion object by handle.
    //

    Status = ObReferenceObjectByHandle(IoCompletionHandle,
                                       &IoCompletionObjectType,
                                       &IoCompletion);

    //
    // If the reference was successful, then read the current state of
    // the I/O completion object, dereference the I/O completion object,
    // fill in the information structure, and return the structure length
    // if specified. If the write of the I/O completion information or
    // the return length fails, then do not report an error. When the
    // caller accesses the information structure or length an access
    // violation will occur.
    //

    if (NT_SUCCESS(Status)) {
        Depth = KeReadStateQueue((PKQUEUE)IoCompletion);
        ObDereferenceObject(IoCompletion);

        IoCompletionInformation->Depth = Depth;
    }

    //
    // Return service status.
    //

    return Status;
}

NTSTATUS
NtSetIoCompletion (
    IN HANDLE IoCompletionHandle,
    IN PVOID KeyContext,
    IN PVOID ApcContext,
    IN NTSTATUS IoStatus,
    IN ULONG_PTR IoStatusInformation
    )
/*++

Routine Description:

    This function allows the caller to queue an Irp to an I/O completion
    port and specify all of the information that is returned out the other
    end using NtRemoveIoCompletion.

Arguments:

    IoCompletionHandle - Supplies a handle to the io completion port
        that the caller intends to queue a completion packet to

    KeyContext - Supplies the key context that is returned during a call
        to NtRemoveIoCompletion

    ApcContext - Supplies the apc context that is returned during a call
        to NtRemoveIoCompletion

    IoStatus - Supplies the IoStatus->Status data that is returned during
        a call to NtRemoveIoCompletion

    IoStatusInformation - Supplies the IoStatus->Information data that
        is returned during a call to NtRemoveIoCompletion

Return Value:

    STATUS_SUCCESS is returned if the function is success. Otherwise, an
    error status is returned.

--*/

{
    PVOID IoCompletion;
    NTSTATUS Status;

    PAGED_CODE();

    Status = ObReferenceObjectByHandle(IoCompletionHandle,
                                       &IoCompletionObjectType,
                                       &IoCompletion);

    if (NT_SUCCESS(Status)) {
        Status = IoSetIoCompletion(IoCompletion,
                                   KeyContext,
                                   ApcContext,
                                   IoStatus,
                                   IoStatusInformation);

        ObDereferenceObject(IoCompletion);
        }
    return Status;

}

NTSTATUS
NtRemoveIoCompletion (
    IN HANDLE IoCompletionHandle,
    OUT PVOID *KeyContext,
    OUT PVOID *ApcContext,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN PLARGE_INTEGER Timeout OPTIONAL
    )

/*++

Routine Description:

    This function removes an entry from an I/O completion object. If there
    are currently no entries available, then the calling thread waits for
    an entry.

Arguments:

    Completion - Supplies a handle to an I/O completion object.

    KeyContext - Supplies a pointer to a variable that receives the key
        context that was specified when the I/O completion object was
        assoicated with a file object.

    ApcContext - Supplies a pointer to a variable that receives the
        context that was specified when the I/O operation was issued.

    IoStatus - Supplies a pointer to a variable that receives the
        I/O completion status.

    Timeout - Supplies a pointer to an optional time out value.

Return Value:

    STATUS_SUCCESS is returned if the function is success. Otherwise, an
    error status is returned.

--*/

{

    PLARGE_INTEGER CapturedTimeout;
    PLIST_ENTRY Entry;
    PVOID IoCompletion;
    PIRP Irp;
    NTSTATUS Status;
    PVOID LocalApcContext;
    PVOID LocalKeyContext;
    IO_STATUS_BLOCK LocalIoStatusBlock;
    PIOP_MINI_COMPLETION_PACKET MiniPacket;

    //
    // Get previous processor mode and probe the I/O context, status,
    // and timeout if necessary.
    //

    CapturedTimeout = NULL;
    if (ARGUMENT_PRESENT(Timeout)) {
        CapturedTimeout = Timeout;
    }

    //
    // Reference the I/O completion object by handle.
    //

    Status = ObReferenceObjectByHandle(IoCompletionHandle,
                                       &IoCompletionObjectType,
                                       &IoCompletion);

    //
    // If the reference was successful, then attempt to remove an entry
    // from the I/O completion object. If an entry is removed from the
    // I/O completion object, then capture the completion information,
    // release the associated IRP, and attempt to write the completion
    // inforamtion. If the write of the completion infomation fails,
    // then do not report an error. When the caller attempts to access
    // the completion information, an access violation will occur.
    //

    if (NT_SUCCESS(Status)) {
        Entry = KeRemoveQueue((PKQUEUE)IoCompletion,
                              KernelMode,
                              CapturedTimeout);

        //
        // N.B. The entry value returned can be the address of a list
        //      entry, STATUS_USER_APC, or STATUS_TIMEOUT.
        //

        if (((LONG_PTR)Entry == STATUS_TIMEOUT) ||
            ((LONG_PTR)Entry == STATUS_USER_APC)) {
            Status = (NTSTATUS)((LONG_PTR)Entry);

        } else {

            //
            // Set the completion status, capture the completion
            // information, deallocate the associated IRP, and
            // attempt to write the completion information.
            //

            Status = STATUS_SUCCESS;

            MiniPacket = CONTAINING_RECORD(Entry,
                                           IOP_MINI_COMPLETION_PACKET,
                                           ListEntry);

            if ( MiniPacket->PacketType == IopCompletionPacketIrp ) {
                Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry);
                LocalApcContext = Irp->Overlay.AsynchronousParameters.UserApcContext;
                LocalKeyContext = (PVOID)Irp->Tail.CompletionKey;
                LocalIoStatusBlock = Irp->IoStatus;
                IoFreeIrp(Irp);

            } else {

                LocalApcContext = MiniPacket->ApcContext;
                LocalKeyContext = (PVOID)MiniPacket->KeyContext;
                LocalIoStatusBlock.Status = MiniPacket->IoStatus;
                LocalIoStatusBlock.Information = MiniPacket->IoStatusInformation;
                IopFreeMiniPacket(MiniPacket);
            }

            *ApcContext = LocalApcContext;
            *KeyContext = LocalKeyContext;
            *IoStatusBlock = LocalIoStatusBlock;
        }

        //
        // Deference I/O completion object.
        //

        ObDereferenceObject(IoCompletion);
    }

    //
    // Return service status.
    //

    return Status;
}

NTKERNELAPI
NTSTATUS
IoSetIoCompletion (
    IN PVOID IoCompletion,
    IN PVOID KeyContext,
    IN PVOID ApcContext,
    IN NTSTATUS IoStatus,
    IN ULONG_PTR IoStatusInformation
    )
/*++

Routine Description:

    This function allows the caller to queue an Irp to an I/O completion
    port and specify all of the information that is returned out the other
    end using NtRemoveIoCompletion.

Arguments:

    IoCompletion - Supplies a a pointer to the completion port that the caller
        intends to queue a completion packet to.

    KeyContext - Supplies the key context that is returned during a call
        to NtRemoveIoCompletion.

    ApcContext - Supplies the apc context that is returned during a call
        to NtRemoveIoCompletion.

    IoStatus - Supplies the IoStatus->Status data that is returned during
        a call to NtRemoveIoCompletion.

    IoStatusInformation - Supplies the IoStatus->Information data that
        is returned during a call to NtRemoveIoCompletion.

Return Value:

    STATUS_SUCCESS is returned if the function is success. Otherwise, an
    error status is returned.

--*/

{

    PIOP_MINI_COMPLETION_PACKET MiniPacket;
    NTSTATUS Status;

    PAGED_CODE();

    //
    // Attempt to allocate the minpacket from the per processor lookaside list.
    //

    MiniPacket = ExAllocatePoolWithTag(sizeof(*MiniPacket), ' pcI');

    //
    // If a minipacket was successfully allocated, then initialize and
    // queue the packet to the specified I/O completion queue.
    //

    if (MiniPacket != NULL) {
        MiniPacket->PacketType = IopCompletionPacketMini;
        MiniPacket->KeyContext = KeyContext;
        MiniPacket->ApcContext = ApcContext;
        MiniPacket->IoStatus = IoStatus;
        MiniPacket->IoStatusInformation = IoStatusInformation;
        KeInsertQueue((PKQUEUE)IoCompletion, &MiniPacket->ListEntry);

        Status = STATUS_SUCCESS;

    } else {
        Status = STATUS_INSUFFICIENT_RESOURCES;
    }

    return Status;
}

VOID
IopDeleteIoCompletion (
    IN PVOID Object
    )

/*++

Routine Description:

    This function is the delete routine for I/O completion objects. Its
    function is to release all the entries in the repsective completion
    queue and to rundown all threads that are current associated.

Arguments:

    Object - Supplies a pointer to an executive I/O completion object.

Return Value:

    None.

--*/

{

    PLIST_ENTRY FirstEntry;
    PIRP Irp;
    PLIST_ENTRY NextEntry;
    PIOP_MINI_COMPLETION_PACKET MiniPacket;

    //
    // Rundown threads associated with the I/O completion object and get
    // the list of unprocessed I/O completion IRPs.
    //

    FirstEntry = KeRundownQueue((PKQUEUE)Object);
    if (FirstEntry != NULL) {
        NextEntry = FirstEntry;
        do {
            MiniPacket = CONTAINING_RECORD(NextEntry,
                                           IOP_MINI_COMPLETION_PACKET,
                                           ListEntry);

            NextEntry = NextEntry->Flink;
            if (MiniPacket->PacketType == IopCompletionPacketIrp) {
                Irp = CONTAINING_RECORD(MiniPacket, IRP, Tail.Overlay.ListEntry);
                IoFreeIrp(Irp);

            } else {
                IopFreeMiniPacket(MiniPacket);
            }

        } while (FirstEntry != NextEntry);
    }
}
