/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    mdlsup.c

Abstract:

    This module implements the Mdl support routines for the Cache subsystem.

--*/

#include "cc.h"

//
//  Debug Trace Level
//

#define me                               (0x00000010)

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,CcMdlRead)
#pragma alloc_text(PAGE,CcMdlReadComplete)
#pragma alloc_text(PAGE,CcMdlReadComplete2)
#pragma alloc_text(PAGE,CcMdlWriteComplete)
#endif


VOID
CcMdlRead (
    __in PFILE_OBJECT FileObject,
    __in PLARGE_INTEGER FileOffset,
    __in ULONG Length,
    __out PMDL *MdlChain,
    __out PIO_STATUS_BLOCK IoStatus
    )

/*++

Routine Description:

    This routine attempts to lock the specified file data in the cache
    and return a description of it in an Mdl along with the correct
    I/O status.  It is *not* safe to call this routine from Dpc level.

    This routine is synchronous, and raises on errors.

    As each call returns, the pages described by the Mdl are
    locked in memory, but not mapped in system space.  If the caller
    needs the pages mapped in system space, then it must map them.

    Note that each call is a "single shot" which should be followed by
    a call to CcMdlReadComplete.  To resume an Mdl-based transfer, the
    caller must form one or more subsequent calls to CcMdlRead with
    appropriately adjusted parameters.

Arguments:

    FileObject - Pointer to the file object for a file which was
                 opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
                 which CcInitializeCacheMap was called by the file system.

    FileOffset - Byte offset in file for desired data.

    Length - Length of desired data in bytes.

    MdlChain - On output it returns a pointer to an Mdl chain describing
               the desired data.  Note that even if FALSE is returned,
               one or more Mdls may have been allocated, as may be ascertained
               by the IoStatus.Information field (see below).

    IoStatus - Pointer to standard I/O status block to receive the status
               for the transfer.  (STATUS_SUCCESS guaranteed for cache
               hits, otherwise the actual I/O status is returned.)  The
               I/O Information Field indicates how many bytes have been
               successfully locked down in the Mdl Chain.

Return Value:

    None

Raises:

    STATUS_INSUFFICIENT_RESOURCES - If a pool allocation failure occurs.

--*/

{
    PSHARED_CACHE_MAP SharedCacheMap;
    PPRIVATE_CACHE_MAP PrivateCacheMap;
    PVOID CacheBuffer;
    LARGE_INTEGER FOffset;
    PMDL Mdl = NULL;
    PMDL MdlTemp;
    PETHREAD Thread = PsGetCurrentThread();
    ULONG SavedState = 0;
    ULONG OriginalLength = Length;
    ULONG Information = 0;
    PVACB Vacb = NULL;
    ULONG SavedMissCounter = 0;

    ULONG ActivePage;
    ULONG PageIsDirty;
    PVACB ActiveVacb = NULL;

    DebugTrace(+1, me, "CcMdlRead\n", 0 );
    DebugTrace( 0, me, "    FileObject = %08lx\n", FileObject );
    DebugTrace2(0, me, "    FileOffset = %08lx, %08lx\n", FileOffset->LowPart,
                                                          FileOffset->HighPart );
    DebugTrace( 0, me, "    Length = %08lx\n", Length );

    //
    //  Save the current readahead hints.
    //

    MmSavePageFaultReadAhead( Thread, &SavedState );

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
    PrivateCacheMap = FileObject->PrivateCacheMap;

    //
    //  See if we have an active Vacb, that we need to free.
    //

    GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );

    //
    //  If there is an end of a page to be zeroed, then free that page now,
    //  so we don't send Greg the uninitialized data...
    //

    if ((ActiveVacb != NULL) || (SharedCacheMap->NeedToZero != NULL)) {

        CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
    }

    //
    //  If read ahead is enabled, then do the read ahead here so it
    //  overlaps with the copy (otherwise we will do it below).
    //  Note that we are assuming that we will not get ahead of our
    //  current transfer - if read ahead is working it should either
    //  already be in memory or else underway.
    //

    if (PrivateCacheMap->Flags.ReadAheadEnabled && (PrivateCacheMap->ReadAheadLength[1] == 0)) {
        CcScheduleReadAhead( FileObject, FileOffset, Length );
    }

    //
    //  Increment performance counters
    //

    CcMdlReadWait += 1;

    //
    //  This is not an exact solution, but when IoPageRead gets a miss,
    //  it cannot tell whether it was CcCopyRead or CcMdlRead, but since
    //  the miss should occur very soon, by loading the pointer here
    //  probably the right counter will get incremented, and in any case,
    //  we hope the errors average out!
    //

    CcMissCounter = &CcMdlReadWaitMiss;

    FOffset = *FileOffset;

    //
    //  Check for read past file size, the caller must filter this case out.
    //

    ASSERT( ( FOffset.QuadPart + (LONGLONG)Length ) <= SharedCacheMap->FileSize.QuadPart );

    //
    //  Put try-finally around the loop to deal with any exceptions
    //

    try {

        //
        //  Not all of the transfer will come back at once, so we have to loop
        //  until the entire transfer is complete.
        //

        while (Length != 0) {

            ULONG ReceivedLength;
            LARGE_INTEGER BeyondLastByte;

            //
            //  Map the data and read it in (if necessary) with the
            //  MmProbeAndLockPages call below.
            //

            CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
                                               FOffset,
                                               &Vacb,
                                               &ReceivedLength );

            if (ReceivedLength > Length) {
                ReceivedLength = Length;
            }

            BeyondLastByte.QuadPart = FOffset.QuadPart + (LONGLONG)ReceivedLength;

            //
            //  Now attempt to allocate an Mdl to describe the mapped data.
            //

            DebugTrace( 0, mm, "IoAllocateMdl:\n", 0 );
            DebugTrace( 0, mm, "    BaseAddress = %08lx\n", CacheBuffer );
            DebugTrace( 0, mm, "    Length = %08lx\n", ReceivedLength );

            Mdl = IoAllocateMdl( CacheBuffer,
                                 ReceivedLength,
                                 FALSE,
                                 FALSE,
                                 NULL );

            DebugTrace( 0, mm, "    <Mdl = %08lx\n", Mdl );

            if (Mdl == NULL) {
                DebugTrace( 0, 0, "Failed to allocate Mdl\n", 0 );

                ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
            }

            DebugTrace( 0, mm, "MmProbeAndLockPages:\n", 0 );
            DebugTrace( 0, mm, "    Mdl = %08lx\n", Mdl );

            //
            //  Set to see if the miss counter changes in order to
            //  detect when we should turn on read ahead.
            //

            SavedMissCounter += CcMdlReadWaitMiss;

            MmSetPageFaultReadAhead( Thread, ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer, ReceivedLength ) - 1);
            MmProbeAndLockPages( Mdl, KernelMode, IoReadAccess );

            SavedMissCounter -= CcMdlReadWaitMiss;

            //
            //  Unmap the data now, now that the pages are locked down.
            //

            CcFreeVirtualAddress( Vacb );
            Vacb = NULL;

            //
            //  Now link the Mdl into the caller's chain
            //

            if ( *MdlChain == NULL ) {
                *MdlChain = Mdl;
            } else {
                MdlTemp = CONTAINING_RECORD( *MdlChain, MDL, Next );
                while (MdlTemp->Next != NULL) {
                    MdlTemp = MdlTemp->Next;
                }
                MdlTemp->Next = Mdl;
            }
            Mdl = NULL;

            //
            //  Assume we did not get all the data we wanted, and set FOffset
            //  to the end of the returned data.
            //

            FOffset = BeyondLastByte;

            //
            //  Update number of bytes transferred.
            //

            Information += ReceivedLength;

            //
            //  Calculate length left to transfer.
            //

            Length -= ReceivedLength;
        }
    }
    finally {

        CcMissCounter = &CcThrowAway;

        //
        //  Restore the readahead hints.
        //

        MmResetPageFaultReadAhead( Thread, SavedState );

        if (AbnormalTermination()) {

            //
            //  We may have failed to allocate an Mdl while still having
            //  data mapped.
            //

            if (Vacb != NULL) {
                CcFreeVirtualAddress( Vacb );
            }

            if (Mdl != NULL) {
                IoFreeMdl( Mdl );
            }

            //
            //  Otherwise loop to deallocate the Mdls
            //

            while (*MdlChain != NULL) {
                MdlTemp = (*MdlChain)->Next;

                DebugTrace( 0, mm, "MmUnlockPages/IoFreeMdl:\n", 0 );
                DebugTrace( 0, mm, "    Mdl = %08lx\n", *MdlChain );

                MmUnlockPages( *MdlChain );
                IoFreeMdl( *MdlChain );

                *MdlChain = MdlTemp;
            }

            DebugTrace(-1, me, "CcMdlRead -> Unwinding\n", 0 );

        }
        else {

            //
            //  Now enable read ahead if it looks like we got any misses, and do
            //  the first one.
            //

            if (!FlagOn( FileObject->Flags, FO_RANDOM_ACCESS ) &&
                !PrivateCacheMap->Flags.ReadAheadEnabled &&
                (SavedMissCounter != 0)) {

                CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
                CcScheduleReadAhead( FileObject, FileOffset, OriginalLength );
            }

            //
            //  Now that we have described our desired read ahead, let's
            //  shift the read history down.
            //

            PrivateCacheMap->FileOffset1 = PrivateCacheMap->FileOffset2;
            PrivateCacheMap->BeyondLastByte1 = PrivateCacheMap->BeyondLastByte2;
            PrivateCacheMap->FileOffset2 = *FileOffset;
            PrivateCacheMap->BeyondLastByte2.QuadPart =
                                FileOffset->QuadPart + (LONGLONG)OriginalLength;

            IoStatus->Status = STATUS_SUCCESS;
            IoStatus->Information = Information;
        }
    }


    DebugTrace( 0, me, "    <MdlChain = %08lx\n", *MdlChain );
    DebugTrace2(0, me, "    <IoStatus = %08lx, %08lx\n", IoStatus->Status,
                                                         IoStatus->Information );
    DebugTrace(-1, me, "CcMdlRead -> VOID\n", 0 );

    return;
}


//
//  First we have the old routine which checks for an entry in the FastIo vector.
//  This routine becomes obsolete for every component that compiles with the new
//  definition of FsRtlMdlReadComplete in fsrtl.h.
//

VOID
CcMdlReadComplete (
    __in PFILE_OBJECT FileObject,
    __in PMDL MdlChain
    )

{
    PDEVICE_OBJECT DeviceObject;
    PFAST_IO_DISPATCH FastIoDispatch;

    DeviceObject = IoGetRelatedDeviceObject( FileObject );
    FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch;

    if ((FastIoDispatch != NULL) &&
        (FastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET(FAST_IO_DISPATCH, MdlWriteComplete)) &&
        (FastIoDispatch->MdlReadComplete != NULL) &&
        FastIoDispatch->MdlReadComplete( FileObject, MdlChain, DeviceObject )) {

        NOTHING;

    } else {
        CcMdlReadComplete2( FileObject, MdlChain );
    }
}

VOID
CcMdlReadComplete2 (
    IN PFILE_OBJECT FileObject,
    IN PMDL MdlChain
    )

/*++

Routine Description:

    This routine must be called at IPL0 after a call to CcMdlRead.  The
    caller must simply supply the address of the MdlChain returned in
    CcMdlRead.

    This call does the following:

        Deletes the MdlChain

Arguments:

    FileObject - Pointer to the file object for a file which was
                 opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
                 which CcInitializeCacheMap was called by the file system.

    MdlChain - same as returned from corresponding call to CcMdlRead.

Return Value:

    None.
--*/

{
    PMDL MdlNext;

    UNREFERENCED_PARAMETER (FileObject);

    DebugTrace(+1, me, "CcMdlReadComplete\n", 0 );
    DebugTrace( 0, me, "    FileObject = %08lx\n", FileObject );
    DebugTrace( 0, me, "    MdlChain = %08lx\n", MdlChain );

    //
    //  Deallocate the Mdls
    //

    while (MdlChain != NULL) {

        MdlNext = MdlChain->Next;

        DebugTrace( 0, mm, "MmUnlockPages/IoFreeMdl:\n", 0 );
        DebugTrace( 0, mm, "    Mdl = %08lx\n", MdlChain );

        MmUnlockPages( MdlChain );

        IoFreeMdl( MdlChain );

        MdlChain = MdlNext;
    }

    DebugTrace(-1, me, "CcMdlReadComplete -> VOID\n", 0 );
}


VOID
CcPrepareMdlWrite (
    __in PFILE_OBJECT FileObject,
    __in PLARGE_INTEGER FileOffset,
    __in ULONG Length,
    __out PMDL *MdlChain,
    __out PIO_STATUS_BLOCK IoStatus
    )

/*++

Routine Description:

    This routine attempts to lock the specified file data in the cache
    and return a description of it in an Mdl along with the correct
    I/O status.  Pages to be completely overwritten may be satisfied
    with emtpy pages.  It is *not* safe to call this routine from Dpc level.

    This call is synchronous and raises on error.

    When this call returns, the caller may immediately begin
    to transfer data into the buffers via the Mdl.

    When the call returns with TRUE, the pages described by the Mdl are
    locked in memory, but not mapped in system space.  If the caller
    needs the pages mapped in system space, then it must map them.
    On the subsequent call to CcMdlWriteComplete the pages will be
    unmapped if they were mapped, and in any case unlocked and the Mdl
    deallocated.

Arguments:

    FileObject - Pointer to the file object for a file which was
                 opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
                 which CcInitializeCacheMap was called by the file system.

    FileOffset - Byte offset in file for desired data.

    Length - Length of desired data in bytes.

    MdlChain - On output it returns a pointer to an Mdl chain describing
               the desired data.  Note that even if FALSE is returned,
               one or more Mdls may have been allocated, as may be ascertained
               by the IoStatus.Information field (see below).

    IoStatus - Pointer to standard I/O status block to receive the status
               for the in-transfer of the data.  (STATUS_SUCCESS guaranteed
               for cache hits, otherwise the actual I/O status is returned.)
               The I/O Information Field indicates how many bytes have been
               successfully locked down in the Mdl Chain.

Return Value:

    None

--*/

{
    PSHARED_CACHE_MAP SharedCacheMap;
    PVOID CacheBuffer;
    LARGE_INTEGER FOffset;
    PMDL Mdl = NULL;
    PMDL MdlTemp;
    LARGE_INTEGER Temp;
    ULONG SavedState = 0;
    ULONG ZeroFlags = 0;
    ULONG Information = 0;

    KLOCK_QUEUE_HANDLE LockHandle;
    ULONG ActivePage;
    ULONG PageIsDirty;
    PVACB Vacb = NULL;

    DebugTrace(+1, me, "CcPrepareMdlWrite\n", 0 );
    DebugTrace( 0, me, "    FileObject = %08lx\n", FileObject );
    DebugTrace2(0, me, "    FileOffset = %08lx, %08lx\n", FileOffset->LowPart,
                                                          FileOffset->HighPart );
    DebugTrace( 0, me, "    Length = %08lx\n", Length );

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;

    if (!FlagOn(FileObject->Flags, FO_WRITE_THROUGH) &&
        CcForceWriteThrough( FileObject, Length, SharedCacheMap, FALSE )) {

        ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
    }

    //
    //  See if we have an active Vacb, that we need to free.
    //

    GetActiveVacb( SharedCacheMap, LockHandle.OldIrql, Vacb, ActivePage, PageIsDirty );

    //
    //  If there is an end of a page to be zeroed, then free that page now,
    //  so it does not cause our data to get zeroed.  If there is an active
    //  page, free it so we have the correct ValidDataGoal.
    //

    if ((Vacb != NULL) || (SharedCacheMap->NeedToZero != NULL)) {

        CcFreeActiveVacb( SharedCacheMap, Vacb, ActivePage, PageIsDirty );
        Vacb = NULL;
    }

    FOffset = *FileOffset;

    //
    //  Put try-finally around the loop to deal with exceptions
    //

    try {

        //
        //  Not all of the transfer will come back at once, so we have to loop
        //  until the entire transfer is complete.
        //

        while (Length != 0) {

            ULONG ReceivedLength;
            LARGE_INTEGER BeyondLastByte;

            //
            //  Map and see how much we could potentially access at this
            //  FileOffset, then cut it down if it is more than we need.
            //

            CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
                                               FOffset,
                                               &Vacb,
                                               &ReceivedLength );

            if (ReceivedLength > Length) {
                ReceivedLength = Length;
            }

            BeyondLastByte.QuadPart = FOffset.QuadPart + (LONGLONG)ReceivedLength;

            //
            //  At this point we can calculate the ZeroFlags.
            //

            //
            //  We can always zero middle pages, if any.
            //

            ZeroFlags = ZERO_MIDDLE_PAGES;

            //
            //  See if we are completely overwriting the first or last page.
            //

            if (((FOffset.LowPart & (PAGE_SIZE - 1)) == 0) &&
                (ReceivedLength >= PAGE_SIZE)) {
                ZeroFlags |= ZERO_FIRST_PAGE;
            }

            if ((BeyondLastByte.LowPart & (PAGE_SIZE - 1)) == 0) {
                ZeroFlags |= ZERO_LAST_PAGE;
            }

            //
            //  See if the entire transfer is beyond valid data length,
            //  or at least starting from the second page.
            //

            Temp = FOffset;
            Temp.LowPart &= ~(PAGE_SIZE -1);
            KeAcquireInStackQueuedSpinLock( &SharedCacheMap->BcbSpinLock, &LockHandle );
            Temp.QuadPart = SharedCacheMap->ValidDataGoal.QuadPart - Temp.QuadPart;
            KeReleaseInStackQueuedSpinLock( &LockHandle );

            if (Temp.QuadPart <= 0) {
                ZeroFlags |= ZERO_FIRST_PAGE | ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
            } else if ((Temp.HighPart == 0) && (Temp.LowPart <= PAGE_SIZE)) {
                ZeroFlags |= ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
            }

            (VOID)CcMapAndRead( SharedCacheMap,
                                &FOffset,
                                ReceivedLength,
                                ZeroFlags,
                                TRUE,
                                CacheBuffer );

            //
            //  Now attempt to allocate an Mdl to describe the mapped data.
            //

            DebugTrace( 0, mm, "IoAllocateMdl:\n", 0 );
            DebugTrace( 0, mm, "    BaseAddress = %08lx\n", CacheBuffer );
            DebugTrace( 0, mm, "    Length = %08lx\n", ReceivedLength );

            Mdl = IoAllocateMdl( CacheBuffer,
                                 ReceivedLength,
                                 FALSE,
                                 FALSE,
                                 NULL );

            DebugTrace( 0, mm, "    <Mdl = %08lx\n", Mdl );

            if (Mdl == NULL) {
                DebugTrace( 0, 0, "Failed to allocate Mdl\n", 0 );

                ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
            }

            DebugTrace( 0, mm, "MmProbeAndLockPages:\n", 0 );
            DebugTrace( 0, mm, "    Mdl = %08lx\n", Mdl );

            MmDisablePageFaultClustering(&SavedState);
            MmProbeAndLockPages( Mdl, KernelMode, IoWriteAccess );
            MmEnablePageFaultClustering(SavedState);
            SavedState = 0;

            //
            //  Now that some data (maybe zeros) is locked in memory and
            //  set dirty, it is safe, and necessary for us to advance
            //  valid data goal, so that we will not subsequently ask
            //  for a zero page.  Note if we are extending valid data,
            //  our caller has the file exclusive.
            //

            KeAcquireInStackQueuedSpinLock( &SharedCacheMap->BcbSpinLock, &LockHandle );
            if (BeyondLastByte.QuadPart > SharedCacheMap->ValidDataGoal.QuadPart) {
                SharedCacheMap->ValidDataGoal = BeyondLastByte;
            }
            KeReleaseInStackQueuedSpinLock( &LockHandle );

            //
            //  Unmap the data now, now that the pages are locked down.
            //

            CcFreeVirtualAddress( Vacb );
            Vacb = NULL;

            //
            //  Now link the Mdl into the caller's chain
            //

            if ( *MdlChain == NULL ) {
                *MdlChain = Mdl;
            } else {
                MdlTemp = CONTAINING_RECORD( *MdlChain, MDL, Next );
                while (MdlTemp->Next != NULL) {
                    MdlTemp = MdlTemp->Next;
                }
                MdlTemp->Next = Mdl;
            }
            Mdl = NULL;

            //
            //  Assume we did not get all the data we wanted, and set FOffset
            //  to the end of the returned data.
            //

            FOffset = BeyondLastByte;

            //
            //  Update number of bytes transferred.
            //

            Information += ReceivedLength;

            //
            //  Calculate length left to transfer.
            //

            Length -= ReceivedLength;
        }
    }
    finally {

        if (AbnormalTermination()) {

            if (SavedState != 0) {
                MmEnablePageFaultClustering(SavedState);
            }

            if (Vacb != NULL) {
                CcFreeVirtualAddress( Vacb );
            }

            if (Mdl != NULL) {
                IoFreeMdl( Mdl );
            }

            //
            //  Otherwise loop to deallocate the Mdls
            //

            FOffset = *FileOffset;
            while (*MdlChain != NULL) {
                MdlTemp = (*MdlChain)->Next;

                DebugTrace( 0, mm, "MmUnlockPages/IoFreeMdl:\n", 0 );
                DebugTrace( 0, mm, "    Mdl = %08lx\n", *MdlChain );

                MmUnlockPages( *MdlChain );

                //
                //  Extract the File Offset for this part of the transfer, and
                //  tell the lazy writer to write these pages, since we have
                //  marked them dirty.  Ignore the only exception (allocation
                //  error), and console ourselves for having tried.
                //

                CcSetDirtyInMask( SharedCacheMap, &FOffset, (*MdlChain)->ByteCount );

                FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)((*MdlChain)->ByteCount);

                IoFreeMdl( *MdlChain );

                *MdlChain = MdlTemp;
            }

            DebugTrace(-1, me, "CcPrepareMdlWrite -> Unwinding\n", 0 );
        }
        else {

            IoStatus->Status = STATUS_SUCCESS;
            IoStatus->Information = Information;

            //
            //  Make sure the SharedCacheMap does not go away while
            //  the Mdl write is in progress.  We decrment below.
            //

            CcAcquireMasterLock( &LockHandle.OldIrql );
            CcIncrementOpenCount( SharedCacheMap, 'ldmP' );
            CcReleaseMasterLock( LockHandle.OldIrql );
        }
    }

    DebugTrace( 0, me, "    <MdlChain = %08lx\n", *MdlChain );
    DebugTrace(-1, me, "CcPrepareMdlWrite -> VOID\n", 0 );

    return;
}


//
//  First we have the old routine which checks for an entry in the FastIo vector.
//  This routine becomes obsolete for every component that compiles with the new
//  definition of FsRtlMdlWriteComplete in fsrtl.h.
//

VOID
CcMdlWriteComplete (
    __in PFILE_OBJECT FileObject,
    __in PLARGE_INTEGER FileOffset,
    __in PMDL MdlChain
    )

{
    PDEVICE_OBJECT DeviceObject;
    PFAST_IO_DISPATCH FastIoDispatch;

    DeviceObject = IoGetRelatedDeviceObject( FileObject );
    FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch;

    if ((FastIoDispatch != NULL) &&
        (FastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET(FAST_IO_DISPATCH, MdlWriteComplete)) &&
        (FastIoDispatch->MdlWriteComplete != NULL) &&
        FastIoDispatch->MdlWriteComplete( FileObject, FileOffset, MdlChain, DeviceObject )) {

        NOTHING;

    } else {
        CcMdlWriteComplete2( FileObject, FileOffset, MdlChain );
    }
}

VOID
CcMdlWriteComplete2 (
    IN PFILE_OBJECT FileObject,
    IN PLARGE_INTEGER FileOffset,
    IN PMDL MdlChain
    )

/*++

Routine Description:

    This routine must be called at IPL0 after a call to CcPrepareMdlWrite.
    The caller supplies the ActualLength of data that it actually wrote
    into the buffer, which may be less than or equal to the Length specified
    in CcPrepareMdlWrite.

    This call does the following:

        Makes sure the data up to ActualLength eventually gets written.
        If WriteThrough is FALSE, the data will not be written immediately.
        If WriteThrough is TRUE, then the data is written synchronously.

        Unmaps the pages (if mapped), unlocks them and deletes the MdlChain

Arguments:

    FileObject - Pointer to the file object for a file which was
                 opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
                 which CcInitializeCacheMap was called by the file system.

    FileOffset - Original file offset read above.

    MdlChain - same as returned from corresponding call to CcPrepareMdlWrite.

Return Value:

    None

--*/

{
    PMDL MdlNext;
    PMDL Mdl;
    PSHARED_CACHE_MAP SharedCacheMap;
    LARGE_INTEGER FOffset;
    IO_STATUS_BLOCK IoStatus;
    KIRQL OldIrql;
    NTSTATUS StatusToRaise = STATUS_SUCCESS;
    BOOLEAN First = FALSE;

    DebugTrace(+1, me, "CcMdlWriteComplete\n", 0 );
    DebugTrace( 0, me, "    FileObject = %08lx\n", FileObject );
    DebugTrace( 0, me, "    MdlChain = %08lx\n", MdlChain );

    SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;

    //
    //  Deallocate the Mdls
    //

    FOffset.QuadPart = *(LONGLONG UNALIGNED *)FileOffset;
    Mdl = MdlChain;

    //
    //  If the MDL is unlocked, this is a retry.
    //
    
    if (FlagOn( MdlChain->MdlFlags, MDL_PAGES_LOCKED )) {
        First = TRUE;
    }
    
    while (Mdl != NULL) {

        MdlNext = Mdl->Next;

        DebugTrace( 0, mm, "MmUnlockPages/IoFreeMdl:\n", 0 );
        DebugTrace( 0, mm, "    Mdl = %08lx\n", Mdl );

        //
        //  Now clear the dirty bits in the Pte and set them in the
        //  Pfn.  The Mdls will not be locked on repeated completion
        //  attempts.
        //

        if (First) {
            MmUnlockPages( Mdl );
        }

        //
        //  Extract the File Offset for this part of the transfer.
        //

        if (FlagOn(FileObject->Flags, FO_WRITE_THROUGH)) {

            MmFlushSection ( FileObject->SectionObjectPointer,
                             &FOffset,
                             Mdl->ByteCount,
                             &IoStatus,
                             MM_FLUSH_ACQUIRE_FILE );

            ASSERT( IoStatus.Status != STATUS_ENCOUNTERED_WRITE_IN_PROGRESS );

            //
            //  If we got an I/O error, remember it.
            //

            if (!NT_SUCCESS(IoStatus.Status)) {
                StatusToRaise = IoStatus.Status;
            }

        } else {

            //
            //  Ignore the only exception (allocation error), and console
            //  ourselves for having tried.
            //

            CcSetDirtyInMask( SharedCacheMap, &FOffset, Mdl->ByteCount );
        }

        FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)(Mdl->ByteCount);

        Mdl = MdlNext;
    }

    //
    //  Remove our open count and check to see if this makes the shared cache
    //  map eligible for lazy close.
    //
    //  We do this now so, on failure, old filesystems which did not expect
    //  writethrough to raise continue to work.  They will be within exception
    //  handling with the Mdl still in the IRP.
    //
    //  Note that non-writethrough is the only one that needs the cache map,
    //  and it'll always work.  Removing the open count for writethrough
    //  could be a minor win.
    //
    
    if (First) {
        
        CcAcquireMasterLock( &OldIrql );

        CcDecrementOpenCount( SharedCacheMap, 'ldmC' );

        if ((SharedCacheMap->OpenCount == 0) &&
            !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) &&
            (SharedCacheMap->DirtyPages == 0)) {

            //
            //  Move to the dirty list.
            //

            RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks );
            InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks,
                            &SharedCacheMap->SharedCacheMapLinks );

            //
            //  Make sure the Lazy Writer will wake up, because we
            //  want him to delete this SharedCacheMap.
            //

            LazyWriter.OtherWork = TRUE;
            if (!LazyWriter.ScanActive) {
                CcScheduleLazyWriteScan( FALSE );
            }
        }

        CcReleaseMasterLock( OldIrql );
    }
    
    //
    //  If we got an I/O error, raise it now.  Note that we have not free'd the Mdl
    //  yet so the owning filesystem can retry the completion.
    //

    if (!NT_SUCCESS(StatusToRaise)) {
        ExRaiseStatus( FsRtlNormalizeNtstatus( StatusToRaise,
                                               STATUS_UNEXPECTED_IO_ERROR ));
    }

    //
    //  Otherwise, free the Mdl chain and clean everything up.
    //
    
    Mdl = MdlChain;
    while (Mdl != NULL) {

        MdlNext = Mdl->Next;
        IoFreeMdl( Mdl );
        Mdl = MdlNext;
    }

    DebugTrace(-1, me, "CcMdlWriteComplete -> TRUE\n", 0 );

    return;
}

VOID
CcMdlWriteAbort (
    __in PFILE_OBJECT FileObject,
    __in PMDL MdlChain
    )

/*++

Routine Description:

    This routine must be called at IPL0 after a call to CcPrepareMdlWrite.

    This call does the following:

        Unmaps the pages (if mapped), unlocks them and deletes the MdlChain
        unlike the CcMdlWriteComplete this is only used to do teardown in a non
        success case where we didn't actually write anything

Arguments:

    FileObject - Pointer to the file object for a file which was
                 opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
                 which CcInitializeCacheMap was called by the file system.

    MdlChain - same as returned from corresponding call to CcPrepareMdlWrite.

Return Value:

    None

--*/

{
    PMDL MdlNext;
    PSHARED_CACHE_MAP SharedCacheMap;
    KIRQL OldIrql;
    BOOLEAN First = FALSE;

    DebugTrace(+1, me, "CcMdlWriteAbort\n", 0 );
    DebugTrace( 0, me, "    FileObject = %08lx\n", FileObject );
    DebugTrace( 0, me, "    MdlChain = %08lx\n", MdlChain );

    SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;

    //
    //  If the MDL is unlocked, we went through completion.
    //
    
    if (FlagOn( MdlChain->MdlFlags, MDL_PAGES_LOCKED )) {
        First = TRUE;
    }
    
    //
    //  Deallocate the Mdls
    //

    while (MdlChain != NULL) {

        MdlNext = MdlChain->Next;

        DebugTrace( 0, mm, "MmUnlockPages/IoFreeMdl:\n", 0 );
        DebugTrace( 0, mm, "    Mdl = %08lx\n", MdlChain );

        if (First) {
            MmUnlockPages( MdlChain );
        }
        IoFreeMdl( MdlChain );
        MdlChain = MdlNext;
    }

    //
    //  Now release our open count.  If this already went through completion,
    //  the opencount is already dropped.
    //

    if (First) {
        
        CcAcquireMasterLock( &OldIrql );

        CcDecrementOpenCount( SharedCacheMap, 'AdmC' );

        //
        //  Check for a possible deletion, this Mdl write may have been the last
        //  reference.
        //

        if ((SharedCacheMap->OpenCount == 0) &&
            !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) &&
            (SharedCacheMap->DirtyPages == 0)) {

            //
            //  Move to the dirty list.
            //

            RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks );
            InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks,
                            &SharedCacheMap->SharedCacheMapLinks );

            //
            //  Make sure the Lazy Writer will wake up, because we
            //  want him to delete this SharedCacheMap.
            //

            LazyWriter.OtherWork = TRUE;
            if (!LazyWriter.ScanActive) {
                CcScheduleLazyWriteScan( FALSE );
            }
        }

        CcReleaseMasterLock( OldIrql );
    }

    return;
}

