/*++

Copyright (c) 2000-2001  Microsoft Corporation

Module Name:

    readwrit.c

Abstract:

    This module implements routines related to handling IRP_MJ_READ and
    IRP_MJ_WRITE.

--*/

#include "fatx.h"

//
// Local support.
//

VOID
FatxStartNextAsynchronousIoEntry(
    IN PFAT_VOLUME_EXTENSION VolumeExtension,
    IN PIRP Irp,
    IN PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor
    );

NTSTATUS
FatxVolumeIoCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
/*++

Routine Description:

    This routine is called to process the completion of a volume I/O transfer.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    Context - Specifies the context that was supplied to IoSetCompletionRoutine.

Return Value:

    Status of operation.

--*/
{
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PIO_STACK_LOCATION IrpSp;
    PFILE_OBJECT FileObject;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    IrpSp = IoGetCurrentIrpStackLocation(Irp);

    //
    // Propagate the pending flag up the IRP stack.
    //

    if (Irp->PendingReturned) {
        IoMarkIrpPending(Irp);
    }

    //
    // Check if this is a read or write completion.  This completion routine is
    // shared by other IRP dispatch routines.
    //

    if ((IrpSp->MajorFunction == IRP_MJ_READ) ||
        (IrpSp->MajorFunction == IRP_MJ_WRITE)) {

        if (NT_SUCCESS(Irp->IoStatus.Status)) {

            ASSERT(Irp->IoStatus.Information > 0);
            ASSERT(FatxIsSectorAligned(VolumeExtension, Irp->IoStatus.Information));

            //
            // If the file is open for synchronous I/O, then we need to update
            // the current file position.
            //

            FileObject = IrpSp->FileObject;

            if (FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO)) {
                FileObject->CurrentByteOffset.QuadPart =
                    IrpSp->Parameters.Read.ByteOffset.QuadPart +
                    Irp->IoStatus.Information;
            }
        }
    }

    FatxDpcReleaseVolumeMutex(VolumeExtension);

    return STATUS_SUCCESS;
}

NTSTATUS
FatxSignalIoEventCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
/*++

Routine Description:

    This routine is called to signal an event when a thread is blocked on an
    I/O operation inside an IRP dispatch routine.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    Context - Specifies the context that was supplied to IoSetCompletionRoutine.

Return Value:

    Status of operation.

--*/
{
    KeSetEvent((PKEVENT)Context, IO_DISK_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS
FatxSynchronousIoTail(
    IN PFAT_VOLUME_EXTENSION VolumeExtension,
    IN PIRP Irp,
    IN UCHAR MajorFunction,
    IN PFILE_OBJECT FileObject,
    IN ULONG FileByteOffset,
    IN ULONG IoLength
    )
/*++

Routine Description:

    This routine is called at the tail of the non-cached and cached synchronous
    I/O routines to update the state in the file control block and file objects.

    This routine is only called on success from these other routines.

Arguments:

    VolumeExtension - Specifies the extension that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    MajorFunction - Specifies the function to be performed; either IRP_MJ_READ
        or IRP_MJ_WRITE.

    FileObject - Specifies the file object that the I/O request is for.

    FileByteOffset - Specifies the file byte offset where the transfer started
        from.

    IoLength - Specifies the number of bytes that were transferred.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_FCB FileFcb;
    ULONG EndingByteOffset;
    ULONG OriginalFileSize;

    FileFcb = (PFAT_FCB)FileObject->FsContext;

    EndingByteOffset = FileByteOffset + IoLength;

    //
    // If we've written to the file, then update the last write time.  The last
    // write time is lazily flushed to the directory entry.  We need to acquire
    // the volume mutex for exclusive access to synchronize with
    // FatxFsdSetInformation and to write out any directory changes below.
    //

    if (MajorFunction == IRP_MJ_WRITE) {

        FatxAcquireVolumeMutexExclusive(VolumeExtension);

        if (FatxIsFlagClear(FileFcb->Flags, FAT_FCB_DISABLE_LAST_WRITE_TIME)) {
            KeQuerySystemTime(&FileFcb->LastWriteTime);
            FileFcb->Flags |= FAT_FCB_UPDATE_DIRECTORY_ENTRY;
        }

        //
        // If the ending byte offset is beyond the current size of the file,
        // then we've extended the file.  Change the file size to the new size
        // and commit the directory change.
        //

        if (EndingByteOffset > FileFcb->FileSize) {

            OriginalFileSize = FileFcb->FileSize;
            FileFcb->FileSize = EndingByteOffset;

            status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, FileFcb);

            if (!NT_SUCCESS(status)) {
                FileFcb->FileSize = OriginalFileSize;
                FatxReleaseVolumeMutex(VolumeExtension);
                return status;
            }
        }

        FatxReleaseVolumeMutex(VolumeExtension);
    }

    //
    // If the file is open for synchronous I/O, then we need to update the
    // current file position.
    //

    if (FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO)) {
        FileObject->CurrentByteOffset.LowPart = EndingByteOffset;
        FileObject->CurrentByteOffset.HighPart = 0;
    }

    //
    // Fill in the number of bytes of transferred.  This number may be less than
    // the actual number of modified bytes in the buffer if we're at the end of
    // file.
    //

    Irp->IoStatus.Information = IoLength;

    return STATUS_SUCCESS;
}

NTSTATUS
FatxNonCachedSynchronousIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN UCHAR MajorFunction,
    IN PFILE_OBJECT FileObject,
    IN ULONG FileByteOffset,
    IN ULONG BufferByteOffset,
    IN ULONG IoLength,
    IN BOOLEAN PartialTransfer
    )
/*++

Routine Description:

    This routine is called to synchronously read or write a sector aligned
    buffer from or to the target device.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    MajorFunction - Specifies the function to be performed; either IRP_MJ_READ
        or IRP_MJ_WRITE.

    FileObject - Specifies the file object that the I/O request is for.

    FileByteOffset - Specifies the file byte offset to start the transfer from.

    BufferByteOffset - Specifies the buffer byte offset ot start the transfer
        from.

    IoLength - Specifies the number of bytes to transfer.

    PartialTransfer - TRUE if called from FatxPartiallyCachedSynchronousIo, else
        FALSE.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PFAT_FCB Fcb;
    PIO_STACK_LOCATION NextIrpSp;
    ULONG OriginalFileByteOffset;
    ULONG IoLengthRemaining;
    ULONGLONG PhysicalByteOffset;
    ULONG PhysicalRunLength;
    ULONGLONG StartingPhysicalByteOffset;
    ULONG PhysicalIoLength;
    ULONGLONG AdjacentPhysicalByteOffset;
    KEVENT IoEvent;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    Fcb = (PFAT_FCB)FileObject->FsContext;
    NextIrpSp = IoGetNextIrpStackLocation(Irp);
    OriginalFileByteOffset = FileByteOffset;
    IoLengthRemaining = FatxRoundToSectors(VolumeExtension, IoLength);

    ASSERT(IoLengthRemaining > 0);
    ASSERT(FatxIsSectorAligned(VolumeExtension, FileByteOffset));

    //
    // Get the physical byte offset corresponding to the starting byte offset.
    //

    status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb,
        FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Do the transfer.
    //

    do {

        StartingPhysicalByteOffset = PhysicalByteOffset;

        //
        // Compute how many physical bytes we can process in this pass.  The
        // cluster cache doesn't follow adjacent clusters that cross cache pages
        // so we handle that logic here by increasing the physical I/O length
        // until we find a non-adjacent cluster run.  This non-adjacent cluster
        // run is the starting cluster for the next iteration of the outer loop.
        //

        PhysicalIoLength = 0;

        do {

            //
            // Limit the number of bytes in the physical run to the number of
            // bytes left to process.
            //

            if (PhysicalRunLength > IoLengthRemaining) {
                PhysicalRunLength = IoLengthRemaining;
                IoLengthRemaining = 0;
            } else {
                IoLengthRemaining -= PhysicalRunLength;
            }

            //
            // Update the number of physical bytes we can process and the offset
            // into the file.
            //

            PhysicalIoLength += PhysicalRunLength;
            FileByteOffset += PhysicalRunLength;

            if (IoLengthRemaining == 0) {
                break;
            }

            //
            // Compute the physical byte offset for the adjacent run.
            //

            AdjacentPhysicalByteOffset = PhysicalByteOffset + PhysicalRunLength;

            //
            // Obtain the next physical run.
            //

            status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension,
                Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset,
                &PhysicalRunLength);

            if (!NT_SUCCESS(status)) {
                return status;
            }

            //
            // If the next physical byte offset isn't adjacent to the last
            // physical run, then we're done.
            //

        } while (PhysicalByteOffset == AdjacentPhysicalByteOffset);

        //
        // Invalidate any file system cache buffers for this byte range if this
        // is a write operation.
        //

        if (MajorFunction == IRP_MJ_WRITE) {
            FscInvalidateByteRange(&VolumeExtension->CacheExtension,
                StartingPhysicalByteOffset, PhysicalIoLength);
        }

        //
        // Initialize (or reinitialize) the synchronization event we'll use to
        // block for the port driver to handle the I/O.
        //

        KeInitializeEvent(&IoEvent, SynchronizationEvent, FALSE);

        //
        // Fill in the starting physical byte offset and the number of bytes to
        // transfer.
        //

        NextIrpSp->Parameters.Read.ByteOffset.QuadPart =
            StartingPhysicalByteOffset;
        NextIrpSp->Parameters.Read.Length = PhysicalIoLength;

        //
        // Fill in the offset into the buffer to start the transfer and update
        // that offset by the number of bytes we'll physically transfer.
        //

        NextIrpSp->Parameters.Read.BufferOffset = BufferByteOffset;
        BufferByteOffset += PhysicalIoLength;

        //
        // Fill in the header for the stack location.  This clears out the
        // MinorFunction, Control, and Flags field as well.
        //

        *((PULONG)&NextIrpSp->MajorFunction) = MajorFunction;

        //
        // Set the completion routine that will signal our synchronization
        // event.
        //

        IoSetCompletionRoutine(Irp, FatxSignalIoEventCompletion, &IoEvent, TRUE,
            TRUE, TRUE);

        //
        // Call down to the target device and block for the I/O to complete.
        //

        status = IoCallDriver(VolumeExtension->TargetDeviceObject, Irp);

        if (status == STATUS_PENDING) {
            KeWaitForSingleObject(&IoEvent, Executive, KernelMode, FALSE, NULL);
            status = Irp->IoStatus.Status;
        }

        if (!NT_SUCCESS(status)) {
            return status;
        }

        //
        // Assert that the device handled as many bytes as we programmed it to.
        //

        ASSERT(Irp->IoStatus.Information == PhysicalIoLength);

    } while (IoLengthRemaining > 0);

    if (!PartialTransfer) {
        status = FatxSynchronousIoTail(VolumeExtension, Irp, MajorFunction,
            FileObject, OriginalFileByteOffset, IoLength);
    } else {
        status = STATUS_SUCCESS;
    }

    return status;
}

NTSTATUS
FatxNonCachedAsynchronousIoCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
/*++

Routine Description:

    This routine is called to process the completion of a non-cached
    asynchronous I/O transfer.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    Context - Specifies the context that was supplied to IoSetCompletionRoutine.

Return Value:

    Status of operation.

--*/
{
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor;
    PFAT_FCB FileFcb;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    AsyncIoDescriptor = (PFAT_ASYNC_IO_DESCRIPTOR)Context;

    if (NT_SUCCESS(Irp->IoStatus.Status)) {

        //
        // If there are still more bytes to transfer, then start the entry and
        // bail out.
        //

        if (AsyncIoDescriptor->IoLengthRemaining > 0) {
            FatxStartNextAsynchronousIoEntry(VolumeExtension, Irp,
                AsyncIoDescriptor);
            return STATUS_MORE_PROCESSING_REQUIRED;
        }

        //
        // If this was a file write, then we need to update the last write time.
        //

        if (AsyncIoDescriptor->MajorFunction == IRP_MJ_WRITE) {

            FileFcb = AsyncIoDescriptor->FileFcb;

            if (FatxIsFlagClear(FileFcb->Flags, FAT_FCB_DISABLE_LAST_WRITE_TIME)) {
                KeQuerySystemTime(&FileFcb->LastWriteTime);
                FileFcb->Flags |= FAT_FCB_UPDATE_DIRECTORY_ENTRY;
            }
        }

        //
        // Fill in the number of bytes of transferred.  This number may be less
        // than the actual number of modified bytes in the buffer if we're at
        // the end of file.
        //

        Irp->IoStatus.Information = AsyncIoDescriptor->IoLength;
    }

    FatxDecrementDismountBlockCount(VolumeExtension);
    FatxDpcReleaseFileMutex(AsyncIoDescriptor->FileFcb);

    ExFreePool(AsyncIoDescriptor);

    return STATUS_SUCCESS;
}

VOID
FatxStartNextAsynchronousIoEntry(
    IN PFAT_VOLUME_EXTENSION VolumeExtension,
    IN PIRP Irp,
    IN PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor
    )
/*++

Routine Description:

    This routine is called to start the next read or write from the supplied
    asynchronous I/O descriptor.

Arguments:

    VolumeExtension - Specifies the extension that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    AsyncIoDescriptor - Specifies the asynchronous I/O descriptor that indicates
        the next starting physical sector and transfer length.

Return Value:

    None.

--*/
{
    PIO_STACK_LOCATION NextIrpSp;
    PFAT_ASYNC_IO_ENTRY AsyncIoEntry;
    ULONG PhysicalIoLength;

    NextIrpSp = IoGetNextIrpStackLocation(Irp);
    AsyncIoEntry = &AsyncIoDescriptor->Entries[AsyncIoDescriptor->NextAsyncIoEntry++];
    PhysicalIoLength = AsyncIoEntry->PhysicalIoLength;

    //
    // Adjust the number of bytes remaining in the transfer.
    //

    AsyncIoDescriptor->IoLengthRemaining -= PhysicalIoLength;

    //
    // Fill in the starting physical byte offset and the number of bytes to
    // transfer.
    //

    NextIrpSp->Parameters.Read.ByteOffset.QuadPart =
        (ULONGLONG)AsyncIoEntry->PhysicalSector << VolumeExtension->SectorShift;
    NextIrpSp->Parameters.Read.Length = PhysicalIoLength;

    //
    // Fill in the offset into the buffer to start the transfer and update
    // that offset by the number of bytes we'll physically transfer.
    //

    NextIrpSp->Parameters.Read.BufferOffset = AsyncIoDescriptor->BufferOffset;
    AsyncIoDescriptor->BufferOffset += PhysicalIoLength;

    //
    // Fill in the header for the stack location.  This clears out the
    // MinorFunction, Control, and Flags field as well.
    //

    *((PULONG)&NextIrpSp->MajorFunction) = AsyncIoDescriptor->MajorFunction;

    //
    // Set the completion routine that will start the next entry or finish the
    // transfer.
    //

    IoSetCompletionRoutine(Irp, FatxNonCachedAsynchronousIoCompletion,
        AsyncIoDescriptor, TRUE, TRUE, TRUE);

    //
    // Call down to the target device.
    //

    IoCallDriver(VolumeExtension->TargetDeviceObject, Irp);
}

NTSTATUS
FatxNonCachedAsynchronousIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN UCHAR MajorFunction,
    IN PFILE_OBJECT FileObject,
    IN ULONG FileByteOffset,
    IN ULONG IoLength
    )
/*++

Routine Description:

    This routine is called to asynchronously read or write a sector aligned
    buffer from or to the target device.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    MajorFunction - Specifies the function to be performed; either IRP_MJ_READ
        or IRP_MJ_WRITE.

    FileObject - Specifies the file object that the I/O request is for.

    FileByteOffset - Specifies the file byte offset to start the transfer from.

    IoLength - Specifies the number of bytes to transfer.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PFAT_FCB Fcb;
    ULONG IoLengthRemaining;
    SIZE_T AsyncIoDescriptorSize;
    PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor;
    ULONGLONG PhysicalByteOffset;
    ULONG PhysicalRunLength;
    PFAT_ASYNC_IO_ENTRY AsyncIoEntry;
    ULONGLONG StartingPhysicalByteOffset;
    ULONG PhysicalIoLength;
    ULONGLONG AdjacentPhysicalByteOffset;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    Fcb = (PFAT_FCB)FileObject->FsContext;
    IoLengthRemaining = FatxRoundToSectors(VolumeExtension, IoLength);

    ASSERT(IoLengthRemaining > 0);
    ASSERT(FatxIsSectorAligned(VolumeExtension, FileByteOffset));

    //
    // Always assume the worst case for the state of the file.  If the file is
    // highly fragmented, then we'll need one entry per cluster.  If the file
    // isn't fragmented, then we'll end up wasting memory, but the application
    // will always be charged a consistent number of bytes per transfer.
    //

    // DW: bug fix
    // If the area to write wraps a cluster, allocate space for one more
    // entry.  XDKs starting with 4627 patch this bug in older kernels.
    // They patch by always adding 8 extra (sizeof(FAT_ASYNC_IO_ENTRY)).
	// Note the 2 below - that used to be a 1.
	//

	AsyncIoDescriptorSize = sizeof(FAT_ASYNC_IO_DESCRIPTOR) +
        ((IoLengthRemaining >> VolumeExtension->ClusterShift) + 2) *
        sizeof(FAT_ASYNC_IO_ENTRY);

    AsyncIoDescriptor = ExAllocatePoolWithTag(AsyncIoDescriptorSize, 'dAtF');

    if (AsyncIoDescriptor == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Initialize the asynchronous I/O descriptor.
    //

    AsyncIoDescriptor->MajorFunction = MajorFunction;
    AsyncIoDescriptor->IoLength = IoLength;
    AsyncIoDescriptor->IoLengthRemaining = IoLengthRemaining;
    AsyncIoDescriptor->BufferOffset = 0;
    AsyncIoDescriptor->NextAsyncIoEntry = 0;
    AsyncIoDescriptor->FileFcb = Fcb;

    //
    // Get the physical byte offset corresponding to the starting byte offset.
    //

    status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb,
        FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Fill in the entries of the asynchronous I/O descriptor.
    //

    AsyncIoEntry = AsyncIoDescriptor->Entries;

    do {

        StartingPhysicalByteOffset = PhysicalByteOffset;

        //
        // Compute how many physical bytes we can process in this pass.  The
        // cluster cache doesn't follow adjacent clusters that cross cache pages
        // so we handle that logic here by increasing the physical I/O length
        // until we find a non-adjacent cluster run.  This non-adjacent cluster
        // run is the starting cluster for the next iteration of the outer loop.
        //

        PhysicalIoLength = 0;

        do {

            //
            // Limit the number of bytes in the physical run to the number of
            // bytes left to process.
            //

            if (PhysicalRunLength > IoLengthRemaining) {
                PhysicalRunLength = IoLengthRemaining;
                IoLengthRemaining = 0;
            } else {
                IoLengthRemaining -= PhysicalRunLength;
            }

            //
            // Update the number of physical bytes we can process and the offset
            // into the file.
            //

            PhysicalIoLength += PhysicalRunLength;
            FileByteOffset += PhysicalRunLength;

            if (IoLengthRemaining == 0) {
                break;
            }

            //
            // Compute the physical byte offset for the adjacent run.
            //

            AdjacentPhysicalByteOffset = PhysicalByteOffset + PhysicalRunLength;

            //
            // Obtain the next physical run.
            //

            status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension,
                Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset,
                &PhysicalRunLength);

            if (!NT_SUCCESS(status)) {
                ExFreePool(AsyncIoDescriptor);
                return status;
            }

            //
            // If the next physical byte offset isn't adjacent to the last
            // physical run, then we're done.
            //

        } while (PhysicalByteOffset == AdjacentPhysicalByteOffset);

        ASSERT(FatxIsSectorAligned(VolumeExtension, PhysicalIoLength));

        //
        // Invalidate any file system cache buffers for this byte range if this
        // is a write operation.
        //

        if (MajorFunction == IRP_MJ_WRITE) {
            FscInvalidateByteRange(&VolumeExtension->CacheExtension,
                StartingPhysicalByteOffset, PhysicalIoLength);
        }

        //
        // Fill in the starting physical byte offset and the number of bytes to
        // transfer.
        //

        AsyncIoEntry->PhysicalSector = (ULONG)(StartingPhysicalByteOffset >>
            VolumeExtension->SectorShift);
        AsyncIoEntry->PhysicalIoLength = PhysicalIoLength;
        AsyncIoEntry++;

    } while (IoLengthRemaining > 0);

    //
    // Start transferring the first entry in the asynchronous I/O descriptor.
    //

    IoMarkIrpPending(Irp);

    FatxStartNextAsynchronousIoEntry(VolumeExtension, Irp,
        AsyncIoDescriptor);

    return STATUS_PENDING;
}

NTSTATUS
FatxFullyCachedSynchronousIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN UCHAR MajorFunction,
    IN PFILE_OBJECT FileObject,
    IN ULONG FileByteOffset,
    IN ULONG BufferByteOffset,
    IN ULONG IoLength,
    IN BOOLEAN PartialTransfer
    )
/*++

Routine Description:

    This routine is called to synchronously read or write a buffer through the
    file system cache.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    MajorFunction - Specifies the function to be performed; either IRP_MJ_READ
        or IRP_MJ_WRITE.

    FileObject - Specifies the file object that the I/O request is for.

    FileByteOffset - Specifies the file byte offset to start the transfer from.

    BufferByteOffset - Specifies the buffer byte offset ot start the transfer
        from.

    IoLength - Specifies the number of bytes to transfer.

    PartialTransfer - TRUE if called from FatxPartiallyCachedSynchronousIo, else
        FALSE.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PFAT_FCB Fcb;
    ULONG OriginalFileByteOffset;
    ULONG IoLengthRemaining;
    PVOID UserBuffer;
    ULONGLONG PhysicalByteOffset;
    ULONG PhysicalRunLength;
    ULONG PhysicalRunBytesRemaining;
    PVOID CacheBuffer;
    ULONG BytesToCopy;
    BOOLEAN MapEmptyBuffer;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    Fcb = (PFAT_FCB)FileObject->FsContext;
    OriginalFileByteOffset = FileByteOffset;
    IoLengthRemaining = IoLength;
    UserBuffer = (PUCHAR)Irp->UserBuffer + BufferByteOffset;

    ASSERT(IoLengthRemaining > 0);

    //
    // Get the physical byte offset corresponding to the starting byte offset.
    //

    status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb,
        FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Do the transfer.
    //

    for (;;) {

        PhysicalRunBytesRemaining = PhysicalRunLength;

        do {

            //
            // Compute the number of bytes remaining on this cache buffer.
            //

            BytesToCopy = PAGE_SIZE - BYTE_OFFSET(PhysicalByteOffset);

            //
            // Adjust the number of bytes remaining in this physical run and the
            // next cache byte offset depending on whether we're near the end of
            // the run or not.
            //

            if (BytesToCopy < PhysicalRunBytesRemaining) {
                PhysicalRunBytesRemaining -= BytesToCopy;
            } else {
                BytesToCopy = PhysicalRunBytesRemaining;
                PhysicalRunBytesRemaining = 0;
            }

            //
            // Limit the number of bytes copied to the number of bytes we
            // actually need.
            //

            if (BytesToCopy > IoLengthRemaining) {
                BytesToCopy = IoLengthRemaining;
            }

            if (MajorFunction == IRP_MJ_WRITE) {

                //
                // If we're going to be writing out an entire page or if we're
                // going to be writing to the first byte of the last page of the
                // file, then we can map in an empty cache page.
                //

                if (BytesToCopy == PAGE_SIZE) {
                    MapEmptyBuffer = TRUE;
                } else if ((BYTE_OFFSET(FileByteOffset) == 0) &&
                    (FileByteOffset + IoLengthRemaining >= Fcb->FileSize)) {
                    MapEmptyBuffer = TRUE;
                } else {
                    MapEmptyBuffer = FALSE;
                }

                //
                // Map in the next page of the physical run.
                //

                if (MapEmptyBuffer) {
                    status = FscMapEmptyBuffer(&VolumeExtension->CacheExtension,
                        PhysicalByteOffset, &CacheBuffer);
                } else {
                    status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
                        PhysicalByteOffset, TRUE, &CacheBuffer);
                }

                if (!NT_SUCCESS(status)) {
                    return status;
                }

                //
                // Write to the cache buffer and unmap the cache buffer.
                //

                RtlCopyMemory(CacheBuffer, UserBuffer, BytesToCopy);

                status = FscWriteBuffer(&VolumeExtension->CacheExtension, Irp,
                    PhysicalByteOffset, BytesToCopy, CacheBuffer);

                if (!NT_SUCCESS(status)) {
                    return status;
                }

            } else {

                //
                // Map in the next page of the physical run.
                //

                status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
                    PhysicalByteOffset, FALSE, &CacheBuffer);

                if (!NT_SUCCESS(status)) {
                    return status;
                }

                //
                // Read from the cache buffer and unmap the cache buffer.
                //

                RtlCopyMemory(UserBuffer, CacheBuffer, BytesToCopy);
                FscUnmapBuffer(CacheBuffer);
            }

            //
            // Adjust the number of bytes remaining and check if we're through
            // with the transfer.
            //

            PhysicalByteOffset += BytesToCopy;
            IoLengthRemaining -= BytesToCopy;

            if (IoLengthRemaining == 0) {

                if (!PartialTransfer) {
                    status = FatxSynchronousIoTail(VolumeExtension, Irp,
                        MajorFunction, FileObject, OriginalFileByteOffset,
                        IoLength);
                } else {
                    status = STATUS_SUCCESS;
                }

                return status;
            }

            //
            // There's still more bytes to transfer.  Update the other loop
            // variables and continue transfering this physical run.
            //

            FileByteOffset += BytesToCopy;
            UserBuffer = (PUCHAR)UserBuffer + BytesToCopy;

        } while (PhysicalRunBytesRemaining > 0);

        //
        // Obtain the next physical run.
        //

        status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp,
            Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength);

        if (!NT_SUCCESS(status)) {
            return status;
        }
    }
}

NTSTATUS
FatxPartiallyCachedSynchronousIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN UCHAR MajorFunction,
    IN PFILE_OBJECT FileObject,
    IN ULONG FileByteOffset,
    IN ULONG IoLength,
    IN BOOLEAN NonCachedEndOfFileTransfer
    )
/*++

Routine Description:

    This routine is called to synchronously read or write a buffer partially
    through the file system cache and partially through direct device I/O.  This
    routine is intended for large I/O transfers where we want to maximize use of
    direct device I/O in order to avoid PAGE_SIZE I/O transfers.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    MajorFunction - Specifies the function to be performed; either IRP_MJ_READ
        or IRP_MJ_WRITE.

    FileObject - Specifies the file object that the I/O request is for.

    FileByteOffset - Specifies the file byte offset to start the transfer from.

    IoLength - Specifies the number of bytes to transfer.

    NonCachedEndOfFileTransfer - Specifies TRUE if the read is to the end of
        file and the output buffer is large enough to hold the transfer length
        rounded up to a sector boundary.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    ULONG OriginalFileByteOffset;
    ULONG OriginalIoLength;
    ULONG PartialIoLength;
    ULONG BufferByteOffset;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    OriginalFileByteOffset = FileByteOffset;
    OriginalIoLength = IoLength;

    //
    // The below code assumes that the relative byte offsets into the file and
    // the file area have the same page alignment as their corresponding
    // physical byte offsets.
    //

    ASSERT(BYTE_OFFSET(VolumeExtension->FileAreaByteOffset) == 0);

    //
    // Transfer the head of the request if it's not page aligned.
    //

    if (BYTE_OFFSET(FileByteOffset) != 0) {

        PartialIoLength = PAGE_SIZE - BYTE_OFFSET(FileByteOffset);

        ASSERT(PartialIoLength < IoLength);

        status = FatxFullyCachedSynchronousIo(DeviceObject, Irp, MajorFunction,
            FileObject, FileByteOffset, 0, PartialIoLength, TRUE);

        if (!NT_SUCCESS(status)) {
            return status;
        }

        IoLength -= PartialIoLength;
        FileByteOffset += PartialIoLength;
        BufferByteOffset = PartialIoLength;

    } else {
        BufferByteOffset = 0;
    }

    //
    // If NonCachedEndOfFileTransfer is TRUE, then we're reading to the end of
    // the file and the transfer buffer is large enough to hold the transfer
    // length rounded up to a sector boundary.
    //

    if (NonCachedEndOfFileTransfer) {
        ASSERT(MajorFunction == IRP_MJ_READ);
        IoLength = FatxRoundToSectors(VolumeExtension, IoLength);
        PartialIoLength = IoLength;
    } else {
        ASSERT(IoLength >= PAGE_SIZE);
        PartialIoLength = (ULONG)PAGE_ALIGN(IoLength);
    }

    status = FatxNonCachedSynchronousIo(DeviceObject, Irp, MajorFunction,
        FileObject, FileByteOffset, BufferByteOffset, PartialIoLength, TRUE);

    //
    // Transfer the remaining non whole page of the request if necessary.
    //

    if (NT_SUCCESS(status)) {

        IoLength -= PartialIoLength;

        if (IoLength > 0) {

            FileByteOffset += PartialIoLength;
            BufferByteOffset += PartialIoLength;

            status = FatxFullyCachedSynchronousIo(DeviceObject, Irp,
                MajorFunction, FileObject, FileByteOffset, BufferByteOffset,
                IoLength, TRUE);
        }
    }

    if (NT_SUCCESS(status)) {
        status = FatxSynchronousIoTail(VolumeExtension, Irp, MajorFunction,
            FileObject, OriginalFileByteOffset, OriginalIoLength);
    }

    return status;
}

NTSTATUS
FatxFsdReadWrite(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is called by the I/O manager to handle IRP_MJ_READ and
    IRP_MJ_WRITE requests.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PIO_STACK_LOCATION IrpSp;
    PIO_STACK_LOCATION NextIrpSp;
    UCHAR MajorFunction;
    PFILE_OBJECT FileObject;
    PFAT_FCB Fcb;
    ULONG IoLength;
    BOOLEAN NonCachedEndOfFileTransfer;
    ULONGLONG PartitionBytesRemaining;
    ULONG FileBytesRemaining;
    BOOLEAN SynchronousIo;
    ULONG EndingByteOffset;
    ULONG ClusterNumber;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NextIrpSp = IoGetNextIrpStackLocation(Irp);
    MajorFunction = IrpSp->MajorFunction;
    FileObject = IrpSp->FileObject;
    Fcb = (PFAT_FCB)FileObject->FsContext;
    IoLength = IrpSp->Parameters.Read.Length;
    NonCachedEndOfFileTransfer = FALSE;

    //
    // Ensure that the file object is not for a directory.
    //

    if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
        status = STATUS_INVALID_DEVICE_REQUEST;
        goto CompleteAndExit;
    }

    //
    // If this is a zero length request, then we can complete the IRP now.
    //

    if (IoLength == 0) {
        Irp->IoStatus.Information = 0;
        status = STATUS_SUCCESS;
        goto CompleteAndExit;
    }

    //
    // Check if we're reading from or writing to the volume file control block.
    //

    if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME)) {

        if (MajorFunction == IRP_MJ_READ) {
            FatxAcquireVolumeMutexShared(VolumeExtension);
        } else {
            FatxAcquireVolumeMutexExclusive(VolumeExtension);
        }

        //
        // Check if the volume has been dismounted.
        //

        if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
            status = STATUS_VOLUME_DISMOUNTED;
            goto UnlockVolumeAndExit;
        }

        //
        // Check if the starting offset is beyond the end of the partition.
        //

        if ((ULONGLONG)IrpSp->Parameters.Read.ByteOffset.QuadPart >=
            (ULONGLONG)VolumeExtension->PartitionLength.QuadPart) {
            status = STATUS_END_OF_FILE;
            goto UnlockVolumeAndExit;
        }

        //
        // If the number of bytes to transfer is greater than the number of
        // bytes remaining in the partition, then truncate the number of bytes
        // we'll actually transfer.
        //

        PartitionBytesRemaining =
            (ULONGLONG)VolumeExtension->PartitionLength.QuadPart -
            (ULONGLONG)IrpSp->Parameters.Read.ByteOffset.QuadPart;

        if ((ULONGLONG)IoLength >= PartitionBytesRemaining) {
            IoLength = (ULONG)PartitionBytesRemaining;
        }

        ASSERT(IoLength > 0);

        //
        // We'll leave it to the target device's driver to validate that the
        // user's buffer, the starting byte offset, and the transfer length are
        // all properly aligned.
        //

        IoCopyCurrentIrpStackLocationToNext(Irp);

        //
        // Lock the user's buffer into memory if necessary.
        //

        IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length);

        //
        // Invalidate any file system cache buffers for this byte range if this
        // is a write operation.
        //

        if (MajorFunction == IRP_MJ_WRITE) {
            FscInvalidateByteRange(&VolumeExtension->CacheExtension,
                IrpSp->Parameters.Read.ByteOffset.QuadPart, IoLength);
        }

        //
        // Limit the number of bytes physically read to the end of the volume.
        //

        NextIrpSp->Parameters.Read.Length = IoLength;

        //
        // Set a completion routine to unlock the volume mutex and update any
        // state in the file control block.
        //

        IoSetCompletionRoutine(Irp, FatxVolumeIoCompletion, NULL, TRUE, TRUE,
            TRUE);

        //
        // Call down to the target device.
        //

        status = IoCallDriver(VolumeExtension->TargetDeviceObject, Irp);

        //
        // Leave the critical region that we acquired when we took the volume
        // mutex.
        //

        KeLeaveCriticalRegion();

        return status;

UnlockVolumeAndExit:
        FatxReleaseVolumeMutex(VolumeExtension);
        goto CompleteAndExit;
    }

    //
    // Otherwise, we're reading from or writing to a standard file.
    //

    if (MajorFunction == IRP_MJ_READ) {
        FatxAcquireFileMutexShared(Fcb);
    } else {
        FatxAcquireFileMutexExclusive(Fcb);
    }

    //
    // Increment the dismount unblock count for the volume.  We won't be
    // holding the volume's mutex throughout the processing of this request,
    // so FatxDismountVolume needs to have some synchronization mechanism to
    // know when all pending read/write IRPs have completed.
    //
    // If a request enters the file system after a dismount has been
    // unblocked or completed, then the dismount flag will have been set for
    // the volume and the code below will fail the request.
    //
    // We don't need to do this for volume file control block because in
    // that code path, the volume's mutex is held for the entire operation.
    //

    FatxIncrementDismountBlockCount(VolumeExtension);

    //
    // Check if the volume has been dismounted.
    //

    if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
        status = STATUS_VOLUME_DISMOUNTED;
        goto UnlockFileAndExit;
    }

    //
    // Check if the file object has already been cleaned up.  We don't allow a
    // a file object to be modified after its handle has been closed.
    //

    if (FatxIsFlagSet(FileObject->Flags, FO_CLEANUP_COMPLETE)) {
        status = STATUS_FILE_CLOSED;
        goto UnlockFileAndExit;
    }

    if (MajorFunction == IRP_MJ_READ) {

        //
        // Check if the starting offset is beyond the end of file.
        //

        if ((IrpSp->Parameters.Read.ByteOffset.HighPart != 0) ||
            (IrpSp->Parameters.Read.ByteOffset.LowPart >= Fcb->FileSize)) {
            status = STATUS_END_OF_FILE;
            goto UnlockFileAndExit;
        }

        //
        // If the number of bytes to read is greater than the number of bytes
        // remaining in the file, then truncate the number of bytes we'll
        // actually read.
        //

        FileBytesRemaining = Fcb->FileSize -
            IrpSp->Parameters.Read.ByteOffset.LowPart;

        if (IoLength >= FileBytesRemaining) {

            //
            // If the user's buffer is large enough to hold the logical read
            // length rounded up to a sector boundary, then set a flag so that
            // the below code will potentially read this part of the file as
            // non cached.
            //

            if (IoLength >= (FatxRoundToSectors(VolumeExtension, Fcb->FileSize) -
                IrpSp->Parameters.Read.ByteOffset.LowPart)) {
                NonCachedEndOfFileTransfer = TRUE;
            }

            IoLength = FileBytesRemaining;
        }

        ASSERT(IoLength > 0);

        //
        // Check if we should do synchronous or asynchronous I/O depending on
        // how the file object was originally opened.
        //

        SynchronousIo = (BOOLEAN)FatxIsFlagSet(FileObject->Flags,
            FO_SYNCHRONOUS_IO);

    } else {

        //
        // If we're supposed to write to the end of the file, then change the
        // starting byte offset to the current end of file.
        //

        if ((IrpSp->Parameters.Read.ByteOffset.LowPart == FILE_WRITE_TO_END_OF_FILE) &&
            (IrpSp->Parameters.Read.ByteOffset.HighPart == -1)) {
            IrpSp->Parameters.Read.ByteOffset.LowPart = Fcb->FileSize;
            IrpSp->Parameters.Read.ByteOffset.HighPart = 0;
        }

        //
        // Verify that the starting or ending offset are only 32-bits.
        //

        EndingByteOffset = IrpSp->Parameters.Read.ByteOffset.LowPart + IoLength;

        if ((IrpSp->Parameters.Read.ByteOffset.HighPart != 0) ||
            (EndingByteOffset <= IrpSp->Parameters.Read.ByteOffset.LowPart)) {
            status = STATUS_DISK_FULL;
            goto UnlockFileAndExit;
        }

        //
        // Determine the number of bytes currently allocated to the file.
        //

        if (Fcb->AllocationSize == MAXULONG) {

            //
            // Attempt to find the cluster corresponding to the maximum byte
            // offset which will have the side effect of filling in the number
            // of bytes allocated.
            //

            FatxAcquireVolumeMutexShared(VolumeExtension);

            status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, Fcb,
                MAXULONG, &ClusterNumber, NULL);

            FatxReleaseVolumeMutex(VolumeExtension);

            if (!NT_SUCCESS(status) && (status != STATUS_END_OF_FILE)) {
                goto UnlockFileAndExit;
            }
        }

        //
        // If the file size is greater than the number of bytes allocated to the
        // file, then the file is corrupt and we won't allow any writes to it.
        //

        if (Fcb->FileSize > Fcb->AllocationSize) {
            status = STATUS_FILE_CORRUPT_ERROR;
            goto UnlockFileAndExit;
        }

        //
        // If the ending byte offset is greater than the number of bytes
        // allocated to the file, then we need to grow the allocation size for
        // the file.  The volume mutex must be acquired exclusively so that the
        // volume can be modified.
        //

        if (EndingByteOffset > Fcb->AllocationSize) {

            FatxAcquireVolumeMutexExclusive(VolumeExtension);

            status = FatxExtendFileAllocation(VolumeExtension, Irp, Fcb,
                FatxRoundToClusters(VolumeExtension, EndingByteOffset));

            FatxReleaseVolumeMutex(VolumeExtension);

            if (!NT_SUCCESS(status)) {
                goto UnlockFileAndExit;
            }
        }

        //
        // Check if we should do synchronous or asynchronous I/O depending on
        // how the file object was originally opened.
        //
        // If this is an asynchronous I/O operation, verify that the ending byte
        // offset is before the end of file.  If it isn't, then we can't change
        // the directory entry's file size asynchronously, so switch this back
        // to a synchronous I/O operation.
        //

        SynchronousIo = (BOOLEAN)FatxIsFlagSet(FileObject->Flags,
            FO_SYNCHRONOUS_IO);

        if (!SynchronousIo && (EndingByteOffset > Fcb->FileSize)) {
            SynchronousIo = TRUE;
        }
    }

    //
    // The upper 32-bits of the byte offset should be zero at this point.  Code
    // below this point will only look at ByteOffset.LowPart.
    //

    ASSERT(IrpSp->Parameters.Read.ByteOffset.HighPart == 0);

    //
    // Check if we're supposed to bypass the file system cache.
    //

    if (FatxIsFlagSet(Irp->Flags, IRP_NOCACHE)) {

        //
        // Verify that the starting byte offset and transfer length are sector
        // aligned.  We'll leave it to the target device's driver to validate
        // that the user's buffer is properly aligned.
        //

        if (!FatxIsSectorAligned(VolumeExtension, IrpSp->Parameters.Read.ByteOffset.LowPart) ||
            !FatxIsSectorAligned(VolumeExtension, IrpSp->Parameters.Read.Length)) {
            status = STATUS_INVALID_PARAMETER;
            goto UnlockFileAndExit;
        }

        //
        // Lock the user's buffer into memory if necessary.
        //

        IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length);

        //
        // Do the transfer depending on whether we can block waiting for the
        // I/O to complete or not.
        //

        if (SynchronousIo) {

            status = FatxNonCachedSynchronousIo(DeviceObject, Irp, MajorFunction,
                FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, 0,
                IoLength, FALSE);

        } else {

            status = FatxNonCachedAsynchronousIo(DeviceObject, Irp, MajorFunction,
                FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, IoLength);

            if (status == STATUS_PENDING) {

                //
                // Leave the critical region that we acquired when we took the
                // file mutex.
                //

                KeLeaveCriticalRegion();

                return status;
            }
        }

    } else {

        //
        // Scatter/gather operations are always non-cached.
        //

        ASSERT(FatxIsFlagClear(Irp->Flags, IRP_SCATTER_GATHER_OPERATION));

        //
        // Otherwise, handle reading the data using the file cache.
        //
        // Check if this transfer should use the fully or partially cached path.
        //

        if (FscTestForFullyCachedIo(Irp, IrpSp->Parameters.Read.ByteOffset.LowPart,
            IoLength, NonCachedEndOfFileTransfer)) {

            status = FatxFullyCachedSynchronousIo(DeviceObject, Irp,
                MajorFunction, FileObject,
                IrpSp->Parameters.Read.ByteOffset.LowPart, 0, IoLength, FALSE);

        } else {

            //
            // Lock the user's buffer into memory if necessary.
            //

            IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length);

            status = FatxPartiallyCachedSynchronousIo(DeviceObject, Irp,
                MajorFunction, FileObject,
                IrpSp->Parameters.Read.ByteOffset.LowPart, IoLength,
                NonCachedEndOfFileTransfer);
        }
    }

    ASSERT(status != STATUS_PENDING);

UnlockFileAndExit:
    FatxDecrementDismountBlockCount(VolumeExtension);
    FatxReleaseFileMutex(Fcb);

CompleteAndExit:
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}
