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

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char *RCSid = "$Id: udb_ochunk.c,v 1.8 1995/03/21 00:01:33 ambar Exp $";
USE(RCSid);
#endif

/* configure all options BEFORE including system stuff. */
#include	"config.h"
#include	"udb_defs.h"

#include	"externs.h"

#ifdef VMS
#include	<malloc.h>
#include        <types.h>
#include        <file.h>
#include        <unixio.h>
#include        "vms_dbm.h"
#else
#include	"autoconf.h"
#include	<sys/param.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/file.h>

#ifdef HAVE_NDBM
#include	<ndbm.h>
#else
#ifdef HAVE_DBM
#include	<dbm.h>
#else
#include	"myndbm.h"
#endif
#endif

#endif	/* VMS */

#include	"udb.h"

/*
#define		DBMCHUNK_DEBUG
*/

/* 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/bsiz)
#define	BLOCK_OFFSET(block)	(block*bsiz)
#define	BLOCKS_NEEDED(siz)	(siz % bsiz ? (siz / bsiz) + 1 : (siz / bsiz))
#define	BLOCKS_TO_ALLOC(siz)	BLOCKS_NEEDED(siz) + (BLOCKS_NEEDED(siz) >> 3)


/*
  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 {
      off_t off;           /* Where it lives in the chunkfile */
      int siz;             /* How long it really is, in bytes */
      unsigned int blox;   /* How manu blocks it owns now     */
  };

static int dddb_mark();

#define DEFAULT_DBMCHUNKFILE "mudDB"

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

#ifdef	HAVE_DBM
static int dbp = 0;

#else
static DBM *dbp = (DBM *) NULL;

#endif

static FILE *dbf = (FILE *) NULL;
static struct hrec hbuf;

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

static void growbit();

int
dddb_init()
{
    static char *copen = "db_init cannot open ";
    char fnam[MAXPATHLEN];
    struct stat sbuf;
    int fxp;

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

    /* Bah.  Posix-y systems break "a+", so use this gore instead */
    if (((dbf = fopen(fnam, "r+")) == (FILE *) NULL)
	&& ((dbf = fopen(fnam, "w+")) == (FILE *) NULL)) {
	logf(copen, fnam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
    /* Some systems (like HP-UX don't like nonexistent binary files.
     * Therefore, create empty files if none exist already.
     */

    sprintf(fnam, "%s.dir", dbfile);
    fxp = open(fnam, O_CREAT | O_RDWR, 0600);
    if (fxp < 0) {
	logf(copen, fnam, " ", (char *) -1, "\n", NULL);
	return (1);
    }
    (void) close(fxp);

    sprintf(fnam, "%s.pag", dbfile);
    fxp = open(fnam, O_CREAT | O_RDWR, 0600);
    if (fxp < 0) {
	logf(copen, fnam, " ", (char *) -1, "\n", NULL);
	return (1);
    }
    (void) close(fxp);


    /* open hash table */
#ifdef	HAVE_DBM
    if ((dbp = dbminit(dbfile)) < 0) {
	logf(copen, dbfile, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
#else
    if ((dbp = dbm_open(dbfile, O_RDWR | O_CREAT, 0600)) == (DBM *) NULL) {
	logf(copen, dbfile, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
#endif

    /* determine size of chunk file for allocation bitmap */
    sprintf(fnam, "%s.db", dbfile);
    if (stat(fnam, &sbuf)) {
	logf("db_init cannot stat ", fnam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
    /* allocate bitmap */
    growbit(LOGICAL_BLOCK(sbuf.st_size) + DDDB_BITBLOCK);


#ifdef	HAVE_DBM
    key = firstkey();
#else
    key = dbm_firstkey(dbp);
#endif
    while (key.dptr != (char *) NULL) {
#ifdef	HAVE_DBM
	dat = fetch(key);
#else
	dat = dbm_fetch(dbp, key);
#endif
	if (dat.dptr == (char *) NULL) {
	    logf("db_init index inconsistent\n", (char *) NULL);
	    return (1);
	}
	bcopy(dat.dptr, (char *) &hbuf, sizeof(hbuf));	/* alignment */


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


#ifdef	HAVE_DBM
	key = nextkey(key);
#else
	key = dbm_nextkey(dbp);
#endif
    }

    db_initted = 1;
    return (0);
}

dddb_initted()
{
    return (db_initted);
}

dddb_setbsiz(nbsiz)
    int nbsiz;
{
    return (bsiz = nbsiz);
}

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

    if (db_initted)
	return (1);

    /* KNOWN memory leak. can't help it. it's small */
    xp = strsave(fil);
    if (xp == (char *) NULL)
	return (1);
    dbfile = xp;
    return (0);
}

int
dddb_close()
{
    if (dbf != (FILE *) NULL) {
	fclose(dbf);
	dbf = (FILE *) NULL;
    }
#ifndef	HAVE_DBM
    if (dbp != (DBM *) NULL) {
	dbm_close(dbp);
	dbp = (DBM *) NULL;
    }
#endif
    if (bitm != (char *) NULL) {
	free((mall_t) bitm);
	bitm = (char *) NULL;
	bitblox = 0;
    }
    db_initted = 0;
    return (0);
}

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

    /* round up to eight and then some */
    nsiz = (maxblok + 15) & 0xfffffff8;

    if (nsiz <= bitblox)
	return;

    if (bitm == (char *) NULL)
	bitm = (char *) XMALLOC(nsiz / 8, "growbit");
    else
	bitm = (char *) realloc(bitm, nsiz / 8);

    if (bitm == (char *) NULL)
	fatal("db_init cannot grow bitmap ", (char *) -1, "\n", (char *) NULL);

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

    bitblox = nsiz - 8;
}

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

    bcnt = 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(siz)
    int siz;
{
    int bcnt;			/* # of blocks to operate on */
    int lbn;			/* logical block offset */
    int tbcnt;
    int slbn;
    int overthetop = 0;
    int offend = 0;

    lbn = last_free;
    bcnt = BLOCKS_TO_ALLOC(siz);    /* This includes possible headroom */

    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));

		if (offend) {
		    last_free = 0;
		} else {
		    last_free = lbn;
		}
		return (slbn);
	    }
	    lbn++;
	    if (lbn >= bitblox - 32) {
		offend++;
		growbit(lbn + DDDB_BITBLOCK);
	    }
	}
	lbn++;
    }
}

Obj *
dddb_get(nam)
    Objname *nam;
{
    Obj *ret;

    if (!db_initted)
	return ((Obj *) NULL);

    key.dptr = (char *) nam;
    key.dsize = sizeof(Objname);
#ifdef	HAVE_DBM
    dat = fetch(key);
#else
    dat = dbm_fetch(dbp, key);
#endif

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

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

    /* if the file is badly formatted, ret == Obj * 0 */
    if ((ret = objfromFILE(dbf)) == (Obj *) NULL)
	logf("db_get: cannot decode ", nam, "\n", (char *) NULL);
    return (ret);
}

int
dddb_put(obj, nam)
    Obj *obj;
    Objname *nam;
{
    int nsiz;

    if (!db_initted)
	return (1);

    nsiz = obj_siz(obj);

    key.dptr = (char *) nam;
    key.dsize = sizeof(Objname);

#ifdef	HAVE_DBM
    dat = fetch(key);
#else
    dat = dbm_fetch(dbp, key);
#endif

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

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

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

#ifdef	DBMCHUNK_DEBUG
	    printf("put: moving obj %d, owned %d blox, required %d, siz %d\n",
		*(unsigned int *) nam, hbuf.blox, BLOCKS_NEEDED(nsiz), nsiz);
#endif
	    /* mark free in bitmap */
	    dddb_mark(LOGICAL_BLOCK(hbuf.off), hbuf.blox, 0);

	    hbuf.off = BLOCK_OFFSET(dddb_alloc(nsiz));
	    hbuf.siz = nsiz;
	    hbuf.blox = BLOCKS_TO_ALLOC(nsiz);
#ifdef	DBMCHUNK_DEBUG
	    printf("put: %d moved to offset %d, size %d\n",
		*(unsigned int *) nam, hbuf.off, hbuf.siz);
#endif
	} else {
	    hbuf.siz = nsiz;
#ifdef	DBMCHUNK_DEBUG
	    printf("put: %d replaced within offset %d, size %d owns %d blox\n",
		*(unsigned int *) nam, hbuf.off, hbuf.siz, hbuf.blox);
#endif
	}
    } else {
	hbuf.off = BLOCK_OFFSET(dddb_alloc(nsiz));
	hbuf.siz = nsiz;
	hbuf.blox = BLOCKS_TO_ALLOC(nsiz);
#ifdef	DBMCHUNK_DEBUG
	printf("put: %d (new) at offset %d, size %d owns %d blox\n",
	    *(unsigned int *) nam, hbuf.off, hbuf.siz, hbuf.blox);
#endif
    }


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

#ifdef	HAVE_DBM
    if (store(key, dat)) {
	logf("db_put: can't store ", nam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
#else
    if (dbm_store(dbp, key, dat, DBM_REPLACE)) {
	logf("db_put: can't dbm_store ", nam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
#endif

#ifdef	DBMCHUNK_DEBUG
    printf("%d offset %d size %d owns %d blox\n",
	*(unsigned int *) nam, hbuf.off, hbuf.siz, hbuf.blox);
#endif
    if (fseek(dbf, (long) hbuf.off, 0)) {
	logf("db_put: can't seek ", nam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
    if (objtoFILE(obj, dbf) != 0 || fflush(dbf) != 0) {
	logf("db_put: can't save ", nam, " ", (char *) -1, "\n", (char *) NULL);
	return (1);
    }
    return (0);
}

int
dddb_check(nam)
    char *nam;
{

    if (!db_initted)
	return (0);

    key.dptr = nam;
    key.dsize = strlen(nam) + 1;
#ifdef	HAVE_DBM
    dat = fetch(key);
#else
    dat = dbm_fetch(dbp, key);
#endif

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

int
dddb_del(nam)
    Objname *nam;
{

    if (!db_initted)
	return (-1);

    key.dptr = (char *) nam;
    key.dsize = sizeof(Objname);
#ifdef	HAVE_DBM
    dat = fetch(key);
#else
    dat = dbm_fetch(dbp, key);
#endif


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

    /* drop key from db */
#ifdef	HAVE_DBM
    if (delete(key)) {
#else
    if (dbm_delete(dbp, key)) {
#endif
	logf("db_del: can't delete key ", nam, "\n", (char *) NULL);
	return (1);
    }
    /* mark free space in bitmap */
    dddb_mark(LOGICAL_BLOCK(hbuf.off), hbuf.blox, 0);
#ifdef	DBMCHUNK_DEBUG
    printf("del: %d free offset %d, size %d owned %d blox\n",
	*(unsigned int *) nam, hbuf.off, hbuf.siz, hbuf.blox);
#endif

    /* mark object dead in file */
    if (fseek(dbf, (long) hbuf.off, 0))
	logf("db_del: can't seek ", nam, " ", (char *) -1, "\n", (char *) NULL);
    else {
	fprintf(dbf, "delobj");
	fflush(dbf);
    }
    return (0);
}
