1393 lines
44 KiB
C
1393 lines
44 KiB
C
/*++
|
||
|
||
Copyright (c) 1989-1993 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
read.c
|
||
|
||
Abstract:
|
||
|
||
This module contains the code to implement the NtReadFile system service.
|
||
|
||
Author:
|
||
|
||
Darryl E. Havens (darrylh) 14-Apr-1989
|
||
|
||
Environment:
|
||
|
||
Kernel mode
|
||
|
||
Revision History:
|
||
|
||
|
||
--*/
|
||
|
||
#include "iomgr.h"
|
||
|
||
#ifdef ALLOC_DATA_PRAGMA
|
||
#pragma const_seg("PAGECONST")
|
||
#endif // ALLOC_DATA_PRAGMA
|
||
const KPRIORITY IopCacheHitIncrement = IO_NO_INCREMENT;
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, NtReadFile)
|
||
#pragma alloc_text(PAGE, NtReadFileScatter)
|
||
#endif
|
||
|
||
NTSTATUS
|
||
NtReadFile(
|
||
IN HANDLE FileHandle,
|
||
IN HANDLE Event OPTIONAL,
|
||
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
|
||
IN PVOID ApcContext OPTIONAL,
|
||
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||
OUT PVOID Buffer,
|
||
IN ULONG Length,
|
||
IN PLARGE_INTEGER ByteOffset OPTIONAL,
|
||
IN PULONG Key OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
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.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// The IoStatusBlock parameter must be writeable by the caller.
|
||
//
|
||
|
||
ProbeForWriteIoStatusEx(IoStatusBlock , ApcRoutine);
|
||
|
||
//
|
||
// 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, TRUE );
|
||
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 appopriately with
|
||
// I/O completion.
|
||
//
|
||
|
||
status = IopSynchronousServiceTail( deviceObject,
|
||
irp,
|
||
fileObject,
|
||
TRUE,
|
||
requestorMode,
|
||
synchronousIo,
|
||
ReadTransfer );
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
NtReadFileScatter(
|
||
IN HANDLE FileHandle,
|
||
IN HANDLE Event OPTIONAL,
|
||
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
|
||
IN PVOID ApcContext OPTIONAL,
|
||
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||
IN PFILE_SEGMENT_ELEMENT SegmentArray,
|
||
IN ULONG Length,
|
||
IN PLARGE_INTEGER ByteOffset OPTIONAL,
|
||
IN PULONG Key OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
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.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// The IoStatusBlock parameter must be writeable by the caller.
|
||
//
|
||
|
||
ProbeForWriteIoStatusEx( IoStatusBlock , ApcRoutine);
|
||
|
||
//
|
||
// 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 paramter 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, TRUE );
|
||
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)(ULONG_PTR) 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)(ULONG_PTR) 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 appopriately with
|
||
// I/O completion.
|
||
//
|
||
|
||
status = IopSynchronousServiceTail( deviceObject,
|
||
irp,
|
||
fileObject,
|
||
TRUE,
|
||
requestorMode,
|
||
synchronousIo,
|
||
ReadTransfer );
|
||
|
||
return status;
|
||
|
||
}
|