/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * FILE:             drivers/filesystems/fastfat/rw.c
 * PURPOSE:          VFAT Filesystem
 * PROGRAMMER:       Jason Filby (jasonfilby@yahoo.com)
 *                   Pierre Schweitzer (pierre@reactos.org)
 *
 */

/* INCLUDES *****************************************************************/

#include "vfat.h"

#define NDEBUG
#include <debug.h>

/*
 * Uncomment to enable strict verification of cluster/offset pair
 * caching. If this option is enabled you lose all the benefits of
 * the caching and the read/write operations will actually be
 * slower. It's meant only for debugging!!!
 * - Filip Navara, 26/07/2004
 */
/* #define DEBUG_VERIFY_OFFSET_CACHING */

/* Arbitrary, taken from MS FastFAT, should be
 * refined given what we experience in common
 * out of stack operations
 */
#define OVERFLOW_READ_THRESHHOLD 0xE00

/* FUNCTIONS *****************************************************************/

/*
 * Return the next cluster in a FAT chain, possibly extending the chain if
 * necessary
 */
NTSTATUS
NextCluster(
    PDEVICE_EXTENSION DeviceExt,
    ULONG FirstCluster,
    PULONG CurrentCluster,
    BOOLEAN Extend)
{
    if (FirstCluster == 1)
    {
        (*CurrentCluster) += DeviceExt->FatInfo.SectorsPerCluster;
        return STATUS_SUCCESS;
    }
    else
    {
        if (Extend)
            return GetNextClusterExtend(DeviceExt, (*CurrentCluster), CurrentCluster);
        else
            return GetNextCluster(DeviceExt, (*CurrentCluster), CurrentCluster);
    }
}

NTSTATUS
OffsetToCluster(
    PDEVICE_EXTENSION DeviceExt,
    ULONG FirstCluster,
    ULONG FileOffset,
    PULONG Cluster,
    BOOLEAN Extend)
{
    ULONG CurrentCluster;
    ULONG i;
    NTSTATUS Status;
/*
    DPRINT("OffsetToCluster(DeviceExt %x, Fcb %x, FirstCluster %x,"
           " FileOffset %x, Cluster %x, Extend %d)\n", DeviceExt,
           Fcb, FirstCluster, FileOffset, Cluster, Extend);
*/
    if (FirstCluster == 0)
    {
        DbgPrint("OffsetToCluster is called with FirstCluster = 0!\n");
        ASSERT(FALSE);
    }

    if (FirstCluster == 1)
    {
        /* root of FAT16 or FAT12 */
        *Cluster = DeviceExt->FatInfo.rootStart + FileOffset
            / (DeviceExt->FatInfo.BytesPerCluster) * DeviceExt->FatInfo.SectorsPerCluster;
        return STATUS_SUCCESS;
    }
    else
    {
        CurrentCluster = FirstCluster;
        if (Extend)
        {
            for (i = 0; i < FileOffset / DeviceExt->FatInfo.BytesPerCluster; i++)
            {
                Status = GetNextClusterExtend (DeviceExt, CurrentCluster, &CurrentCluster);
                if (!NT_SUCCESS(Status))
                    return Status;
            }
            *Cluster = CurrentCluster;
        }
        else
        {
            for (i = 0; i < FileOffset / DeviceExt->FatInfo.BytesPerCluster; i++)
            {
                Status = GetNextCluster (DeviceExt, CurrentCluster, &CurrentCluster);
                if (!NT_SUCCESS(Status))
                    return Status;
            }
            *Cluster = CurrentCluster;
       }
       return STATUS_SUCCESS;
   }
}

/*
 * FUNCTION: Reads data from a file
 */
static
NTSTATUS
VfatReadFileData(
    PVFAT_IRP_CONTEXT IrpContext,
    ULONG Length,
    LARGE_INTEGER ReadOffset,
    PULONG LengthRead)
{
    ULONG CurrentCluster;
    ULONG FirstCluster;
    ULONG StartCluster;
    ULONG ClusterCount;
    LARGE_INTEGER StartOffset;
    PDEVICE_EXTENSION DeviceExt;
    BOOLEAN First = TRUE;
    PVFATFCB Fcb;
    NTSTATUS Status;
    ULONG BytesDone;
    ULONG BytesPerSector;
    ULONG BytesPerCluster;
    ULONG LastCluster;
    ULONG LastOffset;

    /* PRECONDITION */
    ASSERT(IrpContext);
    DeviceExt = IrpContext->DeviceExt;
    ASSERT(DeviceExt);
    ASSERT(DeviceExt->FatInfo.BytesPerCluster);
    ASSERT(IrpContext->FileObject);
    ASSERT(IrpContext->FileObject->FsContext2 != NULL);

    DPRINT("VfatReadFileData(DeviceExt %p, FileObject %p, "
           "Length %u, ReadOffset 0x%I64x)\n", DeviceExt,
           IrpContext->FileObject, Length, ReadOffset.QuadPart);

    *LengthRead = 0;

    Fcb = IrpContext->FileObject->FsContext;
    BytesPerSector = DeviceExt->FatInfo.BytesPerSector;
    BytesPerCluster = DeviceExt->FatInfo.BytesPerCluster;

    ASSERT(ReadOffset.QuadPart + Length <= ROUND_UP_64(Fcb->RFCB.FileSize.QuadPart, BytesPerSector));
    ASSERT(ReadOffset.u.LowPart % BytesPerSector == 0);
    ASSERT(Length % BytesPerSector == 0);

    /* Is this a read of the FAT? */
    if (BooleanFlagOn(Fcb->Flags, FCB_IS_FAT))
    {
        ReadOffset.QuadPart += DeviceExt->FatInfo.FATStart * BytesPerSector;
        Status = VfatReadDiskPartial(IrpContext, &ReadOffset, Length, 0, TRUE);

        if (NT_SUCCESS(Status))
        {
            *LengthRead = Length;
        }
        else
        {
            DPRINT1("FAT reading failed, Status %x\n", Status);
        }
        return Status;
    }

    /* Is this a read of the Volume ? */
    if (BooleanFlagOn(Fcb->Flags, FCB_IS_VOLUME))
    {
        Status = VfatReadDiskPartial(IrpContext, &ReadOffset, Length, 0, TRUE);
        if (NT_SUCCESS(Status))
        {
            *LengthRead = Length;
        }
        else
        {
            DPRINT1("Volume reading failed, Status %x\n", Status);
        }
        return Status;
    }

    /* Find the first cluster */
    FirstCluster =
    CurrentCluster = vfatDirEntryGetFirstCluster (DeviceExt, &Fcb->entry);

    if (FirstCluster == 1)
    {
        /* Directory of FAT12/16 needs a special handling */
        if (ReadOffset.u.LowPart + Length > DeviceExt->FatInfo.rootDirectorySectors * BytesPerSector)
        {
            Length = DeviceExt->FatInfo.rootDirectorySectors * BytesPerSector - ReadOffset.u.LowPart;
        }
        ReadOffset.u.LowPart += DeviceExt->FatInfo.rootStart * BytesPerSector;

        /* Fire up the read command */
        Status = VfatReadDiskPartial (IrpContext, &ReadOffset, Length, 0, TRUE);
        if (NT_SUCCESS(Status))
        {
            *LengthRead = Length;
        }
        return Status;
    }

    ExAcquireFastMutex(&Fcb->LastMutex);
    LastCluster = Fcb->LastCluster;
    LastOffset = Fcb->LastOffset;
    ExReleaseFastMutex(&Fcb->LastMutex);

    /* Find the cluster to start the read from */
    if (LastCluster > 0 && ReadOffset.u.LowPart >= LastOffset)
    {
        Status = OffsetToCluster(DeviceExt, LastCluster,
                                 ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster) -
                                 LastOffset,
                                 &CurrentCluster, FALSE);
#ifdef DEBUG_VERIFY_OFFSET_CACHING
        /* DEBUG VERIFICATION */
        {
            ULONG CorrectCluster;
            OffsetToCluster(DeviceExt, FirstCluster,
                            ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster),
                            &CorrectCluster, FALSE);
            if (CorrectCluster != CurrentCluster)
                KeBugCheck(FAT_FILE_SYSTEM);
        }
#endif
    }
    else
    {
        Status = OffsetToCluster(DeviceExt, FirstCluster,
                                 ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster),
                                 &CurrentCluster, FALSE);
    }

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

    ExAcquireFastMutex(&Fcb->LastMutex);
    Fcb->LastCluster = CurrentCluster;
    Fcb->LastOffset = ROUND_DOWN (ReadOffset.u.LowPart, BytesPerCluster);
    ExReleaseFastMutex(&Fcb->LastMutex);

    KeInitializeEvent(&IrpContext->Event, NotificationEvent, FALSE);
    IrpContext->RefCount = 1;

    while (Length > 0 && CurrentCluster != 0xffffffff)
    {
        StartCluster = CurrentCluster;
        StartOffset.QuadPart = ClusterToSector(DeviceExt, StartCluster) * BytesPerSector;
        BytesDone = 0;
        ClusterCount = 0;

        do
        {
            ClusterCount++;
            if (First)
            {
                BytesDone =  min (Length, BytesPerCluster - (ReadOffset.u.LowPart % BytesPerCluster));
                StartOffset.QuadPart += ReadOffset.u.LowPart % BytesPerCluster;
                First = FALSE;
            }
            else
            {
                if (Length - BytesDone > BytesPerCluster)
                {
                    BytesDone += BytesPerCluster;
                }
                else
                {
                    BytesDone = Length;
                }
            }
            Status = NextCluster(DeviceExt, FirstCluster, &CurrentCluster, FALSE);
        }
        while (StartCluster + ClusterCount == CurrentCluster && NT_SUCCESS(Status) && Length > BytesDone);
        DPRINT("start %08x, next %08x, count %u\n",
               StartCluster, CurrentCluster, ClusterCount);

        ExAcquireFastMutex(&Fcb->LastMutex);
        Fcb->LastCluster = StartCluster + (ClusterCount - 1);
        Fcb->LastOffset = ROUND_DOWN(ReadOffset.u.LowPart, BytesPerCluster) + (ClusterCount - 1) * BytesPerCluster;
        ExReleaseFastMutex(&Fcb->LastMutex);

        /* Fire up the read command */
        Status = VfatReadDiskPartial (IrpContext, &StartOffset, BytesDone, *LengthRead, FALSE);
        if (!NT_SUCCESS(Status) && Status != STATUS_PENDING)
        {
            break;
        }
        *LengthRead += BytesDone;
        Length -= BytesDone;
        ReadOffset.u.LowPart += BytesDone;
    }

    if (InterlockedDecrement((PLONG)&IrpContext->RefCount) != 0)
    {
        KeWaitForSingleObject(&IrpContext->Event, Executive, KernelMode, FALSE, NULL);
    }

    if (NT_SUCCESS(Status) || Status == STATUS_PENDING)
    {
        if (Length > 0)
        {
            Status = STATUS_UNSUCCESSFUL;
        }
        else
        {
            Status = IrpContext->Irp->IoStatus.Status;
        }
    }

    return Status;
}

static
NTSTATUS
VfatWriteFileData(
    PVFAT_IRP_CONTEXT IrpContext,
    ULONG Length,
    LARGE_INTEGER WriteOffset)
{
    PDEVICE_EXTENSION DeviceExt;
    PVFATFCB Fcb;
    ULONG Count;
    ULONG FirstCluster;
    ULONG CurrentCluster;
    ULONG BytesDone;
    ULONG StartCluster;
    ULONG ClusterCount;
    NTSTATUS Status = STATUS_SUCCESS;
    BOOLEAN First = TRUE;
    ULONG BytesPerSector;
    ULONG BytesPerCluster;
    LARGE_INTEGER StartOffset;
    ULONG BufferOffset;
    ULONG LastCluster;
    ULONG LastOffset;

    /* PRECONDITION */
    ASSERT(IrpContext);
    DeviceExt = IrpContext->DeviceExt;
    ASSERT(DeviceExt);
    ASSERT(DeviceExt->FatInfo.BytesPerCluster);
    ASSERT(IrpContext->FileObject);
    ASSERT(IrpContext->FileObject->FsContext2 != NULL);

    Fcb = IrpContext->FileObject->FsContext;
    BytesPerCluster = DeviceExt->FatInfo.BytesPerCluster;
    BytesPerSector = DeviceExt->FatInfo.BytesPerSector;

    DPRINT("VfatWriteFileData(DeviceExt %p, FileObject %p, "
           "Length %u, WriteOffset 0x%I64x), '%wZ'\n", DeviceExt,
           IrpContext->FileObject, Length, WriteOffset.QuadPart,
           &Fcb->PathNameU);

    ASSERT(WriteOffset.QuadPart + Length <= Fcb->RFCB.AllocationSize.QuadPart);
    ASSERT(WriteOffset.u.LowPart % BytesPerSector == 0);
    ASSERT(Length % BytesPerSector == 0);

    /* Is this a write of the volume? */
    if (BooleanFlagOn(Fcb->Flags, FCB_IS_VOLUME))
    {
        Status = VfatWriteDiskPartial(IrpContext, &WriteOffset, Length, 0, TRUE);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Volume writing failed, Status %x\n", Status);
        }
        return Status;
    }

    /* Is this a write to the FAT? */
    if (BooleanFlagOn(Fcb->Flags, FCB_IS_FAT))
    {
        WriteOffset.u.LowPart += DeviceExt->FatInfo.FATStart * BytesPerSector;
        IrpContext->RefCount = 1;
        for (Count = 0; Count < DeviceExt->FatInfo.FATCount; Count++)
        {
            Status = VfatWriteDiskPartial(IrpContext, &WriteOffset, Length, 0, FALSE);
            if (!NT_SUCCESS(Status) && Status != STATUS_PENDING)
            {
                DPRINT1("FAT writing failed, Status %x\n", Status);
                break;
            }
            WriteOffset.u.LowPart += Fcb->RFCB.FileSize.u.LowPart;
        }

        if (InterlockedDecrement((PLONG)&IrpContext->RefCount) != 0)
        {
            KeWaitForSingleObject(&IrpContext->Event, Executive, KernelMode, FALSE, NULL);
        }

        if (NT_SUCCESS(Status) || Status == STATUS_PENDING)
        {
            Status = IrpContext->Irp->IoStatus.Status;
        }
        return Status;
    }

    /*
     * Find the first cluster
     */
    FirstCluster =
    CurrentCluster = vfatDirEntryGetFirstCluster (DeviceExt, &Fcb->entry);

    if (FirstCluster == 1)
    {
        ASSERT(WriteOffset.u.LowPart + Length <= DeviceExt->FatInfo.rootDirectorySectors * BytesPerSector);
        // Directory of FAT12/16 needs a special handling
        WriteOffset.u.LowPart += DeviceExt->FatInfo.rootStart * BytesPerSector;
        // Fire up the write command
        Status = VfatWriteDiskPartial (IrpContext, &WriteOffset, Length, 0, TRUE);
        return Status;
    }

    ExAcquireFastMutex(&Fcb->LastMutex);
    LastCluster = Fcb->LastCluster;
    LastOffset = Fcb->LastOffset;
    ExReleaseFastMutex(&Fcb->LastMutex);

    /*
     * Find the cluster to start the write from
     */
    if (LastCluster > 0 && WriteOffset.u.LowPart >= LastOffset)
    {
        Status = OffsetToCluster(DeviceExt, LastCluster,
                                 ROUND_DOWN(WriteOffset.u.LowPart, BytesPerCluster) -
                                 LastOffset,
                                 &CurrentCluster, FALSE);
#ifdef DEBUG_VERIFY_OFFSET_CACHING
        /* DEBUG VERIFICATION */
        {
            ULONG CorrectCluster;
            OffsetToCluster(DeviceExt, FirstCluster,
                            ROUND_DOWN(WriteOffset.u.LowPart, BytesPerCluster),
                            &CorrectCluster, FALSE);
            if (CorrectCluster != CurrentCluster)
                KeBugCheck(FAT_FILE_SYSTEM);
        }
#endif
    }
    else
    {
        Status = OffsetToCluster(DeviceExt, FirstCluster,
                                 ROUND_DOWN(WriteOffset.u.LowPart, BytesPerCluster),
                                 &CurrentCluster, FALSE);
    }

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

    ExAcquireFastMutex(&Fcb->LastMutex);
    Fcb->LastCluster = CurrentCluster;
    Fcb->LastOffset = ROUND_DOWN (WriteOffset.u.LowPart, BytesPerCluster);
    ExReleaseFastMutex(&Fcb->LastMutex);

    IrpContext->RefCount = 1;
    BufferOffset = 0;

    while (Length > 0 && CurrentCluster != 0xffffffff)
    {
        StartCluster = CurrentCluster;
        StartOffset.QuadPart = ClusterToSector(DeviceExt, StartCluster) * BytesPerSector;
        BytesDone = 0;
        ClusterCount = 0;

        do
        {
            ClusterCount++;
            if (First)
            {
                BytesDone =  min (Length, BytesPerCluster - (WriteOffset.u.LowPart % BytesPerCluster));
                StartOffset.QuadPart += WriteOffset.u.LowPart % BytesPerCluster;
                First = FALSE;
            }
            else
            {
                if (Length - BytesDone > BytesPerCluster)
                {
                    BytesDone += BytesPerCluster;
                }
                else
                {
                    BytesDone = Length;
                }
            }
            Status = NextCluster(DeviceExt, FirstCluster, &CurrentCluster, FALSE);
        }
        while (StartCluster + ClusterCount == CurrentCluster && NT_SUCCESS(Status) && Length > BytesDone);
        DPRINT("start %08x, next %08x, count %u\n",
               StartCluster, CurrentCluster, ClusterCount);

        ExAcquireFastMutex(&Fcb->LastMutex);
        Fcb->LastCluster = StartCluster + (ClusterCount - 1);
        Fcb->LastOffset = ROUND_DOWN(WriteOffset.u.LowPart, BytesPerCluster) + (ClusterCount - 1) * BytesPerCluster;
        ExReleaseFastMutex(&Fcb->LastMutex);

        // Fire up the write command
        Status = VfatWriteDiskPartial (IrpContext, &StartOffset, BytesDone, BufferOffset, FALSE);
        if (!NT_SUCCESS(Status) && Status != STATUS_PENDING)
        {
            break;
        }
        BufferOffset += BytesDone;
        Length -= BytesDone;
        WriteOffset.u.LowPart += BytesDone;
    }

    if (InterlockedDecrement((PLONG)&IrpContext->RefCount) != 0)
    {
        KeWaitForSingleObject(&IrpContext->Event, Executive, KernelMode, FALSE, NULL);
    }

    if (NT_SUCCESS(Status) || Status == STATUS_PENDING)
    {
        if (Length > 0)
        {
            Status = STATUS_UNSUCCESSFUL;
        }
        else
        {
            Status = IrpContext->Irp->IoStatus.Status;
        }
    }

    return Status;
}

NTSTATUS
VfatCommonRead(
    PVFAT_IRP_CONTEXT IrpContext)
{
    PVFATFCB Fcb;
    PVOID Buffer;
    NTSTATUS Status;
    ULONG Length = 0;
    ULONG BytesPerSector;
    LARGE_INTEGER ByteOffset;
    ULONG ReturnedLength = 0;
    BOOLEAN PagingIo, CanWait, IsVolume, NoCache;

    PagingIo = BooleanFlagOn(IrpContext->Irp->Flags, IRP_PAGING_IO);
    CanWait = BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT);
    NoCache = BooleanFlagOn(IrpContext->Irp->Flags, IRP_NOCACHE);
    Fcb = IrpContext->FileObject->FsContext;
    IsVolume = BooleanFlagOn(Fcb->Flags, FCB_IS_VOLUME);

    ByteOffset = IrpContext->Stack->Parameters.Read.ByteOffset;
    Length = IrpContext->Stack->Parameters.Read.Length;
    BytesPerSector = IrpContext->DeviceExt->FatInfo.BytesPerSector;

    if (!PagingIo &&
        FsRtlAreThereCurrentFileLocks(&Fcb->FileLock))
    {
        if (!FsRtlCheckLockForReadAccess(&Fcb->FileLock, IrpContext->Irp))
        {
            return STATUS_FILE_LOCK_CONFLICT;
        }
    }

    Buffer = VfatGetUserBuffer(IrpContext->Irp, PagingIo);

    if (!PagingIo && !NoCache && !IsVolume)
    {
        // cached read
        Status = STATUS_SUCCESS;
        if (ByteOffset.u.LowPart + Length > Fcb->RFCB.FileSize.u.LowPart)
        {
            Length = Fcb->RFCB.FileSize.u.LowPart - ByteOffset.u.LowPart;
            Status = /*STATUS_END_OF_FILE*/STATUS_SUCCESS;
        }

        vfatAddToStat(IrpContext->DeviceExt, Base.UserFileReads, 1);
        vfatAddToStat(IrpContext->DeviceExt, Base.UserFileReadBytes, Length);

        _SEH2_TRY
        {
            if (IrpContext->FileObject->PrivateCacheMap == NULL)
            {
                CcInitializeCacheMap(IrpContext->FileObject,
                                     (PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
                                     FALSE,
                                     &(VfatGlobalData->CacheMgrCallbacks),
                                     Fcb);
            }

            if (!CcCopyRead(IrpContext->FileObject,
                            &ByteOffset,
                            Length,
                            CanWait,
                            Buffer,
                            &IrpContext->Irp->IoStatus))
            {
                ASSERT(!CanWait);
                Status = STATUS_PENDING;
                goto ByeBye;
            }
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
            goto ByeBye;
        }
        _SEH2_END;

        if (!NT_SUCCESS(IrpContext->Irp->IoStatus.Status))
        {
            Status = IrpContext->Irp->IoStatus.Status;
        }
    }
    else
    {
        // non cached read
        Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoWriteAccess);
        if (!NT_SUCCESS(Status))
        {
            goto ByeBye;
        }

        if (ByteOffset.QuadPart + Length > ROUND_UP_64(Fcb->RFCB.FileSize.QuadPart, BytesPerSector))
        {
            Length = (ULONG)(ROUND_UP_64(Fcb->RFCB.FileSize.QuadPart, BytesPerSector) - ByteOffset.QuadPart);
        }

        if (!IsVolume)
        {
            vfatAddToStat(IrpContext->DeviceExt, Fat.NonCachedReads, 1);
            vfatAddToStat(IrpContext->DeviceExt, Fat.NonCachedReadBytes, Length);
        }
        else
        {
            vfatAddToStat(IrpContext->DeviceExt, Base.MetaDataReads, 1);
            vfatAddToStat(IrpContext->DeviceExt, Base.MetaDataReadBytes, Length);
        }

        Status = VfatReadFileData(IrpContext, Length, ByteOffset, &ReturnedLength);
        if (NT_SUCCESS(Status))
        {
            IrpContext->Irp->IoStatus.Information = ReturnedLength;
        }
    }

ByeBye:
    return Status;
}

VOID
NTAPI
VfatStackOverflowRead(
    PVOID Context,
    IN PKEVENT Event)
{
    PVFAT_IRP_CONTEXT IrpContext;

    IrpContext = Context;
    /* In a separate thread, we can wait and resources got locked */
    SetFlag(IrpContext->Flags, IRPCONTEXT_CANWAIT);

    /* Perform the read operation */
    DPRINT1("Performing posted read\n");
    VfatCommonRead(IrpContext);

    KeSetEvent(Event, 0, FALSE);
}

VOID
VfatPostRead(
    PVFAT_IRP_CONTEXT IrpContext,
    PERESOURCE Lock,
    BOOLEAN PagingIo)
{
    KEVENT Event;

    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    ExAcquireResourceSharedLite(Lock, TRUE);

    /* If paging IO, call the non failing but blocking routine */
    if (PagingIo)
    {
        FsRtlPostPagingFileStackOverflow(IrpContext, &Event, VfatStackOverflowRead);
    }
    else
    {
        FsRtlPostStackOverflow(IrpContext, &Event, VfatStackOverflowRead);
    }

    /* Wait till it's done */
    KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
}

NTSTATUS
VfatRead(
    PVFAT_IRP_CONTEXT IrpContext)
{
    NTSTATUS Status;
    PVFATFCB Fcb;
    ULONG Length = 0;
    PERESOURCE Resource = NULL;
    LARGE_INTEGER ByteOffset;
    ULONG BytesPerSector;
    BOOLEAN PagingIo, CanWait, IsVolume, NoCache;

    ASSERT(IrpContext);

    DPRINT("VfatRead(IrpContext %p)\n", IrpContext);

    ASSERT(IrpContext->DeviceObject);

    PagingIo = BooleanFlagOn(IrpContext->Irp->Flags, IRP_PAGING_IO);
    CanWait = BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT);
    NoCache = BooleanFlagOn(IrpContext->Irp->Flags, IRP_NOCACHE);

    // This request is not allowed on the main device object
    if (IrpContext->DeviceObject == VfatGlobalData->DeviceObject)
    {
        DPRINT("VfatRead is called with the main device object.\n");
        Status = STATUS_INVALID_DEVICE_REQUEST;
        goto ByeBye;
    }

    ASSERT(IrpContext->DeviceExt);
    ASSERT(IrpContext->FileObject);
    Fcb = IrpContext->FileObject->FsContext;
    ASSERT(Fcb);

    IsVolume = BooleanFlagOn(Fcb->Flags, FCB_IS_VOLUME);

    if (BooleanFlagOn(Fcb->Flags, FCB_IS_PAGE_FILE))
    {
        PFATINFO FatInfo = &IrpContext->DeviceExt->FatInfo;
        IrpContext->Stack->Parameters.Read.ByteOffset.QuadPart += FatInfo->dataStart * FatInfo->BytesPerSector;
        IoSkipCurrentIrpStackLocation(IrpContext->Irp);
        IrpContext->Flags &= ~IRPCONTEXT_COMPLETE;
        DPRINT("Read from page file, disk offset %I64x\n", IrpContext->Stack->Parameters.Read.ByteOffset.QuadPart);
        Status = IoCallDriver(IrpContext->DeviceExt->StorageDevice, IrpContext->Irp);
        return Status;
    }

    DPRINT("<%wZ>\n", &Fcb->PathNameU);

    ByteOffset = IrpContext->Stack->Parameters.Read.ByteOffset;
    Length = IrpContext->Stack->Parameters.Read.Length;
    BytesPerSector = IrpContext->DeviceExt->FatInfo.BytesPerSector;

    /* fail if file is a directory and no paged read */
    if (vfatFCBIsDirectory(Fcb) && !PagingIo)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto ByeBye;
    }

    DPRINT("'%wZ', Offset: %u, Length %u\n", &Fcb->PathNameU, ByteOffset.u.LowPart, Length);

    if (ByteOffset.u.HighPart && !IsVolume)
    {
       Status = STATUS_INVALID_PARAMETER;
       goto ByeBye;
    }

    if (Length == 0)
    {
        IrpContext->Irp->IoStatus.Information = 0;
        Status = STATUS_SUCCESS;
        goto ByeBye;
    }

    if (ByteOffset.QuadPart >= Fcb->RFCB.FileSize.QuadPart)
    {
       IrpContext->Irp->IoStatus.Information = 0;
       Status = STATUS_END_OF_FILE;
       goto ByeBye;
    }

    if (NoCache || PagingIo || IsVolume)
    {
        if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
        {
            DPRINT("%u %u\n", ByteOffset.u.LowPart, Length);
            // non cached read must be sector aligned
            Status = STATUS_INVALID_PARAMETER;
            goto ByeBye;
        }
    }

    if (IsVolume)
    {
        Resource = &IrpContext->DeviceExt->DirResource;
    }
    else if (PagingIo)
    {
        Resource = &Fcb->PagingIoResource;
    }
    else
    {
        Resource = &Fcb->MainResource;
    }

    /* Are we out of stack for the rest of the operation? */
    if (IoGetRemainingStackSize() < OVERFLOW_READ_THRESHHOLD)
    {
        /* Lock the buffer */
        Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoWriteAccess);
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }

        /* And post the read to the overflow thread */
        VfatPostRead(IrpContext, Resource, PagingIo);

        /* Return the appropriate status */
        return IrpContext->Irp->IoStatus.Status;
    }

    if (!ExAcquireResourceSharedLite(Resource, CanWait))
    {
        Resource = NULL;
        Status = STATUS_PENDING;
        goto ByeBye;
    }

    Status = VfatCommonRead(IrpContext);

ByeBye:
    if (Resource)
    {
        ExReleaseResourceLite(Resource);
    }

    if (Status == STATUS_PENDING)
    {
        Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoWriteAccess);
        if (NT_SUCCESS(Status))
        {
            Status = VfatMarkIrpContextForQueue(IrpContext);
        }
    }
    else
    {
        IrpContext->Irp->IoStatus.Status = Status;
        if (BooleanFlagOn(IrpContext->FileObject->Flags, FO_SYNCHRONOUS_IO) &&
            !PagingIo &&
            (NT_SUCCESS(Status) || Status == STATUS_END_OF_FILE))
        {
            IrpContext->FileObject->CurrentByteOffset.QuadPart =
                ByteOffset.QuadPart + IrpContext->Irp->IoStatus.Information;
        }

        if (NT_SUCCESS(Status))
            IrpContext->PriorityBoost = IO_DISK_INCREMENT;
    }
    DPRINT("%x\n", Status);
    return Status;
}

NTSTATUS
VfatWrite(
    PVFAT_IRP_CONTEXT IrpContext)
{
    PVFATFCB Fcb;
    PERESOURCE Resource = NULL;
    LARGE_INTEGER ByteOffset;
    LARGE_INTEGER OldFileSize;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG Length = 0;
    PVOID Buffer;
    ULONG BytesPerSector;
    BOOLEAN PagingIo, CanWait, IsVolume, IsFAT, NoCache;

    ASSERT(IrpContext);

    DPRINT("VfatWrite(IrpContext %p)\n", IrpContext);

    ASSERT(IrpContext->DeviceObject);

    PagingIo = BooleanFlagOn(IrpContext->Irp->Flags, IRP_PAGING_IO);
    CanWait = BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT);
    NoCache = BooleanFlagOn(IrpContext->Irp->Flags, IRP_NOCACHE);

    // This request is not allowed on the main device object
    if (IrpContext->DeviceObject == VfatGlobalData->DeviceObject)
    {
        DPRINT("VfatWrite is called with the main device object.\n");
        Status = STATUS_INVALID_DEVICE_REQUEST;
        goto ByeBye;
    }

    ASSERT(IrpContext->DeviceExt);
    ASSERT(IrpContext->FileObject);
    Fcb = IrpContext->FileObject->FsContext;
    ASSERT(Fcb);

    IsVolume = BooleanFlagOn(Fcb->Flags, FCB_IS_VOLUME);
    IsFAT = BooleanFlagOn(Fcb->Flags, FCB_IS_FAT);

    if (BooleanFlagOn(Fcb->Flags, FCB_IS_PAGE_FILE))
    {
        PFATINFO FatInfo = &IrpContext->DeviceExt->FatInfo;
        IrpContext->Stack->Parameters.Write.ByteOffset.QuadPart += FatInfo->dataStart * FatInfo->BytesPerSector;
        IoSkipCurrentIrpStackLocation(IrpContext->Irp);
        IrpContext->Flags &= ~IRPCONTEXT_COMPLETE;
        DPRINT("Write to page file, disk offset %I64x\n", IrpContext->Stack->Parameters.Write.ByteOffset.QuadPart);
        Status = IoCallDriver(IrpContext->DeviceExt->StorageDevice, IrpContext->Irp);
        return Status;
    }

    DPRINT("<%wZ>\n", &Fcb->PathNameU);

    /* fail if file is a directory and no paged read */
    if (vfatFCBIsDirectory(Fcb) && !PagingIo)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto ByeBye;
    }

    ByteOffset = IrpContext->Stack->Parameters.Write.ByteOffset;
    if (ByteOffset.u.LowPart == FILE_WRITE_TO_END_OF_FILE &&
        ByteOffset.u.HighPart == -1)
    {
        ByteOffset.QuadPart = Fcb->RFCB.FileSize.QuadPart;
    }
    Length = IrpContext->Stack->Parameters.Write.Length;
    BytesPerSector = IrpContext->DeviceExt->FatInfo.BytesPerSector;

    if (ByteOffset.u.HighPart && !IsVolume)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto ByeBye;
    }

    if (IsFAT || IsVolume ||
        vfatDirEntryGetFirstCluster(IrpContext->DeviceExt, &Fcb->entry) == 1)
    {
        if (ByteOffset.QuadPart + Length > Fcb->RFCB.FileSize.QuadPart)
        {
            // we can't extend the FAT, the volume or the root on FAT12/FAT16
            Status = STATUS_END_OF_FILE;
            goto ByeBye;
        }
    }

    if (PagingIo || NoCache || IsVolume)
    {
        if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
        {
            // non cached write must be sector aligned
            Status = STATUS_INVALID_PARAMETER;
            goto ByeBye;
        }
    }

    OldFileSize = Fcb->RFCB.FileSize;

    if (Length == 0)
    {
        /* Update last write time */
        IrpContext->Irp->IoStatus.Information = 0;
        Status = STATUS_SUCCESS;
        goto Metadata;
    }

    if (PagingIo)
    {
        if (ByteOffset.u.LowPart + Length > Fcb->RFCB.AllocationSize.u.LowPart)
        {
            Status = STATUS_INVALID_PARAMETER;
            goto ByeBye;
        }

        if (ByteOffset.u.LowPart + Length > ROUND_UP(Fcb->RFCB.AllocationSize.u.LowPart, BytesPerSector))
        {
            Length =  ROUND_UP(Fcb->RFCB.FileSize.u.LowPart, BytesPerSector) - ByteOffset.u.LowPart;
        }
    }

    if (!NoCache && !CcCanIWrite(IrpContext->FileObject, Length, CanWait,
                                 BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_DEFERRED_WRITE)))
    {
        BOOLEAN Retrying;

        Retrying = BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_DEFERRED_WRITE);
        SetFlag(IrpContext->Flags, IRPCONTEXT_DEFERRED_WRITE);

        Status = STATUS_PENDING;
        CcDeferWrite(IrpContext->FileObject, VfatHandleDeferredWrite,
                     IrpContext, NULL, Length, Retrying);

        DPRINT1("Dererring write!\n");

        goto ByeBye;
    }

    if (IsVolume)
    {
        Resource = &IrpContext->DeviceExt->DirResource;
    }
    else if (PagingIo)
    {
        Resource = &Fcb->PagingIoResource;
    }
    else
    {
        Resource = &Fcb->MainResource;
    }

    if (PagingIo)
    {
        if (!ExAcquireResourceSharedLite(Resource, CanWait))
        {
            Resource = NULL;
            Status = STATUS_PENDING;
            goto ByeBye;
        }
    }
    else
    {
        if (!ExAcquireResourceExclusiveLite(Resource, CanWait))
        {
            Resource = NULL;
            Status = STATUS_PENDING;
            goto ByeBye;
        }
    }

    if (!PagingIo &&
        FsRtlAreThereCurrentFileLocks(&Fcb->FileLock))
    {
        if (!FsRtlCheckLockForWriteAccess(&Fcb->FileLock, IrpContext->Irp))
        {
            Status = STATUS_FILE_LOCK_CONFLICT;
            goto ByeBye;
        }
    }

    if (!CanWait && !IsVolume)
    {
        if (ByteOffset.u.LowPart + Length > Fcb->RFCB.AllocationSize.u.LowPart)
        {
            Status = STATUS_PENDING;
            goto ByeBye;
        }
    }

    Buffer = VfatGetUserBuffer(IrpContext->Irp, PagingIo);

    if (!IsFAT && !IsVolume && !PagingIo &&
        ByteOffset.u.LowPart + Length > Fcb->RFCB.FileSize.u.LowPart)
    {
        LARGE_INTEGER AllocationSize;

        if (!ExAcquireResourceExclusiveLite(&IrpContext->DeviceExt->DirResource, CanWait))
        {
            Status = STATUS_PENDING;
            goto ByeBye;
        }

        AllocationSize.QuadPart = ByteOffset.u.LowPart + Length;
        Status = VfatSetAllocationSizeInformation(IrpContext->FileObject, Fcb,
                                                  IrpContext->DeviceExt, &AllocationSize);

        ExReleaseResourceLite(&IrpContext->DeviceExt->DirResource);

        if (!NT_SUCCESS (Status))
        {
            goto ByeBye;
        }
    }

    if (!NoCache && !PagingIo && !IsVolume)
    {
        // cached write

        vfatAddToStat(IrpContext->DeviceExt, Base.UserFileWrites, 1);
        vfatAddToStat(IrpContext->DeviceExt, Base.UserFileWriteBytes, Length);

        _SEH2_TRY
        {
            if (IrpContext->FileObject->PrivateCacheMap == NULL)
            {
                CcInitializeCacheMap(IrpContext->FileObject,
                                     (PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
                                     FALSE,
                                     &VfatGlobalData->CacheMgrCallbacks,
                                     Fcb);
            }

            if (ByteOffset.QuadPart > OldFileSize.QuadPart)
            {
                CcZeroData(IrpContext->FileObject, &OldFileSize, &ByteOffset, TRUE);
            }

            if (CcCopyWrite(IrpContext->FileObject,
                            &ByteOffset,
                            Length,
                            TRUE /*CanWait*/,
                            Buffer))
            {
                IrpContext->Irp->IoStatus.Information = Length;
                Status = STATUS_SUCCESS;
            }
            else
            {
                ASSERT(FALSE /*!CanWait*/);
                Status = STATUS_UNSUCCESSFUL;
            }
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;
    }
    else
    {
        // non cached write
        Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoReadAccess);
        if (!NT_SUCCESS(Status))
        {
            Status = STATUS_INVALID_USER_BUFFER;
            goto ByeBye;
        }

        if (ByteOffset.QuadPart > OldFileSize.QuadPart)
        {
            CcZeroData(IrpContext->FileObject, &OldFileSize, &ByteOffset, TRUE);
        }

        if (!IsVolume)
        {
            vfatAddToStat(IrpContext->DeviceExt, Fat.NonCachedWrites, 1);
            vfatAddToStat(IrpContext->DeviceExt, Fat.NonCachedWriteBytes, Length);
        }
        else
        {
            vfatAddToStat(IrpContext->DeviceExt, Base.MetaDataWrites, 1);
            vfatAddToStat(IrpContext->DeviceExt, Base.MetaDataWriteBytes, Length);
        }

        Status = VfatWriteFileData(IrpContext, Length, ByteOffset);
        if (NT_SUCCESS(Status))
        {
            IrpContext->Irp->IoStatus.Information = Length;
        }
    }

Metadata:
    if (!PagingIo && !IsFAT && !IsVolume)
    {
        if(!vfatFCBIsDirectory(Fcb))
        {
            LARGE_INTEGER SystemTime;
            ULONG Filter;

            // set dates and times
            KeQuerySystemTime (&SystemTime);
            if (vfatVolumeIsFatX(IrpContext->DeviceExt))
            {
                FsdSystemTimeToDosDateTime(IrpContext->DeviceExt,
                                           &SystemTime, &Fcb->entry.FatX.UpdateDate,
                                           &Fcb->entry.FatX.UpdateTime);
                Fcb->entry.FatX.AccessDate = Fcb->entry.FatX.UpdateDate;
                Fcb->entry.FatX.AccessTime = Fcb->entry.FatX.UpdateTime;
            }
            else
            {
                FsdSystemTimeToDosDateTime(IrpContext->DeviceExt,
                                           &SystemTime, &Fcb->entry.Fat.UpdateDate,
                                           &Fcb->entry.Fat.UpdateTime);
                Fcb->entry.Fat.AccessDate = Fcb->entry.Fat.UpdateDate;
            }
            /* set date and times to dirty */
            Fcb->Flags |= FCB_IS_DIRTY;

            /* Time to notify the OS */
            Filter = FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES;
            if (ByteOffset.QuadPart != OldFileSize.QuadPart) Filter |= FILE_NOTIFY_CHANGE_SIZE;

            vfatReportChange(IrpContext->DeviceExt, Fcb, Filter, FILE_ACTION_MODIFIED);
        }
    }

ByeBye:
    if (Resource)
    {
        ExReleaseResourceLite(Resource);
    }

    if (Status == STATUS_PENDING)
    {
        Status = VfatLockUserBuffer(IrpContext->Irp, Length, IoReadAccess);
        if (NT_SUCCESS(Status))
        {
            Status = VfatMarkIrpContextForQueue(IrpContext);
        }
    }
    else
    {
        IrpContext->Irp->IoStatus.Status = Status;
        if (BooleanFlagOn(IrpContext->FileObject->Flags, FO_SYNCHRONOUS_IO) &&
            !PagingIo && NT_SUCCESS(Status))
        {
            IrpContext->FileObject->CurrentByteOffset.QuadPart =
                ByteOffset.QuadPart + IrpContext->Irp->IoStatus.Information;
        }

        if (NT_SUCCESS(Status))
            IrpContext->PriorityBoost = IO_DISK_INCREMENT;
    }
    DPRINT("%x\n", Status);
    return Status;
}