/*static char *SCCSID = "@(#)join.c     6.1 90/11/17";*/
/*static char *SCCSID = "@(#)join.c     13.2 90/01/25";*/
#define ASWAP2 ASWAP2
#define INCL_DOSERRORS
#include <os2.h>

#include "heap.h"
#include "api.h"
#include "const.h"
#include "misc.h"
#include "chain.h"
#include "buf.h"
#include "dir.h"
#include "volume.h"
#include "storage.h"
#include "tables.h"
#include "pb_ifs.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "assert.h"
#include "log.h"                                                 // @76193

#include "pr_BITMP.h"
#include "pr_CACHE.h"
#include "pr_DEL.h"
#include "pr_DIR.h"
#include "pr_HEAP.h"
#include "pr_MISC.h"
#include "pr_debug.h"


/*  ISLEAF returns TRUE if DIRBLK is a leaf entry
 */
#define ISLEAF(db) ((FIRSTENT(db)->fFlags & DF_BTP) == 0)

/*  FIRSTENT returns pointer to first DIRENT given a DIRBLK
 */
#define FIRSTENT(t) ((PDIRENT)(&t->bFirst))


/*  DIRBLK_EMPTY returns TRUE if a DIRBLK is empty
 */
#define DIRBLK_EMPTY(x) ((x)->offulFirstFree <= (FIELDOFFSET(DIRBLK, bFirst)+ \
                                (sizeof (DIRENT)) + (sizeof(LSN)) ))

/*  GET_END returns pointer to end record in DIRBLK
 */
#define GET_END(rec)  ( (PDIRENT)( (PBYTE)rec + rec->offulFirstFree     \
                                   - sizeof (DIRENT)                    \
                                   - (FIRSTENT(rec)->fFlags & DF_BTP)))

#define DIRENDSIZE (sizeof (DIRENT) + sizeof(LSN) - BTPadj)

/**     Join - Join two Directory Blocks
 *
 *      Join is called when two ajacent directory blocks are
 *      below a size threshold.  They are joined together (along with
 *      their seperator node) and the separator is removed from
 *      the parent block.
 *
 *      The caller guarantees that the two blocks, together with the
 *      middle parent record, will fit into one block.
 *
 *      Caller guarantees that the three blocks are each held.
 *      We don't need to lock them because we'll do our work in one
 *      chunk, before we yield to anyone.
 *
 *  HISTORY:
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 */

/*      BUGBUG - make sure we strip the BTP bit from demoted up guy */

USHORT
Join (PDIRBLK topblk, PBUFNODE topbufp,
      PDIRBLK botblk, PBUFNODE botbufp,
      PDIRBLK nodeblk, PBUFNODE nodebufp,
      PDIRENT nodeent)
{
    USHORT BTPadj;      /* = size DIR_BTP iff dirblks are leafs */
    USHORT botsiz;     /* size of records in bottom block */
    LSN endBTP;    /* BTP pointer of end record in topblk */
    PDIRENT inpoint;         /* insert point in topblk */

    ASSERT_DB(topblk);
    ASSERT_DB(botblk);
    ASSERT_DB(nodeblk);
    /* We'll molest all three DIRBLKs,
     * so set their dirty bits and edit their CCNT fields. */

    topblk->culChange += 2;
    nodeblk->culChange += 2;

    /* Set xTPadj to sizeof DIR_BTP if this is a leaf block, because we'll
     * have to adjust the size of records that we promote/demote. */

    BTPadj = ISLEAF (botblk) ? sizeof (LSN) : 0;

    /* Make room in top block for the middle record and the botblk records */

    botsiz = (USHORT) (botblk->offulFirstFree - DIRENDSIZE - FIELDOFFSET (DIRBLK, bFirst));
    inpoint = GET_END(topblk);
    endBTP = BTP(inpoint);          /* only valid if not leaf block */

    MkRoom (botsiz + nodeent->cchThisEntry - BTPadj,
            inpoint, topblk);

    /* move nodeent into top block.  Correct if dropping BTP value */

    BYTE_COPY ((PBYTE)nodeent, (PBYTE)inpoint,
               nodeent->cchThisEntry - BTPadj);

    if (BTPadj) {
        inpoint->cchThisEntry -=  BTPadj;
        inpoint->fFlags ^= DF_BTP;
    } else {
        BTP(inpoint) = endBTP;      /* give it a new down pointer */
        ASSERTBTP (inpoint);
    }

    /* copy bottom records into top block.  We'll copy the bottom END record
     * overtop the top end record as an easy way to get it's BTP value */

    if (BTPadj == 0) {
        FixUp (FIRSTENT(botblk), topblk->lsnThisDir, botsiz + DIRENDSIZE);
    }

    BYTE_COPY ((PBYTE) FIRSTENT(botblk), (PBYTE) NEXT_ENTRY (inpoint),
                botsiz + DIRENDSIZE);

    FreeDB (botblk->lsnThisDir);        /* release these blocks */
    pfrm->psbd->culFreeDB++;

    botblk->sig = 0;                /* clear signature for CHKDSK */

    /* closeup the parent record.  The downpointer of the record
     * after the one we demoted pointed to our botblk.  Adjust it
     * to point to the remaining Topblk.  */

#if defined(DEBUG)
    /*
     * The else part of this section causes an internal compiler error if
     * optimization is turned off.  So we put the intermediate result into
     * a local variable to prevent the error.
     */

    {
        PDIRENT pdirent;

        pdirent = NEXT_ENTRY (nodeent);
        BTP(pdirent) = topblk->lsnThisDir;
        ASSERTBTP (pdirent);
    }
#else
    BTP(NEXT_ENTRY (nodeent)) = topblk->lsnThisDir;
    ASSERTBTP (NEXT_ENTRY (nodeent));
#endif
    Closeup (nodeblk, nodeent);

    ASSERT_DB( topblk );
    DecrHoldsBUF (topbufp);  // @76193
    SetDBDirt (topbufp, FALSE);


    /*  botblk is no longer a dirblk, don't assert.  */
    DecrHoldsBUF (botbufp);           /* unhold these guys  @76193 */
    SetDirt (botblk, botbufp, FALSE);     /* botblk only loses it's signature */

    /*
     *  Even if it's empty, nodeblk should still be a valid dirblk
     *  since we haven't called Adjust yet.
     */
    ASSERT_DB( nodeblk );
    DecrHoldsBUF (nodebufp);  // @76193
    SetDBDirt (nodebufp, FALSE);


    if (DIRBLK_EMPTY(nodeblk)) {

        return( Adjust (nodeblk->lsnThisDir) );
    }

    return( NO_ERROR );
}

/*      BUGBUG - interlock entire DIR tree for this op */
/*      two level lock- user count, lock flag, lock waiting
                if (lock+lockwait) == 0
                        usercount++;

                */
