/*static char *SCCSID = "@(#)insert.c   6.2 92/01/31";*/
// @75229 move from ASWAP2 to _BTXT
#define _BTXT _BTXT
#define INCL_DOSERRORS

#include <os2def.h>
#include <bse.h>
#include <cdib.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 "codepage.h"
#include "log.h"                                                 // @76193

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


/*      Get address of first directory entry given a dirblock address */

#define FIRSTENT(t) ((PDIRENT)(&t->bFirst))



/**     Insert - Insert Record
 *
 *      Insert inserts a record into a directory block.
 *      We know that the block isn't a leaf block because
 *      insertions there are only done by LDE() and he does them
 *      in assembly language for speed.  We're called to insert
 *      records into non-leaf blocks because that's a rarer situation.
 *      (Note that LDE() does some non-leaf insertion too; the easy
 *       common case)
 *
 *      Note that if we're doing an insert as a result of a split, our
 *      new record will point to the first of the new dirblk pair, and
 *      will go into the parent dirblk right before the guy who used
 *      to point to that block (before we split the last part off).
 *      In this case, the newdp argument specifies the new downpointer
 *      for this trailing record.
 *
 *      If there's enough room then it's easy; we just make a hole and
 *      copy it in.  Otherwise, we'll split this block.
 *
 *      Note : On entry blkbufp is held. On exit it is still held.
 *
 *      RETURNS NO_ERROR if successful, otherwise an error code.
 *
 *      HISTORY
 *          ....
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 *          27-Sep-1988 14:21:54 by Bill McJohn [t-billm]
 *                              brought up to date with 9/23/88 assembly code
 */

#if !defined (RECURSIVE)
/**     CLEANREC - cleanup structure for nested calls
 */
typedef struct CLEANREC CLEANREC;       /*  cr  */
typedef CLEANREC far *PCLEANREC;        /*  pcr  */

struct CLEANREC {
    PCLEANREC   pcrNext;                /*  link to next field                */
    PBUFNODE    blkbufp;                /*  saved blkufp                      */
    PDIRENT     promote;                /*  saved promote buffer              */
    };
#endif

USHORT far
insert (blkptr, blkbufp, newrec, newdp, pfrm)
PDIRBLK         blkptr;         /* pointer to insert dirblk */
PBUFNODE        blkbufp;        /* pointer to dirblk BUF */
PDIRENT         newrec;         /* pointer to new DIRENT */
LSN             newdp;          /* sector # of 2nd half if split insertion */
                                /*   Zero if not a splitting insertion */
PFRAME          pfrm;
{
    PPASCALSTRING ppsTemp;      /* temp buffer for upcased name */
    USHORT usRet;               /* error code */

#if !defined (RECURSIVE)
    PCLEANREC pcr;

    pcr = NULL;

    /*  Some cleverness gets applied here, however.  We cannot tolerate
     *  true recursion.  The IFS model presents a limited stack so we must
     *  avoid use of the stack if we can possibly help it.
     *
     *  Of the three calls to insert() below, only the last one may involve
     *  unbounded recursion.  The first two are calls to handle inserting
     *  into either half of a split block.  Since the block was just split
     *  and dirblks are large enough to contain at least 3 dirents, it is
     *  clear that these two calls only go as far as one level.
     *
     *  Note, that after the last call to insert we do a small amount of
     *  cleanup.  We unwind the recursive call to insert by saving all the
     *  data that we need to clean up into a linked list of heap records
     *  (just like a stack).  When we return, we just iterate through this
     *  linked list and clean up at the end.
     */

insert_top:
#endif

    ASSERT_DB(blkptr);

    /*  newrec points to a DIRENT.  This means that the directory entry
     *  contains mixed case and has a codepage tag to indicate such.
     *  The NameUpCaseCmp routine will perform a case insensitive, codepage
     *  sensitive comparison, but it's first argument is assumed to be already
     *  upper cased.
     *
     *  This being the case, we must form an upper-cased variant of newrec
     *  for the comparisons.  We allocate some heap space, copy newrec
     *  into it, and then perform the comparison
     */
    IncrHoldsBUF (blkbufp);  // @76193
    /* Get enough space to include the length byte */
    ppsTemp = GHS(newrec->cchName + sizeof (newrec->cchName),pfrm);
    DecrHoldsBUF (blkbufp);  // @76193

    if (ppsTemp == NULL) {
#if defined (DEBUG)
        dprintf (__FILE__": GHS returned NULL\n");
#endif
        usRet = ERROR_NOT_ENOUGH_MEMORY;
        goto return_cleanup;
    }

    ppsTemp->c = newrec->cchName;

    NameUpCase (newrec->bName, newrec->cchName,
                &paCodePageEnt [pfrm->pvol->
                                paiCodePage [newrec->bCodePage]],
                ppsTemp->b);


    /*  See if we can simply add newrec to current DIRBLK.  We assume
     *  that we can (90% case).  We find the correct spot for insertion,
     *  adjust the following down pointer and then try to grow the block.
     *  If we succeed, then we're done.
     */
    {   PDIRENT pdirent;

        pdirent = FIRSTENT(blkptr);


        /*      Scan looking for the right spot to insert */

        while (((pdirent->fFlags & DF_END) == 0) &&
               (NameUpCaseCmp (&(ppsTemp->b[0]), ppsTemp->c,
                               &(pdirent->bName[0]), pdirent->cchName,
                               &paCodePageEnt [pfrm->pvol->paiCodePage
                                              [newrec->bCodePage]]) > 0)) {
            pdirent = NEXT_ENTRY(pdirent);
        }

        /*      We've got our spot.  Adjust the following down pointer */

        if (newdp) {
            BTP(pdirent) = newdp;
            ASSERTBTP (pdirent);
        }

        /*  If we can make some room in the DIRBLK
         */
        if (MkRoom (newrec->cchThisEntry, pdirent, blkptr) != NULL) {

            /*  Enough room exists in the DIRBLK.  Copy in the new
             *  dirent, mark the cache block as dirty, and we're done
             */
            BYTE_COPY ((PCHAR)newrec,
                       (PCHAR)pdirent,
                       newrec->cchThisEntry);
            ASSERT_DB(blkptr);
            /* mark dirty */
            SetDBDirt (blkbufp, FALSE, pfrm);
            RetHeap (ppsTemp);
            usRet = NO_ERROR;
            goto return_cleanup;
        }

    }   /* end scope of pdirent */


    /*  The dirblk does not have enough room for newrec.  The best
     *  that we can do at this point is split the dirblk and insert
     *  newrec into one of the two halves.  BUT, when we split the dirblk
     *  we now have a new directory entry that must be inserted into the
     *  parent of the dirblk.
     */

#if DIRBLKS < 3 * MAX_DIRENT
#error Insert:  DIRBLK too small to guarantee success
#endif

    {
        PDIRBLK botblk;             /* bottom half after a split */
        PDIRENT promote;            /* buffer for promotion entry */
        PBUFNODE botbufp;           /* buffer header for new bottom half */

        /*  Get max possible size of dirent: enough for dirent plus
         *  max length name plus the b-tree pointer plus round up to
         *  dword.
         */

        IncrHoldsBUF (blkbufp);  // @76193
        promote = GHS(sizeof(DIRENT) + CCHMAXPATHCOMP + sizeof (LSN) + 3,pfrm);
        DecrHoldsBUF (blkbufp);  // @76193

#if defined (DEBUG2)
        dprintf ("insert: Reachable check 1\n");
        Reachable (promote);
#endif
        if (promote == NULL) {
#if defined (DEBUG)
                dprintf ("Insert: ghs returned null\n");
#endif
                RetHeap (ppsTemp);
                usRet = ERROR_NOT_ENOUGH_MEMORY;
                goto return_cleanup;
        }
        botblk = SplitDir (blkptr, blkbufp, promote, TRUE, pfrm);

        /*  SplitDir has added holds to blkbufp and botblk for us
         */

#if defined (DEBUG2)
        dprintf ("insert: Reachable check 2\n");
        Reachable (promote);
#endif

        /* find which half our insert guy fits into and put him there
         * Both the topblk and the botblk are now held
         */

        ASSERT_DB(botblk);
        ASSERT_DB(blkptr);
        botbufp = BH(botblk);
        /*
         * SplitDir will never return an end DirEnt, so we don't have to
         * check it here.
         */
        if (NameUpCaseCmp (&(ppsTemp->b[0]), ppsTemp->c,
                           &(promote->bName[0]), promote->cchName,
                           &paCodePageEnt [pfrm->pvol->paiCodePage
                                           [newrec->bCodePage]]) < 0) {

            usRet = insert (blkptr,
                            blkbufp,
                            newrec,
                            newdp, pfrm);
        }
        else {
#if defined (DEBUG2)
            if (newrec->fFlags & DF_BTP) {
               PDIRBLK pdb;
               PBUFNODE pbuf;
               if ((pdb = RdBuf(BTP(newrec),BP_NOOPINION,&pbuf,pfrm)) != NULL) {
                    if (pdb->lsnParent == botblk->lsnThisDir) {
                        dprintf("Lsn DO match child parent %ld ",pdb->lsnParent);
                        dprintf("This dir %ld\n",botblk->lsnThisDir);
                    }
                    else {
                        dprintf("Lsn don't match pdb %ld ",pdb->lsnThisDir);
                        dprintf(" lsnParent %ld ",pdb->lsnParent);
                        dprintf("istead of %ld ",botblk->lsnThisDir);
                        dprintf("Time %ld\n",msNow());
                        pdb->lsnParent = 0x55AA5AA5;
                    }
                    SetDBDirt (pbuf,FALSE,pfrm);
                }
                else {
                    dprintf("Insert: Rdbuf faild while checking lsn Parent\n");
                }
            }
#endif
            usRet = insert (botblk,
                            botbufp,
                            newrec,
                            newdp, pfrm);
        }

        /* we don't need ppsTemp anymore */
        RetHeap (ppsTemp);

#if defined (DEBUG2)
        dprintf ("insert: Reachable check 3\n");
        Reachable (promote);
#endif

        if (usRet != NO_ERROR) {
#if defined (DEBUG)
            dprintf ("Insert: recursive call to insert failed\n");
#endif
            /*  Unhold what SplitDir held for us
             */
            DecrHoldsBUF (blkbufp);   /* can release top part  @76193 */
            DecrHoldsBUF (botbufp);   /* release the bottom half  @76193 */
            goto do_return2;
        }

        /* for all the guys in the 2nd half, read them in and update their
         * parent pointer */

        ASSERT_DB(botblk);
        ASSERT_DB(blkptr);

        FixUp(FIRSTENT(botblk), botblk->lsnThisDir, -1, pfrm);

        if ((blkptr->culChange & 0x01) == 0) {

            /* we just split a non-root block, so promote to it's parent */

            /*  Unhold what SplitDir held for us
             */
            DecrHoldsBUF (blkbufp);       /* can release top part  @76193 */
            DecrHoldsBUF (botbufp);       /* release the bottom half  @76193 */

            /* recursively call ourselves and insert into our parent */

            newdp = botblk->lsnThisDir;

//#127671 changed BP_NOOPINION to BP_KEEP
            blkptr = RdBuf(botblk->lsnParent,BP_KEEP,&blkbufp,pfrm);

            if (blkptr == NULL) {
#if defined (DEBUG)
                dprintf("Insert:  RdBuf returns error. Can't read parent.\n");
                Int3();
#endif
                usRet = ERROR_READ_FAULT;
                goto do_return2;
            }

            /* insert record into parent */

            IncrHoldsBUF (blkbufp);  // @76193
#if defined (DEBUG2)
            Reachable (promote);
#endif

            /*  It is at this point that we call ourselves to insert the new
             *  split point into our parent.  Rather than be recursive, we
             *  save all the variables that are used to clean up here into
             *  a linked list on the heap, reset our variables as if we'd
             *  just been called and then jump back to the top to this routine
             */

#if !defined (RECURSIVE)

            {   PCLEANREC pcrNew;

                pcrNew = GHS(sizeof(*pcrNew),pfrm);
                if (pcrNew == NULL) {
                    DecrHoldsBUF (blkbufp);  // @76193
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    goto do_return2;
                }

                pcrNew->pcrNext = pcr;
                pcrNew->blkbufp = blkbufp;
                pcrNew->promote = promote;
                pcr = pcrNew;
            }

            newrec = promote;

#if defined (DEBUG)
#endif

            goto insert_top;

#else
            insert (blkptr, blkbufp, promote, newdp, pfrm);
            DecrHoldsBUF (blkbufp)  // @76193
#endif

        }
        else {
            PBUFNODE    newrootbufp;
            PDIRBLK     newrootblk;

            /* we just split the root dirblk, so we have to make a new root
             * and update the directory FNODE and SBDIR structure */

            ASSERT_DB(blkptr);
            blkptr->culChange--;                /* remove flag from old root */

            /* NewDirRoot (promote, blkptr, botblk,
             *             &newrootbufp, &newrootblk);
             */


#if defined (DEBUG2)
            dprintf ("insert: Reachable check 4\n");
            Reachable (promote);
#endif
            /*
             *  Note that LDEAddLevel will decrement the hold
             *  count for the buffer headers for blkptr (blkbufp)
             *  and botblk (botbufp), even if it returns an
             *  error.
             */
            usRet = LDEAddLevel(blkptr, botblk, promote, TRUE,
                                &newrootbufp, &newrootblk, pfrm);

            DecrHoldsBUF (newrootbufp);   /* unhold root  @76193 */

            if (usRet != NO_ERROR) {
#if defined (DEBUG)
                dprintf ("Insert: LDEAddLevel returns error\n");
                Int3();
#endif
                usRet = ERROR_READ_FAULT;
                goto do_return2;
            }

            /* mark dirty */
            SetDBDirt (newrootbufp, FALSE, pfrm);
        }

do_return2:
        RetHeap (promote);

return_cleanup:

#if !defined (RECURSIVE)
        /*  We are finally going to return.  Since we may have stacked
         *  several levels of heap guys, due to unwinding the recursion,
         *  we must iterate through the saved list and clean up
         */
        while (pcr != NULL) {
            PCLEANREC pcrTmp;

#if defined (DEBUG)
            Reachable (pcr);
            Reachable (pcr->promote);
#endif
            RetHeap (pcr->promote);

            ASSERT_BUF (pcr->blkbufp);

            DecrHoldsBUF(pcr->blkbufp);  // @76193

            pcrTmp = pcr;
            pcr = pcr->pcrNext;
            RetHeap (pcrTmp);
            }
#endif

        return (usRet);

    }   /* end scope of promote */
}


/**     FixUp - Fix Child Dirblk UpPointers
 *
 *      When we split, balance, or join dirblks we may move entries
 *      between different directory blocks.  If those entries have
 *      descendents (DIR_BTP fields) then their DB_PAR pointers
 *      will have to be adjusted to their parent entry's new DIRBLK.
 *
 *      FixUp (DirPtr, NewSec, Cnt)
 *
 *              DirPtr = Pointer to first moved parent DirEnt
 *              NewSec = sector # of new parent DIRBLK
 *              Cnt    = # of bytes in DIRENTs to process
 *
 *      FixUp always stops if it reaches an entry marked DF_END, so
 *      a Cnt value of -1 means "process 'til end of block"
 *
 *      Since we're not going to look at these lower guys, if we
 *      get a hard error we just make sure that we mark the DIRBLK
 *      as corrupted.  We don't need to return an error value.
 */

void far
FixUp (dirptr, newsec, cnt, pfrm)
PDIRENT dirptr;
LSN newsec;
SHORT cnt;
PFRAME pfrm;
{
        PDIRBLK tdirblk;
        PBUFNODE tdirbuf;

        while (cnt != 0) {

//#127671 changed BP_NOOPINION to BP_KEEP
            tdirblk = RdBuf(BTP(dirptr),BP_KEEP,&tdirbuf,pfrm);

            if ( tdirbuf->bBadMask != 0 ) {

                /*  Oops, an error.  */
#if defined (DEBUG)
                dprintf("Fixup:  Read failed.  Exercising error handling.\n");
                Int3();
#endif
                CorruptDir(pfrm);
                return;
            }
#if defined (DEBUG)
            if (tdirblk->lsnParent == 0x55AA5AA5) {
                dprintf("Fixing %ld ",tdirblk->lsnThisDir);
                dprintf("to %ld ",newsec);
                dprintf("Time %ld\n",msNow());
            }
#endif
            tdirblk->lsnParent = newsec;

            SetDirt((PBYTE)&tdirblk->lsnParent, tdirbuf, FALSE, pfrm);

            if (dirptr->fFlags & DF_END) {
                return;
            }
            cnt -= dirptr->cchThisEntry;
            dirptr = NEXT_ENTRY(dirptr);
        }
}
