/*
    Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/


#include    "cool.h"
#include    "db_config.h"
#include    "proto.h"
#include    "netio.h"
#include    "db_setup.h"

/* default block size to use for bitmapped chunks */
#define DDDB_BLOCK  256
/* bitmap growth increment in BLOCKS not bytes (512 is 64 BYTES) */
#define DDDB_BITBLOCK   512


#define LOGICAL_BLOCK(off)  ((off) / DDDB_BLOCK)
#define BLOCK_OFFSET(block) ((block) * DDDB_BLOCK)
#define BLOCKS_NEEDED(siz)  ((siz) % DDDB_BLOCK ? ((siz) / DDDB_BLOCK) + 1 : ((siz) / DDDB_BLOCK))

/*
dbm-based object storage routines. Somewhat trickier than the default
directory hash. a free space list is maintained as a bitmap of free
blocks, and an object-pointer list is maintained in a dbm database.
*/
struct hrec {
  long off;
  int siz;
};

static void dddb_mark (long lbn, int siz, int taken);

static const char *dbfile = DEFAULT_DBMCHUNKFILE;
static int db_initted = 0;
static int last_free = 0;       /* last known or suspected free block */

static DBM *dbp = (DBM *) 0;

static FILE *dbf = (FILE *) 0;
static struct hrec hbuf;
static int startrav = 0;

static char *bitm = (char *) 0;
static int bitblox = 0;
static datum dat;
static datum key;

static void growbit (int maxblok);

int dddb_init (int db_must_exist)
{
  char fnam[MAXPATHLEN];
  struct stat sbuf;

  /* now open chunk file */
  sprintf (fnam, "%s.chunk", dbfile);

  dbf = fopen (fnam, "r+b");
  if (dbf == (FILE *) 0 && !db_must_exist) {
    dbf = fopen (fnam, "w+b");
  }
  if (dbf == (FILE *) 0) {
    writelog ();
    fprintf (stderr, "db_init:  couldn't open chunkfile - %s", fnam);
    perror (":  ");
	return (1);
  }

  /* open hash table */
  if ((dbp = dbm_open (dbfile, O_RDWR | O_CREAT | O_BINARY, S_IREAD | S_IWRITE)) == (DBM *) 0) {
    writelog ();
    fprintf (stderr, "db_init:  couldn't open ndbm database - %s", dbfile);
    perror (":  ");
    return (1);
  }

  /* determine size of chunk file for allocation bitmap */
  sprintf (fnam, "%s.chunk", dbfile);
  if (stat (fnam, &sbuf)) {
    writelog ();
    fprintf (stderr, "db_init:  cannot stat %s", fnam);
    perror ("");
    return (1);
  }

  /* allocate bitmap */
  growbit (LOGICAL_BLOCK (sbuf.st_size) + DDDB_BITBLOCK);


  key = dbm_firstkey (dbp);
  while (key.dptr != (char *) 0) {
    dat = dbm_fetch (dbp, key);
    if (dat.dptr == (char *) 0) {
      writelog ();
      fprintf (stderr, "db_init:  index inconsistent\n");
      return (1);
    }
    bcopy (dat.dptr, &hbuf, sizeof (hbuf));     /* alignment */


    /* mark it as busy in the bitmap */
    dddb_mark (LOGICAL_BLOCK (hbuf.off), hbuf.siz, 1);


    key = dbm_nextkey (dbp);
  }

  db_initted = 1;
  return (0);
}



int dddb_initted (void)
{
  return (db_initted);
}


int dddb_setfile (const char *fil)
{
  char *xp;

  if (db_initted)
    return (1);

  /* KNOWN memory leak. can't help it. it's small */
  xp = (char *) malloc ((unsigned) strlen (fil) + 1);
  if (xp == (char *) 0)
    return (1);
  (void) strcpy (xp, fil);
  dbfile = xp;
  return (0);
}



int dddb_close (void)
{
  if (dbf != (FILE *) 0) {
    fclose (dbf);
    dbf = (FILE *) 0;
  }
  if (dbp != (DBM *) 0) {
    dbm_close (dbp);
    dbp = (DBM *) 0;
  }
  if (bitm != (char *) 0) {
    free ((void *) bitm);
    bitm = (char *) 0;
    bitblox = 0;
  }
  db_initted = 0;
  return (0);
}




/* grow the bitmap to given size */
static void growbit (int maxblok)
{
  int nsiz;
  char *nbit;

  /* round up to eight and then some */
  nsiz = (maxblok + 8) + (8 - (maxblok % 8));

  if (nsiz <= bitblox)
    return;

  /* this done because some old realloc()s are busted */
  nbit = (char *) malloc ((unsigned) (nsiz / 8));
  if (bitm != (char *) 0) {
    bcopy (bitm, nbit, (bitblox / 8) - 1);
    free ((void *) bitm);
  }
  bitm = nbit;

  if (bitm == (char *) 0)
    panic ("db_init cannot grow bitmap");

  bzero (bitm + (bitblox / 8), ((nsiz / 8) - (bitblox / 8)) - 1);
  bitblox = nsiz - 8;
}



static void dddb_mark (long lbn, int siz, int taken)
{
  int bcnt;

  bcnt = BLOCKS_NEEDED (siz);

  /* remember a free block was here */
  if (!taken)
    last_free = lbn;

  while (bcnt--) {
    if (lbn >= bitblox - 32)
      growbit (lbn + DDDB_BITBLOCK);

    if (taken)
      bitm[lbn >> 3] |= (1 << (lbn & 7));
    else
      bitm[lbn >> 3] &= ~(1 << (lbn & 7));
    lbn++;
  }
}





static int dddb_alloc (int siz)
{
  int bcnt;                     /* # of blocks to operate on */
  int lbn;                      /* logical block offset */
  int tbcnt;
  int slbn;
  int overthetop = 0;

  lbn = last_free;

  bcnt = BLOCKS_NEEDED(siz);

  while (1) {
    if (lbn >= bitblox - 32) {
      /* only check here. can't break around the top */
      if (!overthetop) {
        lbn = 0;
        overthetop++;
      } else {
        growbit (lbn + DDDB_BITBLOCK);
      }
    }

    slbn = lbn;
    tbcnt = bcnt;

    while (1) {
      if ((bitm[lbn >> 3] & (1 << (lbn & 7))) != 0)
        break;

      /* enough free blocks - mark and done */
      if (--tbcnt == 0) {
        for (tbcnt = slbn; bcnt > 0; tbcnt++, bcnt--)
          bitm[tbcnt >> 3] |= (1 << (tbcnt & 7));

        last_free = lbn;
        return (slbn);
      }

      lbn++;
      if (lbn >= bitblox - 32)
        growbit (lbn + DDDB_BITBLOCK);
    }
    lbn++;
  }
}




Object *dddb_get (int oid)
{
  Object *ret;

  if (!db_initted)
    return ((Object *) 0);

  key.dptr = (char *) &oid;
  key.dsize = sizeof (oid);
  dat = dbm_fetch (dbp, key);

  if (dat.dptr == (char *) 0)
    return ((Object *) 0);
  bcopy (dat.dptr, &hbuf, sizeof (hbuf));

  /* seek to location */
  if (fseek (dbf, (long) hbuf.off, 0))
    return ((Object *) 0);

  /* if the file is badly formatted, ret == Object * 0 */
  if ((ret = unpack_object (dbf)) == (Object *) 0) {
    writelog ();
    fprintf (stderr, "db_get: cannot decode #%d\n", oid);
  }
  return (ret);
}




int dddb_put (Object * obj, int oid)
{
  int nsiz;

  if (!db_initted)
    return (1);

  nsiz = size_object (obj);
  key.dptr = (char *) &oid;
  key.dsize = sizeof (oid);

  dat = dbm_fetch (dbp, key);

  if (dat.dptr != (char *) 0) {

    bcopy (dat.dptr, &hbuf, sizeof (hbuf));     /* align */

    if (BLOCKS_NEEDED (nsiz) > BLOCKS_NEEDED (hbuf.siz)) {

#ifdef  DBMCHUNK_DEBUG
      printf ("put: #%d old %d < %d - free %d\n", oid, hbuf.siz, nsiz,
        hbuf.off);
#endif
      /* mark free in bitmap */
      dddb_mark (LOGICAL_BLOCK (hbuf.off), hbuf.siz, 0);

      hbuf.off = BLOCK_OFFSET (dddb_alloc (nsiz));
      hbuf.siz = nsiz;
#ifdef  DBMCHUNK_DEBUG
      printf ("put: #%d moved to offset %d, size %d\n", oid, hbuf.off,
        hbuf.siz);
#endif
    } else {
      hbuf.siz = nsiz;
#ifdef  DBMCHUNK_DEBUG
      printf ("put: #%d replaced within offset %d, size %d\n", oid, hbuf.off,
        hbuf.siz);
#endif
    }
  } else {
    hbuf.off = BLOCK_OFFSET (dddb_alloc (nsiz));
    hbuf.siz = nsiz;
#ifdef  DBMCHUNK_DEBUG
  printf ("put: #%d - nsiz %d\n", oid, nsiz);
#endif
#ifdef  DBMCHUNK_DEBUG
    printf ("put: #%d (new) at offset %ld, size %d\n", oid, hbuf.off,
      hbuf.siz);
#endif
  }


  /* make table entry */
  dat.dptr = (char *) &hbuf;
  dat.dsize = sizeof (hbuf);

  if (dbm_store (dbp, key, dat, DBM_REPLACE)) {
    writelog ();
    fprintf (stderr, "db_put: can't dbm_store #%d\n", oid);
    return (1);
  }

#ifdef  DBMCHUNK_DEBUG
  printf ("#%d offset %d size %d\n", oid, hbuf.off, hbuf.siz);
#endif
  if (fseek (dbf, (long) hbuf.off, 0)) {
    writelog ();
    fprintf (stderr, "db_put: can't seek #%d", oid);
    perror ("");
    return (1);
  }

  if (pack_object (obj, dbf) != 0 || fflush (dbf) != 0) {
    writelog ();
    fprintf (stderr, "db_put: can't save #%d\n", oid);
    return (1);
  }
  return (0);
}




int dddb_check (int oid)
{

  if (!db_initted)
    return (0);

  key.dptr = (char *) &oid;
  key.dsize = sizeof (oid);
  dat = dbm_fetch (dbp, key);

  if (dat.dptr == (char *) 0)
    return (0);
  return (1);
}




int dddb_del (int oid, int flg)
{

  if (!db_initted)
    return (-1);

  key.dptr = (char *) &oid;
  key.dsize = sizeof (oid);
  dat = dbm_fetch (dbp, key);


  /* not there? */
  if (dat.dptr == (char *) 0)
    return (0);
  bcopy (dat.dptr, &hbuf, sizeof (hbuf));

  /* drop key from db */
  if (dbm_delete (dbp, key)) {
    writelog ();
    fprintf (stderr, "db_del: can't delete key #%d\n", oid);
    return (1);
  }

  /* mark free space in bitmap */
  dddb_mark (LOGICAL_BLOCK (hbuf.off), hbuf.siz, 0);
#ifdef  DBMCHUNK_DEBUG
  printf ("del: #%d free offset %d, size %d\n", oid, hbuf.off, hbuf.siz);
#endif

  /* mark object dead in file */
  if (fseek (dbf, (long) hbuf.off, 0)) {
    writelog ();
    fprintf (stderr, "db_del: can't seek #%d", oid);
    perror ("");
  } else {
    fprintf (dbf, "delobj");
    fflush (dbf);
  }

  return (0);
}