/*++

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:

    read.c

Abstract:

    This module contains the code to implement the NtReadFile system service.

--*/

#include "iomgr.h"

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("PAGECONST")
#endif // ALLOC_DATA_PRAGMA

const KPRIORITY IopCacheHitIncrement = IO_NO_INCREMENT;

#pragma alloc_text(PAGE, NtReadFile)
#pragma alloc_text(PAGE, NtReadFileScatter)

NTSTATUS
NtReadFile (
    __in HANDLE FileHandle,
    __in_opt HANDLE Event,
    __in_opt PIO_APC_ROUTINE ApcRoutine,
    __in_opt PVOID ApcContext,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __out_bcount(Length) PVOID Buffer,
    __in ULONG Length,
    __in_opt PLARGE_INTEGER ByteOffset,
    __in_opt PULONG Key
    )

/*++

Routine Description:

    This service reads Length bytes of data from the file associated with
    FileHandle starting at ByteOffset and puts the data into the caller's
    Buffer.  If the end of the file is reached before Length bytes have
    been read, then the operation will terminate.  The actual length of
    the data read from the file will be returned in the second longword
    of the IoStatusBlock.

Arguments:

    FileHandle - Supplies a handle to the file to be read.

    Event - Optionally supplies an event to be signaled when the read operation
        is complete.

    ApcRoutine - Optionally supplies an APC routine to be executed when the read
        operation is complete.

    ApcContext - Supplies a context parameter to be passed to the ApcRoutine, if
        an ApcRoutine was specified.

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

    Buffer - Address of buffer to receive the data read from the file.

    Length - Supplies the length, in bytes, of the data to read from the file.

    ByteOffset - Optionally specifies the starting byte offset within the file
        to begin the read operation.  If not specified and the file is open
        for synchronous I/O, then the current file position is used.  If the
        file is not opened for synchronous I/O and the parameter is not
        specified, then it is an error.

    Key - Optionally specifies a key to be used if there are locks associated
        with the file.

Return Value:

    The status returned is success if the read operation was properly queued
    to the I/O system.  Once the read completes the status of the operation
    can be determined by examining the Status field of the I/O status block.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PFAST_IO_DISPATCH fastIoDispatch;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    NTSTATUS exceptionCode;
    BOOLEAN synchronousIo;
    PKEVENT eventObject = (PKEVENT) NULL;
    ULONG keyValue = 0;
    LARGE_INTEGER fileOffset = {0,0};
    PULONG majorFunction;
    PETHREAD CurrentThread;

    PAGED_CODE();

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

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

    //
    // Reference the file object so the target device can be found.  Note
    // that if the caller does not have read access to the file, the operation
    // will fail.
    //

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

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

    deviceObject = IoGetRelatedDeviceObject( fileObject );

    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.
        //

        exceptionCode = STATUS_SUCCESS;

        try {

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

            ProbeForWriteIoStatus(IoStatusBlock);

            //
            // If this is a 32-bit asynchronous IO, then mark the Iosb being sent as so.
            // Note: IopMarkApcRoutineIfAsyncronousIo32 must be called after probing
            //       the IoStatusBlock structure for write.
            //

            IopMarkApcRoutineIfAsyncronousIo32(IoStatusBlock,ApcRoutine,(fileObject->Flags & FO_SYNCHRONOUS_IO));

            //
            // The caller's data buffer must be writable from the caller's
            // mode.  This check ensures that this is the case.  Since the
            // buffer address is captured, the caller cannot change it,
            // even though he/she can change the protection from another
            // thread.  This error will be caught by the probe/lock or
            // buffer copy operations later.
            //

            ProbeForWrite( Buffer, Length, sizeof( UCHAR ) );

            //
            // If this file has an I/O completion port associated w/it, then
            // ensure that the caller did not supply an APC routine, as the
            // two are mutually exclusive methods for I/O completion
            // notification.
            //

            if (fileObject->CompletionContext && IopApcRoutinePresent( ApcRoutine )) {
                ObDereferenceObject( fileObject );
                return STATUS_INVALID_PARAMETER;
            }

            //
            // Also ensure that the ByteOffset parameter is readable from
            // the caller's mode and capture it if it is present.
            //

            if (ARGUMENT_PRESENT( ByteOffset )) {
                ProbeForReadSmallStructure( ByteOffset,
                                            sizeof( LARGE_INTEGER ),
                                            sizeof( ULONG ) );
                fileOffset = *ByteOffset;
            }

            //
            // Check to see whether the caller has opened the file without
            // intermediate buffering.  If so, perform the following Buffer
            // and ByteOffset parameter checks differently.
            //

            if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {

                //
                // The file was opened without intermediate buffering enabled.
                // Check that the Buffer is properly aligned, and that the
                // length is an integral number of 512-byte blocks.
                //

                if ((deviceObject->SectorSize &&
                    (Length & (deviceObject->SectorSize - 1))) ||
                    (ULONG_PTR) Buffer & deviceObject->AlignmentRequirement) {

                    //
                    // Check for sector sizes that are not a power of two.
                    //

                    if ((deviceObject->SectorSize &&
                        Length % deviceObject->SectorSize) ||
                        (ULONG_PTR) Buffer & deviceObject->AlignmentRequirement) {
                        ObDereferenceObject( fileObject );
                        return STATUS_INVALID_PARAMETER;
                    }
                }

                //
                // If a ByteOffset parameter was specified, ensure that it
                // is a valid argument.
                //

                if (ARGUMENT_PRESENT( ByteOffset )) {
                    if (deviceObject->SectorSize &&
                        (fileOffset.LowPart & (deviceObject->SectorSize - 1))) {
                        ObDereferenceObject( fileObject );
                        return STATUS_INVALID_PARAMETER;
                    }
                }
            }

            //
            // Finally, ensure that if there is a key parameter specified it
            // is readable by the caller.
            //

            if (ARGUMENT_PRESENT( Key )) {
                keyValue = ProbeAndReadUlong( Key );
            }

        } except(IopExceptionFilter( GetExceptionInformation(), &exceptionCode )) {

            //
            // An exception was incurred while attempting to probe the
            // caller's parameters.  Dereference the file object and return
            // an appropriate error status code.
            //

            ObDereferenceObject( fileObject );
            return exceptionCode;

        }

    } else {

        //
        // The caller's mode is kernel.  Get the same parameters that are
        // required from any other mode.
        //

        if (ARGUMENT_PRESENT( ByteOffset )) {
            fileOffset = *ByteOffset;
        }

        if (ARGUMENT_PRESENT( Key )) {
            keyValue = *Key;
        }

#if DBG
        if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {

            //
            // The file was opened without intermediate buffering enabled.
            // Check that the Buffer is properly aligned, and that the
            // length is an integral number of the block size.
            //

            if ((deviceObject->SectorSize &&
                (Length & (deviceObject->SectorSize - 1))) ||
                (ULONG_PTR) Buffer & deviceObject->AlignmentRequirement) {

                //
                // Check for sector sizes that are not a power of two.
                //

                if ((deviceObject->SectorSize &&
                    Length % deviceObject->SectorSize) ||
                    (ULONG_PTR) Buffer & deviceObject->AlignmentRequirement) {
                    ObDereferenceObject( fileObject );
                    ASSERT( FALSE );
                    return STATUS_INVALID_PARAMETER;
                }
            }

            //
            // If a ByteOffset parameter was specified, ensure that it
            // is a valid argument.
            //

            if (ARGUMENT_PRESENT( ByteOffset )) {
                if (deviceObject->SectorSize &&
                    (fileOffset.LowPart & (deviceObject->SectorSize - 1))) {
                    ObDereferenceObject( fileObject );
                    ASSERT( FALSE );
                    return STATUS_INVALID_PARAMETER;
                }
            }
        }
#endif // DBG
    }

    //
    // Get the address of the event object and set the event to the Not-
    // Signaled state, if an one was specified.  Note here too, that if
    // the handle does not refer to an event, then the reference will fail.
    //

    if (ARGUMENT_PRESENT( Event )) {
        status = ObReferenceObjectByHandle( Event,
                                            EVENT_MODIFY_STATE,
                                            ExEventObjectType,
                                            requestorMode,
                                            (PVOID *) &eventObject,
                                            NULL );
        if (!NT_SUCCESS( status )) {
            ObDereferenceObject( fileObject );
            return status;
        } else {
            KeClearEvent( eventObject );
        }
    }

    //
    // Get the address of the driver object's Fast I/O dispatch structure.
    //

    fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;

    //
    // 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 (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

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

        if (!ARGUMENT_PRESENT( ByteOffset ) ||
            (fileOffset.LowPart == FILE_USE_FILE_POINTER_POSITION &&
            fileOffset.HighPart == -1)) {
            fileOffset = fileObject->CurrentByteOffset;
        }

        //
        // Turbo read support.  If the file is currently cached on this
        // file object, then call the Cache Manager directly via FastIoRead
        // and try to successfully complete the request here.  Note if
        // FastIoRead returns FALSE or we get an I/O error, we simply
        // fall through and go the "long way" and create an Irp.
        //

        if (fileObject->PrivateCacheMap) {

            IO_STATUS_BLOCK localIoStatus;

            ASSERT(fastIoDispatch && fastIoDispatch->FastIoRead);

            //
            //  Negative file offsets are illegal.
            //

            if (fileOffset.HighPart < 0) {
                if (eventObject) {
                    ObDereferenceObject( eventObject );
                }
                IopReleaseFileObjectLock( fileObject );
                ObDereferenceObject( fileObject );
                return STATUS_INVALID_PARAMETER;
            }

            if (fastIoDispatch->FastIoRead( fileObject,
                                            &fileOffset,
                                            Length,
                                            TRUE,
                                            keyValue,
                                            Buffer,
                                            &localIoStatus,
                                            deviceObject )

                    &&

                ((localIoStatus.Status == STATUS_SUCCESS) ||
                 (localIoStatus.Status == STATUS_BUFFER_OVERFLOW) ||
                 (localIoStatus.Status == STATUS_END_OF_FILE))) {

                //
                // Boost the priority of the current thread so that it appears
                // as if it just did I/O.  This causes background jobs that
                // get cache hits to be more responsive in terms of getting
                // more CPU time.
                //

                if (IopCacheHitIncrement) {
                    KeBoostPriorityThread( &CurrentThread->Tcb,
                                           IopCacheHitIncrement );
                }

                //
                // Carefully return the I/O status.
                //

                IopUpdateReadOperationCount( );
                IopUpdateReadTransferCount( (ULONG)localIoStatus.Information );

                try {
                    *IoStatusBlock = localIoStatus;
                } except( EXCEPTION_EXECUTE_HANDLER ) {
                    localIoStatus.Status = GetExceptionCode();
                    localIoStatus.Information = 0;
                }

                //
                // If an event was specified, set it.
                //

                if (ARGUMENT_PRESENT( Event )) {
                    KeSetEvent( eventObject, 0, FALSE );
                    ObDereferenceObject( eventObject );
                }

                //
                // Note that the file object event need not be set to the
                // Signaled state, as it is already set.
                //

                //
                // Cleanup and return.
                //

                IopReleaseFileObjectLock( fileObject );
                ObDereferenceObject( fileObject );

                return localIoStatus.Status;
            }
        }
        synchronousIo = TRUE;

    } else if (!ARGUMENT_PRESENT( ByteOffset ) && !(fileObject->Flags & (FO_NAMED_PIPE | FO_MAILSLOT))) {

        //
        // The file is not open for synchronous I/O operations, but the
        // caller did not specify a ByteOffset parameter.
        //

        if (eventObject) {
            ObDereferenceObject( eventObject );
        }
        ObDereferenceObject( fileObject );
        return STATUS_INVALID_PARAMETER;
    } else {
        synchronousIo = FALSE;
    }

    //
    //  Negative file offsets are illegal.
    //

    if (fileOffset.HighPart < 0) {
        if (eventObject) {
            ObDereferenceObject( eventObject );
        }
        if (synchronousIo) {
            IopReleaseFileObjectLock( fileObject );
        }
        ObDereferenceObject( fileObject );
        return STATUS_INVALID_PARAMETER;
    }

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

    KeClearEvent( &fileObject->Event );

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.

    irp = IopAllocateIrp( deviceObject->StackSize, !synchronousIo );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        IopAllocateIrpCleanup( fileObject, eventObject );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->Tail.Overlay.AuxiliaryBuffer = (PVOID) NULL;
    irp->RequestorMode = requestorMode;
    irp->PendingReturned = FALSE;
    irp->Cancel = FALSE;
    irp->CancelRoutine = (PDRIVER_CANCEL) NULL;

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

    irp->UserEvent = eventObject;
    irp->UserIosb = IoStatusBlock;
    irp->Overlay.AsynchronousParameters.UserApcRoutine = ApcRoutine;
    irp->Overlay.AsynchronousParameters.UserApcContext = ApcContext;

    //
    // Get a pointer to the stack location for the first driver.  This will be
    // used to pass the original function codes and parameters.  Note that
    // setting the major function here also sets:
    //
    //      MinorFunction = 0;
    //      Flags = 0;
    //      Control = 0;
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    majorFunction = (PULONG) (&irpSp->MajorFunction);
    *majorFunction = IRP_MJ_READ;
    irpSp->FileObject = fileObject;

    //
    // Now determine whether this device expects to have data buffered to it
    // or whether it performs direct I/O.  This is based on the DO_BUFFERED_IO
    // flag in the device object.  If the flag is set, then a system buffer is
    // allocated and the driver's data will be copied into it.  Otherwise, a
    // Memory Descriptor List (MDL) is allocated and the caller's buffer is
    // locked down using it.
    //

    irp->AssociatedIrp.SystemBuffer = (PVOID) NULL;
    irp->MdlAddress = (PMDL) NULL;

    if (deviceObject->Flags & DO_BUFFERED_IO) {

        //
        // The device does not support direct I/O.  Allocate a system buffer
        // and specify that it should be deallocated on completion.  Also
        // indicate that this is an input operation so the data will be copied
        // into the caller's buffer.  This is done using an exception handler
        // that will perform cleanup if the operation fails.  Note that this
        // is only done if the operation has a non-zero length.
        //

        if (Length) {

            try {

                //
                // Allocate the intermediary system buffer from nonpaged pool
                // and charge quota for it.
                //

                irp->AssociatedIrp.SystemBuffer =
                    ExAllocatePoolWithQuota( NonPagedPoolCacheAligned, Length );

            } except(EXCEPTION_EXECUTE_HANDLER) {

                //
                // An exception was incurred while either probing the caller's
                // buffer or allocating the system buffer.  Determine what
                // actually happened, clean everything up, and return an
                // appropriate error status code.
                //

                IopExceptionCleanup( fileObject,
                                     irp,
                                     eventObject,
                                     (PKEVENT) NULL );

                return GetExceptionCode();

            }

            //
            // Remember the address of the caller's buffer so the copy can take
            // place during I/O completion.  Also, set the flags so that the
            // completion code knows to do the copy and to deallocate the buffer.
            //

            irp->UserBuffer = Buffer;
            irp->Flags = IRP_BUFFERED_IO |
                         IRP_DEALLOCATE_BUFFER |
                         IRP_INPUT_OPERATION;

        } else {

            //
            // This is a zero-length read.  Simply indicate that this is
            // buffered I/O, and pass along the request.  The buffer will
            // not be set to deallocate so the completion path does not
            // have to special-case the length.
            //

            irp->Flags = IRP_BUFFERED_IO | IRP_INPUT_OPERATION;

        }

    } else if (deviceObject->Flags & DO_DIRECT_IO) {

        //
        // This is a direct I/O operation.  Allocate an MDL and invoke the
        // memory management routine to lock the buffer into memory.  This
        // is done using an exception handler that will perform cleanup if
        // the operation fails.  Note that no MDL is allocated, nor is any
        // memory probed or locked if the length of the request was zero.
        //

        PMDL mdl;

        irp->Flags = 0;

        if (Length) {

            try {

                //
                // Allocate an MDL, charging quota for it, and hang it off of
                // the IRP.  Probe and lock the pages associated with the
                // caller's buffer for write access and fill in the MDL with
                // the PFNs of those pages.
                //

                mdl = IoAllocateMdl( Buffer, Length, FALSE, TRUE, irp );
                if (mdl == NULL) {
                    ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
                }
                MmProbeAndLockPages( mdl, requestorMode, IoWriteAccess );

            } except(EXCEPTION_EXECUTE_HANDLER) {

                //
                // An exception was incurred while either probing the caller's
                // buffer or allocating the MDL.  Determine what actually
                // happened, clean everything up, and return an appropriate
                // error status code.
                //

                IopExceptionCleanup( fileObject,
                                     irp,
                                     eventObject,
                                     (PKEVENT) NULL );

                return GetExceptionCode();

            }

        }

    } else {

        //
        // Pass the address of the user's buffer so the driver has access to
        // it.  It is now the driver's responsibility to do everything.
        //

        irp->Flags = 0;
        irp->UserBuffer = Buffer;
    }

    //
    // If this read operation is supposed to be performed with caching disabled
    // set the disable flag in the IRP so no caching is performed.
    //

    if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {
        irp->Flags |= IRP_NOCACHE | IRP_READ_OPERATION | IRP_DEFER_IO_COMPLETION;
    } else {
        irp->Flags |= IRP_READ_OPERATION | IRP_DEFER_IO_COMPLETION;
    }

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Parameters.Read.Length = Length;
    irpSp->Parameters.Read.Key = keyValue;
    irpSp->Parameters.Read.ByteOffset = fileOffset;

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

    status =  IopSynchronousServiceTail( deviceObject,
                                         irp,
                                         fileObject,
                                         TRUE,
                                         requestorMode,
                                         synchronousIo,
                                         ReadTransfer );

    return status;
}

NTSTATUS
NtReadFileScatter (
    __in HANDLE FileHandle,
    __in_opt HANDLE Event,
    __in_opt PIO_APC_ROUTINE ApcRoutine,
    __in_opt PVOID ApcContext,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __in PFILE_SEGMENT_ELEMENT SegmentArray,
    __in ULONG Length,
    __in_opt PLARGE_INTEGER ByteOffset,
    __in_opt PULONG Key
    )

/*++

Routine Description:

    This service reads Length bytes of data from the file associated with
    FileHandle starting at ByteOffset and puts the data into the caller's
    buffer segments.  The buffer segments are not virtually contiguous,
    but are 8 KB in length and alignment. If the end of the file is reached
    before Length bytes have been read, then the operation will terminate.
    The actual length of the data read from the file will be returned in
    the second longword of the IoStatusBlock.

Arguments:

    FileHandle - Supplies a handle to the file to be read.

    Event - Unused the I/O must use a completion port.

    ApcRoutine - Optionally supplies an APC routine to be executed when the read
        operation is complete.

    ApcContext - Supplies a context parameter to be passed to the ApcRoutine, if
        an ApcRoutine was specified.

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

    SegmentArray - An array of buffer segment pointers that specify
        where the data should be placed.

    Length - Supplies the length, in bytes, of the data to read from the file.

    ByteOffset - Optionally specifies the starting byte offset within the file
        to begin the read operation.  If not specified and the file is open
        for synchronous I/O, then the current file position is used.  If the
        file is not opened for synchronous I/O and the parameter is not
        specified, then it is an error.

    Key - Unused.

Return Value:

    The status returned is success if the read operation was properly queued
    to the I/O system.  Once the read completes the status of the operation
    can be determined by examining the Status field of the I/O status block.

Notes:

    This interface is only supported for no buffering and asynchronous I/O.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PFAST_IO_DISPATCH fastIoDispatch;
    PFILE_SEGMENT_ELEMENT capturedArray = NULL;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    NTSTATUS exceptionCode;
    PKEVENT eventObject = (PKEVENT) NULL;
    ULONG keyValue = 0;
    ULONG elementCount;
    LARGE_INTEGER fileOffset = {0,0};
    PULONG majorFunction;
    ULONG i;
    BOOLEAN synchronousIo;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //
    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    //
    // Reference the file object so the target device can be found.  Note
    // that if the caller does not have read access to the file, the operation
    // will fail.
    //

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

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

    deviceObject = IoGetRelatedDeviceObject( fileObject );

    //
    // Verify this is a valid scatter read request.  In particular it must be
    // non cached, asynchronous, use completion ports, non buffer I/O device
    // and directed at a file system device.
    //

    if (!(fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) ||
        (fileObject->Flags & FO_SYNCHRONOUS_IO) ||
        deviceObject->Flags & DO_BUFFERED_IO ||
        (deviceObject->DeviceType != FILE_DEVICE_DISK_FILE_SYSTEM &&
         deviceObject->DeviceType != FILE_DEVICE_DFS &&
         deviceObject->DeviceType != FILE_DEVICE_TAPE_FILE_SYSTEM &&
         deviceObject->DeviceType != FILE_DEVICE_CD_ROM_FILE_SYSTEM &&
         deviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM &&
         deviceObject->DeviceType != FILE_DEVICE_FILE_SYSTEM &&
         deviceObject->DeviceType != FILE_DEVICE_DFS_VOLUME )) {

        ObDereferenceObject( fileObject );
        return STATUS_INVALID_PARAMETER;
    }

    elementCount = BYTES_TO_PAGES(Length);

    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.
        //

        exceptionCode = STATUS_SUCCESS;

        try {

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

            ProbeForWriteIoStatus(IoStatusBlock);

            //
            // If this is a 32-bit asynchronous IO, then mark the Iosb being sent as so.
            // Note: IopMarkApcRoutineIfAsyncronousIo32 must be called after probing
            //       the IoStatusBlock structure for write.
            //

            IopMarkApcRoutineIfAsyncronousIo32(IoStatusBlock,ApcRoutine,(fileObject->Flags & FO_SYNCHRONOUS_IO));

            //
            // If this file has an I/O completion port associated w/it, then
            // ensure that the caller did not supply an APC routine, as the
            // two are mutually exclusive methods for I/O completion
            // notification.
            //

            if (fileObject->CompletionContext && IopApcRoutinePresent( ApcRoutine )) {
                ObDereferenceObject( fileObject );
                return STATUS_INVALID_PARAMETER;
            }

            //
            // Also ensure that the ByteOffset parameter is readable from
            // the caller's mode and capture it if it is present.
            //

            if (ARGUMENT_PRESENT( ByteOffset )) {
                ProbeForReadSmallStructure( ByteOffset,
                                            sizeof( LARGE_INTEGER ),
                                            sizeof( ULONG ) );
                fileOffset = *ByteOffset;
            }

            //
            // Check to see whether the caller has opened the file without
            // intermediate buffering.  If so, perform the following ByteOffset
            // parameter check differently.
            //

            if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {

                //
                // The file was opened without intermediate buffering enabled.
                // Check that the Buffer is properly aligned, and that the
                // length is an integral number of 512-byte blocks.
                //

                if ((deviceObject->SectorSize &&
                    (Length & (deviceObject->SectorSize - 1)))) {

                    //
                    // Check for sector sizes that are not a power of two.
                    //

                    if ((deviceObject->SectorSize &&
                        Length % deviceObject->SectorSize)) {
                        ObDereferenceObject( fileObject );
                        return STATUS_INVALID_PARAMETER;
                    }
                }

                //
                // If a ByteOffset parameter was specified, ensure that it
                // is a valid argument.
                //

                if (ARGUMENT_PRESENT( ByteOffset )) {
                    if (deviceObject->SectorSize &&
                        (fileOffset.LowPart & (deviceObject->SectorSize - 1))) {
                        ObDereferenceObject( fileObject );
                        return STATUS_INVALID_PARAMETER;
                    }
                }
            }

            //
            // The SegmentArray parameter must be accessible.
            //

#ifdef _X86_
            ProbeForRead( SegmentArray,
                          elementCount * sizeof( FILE_SEGMENT_ELEMENT ),
                          sizeof( ULONG )
                          );
#elif defined(_WIN64)

            //
            // If we are a wow64 process, follow the X86 rules
            //

            if (PsGetCurrentProcess()->Wow64Process) {
                ProbeForRead( SegmentArray,
                              elementCount * sizeof( FILE_SEGMENT_ELEMENT ),
                              sizeof( ULONG )
                              );
            } else {
                ProbeForRead( SegmentArray,
                              elementCount * sizeof( FILE_SEGMENT_ELEMENT ),
                              TYPE_ALIGNMENT( FILE_SEGMENT_ELEMENT )
                              );
            }
#else
            ProbeForRead( SegmentArray,
                          elementCount * sizeof( FILE_SEGMENT_ELEMENT ),
                          TYPE_ALIGNMENT( FILE_SEGMENT_ELEMENT )
                          );
#endif

            if (Length != 0) {

                //
                // Capture the segment array so it cannot be changed after
                // it has been looked at.
                //

                capturedArray = ExAllocatePoolWithQuota( PagedPool,
                                                elementCount * sizeof( FILE_SEGMENT_ELEMENT )
                                                );

                RtlCopyMemory( capturedArray,
                               SegmentArray,
                               elementCount * sizeof( FILE_SEGMENT_ELEMENT )
                               );

                SegmentArray = capturedArray;

                //
                // Verify that all the addresses are page aligned.
                //

                for (i = 0; i < elementCount; i++) {

                    if ( SegmentArray[i].Alignment & (PAGE_SIZE - 1)) {
                        ExRaiseStatus( STATUS_INVALID_PARAMETER );
                    }
                }
            }

            //
            // Finally, ensure that if there is a key parameter specified it
            // is readable by the caller.
            //

            if (ARGUMENT_PRESENT( Key )) {
                keyValue = ProbeAndReadUlong( Key );
            }

        } except(IopExceptionFilter( GetExceptionInformation(), &exceptionCode )) {

            //
            // An exception was incurred while attempting to probe the
            // caller's parameters.  Dereference the file object and return
            // an appropriate error status code.
            //

            ObDereferenceObject( fileObject );
            if (capturedArray != NULL) {
                ExFreePool( capturedArray );
            }
            return exceptionCode;

        }

    } else {

        //
        // The caller's mode is kernel.  Get the same parameters that are
        // required from any other mode.
        //

        if (ARGUMENT_PRESENT( ByteOffset )) {
            fileOffset = *ByteOffset;
        }

        if (ARGUMENT_PRESENT( Key )) {
            keyValue = *Key;
        }

#if DBG
        if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {

            //
            // The file was opened without intermediate buffering enabled.
            // Check that the the length is an integral number of the block
            //  size.
            //

            if ((deviceObject->SectorSize &&
                (Length & (deviceObject->SectorSize - 1)))) {

                //
                // Check for sector sizes that are not a power of two.
                //

                if ((deviceObject->SectorSize &&
                    Length % deviceObject->SectorSize)) {
                    ObDereferenceObject( fileObject );
                    ASSERT( FALSE );
                    return STATUS_INVALID_PARAMETER;
                }
            }

            //
            // If a ByteOffset parameter was specified, ensure that it
            // is a valid argument.
            //

            if (ARGUMENT_PRESENT( ByteOffset )) {
                if (deviceObject->SectorSize &&
                    (fileOffset.LowPart & (deviceObject->SectorSize - 1))) {
                    ObDereferenceObject( fileObject );
                    ASSERT( FALSE );
                    return STATUS_INVALID_PARAMETER;
                }
            }
        }

        if (Length != 0) {

            //
            // Verify that all the addresses are page aligned.
            //

            for (i = 0; i < elementCount; i++) {

                if ( SegmentArray[i].Alignment & (PAGE_SIZE - 1)) {

                    ObDereferenceObject( fileObject );
                    ASSERT(FALSE);
                    return STATUS_INVALID_PARAMETER;
                }
            }
        }
#endif // DBG
    }

    //
    // Get the address of the event object and set the event to the Not-
    // Signaled state, if an one was specified.  Note here too, that if
    // the handle does not refer to an event, then the reference will fail.
    //

    if (ARGUMENT_PRESENT( Event )) {
        status = ObReferenceObjectByHandle( Event,
                                            EVENT_MODIFY_STATE,
                                            ExEventObjectType,
                                            requestorMode,
                                            (PVOID *) &eventObject,
                                            NULL );
        if (!NT_SUCCESS( status )) {
            ObDereferenceObject( fileObject );
            if (capturedArray != NULL) {
                ExFreePool( capturedArray );
            }
            return status;
        } else {
            KeClearEvent( eventObject );
        }
    }

    //
    // Get the address of the driver object's Fast I/O dispatch structure.
    //

    fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;

    //
    // 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 (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                if (eventObject) {
                    ObDereferenceObject( eventObject );
                }
                ObDereferenceObject( fileObject );
                if (capturedArray != NULL) {
                    ExFreePool( capturedArray );
                }
                return status;
            }
        }

        if (!ARGUMENT_PRESENT( ByteOffset ) ||
            (fileOffset.LowPart == FILE_USE_FILE_POINTER_POSITION &&
            fileOffset.HighPart == -1)) {
            fileOffset = fileObject->CurrentByteOffset;
        }

        synchronousIo = TRUE;

    } else if (!ARGUMENT_PRESENT( ByteOffset ) && !(fileObject->Flags & (FO_NAMED_PIPE | FO_MAILSLOT))) {

        //
        // The file is not open for synchronous I/O operations, but the
        // caller did not specify a ByteOffset parameter.
        //

        if (eventObject) {
            ObDereferenceObject( eventObject );
        }
        ObDereferenceObject( fileObject );
        if (capturedArray != NULL) {
            ExFreePool( capturedArray );
        }
        return STATUS_INVALID_PARAMETER;
    } else {
        synchronousIo = FALSE;
    }

    //
    //  Negative file offsets are illegal.
    //

    if (fileOffset.HighPart < 0) {
        if (eventObject) {
            ObDereferenceObject( eventObject );
        }
        if (synchronousIo) {
            IopReleaseFileObjectLock( fileObject );
        }
        ObDereferenceObject( fileObject );
        if (capturedArray != NULL) {
            ExFreePool( capturedArray );
        }
        return STATUS_INVALID_PARAMETER;
    }

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

    KeClearEvent( &fileObject->Event );

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.

    irp = IopAllocateIrp( deviceObject->StackSize, !synchronousIo );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        IopAllocateIrpCleanup( fileObject, eventObject );

        if (capturedArray != NULL) {
            ExFreePool( capturedArray );
        }
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->Tail.Overlay.AuxiliaryBuffer = (PVOID) NULL;
    irp->RequestorMode = requestorMode;
    irp->PendingReturned = FALSE;
    irp->Cancel = FALSE;
    irp->CancelRoutine = (PDRIVER_CANCEL) NULL;

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

    irp->UserEvent = eventObject;
    irp->UserIosb = IoStatusBlock;
    irp->Overlay.AsynchronousParameters.UserApcRoutine = ApcRoutine;
    irp->Overlay.AsynchronousParameters.UserApcContext = ApcContext;

    //
    // Get a pointer to the stack location for the first driver.  This will be
    // used to pass the original function codes and parameters.  Note that
    // setting the major function here also sets:
    //
    //      MinorFunction = 0;
    //      Flags = 0;
    //      Control = 0;
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    majorFunction = (PULONG) (&irpSp->MajorFunction);
    *majorFunction = IRP_MJ_READ;
    irpSp->FileObject = fileObject;

    //
    // Always allocate a Memory Descriptor List (MDL) and lock down the
    // caller's buffer. This way the file system do not have change to
    // build a scatter MDL. Note buffered I/O is not supported for this
    // routine.
    //

    irp->AssociatedIrp.SystemBuffer = (PVOID) NULL;
    irp->MdlAddress = (PMDL) NULL;


    //
    // This is a direct I/O operation.  Allocate an MDL and invoke the
    // memory management routine to lock the buffer into memory.  This
    // is done using an exception handler that will perform cleanup if
    // the operation fails.  Note that no MDL is allocated, nor is any
    // memory probed or locked if the length of the request was zero.
    //

    irp->Flags = 0;

    if (Length) {

        PMDL mdl;

        try {

            //
            // Allocate an MDL, charging quota for it, and hang it off of
            // the IRP.  Probe and lock the pages associated with the
            // caller's buffer for write access and fill in the MDL with
            // the PFNs of those pages.
            //

            mdl = IoAllocateMdl( (PVOID) Ptr64ToPtr (SegmentArray[0].Buffer), Length, FALSE, TRUE, irp );
            if (mdl == NULL) {
                ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
            }

            //
            // The address of the first file segment is used as a base
            // address.
            //

            MmProbeAndLockSelectedPages( mdl,
                                         SegmentArray,
                                         requestorMode,
                                         IoWriteAccess );

            irp->UserBuffer = (PVOID) Ptr64ToPtr (SegmentArray[0].Buffer);

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while either probing the caller's
            // buffer or allocating the MDL.  Determine what actually
            // happened, clean everything up, and return an appropriate
            // error status code.
            //

            IopExceptionCleanup( fileObject,
                                 irp,
                                 eventObject,
                                 (PKEVENT) NULL );

            if (capturedArray != NULL) {
                ExFreePool( capturedArray );
            }
            return GetExceptionCode();

        }

    }

    //
    // We are done with the captured buffer.
    //

    if (capturedArray != NULL) {
        ExFreePool( capturedArray );
    }

    //
    // If this read operation is supposed to be performed with caching disabled
    // set the disable flag in the IRP so no caching is performed.
    //

    if (fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {
        irp->Flags |= IRP_NOCACHE | IRP_READ_OPERATION | IRP_DEFER_IO_COMPLETION;
    } else {
        irp->Flags |= IRP_READ_OPERATION | IRP_DEFER_IO_COMPLETION;
    }

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Parameters.Read.Length = Length;
    irpSp->Parameters.Read.Key = keyValue;
    irpSp->Parameters.Read.ByteOffset = fileOffset;

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

    status =  IopSynchronousServiceTail( deviceObject,
                                         irp,
                                         fileObject,
                                         TRUE,
                                         requestorMode,
                                         synchronousIo,
                                         ReadTransfer );

    return status;

}

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg()
#endif // ALLOC_DATA_PRAGMA

