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

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

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

#ifdef	OLDDBM
#include	<dbm.h>
#include	<sys/errno.h>
extern	int	errno;
#else
#include	<ndbm.h>
#endif
#endif VMS


#include	<sys/time.h>
#include	"config.h"
#include	"cool.h"
#include	"proto.h"
#include	"sys_proto.h"
#include	"netio.h"
#include	"db_setup.h"

#ifdef PROTO
int	stat(const char *path, struct stat *buf);
#endif

/* 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))



/*
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;
	int	siz;
};

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

static	const 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	OLDDBM
static	int	dbp = 0;
#else
static	DBM	*dbp = (DBM *)0;
#endif

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
dddb_init(int db_must_exist)
{
	char	fnam[MAXPATHLEN];
	struct	stat	sbuf;

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

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



	/* open hash table */
#ifdef	OLDDBM
	if((dbp = dbminit(dbfile)) < 0) {
		int	fxp;

		/* dbm (old) is stupid about nonexistent files */
		if(errno != ENOENT) {
			writelog();
			fprintf(stderr, "db_init:  couldn't open %s", dbfile);
			perror(":  ");
			return(1);
		}

		sprintf(fnam,"%s.dir",dbfile);
		fxp = open(fnam,O_CREAT|O_EXCL|O_WRONLY,0600);
		if(fxp < 0) {
			writelog();
			fprintf(stderr, "db_init:  couldn't open %s", fnam);
			perror(":  ");
			return(1);
		}
		(void)close(fxp);

		sprintf(fnam,"%s.pag",dbfile);
		fxp = open(fnam,O_CREAT|O_EXCL|O_WRONLY,0600);
		if(fxp < 0) {
			writelog();
			fprintf(stderr, "db_init:  couldn't open %s", fnam);
			perror(":  ");
			return(1);
		}
		(void)close(fxp);

		/* one MORE try */
		if((dbp = dbminit(dbfile)) < 0) {
			writelog();
			fprintf(stderr, "db_init:  couldn't open %s", dbfile);
			perror(":  ");
			return(1);
		}
	}
#else
	if((dbp = dbm_open(dbfile,O_RDWR|O_CREAT,0600)) == (DBM *)0) {
		writelog();
		fprintf(stderr, "db_init:  couldn't open %s", dbfile);
		perror(":  ");
		return(1);
	}
#endif

	/* determine size of chunk file for allocation bitmap */
	sprintf(fnam,"%s.db",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);


#ifdef	OLDDBM
	key = firstkey();
#else
	key = dbm_firstkey(dbp);
#endif
	while(key.dptr != (char *)0) {
#ifdef	OLDDBM
		dat = fetch(key);
#else
		dat = dbm_fetch(dbp,key);
#endif
		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);


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

	db_initted = 1;
	return(0);
}



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


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


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

#ifndef	OLDDBM
	if(dbp != (DBM *)0) {
		dbm_close(dbp);
		dbp = (DBM *)0;
	}
#endif
	if(bitm != (char *)0) {
		free((mall_t)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((mall_t)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(off_t 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 = siz % bsiz ? (siz / bsiz) + 1 : (siz / bsiz);

	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);
#ifdef	OLDDBM
	dat = fetch(key);
#else
	dat = dbm_fetch(dbp,key);
#endif

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

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

	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 (new) at offset %d, size %d\n",oid,hbuf.off,hbuf.siz);
#endif
	}


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

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

#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);
#ifdef	OLDDBM
	dat = fetch(key);
#else
	dat = dbm_fetch(dbp,key);
#endif

	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);
#ifdef	OLDDBM
	dat = fetch(key);
#else
	dat = dbm_fetch(dbp,key);
#endif


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

	/* drop key from db */
#ifdef	OLDDBM
	if(delete(key)) {
#else
	if(dbm_delete(dbp,key)) {
#endif
		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);
}



int
dddb_travstart(void)
{
	startrav = 0;
	return(0);
}



int
dddb_traverse(void)
{
	if(!db_initted)
		return(-1);

	if(!startrav) {
#ifdef	OLDDBM
		key = firstkey();
#else
		key = dbm_firstkey(dbp);
#endif
		startrav = 1;
	} else
#ifdef	OLDDBM
		key = nextkey(key);
#else
		key = dbm_nextkey(dbp);
#endif
	if(key.dptr == (char *)0)
		return(-1);
	return *((int *) key.dptr);
}



int
dddb_travend(void)
{
	startrav = 0;
	return(0);
}





int
dddb_backup(char *out)
{
	FILE	*ou;
	char	vuf[BUFSIZ];
	int	tor;

	if(!db_initted)
		return(1);

	if((ou = fopen(out,"w")) == (FILE *)0) {
		writelog();
		fprintf(stderr, "db_backup:  couldn't open %s", out);
		perror("");
		return(1);
	}


#ifdef	OLDDBM
	key = firstkey();
#else
	key = dbm_firstkey(dbp);
#endif
	while(key.dptr != (char *)0) {
#ifdef	OLDDBM
		dat = fetch(key);
#else
		dat = dbm_fetch(dbp,key);
#endif
		if(dat.dptr == (char *)0) {
			writelog();
			fprintf(stderr, "db_init:  index inconsistent\n");
			continue;
		}

		bcopy(dat.dptr,&hbuf,sizeof(hbuf));

		if(fseek(dbf,(long)hbuf.off,0)) {
			writelog();
			fprintf(stderr, "db_backup: can't seek #%d\n",
			    *((int *) key.dptr));
			perror("");
		} else {

			/* copy out the object, sans interpretation */
			while(hbuf.siz > 0) {
				if((tor = hbuf.siz) > sizeof(vuf))
					tor = sizeof(vuf);
				if(fread(vuf,sizeof(char),tor,dbf) <= 0) {
					writelog();
					fprintf(stderr, "db_backup:  can't read #%d", *((int *) key.dptr));
					break;
				}

				/* what are we gonna do if it fails, anyhow? */
				fwrite(vuf,sizeof(char),tor,ou);
				hbuf.siz -= tor;
			}
		}

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

	fclose(ou);
	return(0);
}