/* BitMap.c - BitMap support routines */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1987 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the author.                                           BBS:      */
/* | o  | ||   John Toebes    Dave Baker    Jim Cooper       (919)-471-6436  */
/* |  . |//                                                                  */
/* ======                                                                    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "handler.h"

#define BMSIZE ((((global->dskenv.de_numblks)-(global->dskenv.de_reservedblks))/32)+1)

int AllocBitMap(global)
GLOBAL global;
{
  struct RootBlock *tmpRoot;
  char *tmpblk;
  register int i, size = BMSIZE*4;

  /* allocate memory for the bit map in long words */
  if ((global->bitmap.bm_bitmap = (ULONG *)
       AllocMem(size,MEMF_PUBLIC|MEMF_CLEAR))==NULL) {
    BUG(("AllocBitMap Failed\n"));
    return(NULL);
  }
  BUG(("AllocBitMap - root %ld - bitmap %08lx - size %ld\n",
        global->Root,global->bitmap.bm_bitmap,BMSIZE));
  tmpRoot = (struct RootBlock *)GetBlock(global,global->Root);
  if (tmpRoot->rb_BitMapFlag == 0)
    {
    /* OH NO!  The bitmap is INVALID!!  Let's call the disk validator */
    return(NULL);
    }

  for (i=0; (i<BITMAPSIZE) && (tmpRoot->rb_BitMapPages[i]); i++)
     {
     tmpblk = GetBlock(global,tmpRoot->rb_BitMapPages[i]);
     memcpy((char *)&global->bitmap.bm_bitmap[i*127],&tmpblk[4],min(508,size));
     size -= 508;
     }
  global->bitmap.bm_blocks = i;
  return(-1);
}

int FreeBitMap(global)
GLOBAL global;
{
  if (global->bitmap.bm_bitmap) {
    FreeMem((char *)global->bitmap.bm_bitmap,(BMSIZE*4));
    global->bitmap.bm_blocks = 0;
    global->bitmap.bm_dirty = 0;
    global->bitmap.bm_bitmap = NULL;
    return(-1);
  }
  return(NULL);
}

int CountBlocks(global)
GLOBAL global;
{
  register int block, bit, count = 0;

  /* count number of used blocks.  bit is 0 if used, 1 if free */
  for (block=0; block<BMSIZE; block++)
    for (bit=0; bit<32; bit++) {
      if ((block == BMSIZE-1) && (bit > 29)) continue;
      if (!(global->bitmap.bm_bitmap[block] & (1<<bit))) {
        count++;
      }
    }
  return(count);
}

void DoCheckSum(global,block)
GLOBAL global;
char *block;
{
  struct DirBlock *dblock;
  register ULONG *ptr;
  register ULONG checksum;

  dblock = (struct DirBlock *)block;
  dblock->db_CheckSum = checksum = 0;
  for (ptr = (ULONG *)dblock; ptr < (ULONG *)(dblock+1);)
     checksum -= *ptr++;
  dblock->db_CheckSum = checksum;
}

void WriteBitMap(global)
GLOBAL global;
{
   register int i, j, block;
   long oldbitmap;
   KEY key;
   struct RootBlock *tmproot;
   ULONG *tmpblk;
   int size = BMSIZE*4;

   if (global->bitmap.bm_dirty)
      {
      tmproot = (struct RootBlock *)ModBlock(global,global->Root);
      if (tmproot == NULL)
         {
         BUG(("Unable to locate the Root block\n"));
         return;
         }

      /* Now update the root block to reset the bitmap valid flag */
      oldbitmap = tmproot->rb_BitMapFlag;
      tmproot->rb_BitMapFlag = 0;

      /* Next we need to force the root block out to disk.  Note that we can */
      /* actually get away with not doing this IF it was already invalid on  */
      /* disk */
      if (oldbitmap == NULL)
         {
         /* Ok, so it hasn't been updated - put it out there */
         FlushOne(global, FindBuffer(global,global->Root,0));
         }

      /* Next we get to allocate a new bitmap */
      for (block=0; block<global->bitmap.bm_blocks; block++)
         {
#if 0
         key = AllocateBlock(global,global->Root,0);
         if (key == 0)
            {
            /* For some reason there was no room on the disk so we can't put */
            /* the bitmap onto the disk.  Gee, what's a person to do.  This  */
            /* can occur with the standard file handler.                     */
            BUG(("No room to put bitmap on disk\n"));
            return;
            }

#endif
         /* I know its not kosher to do this brute force, but I'd like to   */
         /* see it WORK before I get fancy                                  */
         tmproot = (struct RootBlock *)GetBlock(global,global->Root);
         if (tmproot == NULL)
            {
            BUG(("Unable to get root block\n"));
            return;
            }
         
         key = tmproot->rb_BitMapPages[block];
         i = FindBuffer(global, key, 0);  /* do not read from disk */
         if (i == -1)
            {
            /* Some catastrophic error finding a buffer for the bitmap       */
            /* Just propagate the error.  However this error is quite        */
            /* unlikely as we are not asking for any I/O                     */
            BUG(("How can this happen? can't find buffer for bitmap\n"));
            return;
            }
         global->blkbuf[i].bb_used = ST_BITMAP;  /* Prevent the checksum */
         global->blkbuf[i].bb_dirty = 1;         /* make sure it gets written */
         tmpblk = (ULONG *)global->blkbuf[i].bb_blk;

         /* clear the destination block (in case we don't use the whole thing) */
         memset((char *)tmpblk,0,sizeof(struct DataBlock));

         /* now copy the bitmap to the destination */
         memcpy((char *)&tmpblk[1],
                (char *)&global->bitmap.bm_bitmap[block*127],
                min(508, size));

         /* Calculate the checksum for the bitmap block we just created */
         for (j=min(127, size/4); j; j--)
            {
            tmpblk[0] -= tmpblk[j];
            }

         /* And cause it to be output IMMEDIATELY */
         FlushOne(global, i);

         size -= 508;
         }

      global->bitmap.bm_dirty = 0;
      tmproot = (struct RootBlock *)ModBlock(global, global->Root);
      if (tmproot == NULL)
         {
         BUG(("Unable to get Root block again!\n"));
         return;
         }
      tmproot->rb_BitMapFlag = -1;
      FlushOne(global, FindBuffer(global,global->Root,0));
   }
}


void SetBlock(global,key)
GLOBAL global;
KEY key;
{
  ULONG bmindex;
  ULONG bmbit;

  bmindex = ((key-global->dskenv.de_reservedblks) >> 5);
  bmbit   = ((key-global->dskenv.de_reservedblks) & 0x1f);
  if (!((global->bitmap.bm_bitmap[bmindex]) & (1<<bmbit)))
     {
     request(global, REQ_GENERAL, "Attempt to allocate used block");
     }
  (global->bitmap.bm_bitmap[bmindex]) &= ~(1<<bmbit);
  global->bitmap.bm_dirty = 1;
}

void FreeBlock(global,key)
GLOBAL global;
KEY key;
{
  ULONG bmindex;
  ULONG bmbit;

  BUG(("FreeBlock: %ld\n", key));
  bmindex = ((key-global->dskenv.de_reservedblks) >> 5);
  bmbit   = ((key-global->dskenv.de_reservedblks) & 0x1f);
  if ((global->bitmap.bm_bitmap[bmindex]) & (1<<bmbit))
     {
     request(global, REQ_GENERAL, "Attempt to free unused block");
     }
  (global->bitmap.bm_bitmap[bmindex]) |= (1<<bmbit);
  global->bitmap.bm_dirty = 1;
}

KEY AllocateBlock(global,key,type)
GLOBAL global;
KEY key;
int type;
{
  KEY trkstart, newkey = 0;
  ULONG bmindex;
  ULONG bmbit;
  register ULONG bmsize = BMSIZE;

  if (!(global->bitmap.bm_bitmap))
    AllocBitMap(global);

  trkstart = (key - (key - ((key / global->dskenv.de_blkspertrk) * global->dskenv.de_blkspertrk)));
  bmindex = ((trkstart - global->dskenv.de_reservedblks) >> 5);
  bmbit   = ((trkstart - global->dskenv.de_reservedblks) & 0x1f);

  while (bmindex<bmsize) {
    if (global->bitmap.bm_bitmap[bmindex] & (1<<bmbit)) break;
    if (++bmbit > 31) bmbit = 0, bmindex++;
  }
  if (bmindex == bmsize) {
    bmbit=0; bmindex=0;
    while (bmindex<bmsize) {
      if (global->bitmap.bm_bitmap[bmindex] & (1<<bmbit)) break;
      if (++bmbit > 31) bmbit = 0, bmindex++;
    }
    if (bmindex == bmsize) {
      request(global,REQ_GENERAL,"Disk FULL on volume");
      return(newkey);
    }
  }
  newkey = (((bmindex)<<5)+bmbit+global->dskenv.de_reservedblks);
  SetBlock(global, newkey);
  /* Force the buffer to be allocated without reading from disk */
  FindBuffer(global, newkey, 0); /* Don't read it in from disk */

  BUG(("AllocateBlock: Key %ld\n", newkey));

  if ((newkey < global->dskenv.de_reservedblks) ||
      (newkey == global->Root)                  ||
      (newkey > global->dskenv.de_numblks))
     {
     BUG(("Allocated block %ld out of range %ld-%ld\n", newkey, global->dskenv.de_reservedblks,global->dskenv.de_numblks));
     request(global, REQ_GENERAL, "Key out of range on volume");
     }
  return(newkey);
}
