/*static char *SCCSID = "@(#)rmdir.c    6.4 92/01/31";*/
#define BSWAP2 BSWAP2
#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 "volume.h"
#include "storage.h"
#include "fnode.h"
#include "tables.h"
#include "pb_ifs.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "assert.h"
#include "notify.h"
#include "ea.h"
#include "log.h"

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

// @76193  #ifdef DEBUG
#include "pr_DEBUG.h"
// @76193  #endif

#define EMPTYDIRSIZE    (                                               \
    FIELDOFFSET (DIRBLK, bFirst) +               /* DIRBLK hdr     */   \
    ROUND_TO_POW_OF_2 (sizeof(DIRENT), 4) + 4 +  /* \1\1 DIRENT [..] */ \
    ROUND_TO_POW_OF_2 (sizeof(DIRENT), 4)        /* DF_END DIRENT    */ \
)

/*
 ***    RmDir - Delete Directory
 *
 *      Locking is tricky for deleting directories, since we guarantee
 *      that when someone blocks on a held directory, it will still
 *      be there when they get back.  We do this by locking the parent
 *      first, since we'll be editing it.  When we have the parent locked,
 *      we locate the target directory and block on it until no one
 *      has it locked, held, or is waiting to do so.  We then locate and
 *      remove it's SBDIR entry.
 *
 *      At this point, the target directory is 'unknown' to anyone else,
 *      and they can't find out about it until we release our lock on the
 *      parent.  We can now take our time verifying that the target is
 *      empty and getting it out of the parent directory.  If something
 *      goes wrong and we can't complete the RMDIR it's no problem;
 *      the SBDIR entry will get recreated when the dir is next referenced.
 *
 *      ENTRY   pcp = ptr to path name
 *
 *      RETURNS NO_ERROR if successful, else error code.
 *
 *
 *      HISTORY
 *      17-Aug-1993 by D.Azzarito - [@76193] Change holds for LOG support
 *      15-May-1989 by John Rogers [JohnRo@Microsoft]
 *                      Changed names of rounding macros in <pbmacros.h>.
 *      15-Nov-1988 17:51:30 by Jon Parati [jonpa]
 *                      Added HardErr Handling from JGL's 11/09 source
 *      27-Oct-1988 16:45 by Mark Hitch  [markh]
 *                      Update to JGL's 10/24 source
 *      12-Oct-1988 09:51 by Bill McJohn [t-billm]
 *                      translated from Oct 10 assembly code
 *
 */

USHORT
RmDir (pcp, pfrm)
PCRACKEDPATH pcp;
PFRAME       pfrm;
{

    PDIRBLK pdbParent;      /*  ptr to DIRBLK containing deletee    */
    PDIRBLK pdbTarget;      /*  ptr to deletee's top DIRBLK     */
    PDIRENT pdeTarget;      /*  ptr to DIRENT (in pdbParent) of deletee  */
    PBUFNODE pbufParent;    /*  ptr to buffer with pdbParent    */
    PBUFNODE pbufTarget;    /*  ptr to buffer with pdbTarget    */
    PFNODE pfn;         /*  ptr to FNODE for deletee        */
    LSN lsnFNode;       /*  lsn of FNode of deleted directory   */
    PSUBDIR psbd;       /*  ptr to parent's SUBDIR struct       */
    USHORT usRet;


    /*  Find the parent directory.  Note that here, pdbParent is
     *  set to the top DIRBLK in the BTree;  a later call to
     *  LDE will find the DIRBLK we really want.  Note that
     *  FindDir will put the parent SUBDIR into pfrm->sbdir.
     *
     */

    if ( !FindDir (pcp, &pdbParent, &pbufParent, &psbd,pfrm) ) {
        return GetHardErr(ERROR_PATH_NOT_FOUND);
    }


    ASSERT_DB(pdbParent);
    ASSERT_BUF(pbufParent);
    ASSERT_SBD(psbd);

    /*  We've found the parent.  Release our hold and lock him.  */
    DecrHoldsSBD(psbd);


    if ( (psbd->cusHolds != 0) || (psbd->bFlags != 0) ) {

#ifdef DEBUG
        if (psbd->cusHolds == 0)
            dprintf (__FILE__": Old code failure (bFlags=%d)\n", psbd->bFlags);
        else if (psbd->bFlags == 0)
            dprintf (__FILE__": Old code failure (cusHolds=%d)\n",
                     psbd->cusHolds);
#endif

        /*
         * The directory has a pending lock or is held by others.
         *
         * Note that we'll yield here and the directory can be aribitrarily
         * munged, but it won't be deleted so the topmost block is still there.
         * We just gotta hold the sector in the cache.
         */
        IncrHoldsBUF (pbufParent);  // @76193

        LockDir(psbd,pfrm);

        DecrHoldsBUF (pbufParent);  // @76193
    }

    SetLockSBD(psbd);

    /*  Got the directory.  Look for our file.  If we can't find it,
     *  release our hold on the DIRBLK's buffer and our lock on the
     *  parent SUBDIR and return the error.
     */

    if ( LDE (pdbParent,
              OAC_OF,
              FALSE,
              pbufParent,
              FILE_NAME (pcp),
              &pbufParent,
              &pdbParent,
              &pdeTarget,
              FALSE,                              //#90495
              pfrm) == LDE_NOT_FOUND ) {

        /* ASM rmd902: */
        usRet = ERROR_PATH_NOT_FOUND;
        goto rmd904;
    }

    ASSERT_DB (pdbParent);


    /*
     *  If the located directory entry is not a directory,
     *  return an error.  Same if it's a special entry (i.e. ..)
     */
    if ((pdeTarget->fAttr & ATTR_DIRECTORY) == 0) {
        usRet = ERROR_ACCESS_DENIED;
        goto rmd904;
    }

    /* borrow usRet for a minute */
    if (((usRet = pdeTarget->fFlags) & DF_SPEC) ||
        (usRet & DF_ACL)) {

        usRet = ERROR_ACCESS_DENIED;
        goto rmd904;
    }


    /*  Call RemSBDIR to purge the directory from the
     *  SUBDIR ring structure, the directory look aside
     *  table, and the open find table.  RemSBDIR also
     *  ensures that we have exclusive access to the SUBDIR.
     *  We also need to remember the FNode's LSN for later,
     *  so we can release it.
     *
     */
    lsnFNode = pdeTarget->lsnFNode;

    if (RemSBDIR (lsnFNode,pfrm) == FALSE) {

        /*  We cannot remove the subdir */
        usRet = ERROR_ACCESS_DENIED;
        goto rmd904;
    }

#ifdef DEBUG
    {
        /* see if all the SBDIRs are valid */
        USHORT i;
        PDIRLOOK pdirlook;

        pdirlook = (PDIRLOOK) dcpDLHead.pFwd;

        for (i=0; i < DLCNT; i++) {
            if (pdirlook->cks == 0) {
                /* none here */
                continue;
            }

            /* NOTE: The 386 code only checks that the word prior to the
             * sbdDir != 0x8000.  If ASSERT_SBD checks too much, this might
             * have to be changed back.
             */
            ASSERT_SBD (pdirlook->psbdDir);

            pdirlook = (PDIRLOOK) pdirlook->dcpLinks.pFwd;

        }
    }
#endif

    /*  Make sure the directory is empty--i.e. contains only
     *  "\1\1" and "\0xFF".
     *
     *  Note that we don't worry about reading the FNODE to
     *  find the DIRBLK even 'though we could have gotten
     *  that information from the SUBDIR, since we'll have
     *  to read the FNODE anyway, in order to erase its sig.
     *
     */
    if( (pfn = RdBuf(lsnFNode, BP_TOSS, &pbufTarget,pfrm)) == NULL ) {
        /* Hard Error reading FNode (rmd920:) */
        goto rmd920;
    }

//#114986
#ifndef MINIFSD
        if (pbufTarget->fFlag & BFL_LZREAD) {
          if (LazyRead (pbufTarget, pfrm))
            return NULL;
        }
#endif

    /*
     *  Read the top DIRBLK for the directory we want to delete:
     */
    if( (pdbTarget = RdBuf( pfn->fst.a.aall[0].lsnPhys,
                            BP_TOSS,
                            &pbufTarget,pfrm)) == NULL ) {
        /* Hard error reading directory */
rmd920:
        DecrHoldsBUF( pbufParent );  // @76193
        goto RmDirError;
    }

    ASSERT_DB (pdbTarget);

    /*  If the first DIRENT has a Btree pointer, or if the
     *  bytes used (offulFirstFree) is not equal to the size
     *  of two entries with 1-byte names (\1 and 0xFF), both
     *  without Btree pointers, then the directory is not empty,
     *  and should not be deleted.
     */
    if ( (((PDIRENT)&(pdbTarget->bFirst))->fFlags & DF_BTP) ||
        (pdbTarget->offulFirstFree != EMPTYDIRSIZE) ) {

        /*  This is not an empty directory. (rmd908:) */
        usRet = ERROR_ACCESS_DENIED;
rmd904:
        DecrHoldsBUF( pbufParent );  // @76193
        UnLockDir ();
        return GetHardErr (usRet);
    }

    /*  Clear the DIRBLK's signature, mark it dirty (with
     *  write-through), and release its allocated sectors.
     *
     */
    pdbTarget->sig = 0;

    /*  Make sure FreeDB gets a valid cache block (pbufTarget)
     *  after SetDirt
     */
    IncrHoldsBUF (pbufTarget);  // @76193

   /* write the updated dir block lazily to disk   */
    SetDirt((PBYTE)pdbTarget, pbufTarget, FALSE, pfrm);

    FreeDB(pdbTarget->lsnThisDir,pfrm);
    DecrHoldsBUF (pbufTarget);  // @76193

    /*  Delete the entry representing the deletee in its
     *  parent directory's DIRBLK.
     */
    NotifyCheck(N_DELETE, psbd, pdeTarget, pfrm);

    usRet = DelEntry (pdbParent, pdeTarget, pbufParent, pfrm);

    DecrHoldsBUF(pbufParent);  // @76193

    if (usRet != DELENTRY_NO_ERROR) {

        /* BUGBUG-what should we do here? */
        goto RmDirError;
    }


    /*  We've successfully deleted the entry.  Relocate the FNODE,
     *  delete its signature, and set it dirty (with write through).
     *  Since we're done with the target DIRBLK, we can reuse the
     *  variable pbufTarget.
     */
    if( (pfn = RdBuf(lsnFNode, BP_TOSS, &pbufTarget,pfrm)) == NULL ) {
        /* hard error */
RmDirError:
        UnLockDir();
        return GetHrdErr_();
    }

//#114986
#ifndef MINIFSD
    if (pbufTarget->fFlag & BFL_LZREAD) {
      if (LazyRead (pbufTarget, pfrm))
        goto RmDirError;
    }
#endif

    /*
     * We've deleted the file's space. Now delete the EA stuff
     */
#ifdef EAFIX
    if( (pfn->aiEA.usFNL +
         (USHORT) pfn->aiEA.sp.cbRun) != 0 ) {

        IncrHoldsBUF( pbufTarget );  // @76193

     LOCKSWAP(&swapBSWAP0);

         DelEA( pfn,pfrm );

     UNLOCKSWAP(&swapBSWAP0);

        DecrHoldsBUF( pbufTarget );  // @76193
        /*
         * Note that for now we just
         * ignore out of memory errors and just
         * let chkdsk clean up any orphaned disk
         * space.
         */
    }
#endif

    ASSERT_FN (pfn);
    pfn->sig = 0L;

    SetDirt((PBYTE)pfn, pbufTarget, FALSE, pfrm);

    /*  We've waited until now to de-allocate the FNODE sector
     *  to avoid the chance that an untimely context switch
     *  could cause it to be reassigned to another fnode before
     *  we scanned the SUBDIR list.  Release the space now.
     *
     */

//#114986
    if (!SetBit ( lsnFNode, (ULONG)((sizeof (FNODE))/SECSIZE), pfrm ))
      goto RmDirError;

    UnLockDir ();

    return (NO_ERROR);
}
