/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    pinsup.c

Abstract:

    This module implements the pointer-based Pin support routines for the
    Cache subsystem.

Author:

    Tom Miller      [TomM]      4-June-1990

Revision History:

--*/

#include "cc.h"

//
//  Define our debug constant
//

#define me 0x00000008

#if LIST_DBG

#define SetCallersAddress(BCB) {                            \
    RtlGetCallersAddress( &(BCB)->CallerAddress,            \
                          &(BCB)->CallersCallerAddress );   \
}

#endif

//
//  Internal routines
//

POBCB
CcAllocateObcb (
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN PBCB FirstBcb
    );

#ifdef ALLOC_PRAGMA
#if !LIST_DBG
#pragma alloc_text(PAGE,CcMapData)
#pragma alloc_text(PAGE,CcPinMappedData)
#pragma alloc_text(PAGE,CcPinRead)
#pragma alloc_text(PAGE,CcPreparePinWrite)
#endif
#pragma alloc_text(PAGE,CcUnpinData)
#pragma alloc_text(PAGE,CcSetBcbOwnerPointer)
#pragma alloc_text(PAGE,CcUnpinDataForThread)
#pragma alloc_text(PAGE,CcAllocateObcb)
#endif



BOOLEAN
CcMapData (
    IN PFILE_OBJECT FileObject,
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN ULONG Flags,
    OUT PVOID *Bcb,
    OUT PVOID *Buffer
    )

/*++

Routine Description:

    This routine attempts to map the specified file data in the cache.
    A pointer is returned to the desired data in the cache.

    If the caller does not want to block on this call, then
    Wait should be supplied as FALSE.  If Wait was supplied as FALSE and
    it is currently impossible to supply the requested data without
    blocking, then this routine will return FALSE.  However, if the
    data is immediately accessible in the cache and no blocking is
    required, this routine returns TRUE with a pointer to the data.

    Note that a call to this routine with Wait supplied as TRUE is
    considerably faster than a call with Wait supplies as FALSE, because
    in the Wait TRUE case we only have to make sure the data is mapped
    in order to return.

    It is illegal to modify data that is only mapped, and can in fact lead
    to serious problems.  It is impossible to check for this in all cases,
    however CcSetDirtyPinnedData may implement some Assertions to check for
    this.  If the caller wishes to modify data that it has only mapped, then
    it must *first* call CcPinMappedData.

    In any case, the caller MUST subsequently call CcUnpinData.
    Naturally if CcPinRead or CcPreparePinWrite were called multiple
    times for the same data, CcUnpinData must be called the same number
    of times.

    The returned Buffer pointer is valid until the data is unpinned, at
    which point it is invalid to use the pointer further.  This buffer pointer
    will remain valid if CcPinMappedData is called.

    Note that under some circumstances (like Wait supplied as FALSE or more
    than a page is requested), this routine may actually pin the data, however
    it is not necessary, and in fact not correct, for the caller to be concerned
    about this.

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.

    Wait - FALSE if caller may not block, TRUE otherwise (see description
           above)

    Bcb - On the first call this returns a pointer to a Bcb
          parameter which must be supplied as input on all subsequent
          calls, for this buffer

    Buffer - Returns pointer to desired data, valid until the buffer is
             unpinned or freed.  This pointer will remain valid if CcPinMappedData
             is called.

Return Value:

    FALSE - if Wait was supplied as FALSE and the data was not delivered

    TRUE - if the data is being delivered

--*/

{
    PSHARED_CACHE_MAP SharedCacheMap;
    LARGE_INTEGER BeyondLastByte;
    ULONG ReceivedLength;
    ULONG SavedState;
    volatile UCHAR ch;
    PVOID TempBcb;
    ULONG PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES((ULongToPtr(FileOffset->LowPart)), Length);
    PETHREAD Thread = PsGetCurrentThread();

    DebugTrace(+1, me, "CcMapData\n", 0 );

    MmSavePageFaultReadAhead( Thread, &SavedState );

    //
    //  Increment performance counters
    //

    if (FlagOn(Flags, MAP_WAIT)) {

        CcMapDataWait += 1;

        //
        //  Initialize the indirect pointer to our miss counter.
        //

        CcMissCounter = &CcMapDataWaitMiss;

    } else {
        CcMapDataNoWait += 1;
    }

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = *(PSHARED_CACHE_MAP *)((PCHAR)FileObject->SectionObjectPointer
                                            + sizeof(PVOID));

    //
    //  Call local routine to Map or Access the file data.  If we cannot map
    //  the data because of a Wait condition, return FALSE.
    //

    if (FlagOn(Flags, MAP_WAIT)) {

        *Buffer = CcGetVirtualAddress( SharedCacheMap,
                                       *FileOffset,
                                       (PVACB *)&TempBcb,
                                       &ReceivedLength );

        ASSERT( ReceivedLength >= Length );

    } else if (!CcPinFileData( FileObject,
                               FileOffset,
                               Length,
                               TRUE,
                               FALSE,
                               Flags,
                               (PBCB *)&TempBcb,
                               Buffer,
                               &BeyondLastByte )) {

        DebugTrace(-1, me, "CcMapData -> FALSE\n", 0 );

        CcMapDataNoWaitMiss += 1;

        return FALSE;

    } else {

        ASSERT( (BeyondLastByte.QuadPart - FileOffset->QuadPart) >= Length );

#if LIST_DBG
        {
            KIRQL OldIrql;
            PBCB BcbTemp = (PBCB)*Bcb;

            OldIrql = KeAcquireQueuedSpinLock( LockQueueBcbLock );

            if (BcbTemp->CcBcbLinks.Flink == NULL) {

                InsertTailList( &CcBcbList, &BcbTemp->CcBcbLinks );
                CcBcbCount += 1;
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
                SetCallersAddress( BcbTemp );

            } else {
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
            }

        }
#endif

    }

    //
    //  Caller specifically requested he doesn't want data to be faulted in.
    //

    if (!FlagOn( Flags, MAP_NO_READ )) {

        //
        //  Now let's just sit here and take the miss(es) like a man (and count them).
        //

        try {

            //
            //  Loop to touch each page
            //

            BeyondLastByte.LowPart = 0;

            while (PageCount != 0) {

                MmSetPageFaultReadAhead( Thread, PageCount - 1 );

                ch = *((volatile UCHAR *)(*Buffer) + BeyondLastByte.LowPart);

                BeyondLastByte.LowPart += PAGE_SIZE;
                PageCount -= 1;
            }

        } finally {

            MmResetPageFaultReadAhead( Thread, SavedState );

            if (AbnormalTermination() && (TempBcb != NULL)) {
                CcUnpinFileData( (PBCB)TempBcb, TRUE, UNPIN );
            }
        }
    }

    CcMissCounter = &CcThrowAway;

    //
    //  Increment the pointer as a reminder that it is read only, and
    //  return it.  We pend this until now to avoid raising with a valid
    //  Bcb into caller's contexts.
    //

    *(PCHAR *)&TempBcb += 1;
    *Bcb = TempBcb;

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

    return TRUE;
}


BOOLEAN
CcPinMappedData (
    IN PFILE_OBJECT FileObject,
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN ULONG Flags,
    IN OUT PVOID *Bcb
    )

/*++

Routine Description:

    This routine attempts to pin data that was previously only mapped.
    If the routine determines that in fact it was necessary to actually
    pin the data when CcMapData was called, then this routine does not
    have to do anything.

    If the caller does not want to block on this call, then
    Wait should be supplied as FALSE.  If Wait was supplied as FALSE and
    it is currently impossible to supply the requested data without
    blocking, then this routine will return FALSE.  However, if the
    data is immediately accessible in the cache and no blocking is
    required, this routine returns TRUE with a pointer to the data.

    If the data is not returned in the first call, the caller
    may request the data later with Wait = TRUE.  It is not required
    that the caller request the data later.

    If the caller subsequently modifies the data, it should call
    CcSetDirtyPinnedData.

    In any case, the caller MUST subsequently call CcUnpinData.
    Naturally if CcPinRead or CcPreparePinWrite were called multiple
    times for the same data, CcUnpinData must be called the same number
    of times.

    Note there are no performance counters in this routine, as the misses
    will almost always occur on the map above, and there will seldom be a
    miss on this conversion.

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.

    Flags - (PIN_WAIT, PIN_EXCLUSIVE, PIN_NO_READ, etc. as defined in cache.h)
            If the caller specifies PIN_NO_READ and PIN_EXCLUSIVE, then he must
            guarantee that no one else will be attempting to map the view, if he
            wants to guarantee that the Bcb is not mapped (view may be purged).
            If the caller specifies PIN_NO_READ without PIN_EXCLUSIVE, the data
            may or may not be mapped in the return Bcb.

    Bcb - On the first call this returns a pointer to a Bcb
          parameter which must be supplied as input on all subsequent
          calls, for this buffer

Return Value:

    FALSE - if Wait was not set and the data was not delivered

    TRUE - if the data is being delivered

--*/

{
    PVOID Buffer;
    LARGE_INTEGER BeyondLastByte;
    PSHARED_CACHE_MAP SharedCacheMap;
    LARGE_INTEGER LocalFileOffset = *FileOffset;
    POBCB MyBcb = NULL;
    PBCB *CurrentBcbPtr = (PBCB *)&MyBcb;
    BOOLEAN Result = FALSE;

    DebugTrace(+1, me, "CcPinMappedData\n", 0 );

    //
    // If the Bcb is no longer ReadOnly, then just return.
    //

    if ((*(PULONG)Bcb & 1) == 0) {
        return TRUE;
    }

    //
    // Remove the Read Only flag
    //

    *(PCHAR *)Bcb -= 1;

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = *(PSHARED_CACHE_MAP *)((PCHAR)FileObject->SectionObjectPointer
                                            + sizeof(PVOID));

    //
    //  We only count the calls to this routine, since they are almost guaranteed
    //  to be hits.
    //

    CcPinMappedDataCount += 1;

    //
    //  Guarantee we will put the flag back if required.
    //

    try {

        if (((PBCB)*Bcb)->NodeTypeCode != CACHE_NTC_BCB) {

            //
            //  Form loop to handle occasional overlapped Bcb case.
            //

            do {

                //
                //  If we have already been through the loop, then adjust
                //  our file offset and length from the last time.
                //

                if (MyBcb != NULL) {

                    //
                    //  If this is the second time through the loop, then it is time
                    //  to handle the overlap case and allocate an OBCB.
                    //

                    if (CurrentBcbPtr == (PBCB *)&MyBcb) {

                        MyBcb = CcAllocateObcb( FileOffset, Length, (PBCB)MyBcb );

                        //
                        //  Set CurrentBcbPtr to point at the first entry in
                        //  the vector (which is already filled in), before
                        //  advancing it below.
                        //

                        CurrentBcbPtr = &MyBcb->Bcbs[0];
                    }

                    Length -= (ULONG)(BeyondLastByte.QuadPart - LocalFileOffset.QuadPart);
                    LocalFileOffset.QuadPart = BeyondLastByte.QuadPart;
                    CurrentBcbPtr += 1;
                }

                //
                //  Call local routine to Map or Access the file data.  If we cannot map
                //  the data because of a Wait condition, return FALSE.
                //

                if (!CcPinFileData( FileObject,
                                    &LocalFileOffset,
                                    Length,
                                    (BOOLEAN)!FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED),
                                    FALSE,
                                    Flags,
                                    CurrentBcbPtr,
                                    &Buffer,
                                    &BeyondLastByte )) {

                    try_return( Result = FALSE );
                }

            //
            //  Continue looping if we did not get everything.
            //

            } while((BeyondLastByte.QuadPart - LocalFileOffset.QuadPart) < Length);

            //
            //  Free the Vacb before going on.
            //

            CcFreeVirtualAddress( (PVACB)*Bcb );

            *Bcb = MyBcb;

            //
            //  Debug routines used to insert and remove Bcbs from the global list
            //

#if LIST_DBG
            {
                KIRQL OldIrql;
                PBCB BcbTemp = (PBCB)*Bcb;

                OldIrql = KeAcquireQueuedSpinLock( LockQueueBcbLock );

                if (BcbTemp->CcBcbLinks.Flink == NULL) {

                    InsertTailList( &CcBcbList, &BcbTemp->CcBcbLinks );
                    CcBcbCount += 1;
                    KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
                    SetCallersAddress( BcbTemp );

                } else {
                    KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
                }

            }
#endif
        }

        //
        //  If he really has a Bcb, all we have to do is acquire it shared since he is
        //  no longer ReadOnly.
        //

        else {

            if (!ExAcquireSharedStarveExclusive( &((PBCB)*Bcb)->Resource, BooleanFlagOn(Flags, PIN_WAIT))) {

                try_return( Result = FALSE );
            }
        }

        Result = TRUE;

    try_exit: NOTHING;
    }
    finally {

        if (!Result) {

            //
            //  Put the Read Only flag back
            //

            *(PCHAR *)Bcb += 1;

            //
            //  We may have gotten partway through
            //

            if (MyBcb != NULL) {
                CcUnpinData( MyBcb );
            }
        }

        DebugTrace(-1, me, "CcPinMappedData -> %02lx\n", Result );
    }
    return Result;
}


BOOLEAN
CcPinRead (
    IN PFILE_OBJECT FileObject,
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN ULONG Flags,
    OUT PVOID *Bcb,
    OUT PVOID *Buffer
    )

/*++

Routine Description:

    This routine attempts to pin the specified file data in the cache.
    A pointer is returned to the desired data in the cache.  This routine
    is intended for File System support and is not intended to be called
    from Dpc level.

    If the caller does not want to block on this call, then
    Wait should be supplied as FALSE.  If Wait was supplied as FALSE and
    it is currently impossible to supply the requested data without
    blocking, then this routine will return FALSE.  However, if the
    data is immediately accessible in the cache and no blocking is
    required, this routine returns TRUE with a pointer to the data.

    If the data is not returned in the first call, the caller
    may request the data later with Wait = TRUE.  It is not required
    that the caller request the data later.

    If the caller subsequently modifies the data, it should call
    CcSetDirtyPinnedData.

    In any case, the caller MUST subsequently call CcUnpinData.
    Naturally if CcPinRead or CcPreparePinWrite were called multiple
    times for the same data, CcUnpinData must be called the same number
    of times.

    The returned Buffer pointer is valid until the data is unpinned, at
    which point it is invalid to use the pointer further.

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.

    Flags - (PIN_WAIT, PIN_EXCLUSIVE, PIN_NO_READ, etc. as defined in cache.h)
            If the caller specifies PIN_NO_READ and PIN_EXCLUSIVE, then he must
            guarantee that no one else will be attempting to map the view, if he
            wants to guarantee that the Bcb is not mapped (view may be purged).
            If the caller specifies PIN_NO_READ without PIN_EXCLUSIVE, the data
            may or may not be mapped in the return Bcb.

    Bcb - On the first call this returns a pointer to a Bcb
          parameter which must be supplied as input on all subsequent
          calls, for this buffer

    Buffer - Returns pointer to desired data, valid until the buffer is
             unpinned or freed.

Return Value:

    FALSE - if Wait was not set and the data was not delivered

    TRUE - if the data is being delivered

--*/

{
    PSHARED_CACHE_MAP SharedCacheMap;
    PVOID LocalBuffer;
    LARGE_INTEGER BeyondLastByte;
    LARGE_INTEGER LocalFileOffset = *FileOffset;
    POBCB MyBcb = NULL;
    PBCB *CurrentBcbPtr = (PBCB *)&MyBcb;
    BOOLEAN Result = FALSE;

    DebugTrace(+1, me, "CcPinRead\n", 0 );

    //
    //  Increment performance counters
    //

    if (FlagOn(Flags, PIN_WAIT)) {

        CcPinReadWait += 1;

        //
        //  Initialize the indirect pointer to our miss counter.
        //

        CcMissCounter = &CcPinReadWaitMiss;

    } else {
        CcPinReadNoWait += 1;
    }

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = *(PSHARED_CACHE_MAP *)((PCHAR)FileObject->SectionObjectPointer
                                            + sizeof(PVOID));

    try {

        //
        //  Form loop to handle occasional overlapped Bcb case.
        //

        do {

            //
            //  If we have already been through the loop, then adjust
            //  our file offset and length from the last time.
            //

            if (MyBcb != NULL) {

                //
                //  If this is the second time through the loop, then it is time
                //  to handle the overlap case and allocate an OBCB.
                //

                if (CurrentBcbPtr == (PBCB *)&MyBcb) {

                    MyBcb = CcAllocateObcb( FileOffset, Length, (PBCB)MyBcb );

                    //
                    //  Set CurrentBcbPtr to point at the first entry in
                    //  the vector (which is already filled in), before
                    //  advancing it below.
                    //

                    CurrentBcbPtr = &MyBcb->Bcbs[0];

                    //
                    //  Also on second time through, return starting Buffer
                    //

                    *Buffer = LocalBuffer;
                }

                Length -= (ULONG)(BeyondLastByte.QuadPart - LocalFileOffset.QuadPart);
                LocalFileOffset.QuadPart = BeyondLastByte.QuadPart;
                CurrentBcbPtr += 1;
            }

            //
            //  Call local routine to Map or Access the file data.  If we cannot map
            //  the data because of a Wait condition, return FALSE.
            //

            if (!CcPinFileData( FileObject,
                                &LocalFileOffset,
                                Length,
                                (BOOLEAN)!FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED),
                                FALSE,
                                Flags,
                                CurrentBcbPtr,
                                &LocalBuffer,
                                &BeyondLastByte )) {

                CcPinReadNoWaitMiss += 1;

                try_return( Result = FALSE );
            }

        //
        //  Continue looping if we did not get everything.
        //

        } while((BeyondLastByte.QuadPart - LocalFileOffset.QuadPart) < Length);

        *Bcb = MyBcb;

        //
        //  Debug routines used to insert and remove Bcbs from the global list
        //

#if LIST_DBG

        {
            KIRQL OldIrql;
            PBCB BcbTemp = (PBCB)*Bcb;

            OldIrql = KeAcquireQueuedSpinLock( LockQueueBcbLock );

            if (BcbTemp->CcBcbLinks.Flink == NULL) {

                InsertTailList( &CcBcbList, &BcbTemp->CcBcbLinks );
                CcBcbCount += 1;
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
                SetCallersAddress( BcbTemp );

            } else {
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
            }

        }

#endif

        //
        //  In the normal (nonoverlapping) case we return the
        //  correct buffer address here.
        //

        if (CurrentBcbPtr == (PBCB *)&MyBcb) {
            *Buffer = LocalBuffer;
        }

        Result = TRUE;

    try_exit: NOTHING;
    }
    finally {

        CcMissCounter = &CcThrowAway;

        if (!Result) {

            //
            //  We may have gotten partway through
            //

            if (MyBcb != NULL) {
                CcUnpinData( MyBcb );
            }
        }

        DebugTrace(-1, me, "CcPinRead -> %02lx\n", Result );
    }

    return Result;
}


BOOLEAN
CcPreparePinWrite (
    IN PFILE_OBJECT FileObject,
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN BOOLEAN Zero,
    IN ULONG Flags,
    OUT PVOID *Bcb,
    OUT PVOID *Buffer
    )

/*++

Routine Description:

    This routine attempts to lock the specified file data in the cache
    and return a pointer to it along with the correct
    I/O status.  Pages to be completely overwritten may be satisfied
    with emtpy pages.

    If not all of the pages can be prepared, and Wait was supplied as
    FALSE, then this routine will return FALSE, and its outputs will
    be meaningless.  The caller may request the data later with
    Wait = TRUE.  However, it is not required that the caller request
    the data later.

    If Wait is supplied as TRUE, and all of the pages can be prepared
    without blocking, this call will return TRUE immediately.  Otherwise,
    this call will block until all of the pages can be prepared, and
    then return TRUE.

    When this call returns with TRUE, the caller may immediately begin
    to transfer data into the buffers via the Buffer pointer.  The
    buffer will already be marked dirty.

    The caller MUST subsequently call CcUnpinData.
    Naturally if CcPinRead or CcPreparePinWrite were called multiple
    times for the same data, CcUnpinData must be called the same number
    of times.

    The returned Buffer pointer is valid until the data is unpinned, at
    which point it is invalid to use the pointer further.

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.

    Zero - If supplied as TRUE, the buffer will be zeroed on return.

    Flags - (PIN_WAIT, PIN_EXCLUSIVE, PIN_NO_READ, etc. as defined in cache.h)
            If the caller specifies PIN_NO_READ and PIN_EXCLUSIVE, then he must
            guarantee that no one else will be attempting to map the view, if he
            wants to guarantee that the Bcb is not mapped (view may be purged).
            If the caller specifies PIN_NO_READ without PIN_EXCLUSIVE, the data
            may or may not be mapped in the return Bcb.

    Bcb - This returns a pointer to a Bcb parameter which must be
          supplied as input to CcPinWriteComplete.

    Buffer - Returns pointer to desired data, valid until the buffer is
             unpinned or freed.

Return Value:

    FALSE - if Wait was not set and the data was not delivered

    TRUE - if the pages are being delivered

--*/

{
    PSHARED_CACHE_MAP SharedCacheMap;
    PVOID LocalBuffer;
    LARGE_INTEGER BeyondLastByte;
    LARGE_INTEGER LocalFileOffset = *FileOffset;
    POBCB MyBcb = NULL;
    PBCB *CurrentBcbPtr = (PBCB *)&MyBcb;
    ULONG OriginalLength = Length;
    BOOLEAN Result = FALSE;

    DebugTrace(+1, me, "CcPreparePinWrite\n", 0 );

    //
    //  Get pointer to SharedCacheMap.
    //

    SharedCacheMap = *(PSHARED_CACHE_MAP *)((PCHAR)FileObject->SectionObjectPointer
                                            + sizeof(PVOID));

    try {

        //
        //  Form loop to handle occasional overlapped Bcb case.
        //

        do {

            //
            //  If we have already been through the loop, then adjust
            //  our file offset and length from the last time.
            //

            if (MyBcb != NULL) {

                //
                //  If this is the second time through the loop, then it is time
                //  to handle the overlap case and allocate an OBCB.
                //

                if (CurrentBcbPtr == (PBCB *)&MyBcb) {

                    MyBcb = CcAllocateObcb( FileOffset, Length, (PBCB)MyBcb );

                    //
                    //  Set CurrentBcbPtr to point at the first entry in
                    //  the vector (which is already filled in), before
                    //  advancing it below.
                    //

                    CurrentBcbPtr = &MyBcb->Bcbs[0];

                    //
                    //  Also on second time through, return starting Buffer
                    //

                    *Buffer = LocalBuffer;
                }

                Length -= (ULONG)(BeyondLastByte.QuadPart - LocalFileOffset.QuadPart);
                LocalFileOffset.QuadPart = BeyondLastByte.QuadPart;
                CurrentBcbPtr += 1;
            }

            //
            //  Call local routine to Map or Access the file data.  If we cannot map
            //  the data because of a Wait condition, return FALSE.
            //

            if (!CcPinFileData( FileObject,
                                &LocalFileOffset,
                                Length,
                                FALSE,
                                TRUE,
                                Flags,
                                CurrentBcbPtr,
                                &LocalBuffer,
                                &BeyondLastByte )) {

                try_return( Result = FALSE );
            }

        //
        //  Continue looping if we did not get everything.
        //

        } while((BeyondLastByte.QuadPart - LocalFileOffset.QuadPart) < Length);

        //
        //  Debug routines used to insert and remove Bcbs from the global list
        //

#if LIST_DBG

        {
            KIRQL OldIrql;
            PBCB BcbTemp = (PBCB)*Bcb;

            OldIrql = KeAcquireQueuedSpinLock( LockQueueBcbLock );

            if (BcbTemp->CcBcbLinks.Flink == NULL) {

                InsertTailList( &CcBcbList, &BcbTemp->CcBcbLinks );
                CcBcbCount += 1;
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
                SetCallersAddress( BcbTemp );

            } else {
                KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql );
            }

        }

#endif

        //
        //  In the normal (nonoverlapping) case we return the
        //  correct buffer address here.
        //

        if (CurrentBcbPtr == (PBCB *)&MyBcb) {
            *Buffer = LocalBuffer;
        }

        if (Zero) {
            RtlZeroMemory( *Buffer, OriginalLength );
        }

        CcSetDirtyPinnedData( MyBcb, NULL );

        //
        //  Fill in the return argument.
        //

        *Bcb = MyBcb;
        
        Result = TRUE;

    try_exit: NOTHING;
    }
    finally {

        CcMissCounter = &CcThrowAway;

        if (!Result) {

            //
            //  We may have gotten partway through
            //

            if (MyBcb != NULL) {
                CcUnpinData( MyBcb );
            }
        }

        DebugTrace(-1, me, "CcPreparePinWrite -> %02lx\n", Result );
    }

    return Result;
}


VOID
CcUnpinData (
    IN PVOID Bcb
    )

/*++

Routine Description:

    This routine must be called at IPL0, some time after calling CcPinRead
    or CcPreparePinWrite.  It performs any cleanup that is necessary.

Arguments:

    Bcb - Bcb parameter returned from the last call to CcPinRead.

Return Value:

    None.

--*/

{
    DebugTrace(+1, me, "CcUnpinData:\n", 0 );
    DebugTrace( 0, me, "    >Bcb = %08lx\n", Bcb );

    //
    //  Test for ReadOnly and unpin accordingly.
    //

    if (((ULONG_PTR)Bcb & 1) != 0) {

        //
        //  Remove the Read Only flag
        //

        Bcb = (PVOID) ((ULONG_PTR)Bcb & ~1);

        CcUnpinFileData( (PBCB)Bcb, TRUE, UNPIN );

    } else {

        //
        //  Handle the overlapped Bcb case.
        //

        if (((POBCB)Bcb)->NodeTypeCode == CACHE_NTC_OBCB) {

            PBCB *BcbPtrPtr = &((POBCB)Bcb)->Bcbs[0];

            //
            //  Loop to free all Bcbs with recursive calls
            //  (rather than dealing with RO for this uncommon case).
            //

            while (*BcbPtrPtr != NULL) {
                CcUnpinData(*(BcbPtrPtr++));
            }

            //
            //  Then free the pool for the Obcb
            //

            ExFreePool( Bcb );

        //
        //  Otherwise, it is a normal Bcb
        //

        } else {
            CcUnpinFileData( (PBCB)Bcb, FALSE, UNPIN );
        }
    }

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


VOID
CcSetBcbOwnerPointer (
    IN PVOID Bcb,
    IN PVOID OwnerPointer
    )

/*++

Routine Description:

    This routine may be called to set the resource owner for the Bcb resource,
    for cases where another thread will do the unpin *and* the current thread
    may exit.

Arguments:

    Bcb - Bcb parameter returned from the last call to CcPinRead.

    OwnerPointer - A valid resource owner pointer, which means a pointer to
                   an allocated system address, with the low-order two bits
                   set.  The address may not be deallocated until after the
                   unpin call.

Return Value:

    None.

--*/

{
    ASSERT(((ULONG_PTR)Bcb & 1) == 0);

    //
    //  Handle the overlapped Bcb case.
    //

    if (((POBCB)Bcb)->NodeTypeCode == CACHE_NTC_OBCB) {

        PBCB *BcbPtrPtr = &((POBCB)Bcb)->Bcbs[0];

        //
        //  Loop to set owner for all Bcbs.
        //

        while (*BcbPtrPtr != NULL) {
            ExSetResourceOwnerPointer( &(*BcbPtrPtr)->Resource, OwnerPointer );
            BcbPtrPtr++;
        }

    //
    //  Otherwise, it is a normal Bcb
    //

    } else {

        //
        //  Handle normal case.
        //

        ExSetResourceOwnerPointer( &((PBCB)Bcb)->Resource, OwnerPointer );
    }
}


VOID
CcUnpinDataForThread (
    IN PVOID Bcb,
    IN ERESOURCE_THREAD ResourceThreadId
    )

/*++

Routine Description:

    This routine must be called at IPL0, some time after calling CcPinRead
    or CcPreparePinWrite.  It performs any cleanup that is necessary,
    releasing the Bcb resource for the given thread.

Arguments:

    Bcb - Bcb parameter returned from the last call to CcPinRead.

Return Value:

    None.

--*/

{
    DebugTrace(+1, me, "CcUnpinDataForThread:\n", 0 );
    DebugTrace( 0, me, "    >Bcb = %08lx\n", Bcb );
    DebugTrace( 0, me, "    >ResoureceThreadId = %08lx\n", ResoureceThreadId );

    //
    //  Test for ReadOnly and unpin accordingly.
    //

    if (((ULONG_PTR)Bcb & 1) != 0) {

        //
        //  Remove the Read Only flag
        //

        Bcb = (PVOID) ((ULONG_PTR)Bcb & ~1);

        CcUnpinFileData( (PBCB)Bcb, TRUE, UNPIN );

    } else {

        //
        //  Handle the overlapped Bcb case.
        //

        if (((POBCB)Bcb)->NodeTypeCode == CACHE_NTC_OBCB) {

            PBCB *BcbPtrPtr = &((POBCB)Bcb)->Bcbs[0];

            //
            //  Loop to free all Bcbs with recursive calls
            //  (rather than dealing with RO for this uncommon case).
            //

            while (*BcbPtrPtr != NULL) {
                CcUnpinDataForThread( *(BcbPtrPtr++), ResourceThreadId );
            }

            //
            //  Then free the pool for the Obcb
            //

            ExFreePool( Bcb );

        //
        //  Otherwise, it is a normal Bcb
        //

        } else {

            //
            //  If not readonly, we can release the resource for the thread first,
            //  and then call CcUnpinFileData.  Release resource first in case
            //  Bcb gets deallocated.
            //

            ExReleaseResourceForThreadLite( &((PBCB)Bcb)->Resource, ResourceThreadId );
            CcUnpinFileData( (PBCB)Bcb, TRUE, UNPIN );
        }
    }
    DebugTrace(-1, me, "CcUnpinDataForThread -> VOID\n", 0 );
}


POBCB
CcAllocateObcb (
    IN PLARGE_INTEGER FileOffset,
    IN ULONG Length,
    IN PBCB FirstBcb
    )

/*++

Routine Description:

    This routine is called by the various pinning routines to allocate and
    initialize an overlap Bcb.

Arguments:

    FileOffset - Starting file offset for the Obcb (An Obcb starts with a
                 public structure, which someone could use)

    Length - Length of the range covered by the Obcb

    FirstBcb - First Bcb already created, which only covers the start of
               the desired range (low order bit may be set to indicate ReadOnly)

Return Value:

    Pointer to the allocated Obcb

--*/

{
    ULONG LengthToAllocate;
    POBCB Obcb;
    PBCB Bcb = (PBCB)((ULONG_PTR)FirstBcb & ~1);

    //
    //  Allocate according to the worst case, assuming that we
    //  will need as many additional Bcbs as there are pages
    //  remaining. Also throw in one more pointer to guarantee
    //  users of the OBCB can always terminate on NULL.
    //
    //  We remove fron consideration the range described by the
    //  first Bcb (note that the range of the Obcb is not strictly
    //  starting at the first Bcb) and add in locations for the first
    //  bcb and the null.
    //

    LengthToAllocate = FIELD_OFFSET(OBCB, Bcbs) + (2 * sizeof(PBCB)) +
                       ((Length -
                         (Bcb->ByteLength -
                          (FileOffset->HighPart?
                           (ULONG)(FileOffset->QuadPart - Bcb->FileOffset.QuadPart) :
                           FileOffset->LowPart - Bcb->FileOffset.LowPart)) +
                         PAGE_SIZE - 1) / PAGE_SIZE) * sizeof(PBCB);

    Obcb = ExAllocatePoolWithTag( NonPagedPool, LengthToAllocate, 'bOcC' );
    if (Obcb == NULL) {
        ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
    }

    RtlZeroMemory( Obcb, LengthToAllocate );
    Obcb->NodeTypeCode = CACHE_NTC_OBCB;
    Obcb->NodeByteSize = (USHORT)LengthToAllocate;
    Obcb->ByteLength = Length;
    Obcb->FileOffset = *FileOffset;
    Obcb->Bcbs[0] = FirstBcb;

    return Obcb;
}
