/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    EaSup.c

Abstract:

    This module implements the cluster operations on the EA file for Rx.

Author:

    Brian Andrew    [BrianAn]       07-Nov-1990

--*/

//    ----------------------joejoe-----------found-------------#include "RxProcs.h"
#include "precomp.h"
#pragma hdrstop

//
//  The Bug check file id for this module
//

#define BugCheckFileId                   (RDBSS_BUG_CHECK_EASUP)

//
//  Local debug trace level
//

#define Dbg                              (DEBUG_TRACE_EA)

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RxAddEaSet)
#pragma alloc_text(PAGE, RxAppendPackedEa)
#pragma alloc_text(PAGE, RxCreateEa)
#pragma alloc_text(PAGE, RxDeleteEa)
#pragma alloc_text(PAGE, RxDeleteEaSet)
#pragma alloc_text(PAGE, RxDeletePackedEa)
#pragma alloc_text(PAGE, RxGetEaFile)
#pragma alloc_text(PAGE, RxGetEaLength)
#pragma alloc_text(PAGE, RxGetNeedEaCount)
#pragma alloc_text(PAGE, RxIsEaNameValid)
#pragma alloc_text(PAGE, RxLocateEaByName)
#pragma alloc_text(PAGE, RxLocateNextEa)
#pragma alloc_text(PAGE, RxReadEaSet)
#pragma alloc_text(PAGE, RxPinEaRange)
#pragma alloc_text(PAGE, RxMarkEaRangeDirty)
#pragma alloc_text(PAGE, RxUnpinEaRange)
#endif

#define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I)))

//
//  Any access to the Ea file must recognize when a section boundary is being
//  crossed.
//

#define EA_SECTION_SIZE             (0x00040000)


VOID
RxGetEaLength (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN PDIRENT Dirent,
    OUT PULONG EaLength
    )

/*++

Routine Description:

    This routine looks up the Ea length for the Eas of the file.  This
    length is the  length of the packed eas, including the 4 bytes which
    contain the Ea length.

    This routine pins down the Ea set for the desired file and copies
    this field from the Ea set header.

Arguments:

    Vcb - Vcb for the volume containing the Eas.

    Dirent - Supplies a pointer to the dirent for the file in question.

    EaLength - Supplies the address to store the length of the Eas.

Return Value:

    None

--*/

{
    PBCB EaBcb;
    BOOLEAN LockedEaFcb;
    EA_RANGE EaSetRange;

    DebugTrace(+1, Dbg, "RxGetEaLength ...\n", 0);

    //
    //  If the handle is 0 then the Ea length is 0.
    //

    if (Dirent->ExtendedAttributes == 0) {

        *EaLength = 0;
        DebugTrace(-1, Dbg, "RxGetEaLength -> %08lx\n", TRUE);
        return;
    }

    RtlZeroMemory( &EaSetRange, sizeof( EA_RANGE ));

    //
    //  Use a try to facilitate cleanup.
    //

    try {

        PDIRENT EaDirent;
        OEM_STRING ThisFilename;
        UCHAR Buffer[12];
        PEA_SET_HEADER EaSetHeader;

        //
        //  Initial the local values.
        //

        EaBcb = NULL;
        LockedEaFcb = FALSE;

        //
        //  Try to get the Ea file object.  Return FALSE on failure.
        //

        RxGetEaFile( RxContext,
                      Vcb,
                      &EaDirent,
                      &EaBcb,
                      FALSE,
                      FALSE );

        LockedEaFcb = TRUE;

        //
        //  If we didn't get the file because it doesn't exist, then the
        //  disk is corrupted.
        //

        if (Vcb->VirtualEaFile == NULL) {

            DebugTrace(0, Dbg, "RxGetEaLength:  Ea file doesn't exist\n", 0);
            RxRaiseStatus( RxContext, RxStatus(NO_EAS_ON_FILE) );
        }

        //
        //  Try to pin down the Ea set header for the index in the
        //  dirent.  If the operation doesn't complete, return FALSE
        //  from this routine.
        //

        ThisFilename.Buffer = Buffer;
        Rx8dot3ToString( RxContext, Dirent, FALSE, &ThisFilename );

        RxReadEaSet( RxContext,
                      Vcb,
                      Dirent->ExtendedAttributes,
                      &ThisFilename,
                      FALSE,
                      &EaSetRange );

        EaSetHeader = (PEA_SET_HEADER) EaSetRange.Data;

        //
        //  We now have the Ea set header for this file.  We simply copy
        //  the Ea length field.
        //

        CopyUchar4( EaLength, EaSetHeader->cbList );
        DebugTrace(0, Dbg, "RxGetEaLength:  Length of Ea is -> %08lx\n",
                   *EaLength);

    } finally {

        DebugUnwind( RxGetEaLength );

        //
        //  Unpin the EaDirent and the EaSetHeader if pinned.
        //

        RxUnpinBcb( RxContext, EaBcb );

        RxUnpinEaRange( RxContext, &EaSetRange );

        //
        //  Release the Fcb for the Ea file if locked.
        //

        if (LockedEaFcb) {

            RxReleaseFcb( RxContext, Vcb->EaFcb );
        }

        DebugTrace(-1, Dbg, "RxGetEaLength:  Ea length -> %08lx\n", *EaLength);
    }

    return;
}


VOID
RxGetNeedEaCount (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN PDIRENT Dirent,
    OUT PULONG NeedEaCount
    )

/*++

Routine Description:

    This routine looks up the Need Ea count for the file.  The value is the
    in the ea header for the file.

Arguments:

    Vcb - Vcb for the volume containing the Eas.

    Dirent - Supplies a pointer to the dirent for the file in question.

    NeedEaCount - Supplies the address to store the Need Ea count.

Return Value:

    None

--*/

{
    PBCB EaBcb;
    BOOLEAN LockedEaFcb;
    EA_RANGE EaSetRange;

    DebugTrace(+1, Dbg, "RxGetNeedEaCount ...\n", 0);

    //
    //  If the handle is 0 then the Need Ea count is 0.
    //

    if (Dirent->ExtendedAttributes == 0) {

        *NeedEaCount = 0;
        DebugTrace(-1, Dbg, "RxGetNeedEaCount -> %08lx\n", TRUE);
        return;
    }

    RtlZeroMemory( &EaSetRange, sizeof( EA_RANGE ));

    //
    //  Use a try to facilitate cleanup.
    //

    try {

        PDIRENT EaDirent;
        OEM_STRING ThisFilename;
        UCHAR Buffer[12];
        PEA_SET_HEADER EaSetHeader;

        //
        //  Initial the local values.
        //

        EaBcb = NULL;
        LockedEaFcb = FALSE;

        //
        //  Try to get the Ea file object.  Return FALSE on failure.
        //

        RxGetEaFile( RxContext,
                      Vcb,
                      &EaDirent,
                      &EaBcb,
                      FALSE,
                      FALSE );

        LockedEaFcb = TRUE;

        //
        //  If we didn't get the file because it doesn't exist, then the
        //  disk is corrupted.
        //

        if (Vcb->VirtualEaFile == NULL) {

            DebugTrace(0, Dbg, "RxGetNeedEaCount:  Ea file doesn't exist\n", 0);
            RxRaiseStatus( RxContext, RxStatus(NO_EAS_ON_FILE) );
        }

        //
        //  Try to pin down the Ea set header for the index in the
        //  dirent.  If the operation doesn't complete, return FALSE
        //  from this routine.
        //

        ThisFilename.Buffer = Buffer;
        Rx8dot3ToString( RxContext, Dirent, FALSE, &ThisFilename );

        RxReadEaSet( RxContext,
                      Vcb,
                      Dirent->ExtendedAttributes,
                      &ThisFilename,
                      FALSE,
                      &EaSetRange );

        EaSetHeader = (PEA_SET_HEADER) EaSetRange.Data;

        //
        //  We now have the Ea set header for this file.  We simply copy
        //  the Need Ea field.
        //

        *NeedEaCount = EaSetHeader->NeedEaCount;

    } finally {

        DebugUnwind( RxGetNeedEaCount );

        //
        //  Unpin the EaDirent and the EaSetHeader if pinned.
        //

        RxUnpinBcb( RxContext, EaBcb );

        RxUnpinEaRange( RxContext, &EaSetRange );

        //
        //  Release the Fcb for the Ea file if locked.
        //

        if (LockedEaFcb) {

            RxReleaseFcb( RxContext, Vcb->EaFcb );
        }

        DebugTrace(-1, Dbg, "RxGetNeedEaCount:  NeedEaCount -> %08lx\n", *NeedEaCount);
    }

    return;
}


VOID
RxCreateEa (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN PUCHAR Buffer,
    IN ULONG Length,
    IN POEM_STRING FileName,
    OUT PUSHORT EaHandle
    )

/*++

Routine Description:

    This routine adds an entire ea set to the Ea file.  The owning file
    is specified in 'FileName'.  This is used to replace the Ea set attached
    to an existing file during a supersede operation.

    NOTE: This routine may block, it should not be called unless the
    thread is waitable.

Arguments:

    Vcb - Supplies the Vcb for the volume.

    Buffer - Buffer with the Ea list to add.

    Length - Length of the buffer.

    FileName - The Ea's will be attached to this file.

    EaHandle - The new ea handle will be assigned to this address.

Return Value:

    None

--*/

{
    RxCaptureRequestPacket;
    PBCB EaBcb;
    BOOLEAN LockedEaFcb;

    PEA_SET_HEADER EaSetHeader;
    EA_RANGE EaSetRange;

    DebugTrace(+1, Dbg, "RxCreateEa...\n", 0);

    EaBcb = NULL;
    LockedEaFcb = FALSE;

    RtlZeroMemory( &EaSetRange, sizeof( EA_RANGE ));

    //
    //  Use 'try' to facilitate cleanup.
    //

    try {

        PDIRENT EaDirent;

        PPACKED_EA FirstPackedEa;
        ULONG PackedEasLength;
        ULONG AllocationLength;
        ULONG BytesPerCluster;

        PFILE_FULL_EA_INFORMATION FullEa;

        //
        //  We will allocate a buffer and copy the Ea list from the user's
        //  buffer to a RDBSS packed Ea list.  Initial allocation is one
        //  cluster, our starting offset into the packed Ea list is 0.
        //

        PackedEasLength = 0;

        BytesPerCluster = 1 << Vcb->AllocationSupport.LogOfBytesPerCluster;

        AllocationLength = (PackedEasLength
                            + SIZE_OF_EA_SET_HEADER
                            + BytesPerCluster - 1)
                           & ~(BytesPerCluster - 1);

        //
        //  Allocate the memory and store the file name into it.
        //

        EaSetHeader = FsRtlAllocatePool( PagedPool, AllocationLength );

        RtlZeroMemory( EaSetHeader, AllocationLength );

        RtlCopyMemory( EaSetHeader->OwnerFileName,
                       FileName->Buffer,
                       FileName->Length );

        AllocationLength -= SIZE_OF_EA_SET_HEADER;

        FirstPackedEa = (PPACKED_EA) EaSetHeader->PackedEas;

        //
        //  Loop through the user's Ea list.  Catch any error for invalid
        //  name or non-existent Ea value.
        //

        for ( FullEa = (PFILE_FULL_EA_INFORMATION) Buffer;
              FullEa < (PFILE_FULL_EA_INFORMATION) &Buffer[Length];
              FullEa = (PFILE_FULL_EA_INFORMATION) (FullEa->NextEntryOffset == 0 ?
                                   &Buffer[Length] :
                                   (PUCHAR) FullEa + FullEa->NextEntryOffset)) {

            OEM_STRING EaName;
            ULONG EaOffset;

            EaName.Length = FullEa->EaNameLength;
            EaName.Buffer = &FullEa->EaName[0];

            //
            //  Make sure the ea name is valid
            //

            if (!RxIsEaNameValid( RxContext, EaName )) {

                DebugTrace(0, Dbg,
                           "RxCreateEa:  Invalid Ea Name -> %wZ\n",
                           EaName);

                capReqPacket->IoStatus.Information = (PUCHAR)FullEa - Buffer;
                capReqPacket->IoStatus.Status = RxStatus(INVALID_EA_NAME);
                RxRaiseStatus( RxContext, RxStatus(INVALID_EA_NAME) );
            }

            //
            //  Check that no invalid ea flags are set.
            //

            //
            //  TEMPCODE  We are returning RxStatus(INVALID_EA_NAME)
            //  until a more appropriate error code exists.
            //

            if (FullEa->Flags != 0
                && FullEa->Flags != FILE_NEED_EA) {

                capReqPacket->IoStatus.Information = (PUCHAR)FullEa - Buffer;
                capReqPacket->IoStatus.Status = RxStatus(INVALID_EA_NAME);
                RxRaiseStatus( RxContext, RxStatus(INVALID_EA_NAME) );
            }

            //
            //  If this is a duplicate name then delete the current ea
            //  value.
            //

            if (RxLocateEaByName( RxContext,
                                   FirstPackedEa,
                                   PackedEasLength,
                                   &EaName,
                                   &EaOffset )) {

                DebugTrace(0, Dbg, "RxCreateEa:  Duplicate name found\n", 0);

                RxDeletePackedEa( RxContext,
                                   EaSetHeader,
                                   &PackedEasLength,
                                   EaOffset );
            }

            //
            //  We ignore this value if the eavalue length is zero.
            //

            if (FullEa->EaValueLength == 0) {

                DebugTrace(0, Dbg,
                           "RxCreateEa:  Empty ea\n",
                           0);

                continue;
            }

            RxAppendPackedEa( RxContext,
                               &EaSetHeader,
                               &PackedEasLength,
                               &AllocationLength,
                               FullEa,
                               BytesPerCluster );
        }

        //
        //  If the resulting length isn't zero, then allocate a RDBSS cluster
        //  to store the data.
        //

        if (PackedEasLength != 0) {

            PEA_SET_HEADER NewEaSetHeader;

            //
            //  If the packed eas length (plus 4 bytes) is greater
            //  than the maximum allowed ea size, we return an error.
            //

            if (PackedEasLength + 4 > MAXIMUM_EA_SIZE) {

                DebugTrace( 0, Dbg, "Ea length is greater than maximum\n", 0 );

                RxRaiseStatus( RxContext, RxStatus(EA_TOO_LARGE) );
            }

            //
            //  Get the Ea file.
            //

            RxGetEaFile( RxContext,
                          Vcb,
                          &EaDirent,
                          &EaBcb,
                          TRUE,
                          TRUE );

            LockedEaFcb = TRUE;

            RxAddEaSet( RxContext,
                         Vcb,
                         PackedEasLength + SIZE_OF_EA_SET_HEADER,
                         EaBcb,
                         EaDirent,
                         EaHandle,
                         &EaSetRange );

            NewEaSetHeader = (PEA_SET_HEADER) EaSetRange.Data;

            //
            //  Store the length of the new Ea's into the NewEaSetHeader.
            //  This is the PackedEasLength + 4.
            //

            PackedEasLength += 4;

            CopyU4char( EaSetHeader->cbList, &PackedEasLength );

            //
            //  Copy all but the first four bytes of EaSetHeader into
            //  the new ea.  The signature and index fields have
            //  already been filled in.
            //

            RtlCopyMemory( &NewEaSetHeader->NeedEaCount,
                           &EaSetHeader->NeedEaCount,
                           PackedEasLength + SIZE_OF_EA_SET_HEADER - 8 );

            RxMarkEaRangeDirty( RxContext, Vcb->VirtualEaFile, &EaSetRange );
            RxUnpinEaRange( RxContext, &EaSetRange );

            CcFlushCache( Vcb->VirtualEaFile->SectionObjectPointer, NULL, 0, NULL );

        //
        //  There was no data added to the Ea file.  Return a handle
        //  of 0.
        //

        } else {

            *EaHandle = 0;
        }

    } finally {

        DebugUnwind( RxCreateEa );

        //
        //  Deallocate the EaSetHeader if present.
        //

        if (EaSetHeader) {

            ExFreePool( EaSetHeader );
        }

        //
        //  Release the EaFcb if held.
        //

        if (LockedEaFcb) {

            RxReleaseFcb( RxContext, Vcb->EaFcb );
        }

        //
        //  Unpin the dirents for the EaFcb and EaSetFcb if necessary.
        //

        RxUnpinBcb( RxContext, EaBcb );
        RxUnpinEaRange( RxContext, &EaSetRange );

        DebugTrace(-1, Dbg, "RxCreateEa -> Exit\n", 0);
    }

    return;
}

VOID
RxDeleteEa (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN USHORT EaHandle,
    IN POEM_STRING FileName
    )

/*++

Routine Description:

    This routine is called to remove an entire ea set.  Most of the work
    is done in the call to 'RxDeleteEaSet'.  This routine opens the
    Ea file and then calls the support routine.

    NOTE: This routine may block, it should not be called unless the
    thread is waitable.

Arguments:

    Vcb - Vcb for the volume

    EaHandle - The handle for the Ea's to remove.  This handle will be
               verified during this operation.

    FileName - The name of the file whose Ea's are being removed.  This
               name is compared against the Ea owner's name in the Ea set.

Return Value:

    None.

--*/

{
    PBCB EaBcb;
    BOOLEAN LockedEaFcb;

    DebugTrace(+1, Dbg, "RxDeleteEa...\n", 0);

    //
    //  Initialize local values.
    //

    EaBcb = NULL;
    LockedEaFcb = FALSE;

    //
    //  Use a try statement to facilitate cleanup.
    //

    try {

        PDIRENT EaDirent;

        //
        //  Get the Ea stream file.  If the file doesn't exist on the disk
        //  then the disk has been corrupted.
        //

        RxGetEaFile( RxContext,
                      Vcb,
                      &EaDirent,
                      &EaBcb,
                      FALSE,
                      TRUE );

        LockedEaFcb = TRUE;

        //
        //  If we didn't get the Ea file, then the disk is corrupt.
        //

        if ( EaBcb == NULL ) {


            DebugTrace(0, Dbg,
                       "RxDeleteEa:  No Ea file exists\n",
                       0);

            RxRaiseStatus( RxContext, RxStatus(NO_EAS_ON_FILE) );
        }

        //
        //  We now have everything we need to delete the ea set.  Call the
        //  support routine to do this.
        //

        RxDeleteEaSet( RxContext,
                        Vcb,
                        EaBcb,
                        EaDirent,
                        EaHandle,
                        FileName );

        CcFlushCache( Vcb->VirtualEaFile->SectionObjectPointer, NULL, 0, NULL );

    } finally {

        DebugUnwind( RxDeleteEa );

        //
        //  Release the EaFcb if held.
        //

        if (LockedEaFcb) {

            RxReleaseFcb( RxContext, Vcb->EaFcb );
        }

        //
        //  Unpin the dirent for the Ea file if pinned.
        //

        RxUnpinBcb( RxContext, EaBcb );

        DebugTrace(-1, Dbg, "RxDeleteEa -> Exit\n", 0);
    }

    return;
}


VOID
RxGetEaFile (
    IN PRX_CONTEXT RxContext,
    IN OUT PVCB Vcb,
    OUT PDIRENT *EaDirent,
    OUT PBCB *EaBcb,
    IN BOOLEAN CreateFile,
    IN BOOLEAN ExclusiveFcb
    )

/*++

Routine Description:

    This routine is used to completely initialize the Vcb and
    the Ea file for the Vcb.

    If the Vcb doesn't have the Ea file object, then we first try to
    lookup the Ea data file in the root directory and if that fails
    we try to create the file.  The 'CreateFile' flag is used to check
    whether it is necessary to create the Ea file.

    This routine will lock down the Fcb for exclusive or shared access before
    performing any operations.  If the operation does not complete due
    to blocking, exclusive or shared access will be given up before returning.

    If we are creating the Ea file and marking sections of it dirty,
    we can't use the repin feature through the cache map.  In that case
    we use a local RxContext and then unpin all of the Bcb's before
    continuing.

    Note: If this routine will be creating the Ea file, we are guaranteed
    to be waitable.

Arguments:

    Vcb - Vcb for the volume

    EaDirent - Location to store the address of the pinned dirent for the
               Ea file.

    EaBcb - Location to store the address of the Bcb for the pinned dirent.

    CreateFile - Boolean indicating whether we should create the Ea file
                 on the disk.

    ExclusiveFcb - Indicates whether shared or exclusive access is desired
                   for the EaFcb.

Return Value:

    None.

--*/

{
    PFILE_OBJECT EaStreamFile;
    EA_RANGE EaFileRange;

    BOOLEAN UnwindLockedEaFcb;
    BOOLEAN UnwindAllocatedDiskSpace;
    BOOLEAN UnwindEaDirentCreated;
    BOOLEAN UnwindUpdatedSizes;

    DebugTrace(+1, Dbg, "RxGetEaFile ...\n", 0);

    EaStreamFile = NULL;

    UnwindLockedEaFcb = FALSE;
    UnwindAllocatedDiskSpace = FALSE;
    UnwindEaDirentCreated = FALSE;
    UnwindUpdatedSizes = FALSE;

    RtlZeroMemory( &EaFileRange, sizeof( EA_RANGE ));

    //
    //  Use a try to facilitate cleanup
    //

    try {

        OEM_STRING EaFileName;
        LARGE_INTEGER SectionSize;

        //
        //  Check if the Vcb already has the file object.  If it doesn't, then
        //  we need to search the root directory for the Ea data file.
        //

        if (Vcb->VirtualEaFile == NULL) {

            //
            //  Always lock the Ea file exclusively if we have to create the file.
            //

            if ( !RxAcquireExclusiveFcb( RxContext, Vcb->EaFcb )) {

                DebugTrace(0, Dbg, "RxGetEaFile:  Can't grab exclusive\n", 0);
                RxRaiseStatus( RxContext, RxStatus(CANT_WAIT) );
            }

            UnwindLockedEaFcb = TRUE;

        //
        //  Otherwise we acquire the Fcb as the caller requested.
        //

        } else {

            if ((ExclusiveFcb && !RxAcquireExclusiveFcb( RxContext, Vcb->EaFcb ))
                || (!ExclusiveFcb && !RxAcquireSharedFcb( RxContext, Vcb->EaFcb))) {

                DebugTrace(0, Dbg, "RxGetEaFile:  Can't grab EaFcb\n", 0);

                RxRaiseStatus( RxContext, RxStatus(CANT_WAIT) );
            }

            UnwindLockedEaFcb = TRUE;

            //
            //  If the file now does not exist we need to release the Fcb and
            //  reacquire exclusive if we acquired shared.
            //

            if ((Vcb->VirtualEaFile == NULL) && !ExclusiveFcb) {

                RxReleaseFcb( RxContext, Vcb->EaFcb );
                UnwindLockedEaFcb = FALSE;

                if (!RxAcquireExclusiveFcb( RxContext, Vcb->EaFcb )) {

                    DebugTrace(0, Dbg, "RxGetEaFile:  Can't grab EaFcb\n", 0);

                    RxRaiseStatus( RxContext, RxStatus(CANT_WAIT) );
                }

                UnwindLockedEaFcb = TRUE;
            }
        }

        //
        //  If the file object is now there we only need to get the
        //  dirent for the Ea file.
        //

        if (Vcb->VirtualEaFile != NULL) {

            RxVerifyFcb( RxContext, Vcb->EaFcb );

            RxGetDirentFromFcbOrDcb( RxContext,
                                      Vcb->EaFcb,
                                      EaDirent,
                                      EaBcb );

            try_return( NOTHING );

        } else {

            VBO ByteOffset;

            //
            //  Always mark the ea fcb as good.
            //

            Vcb->EaFcb->FcbCondition = FcbGood;

            //
            //  We try to lookup the dirent for the Ea Fcb.
            //

            EaFileName.Buffer = "EA DATA. SF";
            EaFileName.Length = 11;
            EaFileName.MaximumLength = 12;

            RxLocateSimpleOemDirent( RxContext,
                                      Vcb->EaFcb->ParentDcb,
                                      &EaFileName,
                                      EaDirent,
                                      EaBcb,
                                      &ByteOffset );

            //
            //  If the file exists, we need to create the virtual file
            //  object for it.
            //

            if (*EaDirent != NULL) {

                //
                //  Since we may be modifying the dirent, pin the data now.
                //

                RxPinMappedData( RxContext,
                                  Vcb->EaFcb->ParentDcb,
                                  ByteOffset,
                                  sizeof(DIRENT),
                                  EaBcb );

                //
                //  Update the Fcb with information on the file size
                //  and disk location.  Also increment the open/unclean
                //  counts in the EaFcb and the open count in the
                //  Vcb.
                //

                Vcb->EaFcb->FirstClusterOfFile = (*EaDirent)->FirstClusterOfFile;
                Vcb->EaFcb->DirentOffsetWithinDirectory = ByteOffset;

                //
                //  Find the allocation size.  The purpose here is
                //  really to completely fill in the Mcb for the
                //  file.
                //

                Vcb->EaFcb->Header.AllocationSize.QuadPart = -1;

                RxLookupFileAllocationSize( RxContext, Vcb->EaFcb );

                //
                //  Start by computing the section size for the cache
                //  manager.
                //

                SectionSize.QuadPart = (*EaDirent)->FileSize;
                Vcb->EaFcb->Header.AllocationSize = SectionSize;
                Vcb->EaFcb->Header.FileSize = SectionSize;

                //
                //  Create and initialize the file object for the
                //  Ea virtual file.
                //

                EaStreamFile = RxOpenEaFile( RxContext, Vcb->EaFcb );

                Vcb->VirtualEaFile = EaStreamFile;

            //
            //  Else there was no dirent.  If we were instructed to
            //  create the file object, we will try to create the dirent,
            //  allocate disk space, initialize the Ea file header and
            //  return this information to the user.
            //

            } else if (CreateFile) {

                ULONG BytesPerSector;
                ULONG BytesPerCluster;
                ULONG OffsetTableSize;
                ULONG AllocationSize;
                PEA_FILE_HEADER FileHeader;
                USHORT AllocatedClusters;
                PUSHORT CurrentIndex;
                ULONG Index;
                RXSTATUS Status;

                DebugTrace(0, Dbg, "RxGetEaFile:  Creating local RxContext\n", 0);

                BytesPerSector = Vcb->Bpb.BytesPerSector;
                BytesPerCluster = 1 << Vcb->AllocationSupport.LogOfBytesPerCluster;

                AllocationSize = ((BytesPerSector << 1) + BytesPerCluster - 1)
                                 & ~(BytesPerCluster - 1);

                AllocatedClusters = (USHORT) (AllocationSize
                                    >> Vcb->AllocationSupport.LogOfBytesPerCluster);

                OffsetTableSize = AllocationSize - BytesPerSector;

                //
                //  Allocate disk space, the space allocated is 2 sectors
                //  rounded up to the nearest cluster size.
                //

                RxAllocateDiskSpace( RxContext,
                                      Vcb,
                                      0,
                                      &AllocationSize,
                                      &Vcb->EaFcb->Mcb );

                UnwindAllocatedDiskSpace = TRUE;

                //
                //  Allocate and initialize a dirent in the root directory
                //  to describe this new file.
                //

                Vcb->EaFcb->DirentOffsetWithinDirectory =
                    RxCreateNewDirent( RxContext,
                                        Vcb->EaFcb->ParentDcb,
                                        1 );

                RxPrepareWriteDirectoryFile( RxContext,
                                              Vcb->EaFcb->ParentDcb,
                                              Vcb->EaFcb->DirentOffsetWithinDirectory,
                                              sizeof(DIRENT),
                                              EaBcb,
                                              EaDirent,
                                              FALSE,
                                              &Status );

                ASSERT( NT_SUCCESS( Status ));

                UnwindEaDirentCreated = TRUE;

                RxConstructDirent( RxContext,
                                    *EaDirent,
                                    &EaFileName,
                                    FALSE,
                                    FALSE,
                                    NULL,
                                    RDBSS_DIRENT_ATTR_READ_ONLY
                                    | RDBSS_DIRENT_ATTR_HIDDEN
                                    | RDBSS_DIRENT_ATTR_SYSTEM
                                    | RDBSS_DIRENT_ATTR_ARCHIVE,
                                    TRUE );

                (*EaDirent)->FileSize = AllocationSize;

                RxSetDirtyBcb( RxContext, *EaBcb, Vcb );

                //
                //  Initialize the Fcb for this file and initialize the
                //  cache map as well.
                //

                //
                //  Start by computing the section size for the cache
                //  manager.
                //

                SectionSize.QuadPart = (*EaDirent)->FileSize;
                Vcb->EaFcb->Header.AllocationSize = SectionSize;
                Vcb->EaFcb->Header.FileSize = SectionSize;
                UnwindUpdatedSizes = TRUE;

                //
                //  Create and initialize the file object for the
                //  Ea virtual file.
                //

                EaStreamFile = RxOpenEaFile( RxContext, Vcb->EaFcb );

                //
                //  Update the Fcb with information on the file size
                //  and disk location.  Also increment the open/unclean
                //  counts in the EaFcb and the open count in the
                //  Vcb.
                //

                {
                    LBO FirstLboOfFile;

                    FsRtlLookupMcbEntry( &Vcb->EaFcb->Mcb,
                                         0,
                                         &FirstLboOfFile,
                                         NULL,
                                         NULL );

                    (*EaDirent)->FirstClusterOfFile =
                            (RDBSS_ENTRY) RxGetIndexFromLbo( Vcb, FirstLboOfFile );
                }

                RxSetDirtyBcb( RxContext, *EaBcb, Vcb );

                Vcb->EaFcb->FirstClusterOfFile = (*EaDirent)->FirstClusterOfFile;

                //
                //  Initialize the Ea file header and mark the Bcb as dirty.
                //

                RxPinEaRange( RxContext,
                               EaStreamFile,
                               Vcb->EaFcb,
                               &EaFileRange,
                               0,
                               AllocationSize,
                               RxStatus(DATA_ERROR) );

                FileHeader = (PEA_FILE_HEADER) EaFileRange.Data;

                RtlZeroMemory( FileHeader, AllocationSize );
                FileHeader->Signature = EA_FILE_SIGNATURE;

                for (Index = MAX_EA_BASE_INDEX, CurrentIndex = FileHeader->EaBaseTable;
                     Index;
                     Index--, CurrentIndex++) {

                    *CurrentIndex = AllocatedClusters;
                }

                //
                //  Initialize the offset table with the offset set to
                //  after the just allocated clusters.
                //

                for (Index = OffsetTableSize >> 1,
                        CurrentIndex = (PUSHORT) ((PUCHAR) FileHeader + sizeof( EA_FILE_HEADER ));
                     Index;
                     Index--, CurrentIndex++) {

                    *CurrentIndex = UNUSED_EA_HANDLE;
                }

                //
                //  Unpin the file header and offset table.
                //

                RxMarkEaRangeDirty( RxContext, EaStreamFile, &EaFileRange );
                RxUnpinEaRange( RxContext, &EaFileRange );

                CcFlushCache( EaStreamFile->SectionObjectPointer, NULL, 0, NULL );

                //
                //  Return the Ea file object to the user.
                //

                Vcb->VirtualEaFile = EaStreamFile;
            }
        }
    try_exit:  NOTHING;
    } finally {

        DebugUnwind( RxGetEaFile );

        //
        //  If this is abnormal termination and disk space has been
        //  allocated.  We deallocate it now.
        //

        if (AbnormalTermination()) {

            //
            //  Deallocate the Ea file
            //

            if (UnwindAllocatedDiskSpace) {

                RxDeallocateDiskSpace( RxContext,
                                        Vcb,
                                        &Vcb->EaFcb->Mcb );
            }

            //
            //  Delete the dirent for the Ea file, if created.
            //

            if (UnwindEaDirentCreated) {

                if (UnwindUpdatedSizes) {

                    Vcb->EaFcb->Header.AllocationSize = RxLargeZero;
                    Vcb->EaFcb->Header.FileSize = RxLargeZero;
                }

                RxUnpinBcb( RxContext, EaBcb );
                RxDeleteDirent( RxContext, Vcb->EaFcb, NULL, TRUE );
            }

            //
            //  Release the Fcb if held
            //

            if (UnwindLockedEaFcb) {

                RxReleaseFcb( RxContext, Vcb->EaFcb );
            }

            //
            //  Dereference the Ea stream file if created.
            //

            if (EaStreamFile != NULL) {

                ObDereferenceObject( EaStreamFile );
            }
        }

        //
        //  If the Ea file header is locked down.  We unpin it now.
        //

        RxUnpinEaRange( RxContext, &EaFileRange );

        DebugTrace(-1, Dbg, "RxGetEaFile:  Exit\n", 0);
    }

    return;
}


VOID
RxReadEaSet (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN USHORT EaHandle,
    IN POEM_STRING FileName,
    IN BOOLEAN ReturnEntireSet,
    OUT PEA_RANGE EaSetRange
    )

/*++

Routine Description:

    This routine pins the Ea set for the given ea handle within the
    Ea stream file.  The EaHandle, after first comparing against valid
    index values, is used to compute the cluster offset for this
    this Ea set.  The Ea set is then verified as belonging to this
    index and lying within the Ea data file.

    The caller of this function will have verified that the Ea file
    exists and that the Vcb field points to an initialized cache file.
    The caller will already have gained exclusive access to the
    EaFcb.

Arguments:

    Vcb - Supplies the Vcb for the volume.

    EaHandle - Supplies the handle for the Ea's to read.

    FileName - Name of the file whose Ea's are being read.

    ReturnEntireSet - Indicates if the caller needs the entire set
        as opposed to just the header.

    EaSetRange - Pointer to the EaRange structure which will describe the Ea
        on return.

Return Value:

    None

--*/

{
    ULONG BytesPerCluster = 1 << Vcb->AllocationSupport.LogOfBytesPerCluster;

    ULONG EaOffsetVbo;
    EA_RANGE EaOffsetRange;
    USHORT EaOffsetCluster;

    EA_RANGE EaHeaderRange;
    PEA_FILE_HEADER EaHeader;

    ULONG EaSetVbo;
    PEA_SET_HEADER EaSet;

    ULONG CbList;

    DebugTrace(+1, Dbg, "RxReadEaSet\n", 0);
    DebugTrace( 0, Dbg, "  Vcb      = %8lx\n", Vcb);

    //
    //  Verify that the Ea index has a legal value.  Raise status
    //  RxStatus(NONEXISTENT_EA_ENTRY) if illegal.
    //

    if (EaHandle < MIN_EA_HANDLE
        || EaHandle > MAX_EA_HANDLE) {

        DebugTrace(-1, Dbg, "RxReadEaSet: Illegal handle value\n", 0);
        RxRaiseStatus( RxContext, RxStatus(NONEXISTENT_EA_ENTRY) );
    }

    //
    //  Verify that the virtual Ea file is large enough for us to read
    //  the EaOffet table for this index.
    //

    EaOffsetVbo = Vcb->Bpb.BytesPerSector + ((EaHandle >> 7) << 8);

    //
    //  Zero the Ea range structures.
    //

    RtlZeroMemory( &EaHeaderRange, sizeof( EA_RANGE ));
    RtlZeroMemory( &EaOffsetRange, sizeof( EA_RANGE ));

    //
    //  Use a try statement to clean up on exit.
    //

    try {

        //
        //  Pin down the EA file header.
        //

        RxPinEaRange( RxContext,
                       Vcb->VirtualEaFile,
                       Vcb->EaFcb,
                       &EaHeaderRange,
                       0,
                       sizeof( EA_FILE_HEADER ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        EaHeader = (PEA_FILE_HEADER) EaHeaderRange.Data;

        //
        //  Pin down the Ea offset table for the particular index.
        //

        RxPinEaRange( RxContext,
                       Vcb->VirtualEaFile,
                       Vcb->EaFcb,
                       &EaOffsetRange,
                       EaOffsetVbo,
                       sizeof( EA_OFF_TABLE ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        //
        //  Check if the specifific handle is currently being used.
        //

        EaOffsetCluster = *((PUSHORT) EaOffsetRange.Data
                            + (EaHandle & (MAX_EA_OFFSET_INDEX - 1)));

        if (EaOffsetCluster == UNUSED_EA_HANDLE) {

            DebugTrace(0, Dbg, "RxReadEaSet: Ea handle is unused\n", 0);
            RxRaiseStatus( RxContext, RxStatus(NONEXISTENT_EA_ENTRY) );
        }

        //
        //  Compute the file offset for the Ea data.
        //

        EaSetVbo = (EaHeader->EaBaseTable[EaHandle >> 7] + EaOffsetCluster)
                   << Vcb->AllocationSupport.LogOfBytesPerCluster;

        //
        //  Unpin the file header and offset table.
        //

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );

        //
        //  Pin the ea set.
        //

        RxPinEaRange( RxContext,
                       Vcb->VirtualEaFile,
                       Vcb->EaFcb,
                       EaSetRange,
                       EaSetVbo,
                       BytesPerCluster,
                       RxStatus(DATA_ERROR) );

        //
        //  Verify that the Ea set is valid and belongs to this index.
        //  Raise RxStatus(DATA_ERROR) if there is a data conflict.
        //

        EaSet = (PEA_SET_HEADER) EaSetRange->Data;

        if (EaSet->Signature != EA_SET_SIGNATURE
            || EaSet->OwnEaHandle != EaHandle ) {

            DebugTrace(0, Dbg, "RxReadEaSet: Ea set header is corrupt\n", 0);
            RxRaiseStatus( RxContext, RxStatus(DATA_ERROR) );
        }

        //
        //  At this point we have pinned a single cluster of Ea data.  If
        //  this represents the entire Ea data for the Ea index, we are
        //  done.  Otherwise we need to check on the entire size of
        //  of the Ea set header and whether it is contained in the allocated
        //  size of the Ea virtual file.  At that point we can unpin
        //  the partial Ea set header and repin the entire header.
        //

        CbList = GetcbList( EaSet );

        if (ReturnEntireSet
            && CbList > BytesPerCluster ) {

            //
            //  Round up to the cluster size.
            //

            CbList = (CbList + EA_CBLIST_OFFSET + BytesPerCluster - 1)
                     & ~(BytesPerCluster - 1);

            RxUnpinEaRange( RxContext, EaSetRange );

            RtlZeroMemory( EaSetRange, sizeof( PEA_RANGE ));

            RxPinEaRange( RxContext,
                           Vcb->VirtualEaFile,
                           Vcb->EaFcb,
                           EaSetRange,
                           EaSetVbo,
                           CbList,
                           RxStatus(DATA_ERROR) );
        }

    } finally {

        DebugUnwind( RxReadEaSet );

        //
        //  Unpin the Ea base and offset tables if locked down.
        //

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );

        DebugTrace(-1, Dbg, "RxReadEaSet:  Exit\n", 0);
    }

    return;
}


VOID
RxDeleteEaSet (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN PBCB EaBcb,
    OUT PDIRENT EaDirent,
    IN USHORT EaHandle,
    IN POEM_STRING FileName
    )

/*++

Routine Description:

    This routines clips the Ea set for a particular index out of the
    Ea file for a volume.  The index is verified as belonging to a valid
    handle.  The clusters are removed and the Ea stream file along with
    the Ea base and offset files are updated.

    The caller of this function will have verified that the Ea file
    exists and that the Vcb field points to an initialized cache file.
    The caller will already have gained exclusive access to the
    EaFcb.

Arguments:

    Vcb - Supplies the Vcb for the volume.

    VirtualEeFile - Pointer to the file object for the virtual Ea file.

    EaFcb - Supplies the pointer to the Fcb for the Ea file.

    EaBcb - Supplies a pointer to the Bcb for the Ea dirent.

    EaDirent - Supplies a pointer to the dirent for the Ea file.

    EaHandle - Supplies the handle for the Ea's to read.

    FileName - Name of the file whose Ea's are being read.

Return Value:

    None.

--*/

{
    ULONG BytesPerCluster = 1 << Vcb->AllocationSupport.LogOfBytesPerCluster;
    ULONG CbList;
    LARGE_INTEGER FileOffset;

    MCB DataMcb;
    BOOLEAN UnwindInitializeDataMcb = FALSE;
    BOOLEAN UnwindSplitData = FALSE;

    MCB TailMcb;
    BOOLEAN UnwindInitializeTailMcb = FALSE;
    BOOLEAN UnwindSplitTail = FALSE;
    BOOLEAN UnwindMergeTail = FALSE;

    BOOLEAN UnwindModifiedEaHeader = FALSE;
    BOOLEAN UnwindPrevFcbValues = FALSE;
    ULONG UnwindPrevFileSize;

    ULONG EaOffsetVbo;
    USHORT EaOffsetIndex;
    EA_RANGE EaOffsetRange;
    USHORT EaOffsetCluster;

    PFILE_OBJECT VirtualEaFile = Vcb->VirtualEaFile;
    PFCB EaFcb = Vcb->EaFcb;

    EA_RANGE EaHeaderRange;
    PEA_FILE_HEADER EaHeader;
    USHORT EaHeaderBaseIndex;

    ULONG EaSetVbo;
    ULONG EaSetLength;
    EA_RANGE EaSetRange;
    PEA_SET_HEADER EaSet;
    USHORT EaSetClusterCount;

    //
    //  Verify that the Ea index has a legal value.  Raise status
    //  RxStatus(INVALID_HANDLE) if illegal.
    //

    if (EaHandle < MIN_EA_HANDLE
        || EaHandle > MAX_EA_HANDLE) {

        DebugTrace(-1, Dbg, "RxDeleteEaSet: Illegal handle value\n", 0);
        RxRaiseStatus( RxContext, RxStatus(NONEXISTENT_EA_ENTRY) );
    }

    //
    //  Verify that the virtual Ea file is large enough for us to read
    //  the EaOffet table for this index.
    //

    EaOffsetVbo = Vcb->Bpb.BytesPerSector + ((EaHandle >> 7) << 8);

    //
    //  Zero the Ea range structures.
    //

    RtlZeroMemory( &EaHeaderRange, sizeof( EA_RANGE ));
    RtlZeroMemory( &EaOffsetRange, sizeof( EA_RANGE ));
    RtlZeroMemory( &EaSetRange, sizeof( EA_RANGE ));

    //
    //  Use a try to facilitate cleanup.
    //

    try {

        //
        //  Pin down the EA file header.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaHeaderRange,
                       0,
                       sizeof( EA_FILE_HEADER ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        EaHeader = (PEA_FILE_HEADER) EaHeaderRange.Data;

        //
        //  Pin down the Ea offset table for the particular index.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaOffsetRange,
                       EaOffsetVbo,
                       sizeof( EA_OFF_TABLE ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        //
        //  Check if the specifific handle is currently being used.
        //

        EaOffsetIndex = EaHandle & (MAX_EA_OFFSET_INDEX - 1);
        EaOffsetCluster = *((PUSHORT) EaOffsetRange.Data + EaOffsetIndex);

        if (EaOffsetCluster == UNUSED_EA_HANDLE) {

            DebugTrace(0, Dbg, "RxReadEaSet: Ea handle is unused\n", 0);
            RxRaiseStatus( RxContext, RxStatus(NONEXISTENT_EA_ENTRY) );
        }

        //
        //  Compute the file offset for the Ea data.
        //

        EaHeaderBaseIndex = EaHandle >> 7;
        EaSetVbo = (EaHeader->EaBaseTable[EaHeaderBaseIndex] + EaOffsetCluster)
                   << Vcb->AllocationSupport.LogOfBytesPerCluster;

        //
        //  Unpin the file header and offset table.
        //

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );

        //
        //  Try to pin the requested Ea set.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaSetRange,
                       EaSetVbo,
                       BytesPerCluster,
                       RxStatus(DATA_ERROR) );

        EaSet = (PEA_SET_HEADER) EaSetRange.Data;

        if (EaSet->Signature != EA_SET_SIGNATURE
            || EaSet->OwnEaHandle != EaHandle ) {

            DebugTrace(0, Dbg, "RxReadEaSet: Ea set header is corrupt\n", 0);
            RxRaiseStatus( RxContext, RxStatus(DATA_ERROR) );
        }

        //
        //  At this point we have pinned a single cluster of Ea data.  If
        //  this represents the entire Ea data for the Ea index, we know
        //  the number of clusters to remove.  Otherwise we need to check
        //  on the entire size of the Ea set header and whether it is
        //  contained in the allocated size of the Ea virtual file.  At
        //  that point we unpin the partial Ea set header and remember the
        //  starting cluster offset and number of clusters in both cluster
        //  and Vbo formats.
        //
        //  At that point the following variables have the described
        //  values.
        //
        //      EaSetVbo - Vbo to start splice at.
        //      EaSetLength - Number of bytes to splice.
        //      EaSetClusterCount - Number of clusters to splice.
        //

        CbList = GetcbList( EaSet );

        EaSetClusterCount = (USHORT) ((CbList + EA_CBLIST_OFFSET + BytesPerCluster - 1)
                                      >> Vcb->AllocationSupport.LogOfBytesPerCluster);

        EaSetLength = EaSetClusterCount << Vcb->AllocationSupport.LogOfBytesPerCluster;

        if (EaSetLength > BytesPerCluster) {

            if (EaFcb->Header.FileSize.LowPart - EaSetVbo < EaSetLength) {

                DebugTrace(0, Dbg, "RxDeleteEaSet: Full Ea set not contained in file\n", 0);

                RxRaiseStatus( RxContext, RxStatus(DATA_ERROR) );
            }
        }

        RxUnpinEaRange( RxContext, &EaSetRange );

        //
        //  Update the cache manager for this file.  This is done by
        //  truncating to the point where the data was spliced and
        //  reinitializing with the modified size of the file.
        //
        //  NOTE: Even if the all the EA's are removed the Ea file will
        //  always exist and the header area will never shrink.
        //

        FileOffset.LowPart = EaSetVbo;
        FileOffset.HighPart = 0;

        //
        //  Round the cache map down to a system page boundary.
        //

        FileOffset.LowPart &= ~(PAGE_SIZE - 1);

        //
        //  Make sure all the data gets out to the disk.
        //

        {
            IO_STATUS_BLOCK Iosb;
            ULONG PurgeCount = 5;

            while (--PurgeCount) {

                Iosb.Status = RxStatus(SUCCESS);

                CcFlushCache( VirtualEaFile->SectionObjectPointer,
                              NULL,
                              0,
                              &Iosb );

                ASSERT( Iosb.Status == RxStatus(SUCCESS) );

                //
                //  We throw the unwanted pages out of the cache and then
                //  truncate the Ea File for the new size.
                //

                if (CcPurgeCacheSection( VirtualEaFile->SectionObjectPointer,
                                         &FileOffset,
                                         0,
                                         FALSE )) {

                    break;
                }
            }

            if (!PurgeCount) {

                RxRaiseStatus( RxContext, RxStatus(UNABLE_TO_DELETE_SECTION) );
            }
        }

        FileOffset.LowPart = EaFcb->Header.FileSize.LowPart - EaSetLength;

        //
        //  Perform the splice operation on the RDBSS chain.  This is done
        //  by splitting the target clusters out and merging the remaining
        //  clusters around them.  We can ignore the return value from
        //  the merge and splice functions because we are guaranteed
        //  to be able to block.
        //

        {
            FsRtlInitializeMcb( &DataMcb, PagedPool );

            UnwindInitializeDataMcb = TRUE;

            RxSplitAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                EaSetVbo,
                                &DataMcb );

            UnwindSplitData = TRUE;

            if (EaSetLength + EaSetVbo != EaFcb->Header.FileSize.LowPart) {

                FsRtlInitializeMcb( &TailMcb, PagedPool );

                UnwindInitializeTailMcb = TRUE;

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &DataMcb,
                                    EaSetLength,
                                    &TailMcb );

                UnwindSplitTail = TRUE;

                RxMergeAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    &TailMcb );

                UnwindMergeTail = TRUE;
            }
        }

        //
        //  Update the Fcb for the Ea file
        //

        UnwindPrevFileSize = EaFcb->Header.FileSize.LowPart;

        (VOID)ExAcquireResourceExclusive( EaFcb->Header.PagingIoResource,
                                          TRUE );

        EaFcb->Header.FileSize.LowPart = EaFcb->Header.FileSize.LowPart - EaSetLength;
        EaFcb->Header.AllocationSize = EaFcb->Header.FileSize;

        ExReleaseResource( EaFcb->Header.PagingIoResource );

        CcSetFileSizes( VirtualEaFile,
                        (PCC_FILE_SIZES)&EaFcb->Header.AllocationSize );

        EaDirent->FileSize = EaFcb->Header.FileSize.LowPart;

        RxSetDirtyBcb( RxContext, EaBcb, Vcb );

        UnwindPrevFcbValues = TRUE;

        //
        //  Update the Ea base and offset tables.  For the Ea base table,
        //  all subsequent index values must be decremented by the number
        //  of clusters removed.
        //
        //  For the entries in the relevant Ea offset table, all entries
        //  after this index must also be decreased by the number of
        //  clusters removed.
        //

        //
        //  Pin down the EA file header.
        //

        RtlZeroMemory( &EaHeaderRange,
                       sizeof( EA_RANGE ));

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaHeaderRange,
                       0,
                       sizeof( EA_FILE_HEADER ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        EaHeader = (PEA_FILE_HEADER) EaHeaderRange.Data;

        //
        //  Pin down the Ea offset table for the particular index.
        //

        RtlZeroMemory( &EaOffsetRange,
                       sizeof( EA_RANGE ));

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaOffsetRange,
                       EaOffsetVbo,
                       sizeof( EA_OFF_TABLE ),
                       RxStatus(NONEXISTENT_EA_ENTRY) );

        {
            ULONG Count;
            PUSHORT NextEaIndex;

            Count = MAX_EA_BASE_INDEX - EaHeaderBaseIndex - 1;

            NextEaIndex = &EaHeader->EaBaseTable[EaHeaderBaseIndex + 1];

            while (Count--) {

                *(NextEaIndex++) -= EaSetClusterCount;
            }

            RxMarkEaRangeDirty( RxContext, VirtualEaFile, &EaHeaderRange );

            Count = MAX_EA_OFFSET_INDEX - EaOffsetIndex - 1;
            NextEaIndex = (PUSHORT) EaOffsetRange.Data + EaOffsetIndex;

            *(NextEaIndex++) = UNUSED_EA_HANDLE;

            while (Count--) {

                if (*NextEaIndex != UNUSED_EA_HANDLE) {

                    *NextEaIndex -= EaSetClusterCount;
                }

                NextEaIndex++;
            }

            RxMarkEaRangeDirty( RxContext, VirtualEaFile, &EaOffsetRange );
        }

        UnwindModifiedEaHeader = TRUE;

        //
        //  Deallocate the ea set removed
        //

        RxDeallocateDiskSpace( RxContext,
                                Vcb,
                                &DataMcb );

    } finally {

        DebugUnwind( RxDeleteEaSet );

        //
        //  Restore file if abnormal termination.
        //
        //  If we have modified the ea file header we ignore this
        //  error.  Otherwise we walk through the state variables.
        //

        if (AbnormalTermination()
            && !UnwindModifiedEaHeader) {

            //
            //  If we modified the Ea dirent or Fcb, recover the previous
            //  values.
            //

            if (UnwindPrevFcbValues) {

                EaFcb->Header.FileSize.LowPart = UnwindPrevFileSize;
                EaFcb->Header.AllocationSize.LowPart = UnwindPrevFileSize;
                EaDirent->FileSize = UnwindPrevFileSize;

                CcSetFileSizes( VirtualEaFile,
                                (PCC_FILE_SIZES)&EaFcb->Header.AllocationSize );
            }

            //
            //  If we merged the tail with the
            //  ea file header.  We split it out
            //  again.
            //

            if (UnwindMergeTail) {

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    EaSetVbo,
                                    &TailMcb );
            }

            //
            //  If we split the tail off we merge the tail back
            //  with the ea data to remove.
            //

            if (UnwindSplitTail) {

                RxMergeAllocation( RxContext,
                                    Vcb,
                                    &DataMcb,
                                    &TailMcb );
            }

            //
            //  If the ea set has been split out, we merge that
            //  cluster string back in the file.  Otherwise we
            //  simply uninitialize the local Mcb.
            //

            if (UnwindSplitData) {

                RxMergeAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    &DataMcb );
            }
        }

        //
        //  Unpin any Bcb's still active.
        //

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );
        RxUnpinEaRange( RxContext, &EaSetRange );

        //
        //  Uninitialize any initialized Mcbs
        //

        if (UnwindInitializeDataMcb) {

            FsRtlUninitializeMcb( &DataMcb );
        }

        if (UnwindInitializeTailMcb) {

            FsRtlUninitializeMcb( &TailMcb );
        }

        DebugTrace(-1, Dbg, "RxDeleteEaSet -> Exit\n", 0);
    }

    return;
}


VOID
RxAddEaSet (
    IN PRX_CONTEXT RxContext,
    IN PVCB Vcb,
    IN ULONG EaSetLength,
    IN PBCB EaBcb,
    OUT PDIRENT EaDirent,
    OUT PUSHORT EaHandle,
    OUT PEA_RANGE EaSetRange
    )

/*++

Routine Description:

    This routine will add the necessary clusters to support a new
    Ea set of the given size.  This is done by splicing a chain of
    clusters into the existing Ea file.  An Ea index is assigned to
    this new chain and the Ea base and offset tables are updated to
    include this new handle.  This routine also pins the added
    clusters and returns their address and a Bcb.

    The caller of this function will have verified that the Ea file
    exists and that the Vcb field points to an initialized cache file.
    The caller will already have gained exclusive access to the
    EaFcb.

Arguments:

    Vcb - Supplies the Vcb to fill in.

    EaSetLength - The number of bytes needed to contain the Ea set.  This
        routine will round this up the next cluster size.

    EaBcb - Supplies a pointer to the Bcb for the Ea dirent.

    EaDirent - Supplies a pointer to the dirent for the Ea file.

    EaHandle - Supplies the address to store the ea index generated here.

    EaSetRange - This is the structure that describes new range in the Ea file.

Return Value:

    None.

--*/

{
    ULONG BytesPerSector = Vcb->Bpb.BytesPerSector;
    ULONG BytesPerCluster = 1 << Vcb->AllocationSupport.LogOfBytesPerCluster;

    EA_RANGE EaHeaderRange;
    USHORT EaHeaderIndex;
    PEA_FILE_HEADER EaHeader;

    EA_RANGE EaOffsetRange;
    ULONG EaNewOffsetVbo;
    USHORT EaOffsetIndex;
    ULONG EaOffsetTableSize;
    PUSHORT EaOffsetTable;

    ULONG EaSetClusterOffset;
    ULONG EaSetVbo;
    USHORT EaSetClusterCount;
    PEA_SET_HEADER EaSet;

    PFILE_OBJECT VirtualEaFile = Vcb->VirtualEaFile;
    PFCB EaFcb = Vcb->EaFcb;

    MCB EaSetMcb;
    BOOLEAN UnwindInitializedEaSetMcb = FALSE;
    BOOLEAN UnwindAllocatedNewAllocation = FALSE;
    BOOLEAN UnwindMergedNewEaSet = FALSE;

    MCB EaOffsetMcb;
    BOOLEAN UnwindInitializedOffsetMcb = FALSE;
    BOOLEAN UnwindSplitNewAllocation = FALSE;
    BOOLEAN UnwindMergedNewOffset = FALSE;

    MCB EaTailMcb;
    BOOLEAN UnwindInitializedTailMcb = FALSE;
    BOOLEAN UnwindSplitTail = FALSE;
    BOOLEAN UnwindMergedTail = FALSE;

    MCB EaInitialEaMcb;
    BOOLEAN UnwindInitializedInitialEaMcb = FALSE;
    BOOLEAN UnwindSplitInitialEa = FALSE;
    BOOLEAN UnwindMergedInitialEa = FALSE;

    USHORT NewEaIndex;
    PUSHORT NextEaOffset;

    ULONG NewAllocation;
    LARGE_INTEGER FileOffset;
    ULONG Count;

    ULONG UnwindPrevFileSize;
    BOOLEAN UnwindPrevFcbValues = FALSE;

    BOOLEAN TailExists = FALSE;
    BOOLEAN AddedOffsetTableCluster = FALSE;
    BOOLEAN UnwindPurgeCacheMap = FALSE;

    DebugTrace(+1, Dbg, "RxAddEaSet\n", 0);
    DebugTrace( 0, Dbg, "  Vcb         = %8lx\n", Vcb);
//    DebugTrace( 0, Dbg, "  EaSetSize = %ul\n", EaSetSize );
    DebugTrace( 0, Dbg, "  EaSetLength = %ul\n", EaSetLength );

    //
    //  Zero the Ea range structures.
    //

    RtlZeroMemory( &EaHeaderRange, sizeof( EA_RANGE ));
    RtlZeroMemory( &EaOffsetRange, sizeof( EA_RANGE ));

    //
    //  Use a try statement to facilitate cleanup.
    //

    try {

        //
        //  Pin down the file header.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaHeaderRange,
                       0,
                       sizeof( EA_FILE_HEADER ),
                       RxStatus(DATA_ERROR) );

        EaHeader = (PEA_FILE_HEADER) EaHeaderRange.Data;

        //
        //  Compute the size of the offset table.
        //

        EaNewOffsetVbo = EaHeader->EaBaseTable[0] << Vcb->AllocationSupport.LogOfBytesPerCluster;
        EaOffsetTableSize = EaNewOffsetVbo - BytesPerSector;

        //
        //  Pin down the entire offset table.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaOffsetRange,
                       BytesPerSector,
                       EaOffsetTableSize,
                       RxStatus(DATA_ERROR) );

        //
        //  We now look for a valid handle out of the existing offset table.
        //  We start at the last entry and walk backwards.  We stop at the
        //  first unused handle which is preceded by a used handle (or handle
        //  1).
        //
        //  As we walk backwards, we need to remember the file offset of the
        //  cluster which will follow the clusters we add.  We initially
        //  remember the end of the file.  If the end of the offset table
        //  consists of a string of used handles, we remember the offset of
        //  the handle prior to the transition from used to unused handles.
        //

        EaSetClusterOffset = EaFcb->Header.FileSize.LowPart
                             >> Vcb->AllocationSupport.LogOfBytesPerCluster;

        NewEaIndex = (USHORT) ((EaOffsetTableSize >> 1) - 1);

        NextEaOffset = (PUSHORT) EaOffsetRange.Data + NewEaIndex;

        //
        //  Walk through the used handles at the end of the offset table.
        //

        if (*NextEaOffset != UNUSED_EA_HANDLE) {

            while (NewEaIndex != 0) {

                if (*(NextEaOffset - 1) == UNUSED_EA_HANDLE) {

                    //
                    //  If the handle is 1, we take no action.  Otherwise
                    //  we save the cluster offset of the current handle
                    //  knowing we will use a previous handle and insert
                    //  a chain of clusters.
                    //

                    if (NewEaIndex != 1) {

                        EaSetClusterOffset = *NextEaOffset
                                             + EaHeader->EaBaseTable[NewEaIndex >> 7];

                        TailExists = TRUE;
                    }

                    NewEaIndex--;
                    NextEaOffset--;

                    break;
                }

                NewEaIndex--;
                NextEaOffset--;
            }
        }

        //
        //  Walk through looking for the first unused handle in a string
        //  of unused handles.
        //

        while (NewEaIndex) {

            if (*(NextEaOffset - 1) != UNUSED_EA_HANDLE) {

                break;
            }

            NextEaOffset--;
            NewEaIndex--;
        }

        //
        //  If the handle is zero, we do a special test to see if handle 1
        //  is available.  Otherwise we will use the first handle of a new
        //  cluster.  A non-zero handle now indicates that a handle was found
        //  in an existing offset table cluster.
        //

        if (NewEaIndex == 0) {

            if (*(NextEaOffset + 1) == UNUSED_EA_HANDLE) {

                NewEaIndex = 1;

            } else {

                NewEaIndex = (USHORT) EaOffsetTableSize >> 1;
                AddedOffsetTableCluster = TRUE;
            }
        }

        //
        //  If the Ea index is outside the legal range then raise an
        //  exception.
        //

        if (NewEaIndex > MAX_EA_HANDLE) {

            DebugTrace(-1, Dbg,
                       "RxAddEaSet: Illegal handle value for new handle\n", 0);

            RxRaiseStatus( RxContext, RxStatus(INSUFFICIENT_RESOURCES) );
        }

        //
        //  Compute the base and offset indexes.
        //

        EaHeaderIndex = NewEaIndex >> 7;
        EaOffsetIndex = NewEaIndex & (MAX_EA_OFFSET_INDEX - 1);

        //
        //  Compute the byte offset of the new ea data in the file.
        //

        EaSetVbo = EaSetClusterOffset << Vcb->AllocationSupport.LogOfBytesPerCluster;

        //
        //  Allocate all the required disk space together to insure this
        //  operation is atomic.  We don't want to allocate one block
        //  of disk space and then fail on a second allocation.
        //

        EaSetLength = (EaSetLength + BytesPerCluster - 1)
                      & ~(BytesPerCluster - 1);

        NewAllocation = EaSetLength
                        + (AddedOffsetTableCluster ? BytesPerCluster : 0);

        //
        //  Verify that adding these clusters will not grow the Ea file
        //  beyond its legal value.  The maximum number of clusters is
        //  2^16 since the Ea sets are referenced by a 16 bit cluster
        //  offset value.
        //

        if ((ULONG) ((0x0000FFFF << Vcb->AllocationSupport.LogOfBytesPerCluster)
                     - EaFcb->Header.FileSize.LowPart)
            < NewAllocation) {

            DebugTrace(-1, Dbg,
                       "RxAddEaSet: New Ea file size is too large\n", 0);

            RxRaiseStatus( RxContext, RxStatus(INSUFFICIENT_RESOURCES) );
        }

        FsRtlInitializeMcb( &EaSetMcb, PagedPool );

        UnwindInitializedEaSetMcb = TRUE;

        RxAllocateDiskSpace( RxContext,
                              Vcb,
                              0,
                              &NewAllocation,
                              &EaSetMcb );

        UnwindAllocatedNewAllocation = TRUE;

        EaSetClusterCount = (USHORT) (EaSetLength >> Vcb->AllocationSupport.LogOfBytesPerCluster);

        if (AddedOffsetTableCluster) {

            FsRtlInitializeMcb( &EaOffsetMcb, PagedPool );

            UnwindInitializedOffsetMcb = TRUE;

            RxSplitAllocation( RxContext,
                                Vcb,
                                &EaSetMcb,
                                EaSetLength,
                                &EaOffsetMcb );

            UnwindSplitNewAllocation = TRUE;
        }

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );

        if (AddedOffsetTableCluster) {

            FileOffset.LowPart = EaNewOffsetVbo;

        } else {

            FileOffset.LowPart = EaSetVbo;
        }

        FileOffset.HighPart = 0;

        //
        //  Round the cache map down to a system page boundary.
        //

        FileOffset.LowPart &= ~(PAGE_SIZE - 1);

        {
            IO_STATUS_BLOCK Iosb;
            ULONG PurgeCount = 5;

            while (--PurgeCount) {

                Iosb.Status = RxStatus(SUCCESS);

                CcFlushCache( VirtualEaFile->SectionObjectPointer,
                              NULL,
                              0,
                              &Iosb );

                ASSERT( Iosb.Status == RxStatus(SUCCESS) );

                //
                //  We throw the unwanted pages out of the cache and then
                //  truncate the Ea File for the new size.
                //
                //

                if (CcPurgeCacheSection( VirtualEaFile->SectionObjectPointer,
                                         &FileOffset,
                                         0,
                                         FALSE )) {

                    break;
                }
            }

            if (!PurgeCount) {

                RxRaiseStatus( RxContext, RxStatus(UNABLE_TO_DELETE_SECTION) );
            }
        }

        UnwindPurgeCacheMap = TRUE;

        FileOffset.LowPart = EaFcb->Header.FileSize.LowPart + NewAllocation;

        //
        //  If there is a tail to the file, then we initialize an Mcb
        //  for the file section and split the tail from the file.
        //

        if (TailExists) {

            FsRtlInitializeMcb( &EaTailMcb, PagedPool );

            UnwindInitializedTailMcb = TRUE;

            RxSplitAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                EaSetVbo,
                                &EaTailMcb );

            UnwindSplitTail = TRUE;
        }

        //
        //  If there is an initial section of ea data, we initialize an
        //  Mcb for that section.
        //

        if (AddedOffsetTableCluster
            && EaSetVbo != EaNewOffsetVbo) {

            FsRtlInitializeMcb( &EaInitialEaMcb, PagedPool );

            UnwindInitializedInitialEaMcb = TRUE;

            RxSplitAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                EaNewOffsetVbo,
                                &EaInitialEaMcb );

            UnwindSplitInitialEa = TRUE;
        }

        //
        //  We have now split the new file allocation into the new
        //  ea set and possibly a new offset table.
        //
        //  We have also split the existing file data into a file
        //  header, an initial section of ea data and the tail of the
        //  file.  These last 2 may not exist.
        //
        //  Each section is described by an Mcb.
        //

        //
        //  Merge the new offset information if it exists.
        //

        if (AddedOffsetTableCluster) {

            RxMergeAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                &EaOffsetMcb );

            FsRtlUninitializeMcb( &EaOffsetMcb );
            FsRtlInitializeMcb( &EaOffsetMcb, PagedPool );

            UnwindMergedNewOffset = TRUE;
        }

        //
        //  Merge the existing initial ea data if it exists.
        //

        if (UnwindInitializedInitialEaMcb) {

            RxMergeAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                &EaInitialEaMcb );

            FsRtlUninitializeMcb( &EaInitialEaMcb );
            FsRtlInitializeMcb( &EaInitialEaMcb, PagedPool );

            UnwindMergedInitialEa = TRUE;
        }

        //
        //  We modify the offset of the new ea set by one cluster if
        //  we added one to the offset table.
        //

        if (AddedOffsetTableCluster) {

            EaSetClusterOffset += 1;
            EaSetVbo += BytesPerCluster;
        }

        //
        //  Merge the new ea set.
        //

        RxMergeAllocation( RxContext,
                            Vcb,
                            &EaFcb->Mcb,
                            &EaSetMcb );

        FsRtlUninitializeMcb( &EaSetMcb );
        FsRtlInitializeMcb( &EaSetMcb, PagedPool );

        UnwindMergedNewEaSet = TRUE;

        //
        //  Merge the tail if it exists.
        //

        if (UnwindInitializedTailMcb) {

            RxMergeAllocation( RxContext,
                                Vcb,
                                &EaFcb->Mcb,
                                &EaTailMcb );

            FsRtlUninitializeMcb( &EaTailMcb );
            FsRtlInitializeMcb( &EaTailMcb, PagedPool );

            UnwindMergedTail = TRUE;
        }

        //
        //  If we added a new cluster for the offset table, we need to
        //  lock the entire cluster down and initialize all the handles to
        //  the unused state except the first one.
        //

        //
        //  Update the Fcb information.
        //

        UnwindPrevFileSize = EaFcb->Header.FileSize.LowPart;

        EaFcb->Header.FileSize.LowPart += NewAllocation;
        EaFcb->Header.AllocationSize = EaFcb->Header.FileSize;
        EaDirent->FileSize = EaFcb->Header.FileSize.LowPart;

        RxSetDirtyBcb( RxContext, EaBcb, Vcb );

        //
        //  Let Mm and Cc know the new file sizes.
        //

        CcSetFileSizes( VirtualEaFile,
                        (PCC_FILE_SIZES)&EaFcb->Header.AllocationSize );

        UnwindPrevFcbValues = TRUE;

        //
        //  Pin down the file header.
        //

        RtlZeroMemory( &EaHeaderRange, sizeof( EA_RANGE ));

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaHeaderRange,
                       0,
                       sizeof( EA_FILE_HEADER ),
                       RxStatus(DATA_ERROR) );

        EaHeader = (PEA_FILE_HEADER) EaHeaderRange.Data;

        //
        //  Pin down the entire offset table.
        //


        RtlZeroMemory( &EaOffsetRange, sizeof( EA_RANGE ));

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       &EaOffsetRange,
                       BytesPerSector + ((NewEaIndex >> 7) << 8),
                       sizeof( EA_OFF_TABLE ),
                       RxStatus(DATA_ERROR) );

        EaOffsetTable = (PUSHORT) EaOffsetRange.Data;

        //
        //  Pin the Ea set header for the added clusters and initialize
        //  the fields of interest.  These are the signature field, the
        //  owning handle field, the need Ea field and the cbList field.
        //  Also mark the data as dirty.
        //

        //
        //  Pin the ea set.
        //

        RxPinEaRange( RxContext,
                       VirtualEaFile,
                       EaFcb,
                       EaSetRange,
                       EaSetVbo,
                       EaSetLength,
                       RxStatus(DATA_ERROR) );

        EaSet = (PEA_SET_HEADER) EaSetRange->Data;

        EaSet->Signature = EA_SET_SIGNATURE;
        EaSet->OwnEaHandle = NewEaIndex;

        RxMarkEaRangeDirty( RxContext, VirtualEaFile, EaSetRange );

        //
        //  Update the Ea base and offset tables.  For the Ea base table,
        //  all subsequent index values must be incremented by the number
        //  of clusters added.
        //
        //  For the entries in the relevant Ea offset table, all entries
        //  after this index must also be increased by the number of
        //  clusters added.
        //
        //  If we added another cluster to the offset table, then we increment
        //  all the base table values by 1.
        //

        Count = MAX_EA_BASE_INDEX - EaHeaderIndex - 1;

        NextEaOffset = &EaHeader->EaBaseTable[EaHeaderIndex + 1];

        while (Count--) {

            *(NextEaOffset++) += EaSetClusterCount;
        }

        if (AddedOffsetTableCluster) {

            Count = MAX_EA_BASE_INDEX;

            NextEaOffset = &EaHeader->EaBaseTable[0];

            while (Count--) {

                *(NextEaOffset++) += 1;
            }
        }

        RxMarkEaRangeDirty( RxContext, VirtualEaFile, &EaHeaderRange );

        //
        //  If we added an offset table cluster, we need to initialize
        //  the handles to unused.
        //

        if (AddedOffsetTableCluster) {

            Count = (BytesPerCluster >> 1) - 1;
            NextEaOffset = EaOffsetTable;

            *NextEaOffset++ = 0;

            while (Count--) {

                *NextEaOffset++ = UNUSED_EA_HANDLE;
            }
        }

        //
        //  We need to compute the offset of the added Ea set clusters
        //  from their base.
        //

        NextEaOffset = EaOffsetTable + EaOffsetIndex;

        *NextEaOffset++ = (USHORT) (EaSetClusterOffset
                                    - EaHeader->EaBaseTable[EaHeaderIndex]);

        Count = MAX_EA_OFFSET_INDEX - EaOffsetIndex - 1;

        while (Count--) {

            if (*NextEaOffset != UNUSED_EA_HANDLE) {

                *NextEaOffset += EaSetClusterCount;
            }

            NextEaOffset++;
        }

        RxMarkEaRangeDirty( RxContext, VirtualEaFile, &EaOffsetRange );

        //
        //  Update the callers parameters.
        //

        *EaHandle = NewEaIndex;

        DebugTrace(0, Dbg, "RxAddEaSet: Return values\n", 0);

        DebugTrace(0, Dbg, "RxAddEaSet: New Handle -> %x\n", *EaHandle);

//no longer        DebugTrace(0, Dbg, "RxAddEaSet: EaSetOffset -> %08lx\n", *EaSetOffset);

    } finally {

        DebugUnwind( RxAddEaSet );

        //
        //  Handle cleanup for abnormal termination only if we allocated
        //  disk space for the new ea set.
        //

        if (AbnormalTermination() && UnwindAllocatedNewAllocation) {

            //
            //  If we modified the Ea dirent or Fcb, recover the previous
            //  values.  Even though we are decreasing FileSize here, we
            //  don't need to synchronize to synchronize with paging Io
            //  because there was no dirty data generated in the new allocation.
            //

            if (UnwindPrevFcbValues) {

                EaFcb->Header.FileSize.LowPart = UnwindPrevFileSize;
                EaFcb->Header.AllocationSize.LowPart = UnwindPrevFileSize;
                EaDirent->FileSize = UnwindPrevFileSize;

                CcSetFileSizes( VirtualEaFile,
                                (PCC_FILE_SIZES)&EaFcb->Header.AllocationSize );
            }

            //
            //  If we merged the tail then split it off.
            //

            if (UnwindMergedTail) {

                VBO NewTailPosition;

                NewTailPosition = EaSetVbo + EaSetLength;

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    NewTailPosition,
                                    &EaTailMcb );
            }

            //
            //  If we merged the new ea data then split it out.
            //

            if (UnwindMergedNewEaSet) {

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    EaSetVbo,
                                    &EaSetMcb );
            }

            //
            //  If we merged the initial ea data then split it out.
            //

            if (UnwindMergedInitialEa) {

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    EaNewOffsetVbo + BytesPerCluster,
                                    &EaInitialEaMcb );
            }

            //
            //  If we added a new offset cluster, then split it out.
            //

            if (UnwindMergedNewOffset) {

                RxSplitAllocation( RxContext,
                                    Vcb,
                                    &EaFcb->Mcb,
                                    EaNewOffsetVbo,
                                    &EaOffsetMcb );
            }

            //
            //  If there is an initial ea section prior to the new section, merge
            //  it with the rest of the file.
            //

            if (UnwindSplitInitialEa) {

                RxMergeAllocation( RxContext, Vcb, &EaFcb->Mcb, &EaInitialEaMcb );
            }

            //
            //  If there is a file tail split off, merge it with the
            //  rest of the file.
            //

            if (UnwindSplitTail) {

                RxMergeAllocation( RxContext, Vcb, &EaFcb->Mcb, &EaTailMcb );
            }

            //
            //  If we modified the cache initialization for the ea file,
            //  then throw away the ea file object.
            //

            if (UnwindPurgeCacheMap) {

                Vcb->VirtualEaFile = NULL;
                ObDereferenceObject( VirtualEaFile );
            }

            //
            //  If we split the allocation, then deallocate the block for
            //  the new offset information.
            //

            if (UnwindSplitNewAllocation) {

                RxDeallocateDiskSpace( RxContext, Vcb, &EaOffsetMcb );
            }

            //
            //  Deallocate the disk space.
            //

            RxDeallocateDiskSpace( RxContext, Vcb, &EaSetMcb );
        }

        //
        //  Unpin the Ea ranges.
        //

        RxUnpinEaRange( RxContext, &EaHeaderRange );
        RxUnpinEaRange( RxContext, &EaOffsetRange );

        //
        //  Uninitialize any local Mcbs
        //

        if (UnwindInitializedEaSetMcb) {

            FsRtlUninitializeMcb( &EaSetMcb );
        }

        if (UnwindInitializedOffsetMcb) {

            FsRtlUninitializeMcb( &EaOffsetMcb );
        }

        if (UnwindInitializedTailMcb) {

            FsRtlUninitializeMcb( &EaTailMcb );
        }

        if (UnwindInitializedInitialEaMcb) {

            FsRtlUninitializeMcb( &EaInitialEaMcb );
        }

        DebugTrace(-1, Dbg, "RxAddEaSet ->  Exit\n", 0);
    }

    return;
}


VOID
RxAppendPackedEa (
    IN PRX_CONTEXT RxContext,
    IN OUT PEA_SET_HEADER *EaSetHeader,
    IN OUT PULONG PackedEasLength,
    IN OUT PULONG AllocationLength,
    IN PFILE_FULL_EA_INFORMATION FullEa,
    IN ULONG BytesPerCluster
    )

/*++

Routine Description:

    This routine appends a new packed ea onto an existing packed ea list,
    it also will allocate/dealloate pool as necessary to hold the ea list.

Arguments:

    EaSetHeader - Supplies the address to store the pointer to pool memory
                  which contains the Ea list for a file.

    PackedEasLength - Supplies the length of the actual Ea data.  The
                      new Ea data will be appended at this point.

    AllocationLength - Supplies the allocated length available for Ea
                       data.

    FullEa - Supplies a pointer to the new full ea that is to be appended
             (in packed form) to the packed ea list.

    BytesPerCluster - Number of bytes per cluster on this volume.

    NOTE: The EaSetHeader refers to the entire block of Ea data for a
          file.  This includes the Ea's and their values as well as the
          header information.  The PackedEasLength and AllocationLength
          parameters refer to the name/value pairs only.

Return Value:

    None.

--*/

{
    ULONG PackedEaSize;
    PPACKED_EA ThisPackedEa;
    OEM_STRING EaName;

    DebugTrace(+1, Dbg, "RxAppendPackedEa...\n", 0);

    //
    //  As a quick check see if the computed packed ea size plus the
    //  current packed ea list size will overflow the buffer.  Full Ea and
    //  packed Ea only differ by 4 in their size
    //

    PackedEaSize = SizeOfFullEa( FullEa ) - 4;

    if ( PackedEaSize + *PackedEasLength > *AllocationLength ) {

        //
        //  We will overflow our current work buffer so allocate a larger
        //  one and copy over the current buffer
        //

        PVOID Temp;
        ULONG NewAllocationSize;
        ULONG OldAllocationSize;

        DebugTrace(0, Dbg, "Allocate a new ea list buffer\n", 0);

        //
        //  Compute a new size and allocate space.  Always increase the
        //  allocation in cluster increments.
        //

        NewAllocationSize = (SIZE_OF_EA_SET_HEADER
                             + PackedEaSize
                             + *PackedEasLength
                             + BytesPerCluster - 1)
                            & ~(BytesPerCluster - 1);

        Temp = FsRtlAllocatePool( PagedPool, NewAllocationSize );

        //
        //  Move over the existing ea list, and deallocate the old one
        //

        RtlCopyMemory( Temp,
                       *EaSetHeader,
                       OldAllocationSize = *AllocationLength
                                           + SIZE_OF_EA_SET_HEADER );

        ExFreePool( *EaSetHeader );

        //
        //  Set up so we will use the new packed ea list
        //

        *EaSetHeader = Temp;

        //
        //  Zero out the added memory.
        //

        RtlZeroMemory( &(*EaSetHeader)->PackedEas[*AllocationLength],
                       NewAllocationSize - OldAllocationSize );

        *AllocationLength = NewAllocationSize - SIZE_OF_EA_SET_HEADER;
    }

    //
    //  Determine if we need to increment our need ea changes count
    //

    if ( FlagOn(FullEa->Flags, FILE_NEED_EA )) {

        //
        //  The NeedEaCount field is long aligned so we will write
        //  directly to it.
        //

        (*EaSetHeader)->NeedEaCount++;
    }

    //
    //  Now copy over the ea, full ea's and packed ea are identical except
    //  that full ea also have a next ea offset that we skip over
    //
    //  Before:
    //             UsedSize                     Allocated
    //                |                             |
    //                V                             V
    //      +xxxxxxxx+-----------------------------+
    //
    //  After:
    //                              UsedSize    Allocated
    //                                 |            |
    //                                 V            V
    //      +xxxxxxxx+yyyyyyyyyyyyyyyy+------------+
    //

    ThisPackedEa = (PPACKED_EA) (RtlOffsetToPointer( (*EaSetHeader)->PackedEas,
                                                     *PackedEasLength ));

    RtlCopyMemory( ThisPackedEa,
                   (PUCHAR) FullEa + 4,
                   PackedEaSize );

    //
    //  Now convert the name to uppercase.
    //

    EaName.MaximumLength = EaName.Length = FullEa->EaNameLength;
    EaName.Buffer = ThisPackedEa->EaName;

    RxUpcaseEaName( RxContext, &EaName, &EaName );

    //
    //  Increment the used size in the packed ea list structure
    //

    *PackedEasLength += PackedEaSize;

    //
    //  And return to our caller
    //

    DebugTrace(-1, Dbg, "RxAppendPackedEa -> VOID\n", 0);

    UNREFERENCED_PARAMETER( RxContext );

    return;
}


VOID
RxDeletePackedEa (
    IN PRX_CONTEXT RxContext,
    IN OUT PEA_SET_HEADER EaSetHeader,
    IN OUT PULONG PackedEasLength,
    IN ULONG Offset
    )

/*++

Routine Description:

    This routine deletes an individual packed ea from the supplied
    packed ea list.

Arguments:

    EaSetHeader - Supplies the address to store the pointer to pool memory
                  which contains the Ea list for a file.

    PackedEasLength - Supplies the length of the actual Ea data.  The
                      new Ea data will be appended at this point.

    Offset - Supplies the offset to the individual ea in the list to delete

    NOTE: The EaSetHeader refers to the entire block of Ea data for a
          file.  This includes the Ea's and their values as well as the
          header information.  The PackedEasLength parameter refer to the
          name/value pairs only.

Return Value:

    None.

--*/

{
    PPACKED_EA PackedEa;
    ULONG PackedEaSize;

    DebugTrace(+1, Dbg, "RxDeletePackedEa, Offset = %08lx\n", Offset);

    //
    //  Get a reference to the packed ea and figure out its size
    //

    PackedEa = (PPACKED_EA) (&EaSetHeader->PackedEas[Offset]);

    SizeOfPackedEa( PackedEa, &PackedEaSize );

    //
    //  Determine if we need to decrement our need ea changes count
    //

    if (FlagOn(PackedEa->Flags, EA_NEED_EA_FLAG)) {

        EaSetHeader->NeedEaCount--;
    }

    //
    //  Shrink the ea list over the deleted ea.  The amount to copy is the
    //  total size of the ea list minus the offset to the end of the ea
    //  we're deleting.
    //
    //  Before:
    //              Offset    Offset+PackedEaSize      UsedSize    Allocated
    //                |                |                  |            |
    //                V                V                  V            V
    //      +xxxxxxxx+yyyyyyyyyyyyyyyy+zzzzzzzzzzzzzzzzzz+------------+
    //
    //  After
    //              Offset            UsedSize                     Allocated
    //                |                  |                             |
    //                V                  V                             V
    //      +xxxxxxxx+zzzzzzzzzzzzzzzzzz+-----------------------------+
    //

    RtlCopyMemory( PackedEa,
                   (PUCHAR) PackedEa + PackedEaSize,
                   *PackedEasLength - (Offset + PackedEaSize) );

    //
    //  And zero out the remaing part of the ea list, to make things
    //  nice and more robust
    //

    RtlZeroMemory( &EaSetHeader->PackedEas[*PackedEasLength - PackedEaSize],
                   PackedEaSize );

    //
    //  Decrement the used size by the amount we just removed
    //

    *PackedEasLength -= PackedEaSize;

    //
    //  And return to our caller
    //

    DebugTrace(-1, Dbg, "RxDeletePackedEa -> VOID\n", 0);

    UNREFERENCED_PARAMETER( RxContext );

    return;
}


ULONG
RxLocateNextEa (
    IN PRX_CONTEXT RxContext,
    IN PPACKED_EA FirstPackedEa,
    IN ULONG PackedEasLength,
    IN ULONG PreviousOffset
    )

/*++

Routine Description:

    This routine locates the offset for the next individual packed ea
    inside of a packed ea list, given the offset to a previous Ea.
    Instead of returing boolean to indicate if we've found the next one
    we let the return offset be so large that it overuns the used size
    of the packed ea list, and that way it's an easy construct to use
    in a for loop.

Arguments:

    FirstPackedEa - Supplies a pointer to the packed ea list structure

    PackedEasLength - Supplies the length of the packed ea list

    PreviousOffset - Supplies the offset to a individual packed ea in the
        list

Return Value:

    ULONG - The offset to the next ea in the list or 0xffffffff of one
        does not exist.

--*/

{
    PPACKED_EA PackedEa;
    ULONG PackedEaSize;
    ULONG Offset;

    DebugTrace(+1, Dbg, "RxLocateNextEa, PreviousOffset = %08lx\n",
               PreviousOffset);

    //
    //  Make sure the previous offset is within the used size range
    //

    if ( PreviousOffset >= PackedEasLength ) {

        DebugTrace(-1, Dbg, "RxLocateNextEa -> 0xffffffff\n", 0);
        return 0xffffffff;
    }

    //
    //  Get a reference to the previous packed ea, and compute its size
    //

    PackedEa = (PPACKED_EA) ((PUCHAR) FirstPackedEa + PreviousOffset );
    SizeOfPackedEa( PackedEa, &PackedEaSize );

    //
    //  Compute to the next ea
    //

    Offset = PreviousOffset + PackedEaSize;

    //
    //  Now, if the new offset is beyond the ea size then we know
    //  that there isn't one so, we return an offset of 0xffffffff.
    //  otherwise we'll leave the new offset alone.
    //

    if ( Offset >= PackedEasLength ) {

        Offset = 0xffffffff;
    }

    DebugTrace(-1, Dbg, "RxLocateNextEa -> %08lx\n", Offset);

    UNREFERENCED_PARAMETER( RxContext );

    return Offset;
}


BOOLEAN
RxLocateEaByName (
    IN PRX_CONTEXT RxContext,
    IN PPACKED_EA FirstPackedEa,
    IN ULONG PackedEasLength,
    IN POEM_STRING EaName,
    OUT PULONG Offset
    )

/*++

Routine Description:

    This routine locates the offset for the next individual packed ea
    inside of a packed ea list, given the name of the ea to locate

Arguments:

    FirstPackedEa - Supplies a pointer to the packed ea list structure

    PackedEasLength - Supplies the length of the packed ea list

    EaName - Supplies the name of the ea search for

    Offset - Receives the offset to the located individual ea in the list
        if one exists.

Return Value:

    BOOLEAN - TRUE if the named packed ea exists in the list and FALSE
        otherwise.

--*/

{
    PPACKED_EA PackedEa;
    OEM_STRING Name;

    DebugTrace(+1, Dbg, "RxLocateEaByName, EaName = %wZ\n", EaName);

    //
    //  For each packed ea in the list check its name against the
    //  ea name we're searching for
    //

    for ( *Offset = 0;
          *Offset < PackedEasLength;
          *Offset = RxLocateNextEa( RxContext,
                                     FirstPackedEa,
                                     PackedEasLength,
                                     *Offset )) {

        //
        //  Reference the packed ea and get a string to its name
        //

        PackedEa = (PPACKED_EA) ((PUCHAR) FirstPackedEa + *Offset);

        Name.Buffer = &PackedEa->EaName[0];
        Name.Length = PackedEa->EaNameLength;
        Name.MaximumLength = PackedEa->EaNameLength;

        //
        //  Compare the two strings, if they are equal then we've
        //  found the caller's ea
        //

        if ( RtlCompareString( EaName, &Name, TRUE ) == 0 ) {

            DebugTrace(-1, Dbg, "RxLocateEaByName -> TRUE, *Offset = %08lx\n", *Offset);
            return TRUE;
        }
    }

    //
    //  We've exhausted the ea list without finding a match so return false
    //

    DebugTrace(-1, Dbg, "RxLocateEaByName -> FALSE\n", 0);
    return FALSE;
}


BOOLEAN
RxIsEaNameValid (
    IN PRX_CONTEXT RxContext,
    IN OEM_STRING Name
    )

/*++

Routine Description:

    This routine simple returns whether the specified file names conforms
    to the file system specific rules for legal Ea names.

    For Ea names, the following rules apply:

    A. An Ea name may not contain any of the following characters:

       0x0000 - 0x001F  \ / : * ? " < > | , + = [ ] ;

Arguments:

    Name - Supllies the name to check.

Return Value:

    BOOLEAN - TRUE if the name is legal, FALSE otherwise.

--*/

{
    ULONG Index;

    UCHAR Char;

    //
    //  Empty names are not valid.
    //

    if ( Name.Length == 0 ) { return FALSE; }

    //
    //  At this point we should only have a single name, which can't have
    //  more than 254 characters
    //

    if ( Name.Length > 254 ) { return FALSE; }

    for ( Index = 0; Index < (ULONG)Name.Length; Index += 1 ) {

        Char = Name.Buffer[ Index ];

        //
        //  Skip over and Dbcs chacters
        //

        if ( FsRtlIsLeadDbcsCharacter( Char ) ) {

            ASSERT( Index != (ULONG)(Name.Length - 1) );

            Index += 1;

            continue;
        }

        //
        //  Make sure this character is legal, and if a wild card, that
        //  wild cards are permissible.
        //

        if ( !FsRtlIsAnsiCharacterLegalFat(Char, FALSE) ) {

            return FALSE;
        }
    }

    return TRUE;
}


VOID
RxPinEaRange (
    IN PRX_CONTEXT RxContext,
    IN PFILE_OBJECT VirtualEaFile,
    IN PFCB EaFcb,
    IN OUT PEA_RANGE EaRange,
    IN ULONG StartingVbo,
    IN ULONG Length,
    IN RXSTATUS ErrorStatus
    )

/*++

Routine Description:

    This routine is called to pin a range within the Ea file.  It will follow all the
    rules required by the cache manager so that we don't have overlapping pin operations.
    If the range being pinned spans a section then the desired data will be copied into
    an auxilary buffer.  RxMarkEaRangeDirty will know whether to copy the data back
    into the cache or whether to simply mark the pinned data dirty.

Arguments:

    VirtualEaFile - This is the stream file for the Ea file.

    EaFcb - This is the Fcb for the Ea file.

    EaRange - This is the Ea range structure for this request.

    StartingVbo - This is the starting offset in the Ea file to read from.

    Length - This is the length of the read.

    ErrorStatus - This is the error status to use if we are reading outside
        of the file.

Return Value:

    None.

--*/

{
    LARGE_INTEGER LargeVbo;
    ULONG ByteCount;
    PBCB *NextBcb;
    PVOID Buffer;
    PCHAR DestinationBuffer;
    BOOLEAN FirstPage = TRUE;

    //
    //  Verify that the entire read is contained within the Ea file.
    //

    if (Length == 0
        || StartingVbo >= EaFcb->Header.AllocationSize.LowPart
        || (EaFcb->Header.AllocationSize.LowPart - StartingVbo) < Length) {

        RxRaiseStatus( RxContext, ErrorStatus );
    }

    //
    //  If the read will span a section, the system addresses may not be contiguous.
    //  Allocate a separate buffer in this case.
    //

    if (((StartingVbo & (EA_SECTION_SIZE - 1)) + Length) > EA_SECTION_SIZE) {

        EaRange->Data = FsRtlAllocatePool( PagedPool, Length );
        EaRange->AuxilaryBuffer = TRUE;

        DestinationBuffer = EaRange->Data;
    }

    //
    //  If the read will require more pages than our structure will hold then
    //  allocate an auxilary buffer.  We have to figure the number of pages
    //  being requested so we have to include the page offset of the first page of
    //  the request.
    //

    EaRange->BcbChainLength = (USHORT) (((StartingVbo & (PAGE_SIZE - 1)) + Length + PAGE_SIZE - 1) / PAGE_SIZE);

    if (EaRange->BcbChainLength > EA_BCB_ARRAY_SIZE) {

        EaRange->BcbChain = FsRtlAllocatePool( PagedPool, sizeof( PBCB ) * EaRange->BcbChainLength );
        RtlZeroMemory( EaRange->BcbChain, sizeof( PBCB ) * EaRange->BcbChainLength );

    } else {

        EaRange->BcbChain = (PBCB *) &EaRange->BcbArray;
    }

    //
    //  Store the byte range data in the Ea Range structure.
    //

    EaRange->StartingVbo = StartingVbo;
    EaRange->Length = Length;

    //
    //  Compute the initial pin length.
    //

    ByteCount = PAGE_SIZE - (StartingVbo & (PAGE_SIZE - 1));

    //
    //  For each page in the range; pin the page and update the Bcb count, copy to
    //  the auxiliary buffer.
    //

    NextBcb = EaRange->BcbChain;

    while (Length != 0) {

        //
        //  Pin the page and remember the data start.
        //

        LargeVbo.QuadPart = StartingVbo;

        if (ByteCount > Length) {

            ByteCount = Length;
        }

        if (!CcPinRead( VirtualEaFile,
                        &LargeVbo,
                        ByteCount,
                        BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_WAIT),
                        NextBcb,
                        &Buffer )) {

            //
            // Could not read the data without waiting (cache miss).
            //

            RxRaiseStatus( RxContext, RxStatus(CANT_WAIT) );
        }

        //
        //  Increment the Bcb pointer and copy to the auxilary buffer if necessary.
        //

        NextBcb += 1;

        if (EaRange->AuxilaryBuffer == TRUE) {

            RtlCopyMemory( DestinationBuffer,
                           Buffer,
                           ByteCount );

            DestinationBuffer = (PCHAR) Add2Ptr( DestinationBuffer, ByteCount );
        }

        StartingVbo += ByteCount;
        Length -= ByteCount;

        //
        //  If this is the first page then update the Ea Range structure.
        //

        if (FirstPage) {

            FirstPage = FALSE;
            ByteCount = PAGE_SIZE;

            if (EaRange->AuxilaryBuffer == FALSE) {

                EaRange->Data = Buffer;
            }
        }
    }

    return;
}


VOID
RxMarkEaRangeDirty (
    IN PRX_CONTEXT RxContext,
    IN PFILE_OBJECT EaFileObject,
    IN OUT PEA_RANGE EaRange
    )

/*++

Routine Description:

    This routine is called to mark a range of the Ea file as dirty.  If the modified
    data is sitting in an auxilary buffer then we will copy it back into the cache.
    In any case we will go through the list of Bcb's and mark them dirty.

Arguments:

    EaFileObject - This is the file object for the Ea file.

    EaRange - This is the Ea range structure for this request.

Return Value:

    None.

--*/

{
    PBCB *NextBcb;
    ULONG BcbCount;

    //
    //  If there is an auxilary buffer we need to copy the data back into the cache.
    //

    if (EaRange->AuxilaryBuffer == TRUE) {

        LARGE_INTEGER LargeVbo;

        LargeVbo.QuadPart = EaRange->StartingVbo;

        CcCopyWrite( EaFileObject,
                     &LargeVbo,
                     EaRange->Length,
                     TRUE,
                     EaRange->Data );
    }

    //
    //  Now walk through the Bcb chain and mark everything dirty.
    //

    BcbCount = EaRange->BcbChainLength;
    NextBcb = EaRange->BcbChain;

    while (BcbCount--) {

        if (*NextBcb != NULL) {

            CcSetDirtyPinnedData( *NextBcb, NULL );
        }

        NextBcb += 1;
    }

    return;
}


VOID
RxUnpinEaRange (
    IN PRX_CONTEXT RxContext,
    IN OUT PEA_RANGE EaRange
    )

/*++

Routine Description:

    This routine is called to unpin a range in the Ea file.  Any structures allocated
    will be deallocated here.

Arguments:

    EaRange - This is the Ea range structure for this request.

Return Value:

    None.

--*/

{
    PBCB *NextBcb;
    ULONG BcbCount;

    //
    //  If we allocated a auxilary buffer, deallocate it here.
    //

    if (EaRange->AuxilaryBuffer == TRUE) {

        ExFreePool( EaRange->Data );
        EaRange->AuxilaryBuffer = FALSE;
    }

    //
    //  Walk through the Bcb chain and unpin the data.
    //

    if (EaRange->BcbChain != NULL) {

        BcbCount = EaRange->BcbChainLength;
        NextBcb = EaRange->BcbChain;

        while (BcbCount--) {

            if (*NextBcb != NULL) {

                CcUnpinData( *NextBcb );
                *NextBcb = NULL;
            }

            NextBcb += 1;
        }

        //
        //  If we allocated a Bcb chain, deallocate it here.
        //

        if (EaRange->BcbChain != &EaRange->BcbArray[0]) {

            ExFreePool( EaRange->BcbChain );
        }

        EaRange->BcbChain = NULL;
    }

    return;
}
