/*static char *SCCSID = "@(#)delentry.c 6.2 92/01/31";*/
#define ASWAP2 ASWAP2
#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>

#include "heap.h"
#include "api.h"
#include "const.h"
#include "misc.h"
#include "chain.h"
#include "buf.h"
#include "dir.h"
#include "storage.h"
#include "fnode.h"
#include "volume.h"
#include "tables.h"
#include "pb_ifs.h"
#include "superb.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "assert.h"
#include "log.h"                                                 // @76193
#include "pr_CACHE.h"
#include "pr_DEL.h"
#include "pr_DIR.h"
#include "pr_MISC.h"
#include "pr_HEAP.h"
#ifdef DEBUG
#include "pr_debug.h"
#endif


/*
 ***    DelEntry - Delete Directory Entry
 *
 *      DelEntry deletes an entry from a directory tree.  It doesn't
 *      matter whether the entry is a file or a directory; the caller
 *      is responsible for cleaning up FNODES, etc.  DelEntry
 *      only takes the record out of the BTREE and, if necessary,
 *      shuffles the BTREE.
 *
 *      The dirty bits are set as necessary, as well as the DIRBLK
 *      change count fields.
 *
 *      Note that if we delete a small guy who is not in a leaf node
 *      the guy we promote in his place might be so big that we have
 *      to split the node block.  This split could, super worst case,
 *      propagate upwards.  We *don't* reserve dirblks for this split,
 *      because
 *              1) it's super rare that this happens, and that we split
 *                 very far.  Very few trees are even that big, and it's
 *                 super unlikely that the thing will split at level
 *                 after level.
 *              2) we reserve a very generous amount anyway, so even if we're
 *                 really low on available dirblks and they're all reserved
 *                 many of those reserved ones won't really be used by
 *                 their reserver
 *              3) if we do allow DELENTRY to fail because he MIGHT split
 *                 too far, we can get into hot water with OPEN.  If OPEN
 *                 finds that there's not enough space for a file it will
 *                 need to DELENTRY the entry it just made.  This would be
 *                 a prime time for the VERIFYDB guy to think that there
 *                 aren't enough DIRBLKs, and we're stuck with either hanging
 *                 in open, returning an internal error, or leaving this
 *                 incorrect entry in the DIRBLK, corrupting the disk and
 *                 perhaps circumventing protection.
 *
 *              So we'll take the lesser risk and just ignore
 *              dirblk reservation.
 *
 *      ENTRY   pdb = address of start of directory block, HELD
 *              pde = address of entry
 *              pbuf = address of buffer header for dirblk
 *
 *      RETURNS NO_ERROR if no error
 *              otherwise returns appropriate error code.
 *
 *
 *      HISTORY
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 *          16-May-1989 by John Rogers [JohnRo@Microsoft]
 *              - Changed names of rounding macros in <pbmacros.h>.
 *              - PTR B783576: prevent overflow.
 *              - Changed #include files per Leif Pederson.
 *          29-Sep-1988 17:10 by Bill McJohn [t-billm]
 *                              translated from 9/28/88 assembly code
 */
USHORT
DelEntry (pdb, pde, pbuf, pfrm)
PDIRBLK  pdb;           /*  Address of DIRBLK to delete from    */
PDIRENT  pde;           /*  Address of DIRENT to delete         */
PBUFNODE pbuf;          /*  Address of buffer with DIRBLK       */
PFRAME   pfrm;
{

    PDIRENT pdeLeaf;        /*  leaf DIRENT we extract to promote   */
    PDIRENT pdeTemp;        /*  temporary buffer for leaf DIRENT    */
    PDIRBLK pdbLeaf;        /*  DIRBLK for leaf DIRENT              */
    PBUFNODE pbufLeaf;      /*  Buffer for leaf DIRBLK              */
    LSN lsnAdjust;          /*  used if we need to adjust leaf      */
    USHORT usRet;


    ASSERT_DB (pdb);
    ASSERT_DE (pde);

    if (!(pde->fFlags & DF_BTP)) {

        /*  This entry does not have a btree down pointer, so
         *  we can just rip it out and move the remaining entries
         *  over to fill up the gap.  Closeup does all this work
         *  for us.
         *
         */
        Closeup (pdb, pde);

        SetDBDirt(pbuf, FALSE, pfrm);

        if (pdb->offulFirstFree <= DIRSIZP + 1 +
                                 ((PBYTE)&(pdb->bFirst) -
                                 (PBYTE)(pdb)) ){
            /*
             *  Emptied this guy out.  Gotta adjust
             *  his size.
             */
            if( (usRet = Adjust (pdb->lsnThisDir, pfrm)) != NO_ERROR ) {
                return CorruptDir(pfrm);
            }
        }

        return (NO_ERROR);
    }

    /*  If we missed the explicit return above, we have a btree
     *  down pointer.  The DIRENT can't just be removed, we've
     *  got to promote someone from down below to take its place.
     *
     *  Go down the tree looking for the first entry after this one.
     *  We know that there is an entry after this one, otherwise we'd
     *  be a leaf entry.  We keep following the down pointer in
     *  the first entry in each block until we come to a block
     *  whose first entry has no down pointer.  We will promote
     *  the first entry of that DIRBLK.
     *
     */

    pdeLeaf = NEXT_ENTRY(pde);

    while (pdeLeaf->fFlags & DF_BTP) {


        pdbLeaf = RdBuf( BTP(pdeLeaf),
                         BP_TOSS,
                         &pbufLeaf,
                         pfrm);

        if( pbufLeaf->bBadMask != 0 ) {
            /*
             * Got hard error - corrupt directory and return error
             */
            return CorruptDir(pfrm);
        }

#ifdef DEBUG2
        ASSERT_DB (pdbLeaf);
#endif

        pdeLeaf = (PDIRENT)&(pdbLeaf->bFirst);
    }

    /*  Allocate a buffer from the heap to hold the replacement.
     *  Note that we have to add room for a btree down-pointer,
     *  and that it must be rounded up to a multiple of 4.
     *  (It should already be a multiple of 4, but just in case...)
     *  Hold the buffer in case GHS blocks.
     *
     */
    IncrHoldsBUF (pbufLeaf);  // @76193

    pdeTemp = GHS(ROUND_TO_POW_OF_2(pdeLeaf->cchThisEntry + sizeof(LSN),4),
                  pfrm);

    DecrHoldsBUF(pbufLeaf);  // @76193

    if (pdeTemp == NULL) {
#ifdef DEBUG
        dprintf ("DelEntry: ghs returned null\n");
#endif
        return (ERROR_NOT_ENOUGH_MEMORY);
    }



    /*  Copy the pde to be promoted to the pdeTemp buffer, and
     *  then delete it from the leaf DIRBLK
     */

    BYTE_COPY (pdeLeaf, pdeTemp, pdeLeaf->cchThisEntry);
    Closeup (pdbLeaf, pdeLeaf);

    /*  If we have emptied the leaf DIRBLK, we save its LSN
     *  for later adjustment.  (Remembering the LSN keeps us
     *  out of trouble if we block or yield before adjusting.)
     *
     */
    if (pdbLeaf->offulFirstFree <= DIRSIZP + 1 +
                                 ((PBYTE)&(pdb->bFirst) -
                                 (PBYTE)(pdb)) ){

        lsnAdjust = pdbLeaf->lsnThisDir;

    } else {
        /*  No adjustment will be needed.  */
        lsnAdjust = 0;
    }

    /*  Mark the leaf DIRBLK as dirty (without write-through)
     *  and we're done with it, although we may need to adjust
     *  the btree (which is why we saved lsnAdjust)
     */
    SetDBDirt(pbufLeaf, FALSE, pfrm);

    /*  OK, we've found a replacement DIRENT (pdeTemp).
     *  Before we delete the targeted DIRENT, transfer
     *  its btree down pointer to the replacement DIRENT.
     *  (We also update the replacement's size and flags.)
     *  Then do the deletion with Closeup and call insert
     *  to put the replacement DIRENT into the DIRBLK.
     *  The 0L parameter to insert indicates that the
     *  insertion is not the result of DIRBLK splitting.
     *  We pass FALSE in for the write-through flag.
     *
     *  Note that we have to change the size before the BTP
     *  macro will work correctly.
     */
    pdeTemp->cchThisEntry += sizeof (LSN);
    BTP(pdeTemp) = BTP(pde);
    pdeTemp->fFlags |= DF_BTP;

    Closeup (pdb, pde);

    usRet = insert (pdb, pbuf, pdeTemp, 0L, pfrm);
    RetHeap (pdeTemp);

    SetDBDirt(pbuf, FALSE, pfrm);

    ASSERT_DB (pdb);

    if( usRet != NO_ERROR) {

        /* Got hard error - corrupt directory and return error */
        goto DelEntError;
    }

    /*
     *  if we need to adjust the leaf block from which we
     *  wrested our replacement DIRENT, this is the time
     *  to do it.
     */
    if (lsnAdjust) {
        if (Adjust(lsnAdjust, pfrm) != NO_ERROR) {
            /* Got hard error - mark directory corrupt and return error */
            goto DelEntError;
        }

        /*
         * do not do an ASSERT_DB (pdbLeaf) here since Adjust may have
         * emptied it.
         */
    }

    return (NO_ERROR);

DelEntError:
    return CorruptDir(pfrm);
}
