/*static char *SCCSID = "@(#)allocdb.c	6.3 92/01/31";*/
#define ASWAP0 ASWAP0
#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsh.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 "pbmacros.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmsg.h"

#include "pr_ALLOC.h"
#include "pr_BITMP.h"
#include "pr_CACHE.h"
#include "pr_MISC.h"
#include "pr_debug.h"

#define CSEC_ALLOCDB	500	    /* LOTS of sectors to search for */


/*
 ***	AllocDB - Allocate Directory Block
 *
 *	AllocDB allocates a directory block, which is a multiple
 *	of the sector size.  The directory blocks are 2048 bytes
 *	and they take up an entire cache block.  If you change the
 *	directory block length, you gotta worry about this.
 *
 *	Because we want exactly 4 sectors, and we want them quad aligned,
 *	our job is considerably simplified over the general SFB routine.
 *
 *	NOTE: The following paragraph is no longer valid.
 *	The bit map table must have at least two zero bytes on the end,
 *	which are included in the size count and two more which aren't.
 *
 *	ENTRY	lsnNearby = sector to locate block nearby
 *
 *	EXIT	Returns FALSE if returning a spare dirblk
 *		Returns TRUE if found (reserved or dirblk from available space)
 *		  *plsnFirstFound = first sector got in the range
 *		Yields CPU
 *
 *	HISTORY
 *	    19-Sep-89 by JR (John Rogers, JohnRo@Microsoft)
 *		- Avoid warnings from Microsoft C 5.2 (on slash-slash comments).
 *	    20-Oct-1988 03:43:14 by Jon Parati [jonpa]	Brought up todate
 *	    26-Jul-1988 11:00:16 by Jon Parati [jonpa]	Wrote it
 */

BOOL
AllocDB (lsnNearby, plsnFirstFound, pfrm)
LSN lsnNearby;		/* sector to locate block nearby */
PLSN plsnFirstFound;	/* first sector found in the range */
PFRAME pfrm;
{
    PBANDTAB pbtbVol;
    PVOLTAB pvol = pfrm->pvol;

    /* Conditionally zap buffers */
#ifdef BUFZAP
    DoZap();
#endif

    pbtbVol = &(pvol->bndDirBand);

#ifdef DEBUG
    if ((pbtbVol->lsnBase & ~SECMASK) != 0) {
	    dprintf ("AllocDB: vol bits turned on in pbtbVol->lsnBase\n");
    }
#endif

    /* I MEAN to compare and then decrement! */
    if (pbtbVol->csecFree-- != 0) {
	USHORT offNearby;	/* sector offset into DIRBLK area	*/
	PBYTE pbStart;		/*  ptr to first byte of DIRBLK bitmap	*/
	PBYTE pb;		/*  ptr to scan for nonzero byte	*/
	PBUFNODE pbuf;		/*  ptr to buffer for DIRBLK bitmap	*/
	USHORT cbRem;		/*  remaining bytes to check		*/

	/*
	 * We've got room in the special DIRBLK allocation area.
	 * Find our nearest guy after our locate point.  Don't forget
	 * that there's one bit per DIRBLK, not per sector
	 *
	 * If our locate point isn't in the dirblk area or if we
	 * can't find a block after our locate point then we'll
	 * just take any dirblk.
	 *
	 * lsnNearby (eax) = sector to locate block near to
	 */
	if (pbtbVol->lsnBase <= lsnNearby  &&
		    (lsnNearby -= pbtbVol->lsnBase) <
				    pvol->csecDirBlkBand ) {

	    /* if we get in here, then lsnNearby is <= 16 bits */
	    /* The byte offset of lsnNearby is lsnNearby shifted
	     * right 5 bits (8 bits, each representing four
	     * lsns [i.e. one DIRBLK]).
	     */
#if DIRBLKS / SECSIZE != 4
#error ALLOCDB:	 DIRBLK is not four sectors
#endif

	    offNearby = (USHORT)lsnNearby / (32);
	} else {
	    offNearby = 0;
	}

	/* i = byte index into DIRBLK map for locate point */
/* aldb4: */

	/*
	 * Read in the map
	 */
                                                                    //#85759
	if ((pbStart = LSDBM(&(pbtbVol->scpMap), BP_KEEP, &pbuf, pfrm)) == NULL) {
		*plsnFirstFound = LSN_BOGUS;
	    return TRUE;
   }

	/*  Advance forward from the recommended location until
	 *  we find a non-zero byte or run out of bytes.
	 *
	 */
	cbRem = pbtbVol->cbMapLen - offNearby;
	pb = pbStart + offNearby;
	while ( (cbRem-- != 0) && (*pb == 0) ) {
	    pb++;
	}

	/*  if we didn't find a non-zero byte, let's try
	 *  scanning backwards.
	 *
	 */
	if ( *pb == 0 ) {

	    cbRem = offNearby;
	    pb = pbStart + offNearby - 1;
	    while ( (cbRem-- != 0) && (*pb == 0) ) {
		pb--;
	    }

#ifdef DEBUG
	    if ( *pb == 0 )
		INTERR("AllocDB:  bit not found.");
#endif
	}


	/*
	 * We've got a byte with our bit in it.  We'll brute
	 * force to find the bit
	 *
	 * pbStart (ebx) base addr of DIRBLK bitmap
	 * pb = bitmap byte we chose
	 * pbuf = buffer header addr for DIRBLK bitmap
	 */

	/*  Determine the LSN that corresponds to the first
	 *  non-zero bit in *pb, then clear that bit and mark
	 *  the DIRBLK bitmap buffer as dirty.
	 *
	 */
	{
	    BYTE i;

	    i = (*pb) ^ 0xFF;

	    *plsnFirstFound = ( 8 * (pb - pbStart) + abLCont[i] ) *
				    (DIRBLKS/SECSIZE) + pbtbVol->lsnBase;

#ifdef DEBUG
	    if ( (*pb & (1 << abLCont[i])) == 0 )
		INTERR("AllocDB:  Bit already cleared.");
#endif

	    *pb &= ~(1 << abLCont[i]);
	}

	SetDirt(pb, pbuf, FALSE, pfrm);

	return TRUE;

    }

    /*
     *** Try to Allocate a DIRBLK from general storage.
     *
     *	 We don't have a guy from the reserved area, we'll have to allocate
     *	 from the general pool.  If we can't do this, take one from the
     *	 emergency pool.
     *
     *	 btbDirBlks.csecFree has been decremented one too many
     *
     *	 lsnNearby (EAX) = sector to locate block nearby
     */
/* allgen: */


#ifdef DEBUG
    if( pbtbVol->csecFree != (USHORT)(-1) )
	INTERR( "allocdb: in general pool case but pbtbVol->csecFree != 0" );
#endif

    /* reset the free dirblock count to zero */
    pbtbVol->csecFree = 0;

    if( !ADBBand(pvol->apbndList [USSHIFTRUL(lsnNearby, BANDSHIFT)],
				     lsnNearby, plsnFirstFound, pfrm)) {
	PBANDTAB pbtb;
	PBANDTAB pbtb2;
	UINT i;

	/*
	 ** Couldn't get our dir block in the favored band.  Try the band
	 *  with the most free space.
	 */
/* aldb20: */

	ChooseBand (CSEC_ALLOCDB, (LSN) 0, pvol, &pbtb, &pbtb2);
		
	if (!ADBBand (pbtb, 0L, plsnFirstFound,pfrm)) {
	    /*
	     ** Well, things are looking grim.  We're out of DIRBLKs in our
	     *	special reserve, and we couldn't even find one in one of our
	     *	"emptiest" bands.  Let's look in each band, sequentially.
	     */
/* aldb30: */
	    pbtb = pvol->apbndList [0];
	    for (i = 0; i < pvol->cBands; pbtb = pvol->apbndList [++i]) {
		if( ADBBand( pbtb, 0L, plsnFirstFound,pfrm ) )
		    return TRUE;
/* aldb40: */
	    }

	    /*
	     ** This is the pits.  There's not a dirblk to be had anywhere.
	     *	We're going to draw one from our emergency supply.
	     */

	    {
		PSPAREB pspb;

		/* this is a critical section (only one reserved buffer) */
		SemRequest (&semHotFix,pfrm);

		pspb = RdHF(SEC_SPAREB | pvol->ulVolBits, pfrm);

		if( pspb != NULL ) {

		   /* decrement and check spare count */
		   if (pspb->cdbSpares == 0) {
		       INTERR (PBMSG_ALLOCDB_NOSPARES);
		   }
		   pspb->cdbSpares--;

		   /* show we're consuming spares */
		   pvol->cSpareDirblks = 0;

		   /*
		    *  Since we have the spares block handy, let's
		    *  go ahead and set the dirty bit and update
		    *  the volume table's spares block flags.  This
		    *  will prevent DoVolIO from calling WriteSparesFlag
		    *  (which also uses the hotfix buffer).  Of course,
		    *  the disk might already be dirty, in which case
		    *  this is a no-op.
		    */
		   pspb->bFlag |= SPF_DIRT;
		   pvol->bSpareFlags = pspb->bFlag;

		   WtHF(SEC_SPAREB | pvol->ulVolBits,pfrm);
		}

		/* end of critical section */
		SemClear (&semHotFix);

		*plsnFirstFound = pspb->alsnSpareDirBlks [pspb->cdbSpares];
	    }

	    return FALSE;
	}
    }

    /*
     * Found the blocks normally
     */
    return TRUE;
}
