/
teeny/db/
teeny/dbm/
teeny/doc/
teeny/includes/
#include <stdio.h>
#include <strings.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>

#include "teeny.h"
#include "db.h"

/*
Copyright(C) 1990, Andrew Molitor, All Rights Reserved.
This software may be freely used, modified, and redistributed,
as long as this copyright message is left intact, and this
software is not used to develop any commercial product, or used
in any product that is provided on a pay-for-use basis.

No warranties whatsoever. This is not guaranteed to compile, nor,
in the event that it does compile to binaries, are these binaries
guaranteed to perform any function whatsoever.
*/

/*

	Primary DB handling routines. Uses the caching routines and stuff to
access object data, etc.. You know.

	read_descriptors() is the function to call to bring up an existing DB.
initialize_db() will bring up a blank DB.

Provides the following funtions to implement the DB:

	get_int_elt(), get_str_elt(), get_lock_elt()

	These return various data from an object. Not that the latter two
return literal pointers to the object data, so a) don't spam it, and b) it may
be swapped out, so use it soon, or hide it away somewhere safe.

	set_int_elt(), set_str_elt(), set_lock_elt()

	These store data away in objects. Note that set_str_elt() *does*
allocate fresh new memory for the string, but set_lock_elt() does *not*
allocate memory, since bool_parse() assembles locks into freshly allocated
memory.

	animate(), deanimate()

	These set and reset the ALIVE flags on objects. Something of an
anachronism left over from an earlier design.

	exists_object()

	Probes the DB, returns 1 if the object does exist, 0 if not.


	create_obj(), destroy_obj()

	do the obvious things.

	do_top()

	returns 1 + the largest object number of anything in the DB. Note that
there are quite possibly garbage objects with lower object numbers. Use
exists_object().

*/
/*
#define LOADDEBUG
*/
/*
	This is what we use to reference objects by number, a big array of
pointers to descriptors. Garbage object slots are indicated by a NULL entry.
Ooog. This is way inefficient. I don't like it.

*/

struct dsc **main_index;

int total_objects;	/* Number of real objects/garbage objects */
int actual_objects;	/* Number of real objects */
int garbage_count;	/* number of garbage objects */
int slack;		/* Space after all the real objects/garbage in the */
			/* main index, in # of objects */
int growth_increment;	/* Amount to grow the main index, in # of objects */

struct obj_data *lookup_obj();

char *realloc();


db_top()
{
	return(total_objects);
}

/*
	Routines to dig elements out of objects. They return an error status
of 0 if OK, -1 if not.

*/

get_str_elt(num,elt,ret)

int num,elt;
char **ret;

{
	struct dsc *thedsc;
	struct obj_data *theobj;

	if(elt == NAME && num >=0  && num < total_objects){
		thedsc = main_index[num];
		if(thedsc == NULL){
			return(-1);
		} else {
			*ret = DSC_NAME(thedsc);
			return(0);
		}
	}

	/* It's not in the descriptor, so go get the object data */

	if( (theobj = lookup_obj(num)) == NULL){
		return(-1);
	}

	switch(elt){
	case SUC:
		*ret = theobj->suc;
		break;
	case OSUC:
		*ret = theobj->osuc;
		break;
	case FAIL:
		*ret = theobj->fail;
		break;
	case OFAIL:
		*ret = theobj->ofail;
		break;
	case DESC:
		*ret = theobj->desc;
		break;
	default:
		warning("get_str_elt","invalid element code");
		return(-1);
		break;
	}
	return(0);
}

int get_int_elt(num,elt,ret)

int num,elt;
int *ret;

{
	struct dsc *thedsc;
	struct obj_data *theobj;

	/* Check for things that live right in the descriptor */

	if((elt == FLAGS  || elt == NEXT) && num >= 0 && num < total_objects){
		thedsc = main_index[num];
		if(thedsc == NULL){
			return(-1);
		} else {
			switch(elt){
			case FLAGS:
				*ret = DSC_FLAGS(thedsc);
				break;
			case NEXT:
				*ret = thedsc->list_next;
				break;
			}
			return(0);
		}
	}

	/* It's not in the descriptor, so go get the object data */

	if( (theobj = lookup_obj(num)) == NULL ){
		return(-1);
	}

	switch(elt){
	case PENNIES:
		*ret = theobj->pennies;
		break;
    	case LOC:
		*ret = theobj->loc;
		break;
    	case OWNER:
		*ret = theobj->owner;
		break;

	/* case HOME:  -- same as DROPTO */
	/* case DESTINATION:  -- same as DROPTO */
	case DROPTO:
		*ret = theobj->home_drpto;
		break;
	case CONTENTS:
		*ret = theobj->contents;
		break;
	case EXITS:
		*ret = theobj->exits;
		break;
#ifdef TIMESTAMPS
	case TIMESTAMP:
		*ret = theobj->timestamp;
		break;
#endif
	default:
		warning("get_int_elt","invalid element code");
		return(-1);
		break;
	}
	return(0);
}

get_lock_elt(num,elt,ret)

int num,elt;
int **ret;

{
	struct obj_data *theobj;
	if(elt != LOCK){
		warning("get_lock_elt","field code other that LOCK!");
		return(-1);
	}
	if( (theobj = lookup_obj(num)) == NULL ){
		return(-1);
	}

	*ret = theobj->lock;
	return(0);
}

/*
	These set the values of elements. They return 0 is OK, -1 if not.
*/

set_str_elt(num,elt,value)

int num,elt;
char *value;

{
	struct dsc *thedsc;
	struct obj_data *theobj;
	int newsize;
	int valsize;
	char **thestr;
	extern int cache_usage;

	if(num < 0 || num >= total_objects)
		return(-1);

	/* Check the descriptor resident field first */

	if(elt == NAME){
		thedsc = main_index[num];
		if(thedsc == NULL){
			return(-1);
		} else {
			thestr = &(DSC_NAME(thedsc));
		}
		if(value == NULL){
			valsize = 0;
		} else {
			valsize = strlen(value);
		}
	} else {

	/* It's not in the descriptor, so go get the object data */

		if(num >= total_objects || (thedsc = main_index[num]) == NULL){
			return(-1);
		}
		if( (theobj = lookup_obj(num)) == NULL){
			return(-1);
		}

		switch(elt){
		case SUC:
			thestr = &(theobj->suc);
			break;
		case OSUC:
			thestr = &(theobj->osuc);
			break;
		case FAIL:
			thestr = &(theobj->fail);
			break;
		case OFAIL:
			thestr = &(theobj->ofail);
			break;
		case DESC:
			thestr = &(theobj->desc);
			break;
		default:
			warning("set_str_elt","invalid element code");
			return(-1);
			break;
		}

       		/* Recompute the on disk size. */

		if(value == NULL)
			valsize = 0;
		else
			valsize = strlen(value);

		if(*thestr == NULL){
			newsize = DSC_SIZE(thedsc) + valsize;
		} else {
			newsize = (DSC_SIZE(thedsc) - strlen(*thestr))
				+ valsize;
		}

		if(newsize > MAX_DISK_SIZE){
			warning("set_str_elt"
				,"object grew too large for disk!");
			return(-1);
		}
		cache_usage = (cache_usage - DSC_SIZE(thedsc)) + newsize;
		DSC_SIZE(thedsc) = newsize;
		DSC_FLAGS(thedsc) |= DIRTY;
	}

	/* Free old value. ty_free() copes with NULL */

	ty_free(*thestr);
	if(value == NULL){
		*thestr = NULL;
	} else {
		*thestr = ty_malloc(valsize + 1,"set_str_elt");
		strcpy(*thestr,value);
	}

	return(0);
}

set_int_elt(num,elt,value)

int num,elt;
int value;

{
	struct dsc *thedsc;
	struct obj_data *theobj;

	/* Check for things that live right in the descriptor */

	if((elt == FLAGS  || elt == NEXT) && num >= 0 && num < total_objects){
		thedsc = main_index[num];
		if(thedsc == NULL){
			return(-1);
		} else {
			switch(elt){
			case FLAGS:
				DSC_FLAGS(thedsc) = value;
				break;
			case NEXT:
				thedsc->list_next = value;
				break;
			}
			return(0);
		}
	}

	/* It's not in the descriptor, so go get the object data */

	if(num >= total_objects || (thedsc = main_index[num]) == NULL){
		return(-1);
	}
	if( (theobj = lookup_obj(num)) == NULL ){
		return(-1);
	}

	switch(elt){
	case PENNIES:
		theobj->pennies = value;
		break;
	case LOC:
		theobj->loc = value;
		break;
	case OWNER:
		theobj->owner = value;
		break;

	/* case HOME:  -- same as DROPTO */
	case DROPTO:
		theobj->home_drpto = value;
		break;
	case CONTENTS:
		theobj->contents = value;
		break;
	case EXITS:
		theobj->exits = value;
		break;
#ifdef TIMESTAMPS
	case TIMESTAMP:
		theobj->timestamp = value;
		break;
#endif
	default:
		warning("set_int_elt","invalid element code");
		return(-1);
		break;
	}
	DSC_FLAGS(thedsc) |= DIRTY;
	return(0);
}

/*
	Unlike set_str_elt(), this DOES NOT allocate memory to put the lock in
to. bool_parse() does this for us, so we are free to use the memory it gave us
as permanenet storage here.

*/
set_lock_elt(num,elt,value)

int num,elt;
int *value;

{
	struct obj_data *theobj;
	struct dsc *thedsc;
	int valsize;
	int oldsize;
	extern int cache_usage;

	if(elt != LOCK){
		warning("set_lock_elt","bad element type code");
		return(-1);
	}
	if( (theobj = lookup_obj(num)) == NULL){
		return(-1);
	}
	thedsc = theobj->descriptor;

	/* Size computations are actually all low by 1 == basic on-disk
		lock overhead. */

	if(value == NULL){
		valsize = 0;
	} else {
		valsize = value[0] * sizeof(int);
	}

	if(theobj->lock == NULL){
		oldsize = 0;
	} else {
		oldsize = (theobj->lock)[0] * sizeof(int);
	}
	DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;

	ty_free((char *)theobj->lock);
	theobj->lock = value;
	DSC_FLAGS(thedsc) |= DIRTY;
	return(0);
}

/*
	Destroy an existing object.
*/

void destroy_obj(num)

int num;

{
	struct dsc *thedsc;
	struct obj_data *theobj;

	if( (theobj = lookup_obj(num)) == NULL ){
		return;
	}

	cache_delete(theobj);  /* Get it out of cache */

	/* Free all the data on the object */

	ty_free( (char *) theobj->lock);

	ty_free(theobj->suc);
	ty_free(theobj->osuc);
	ty_free(theobj->fail);
	ty_free(theobj->ofail);
	ty_free(theobj->desc);

	thedsc = theobj->descriptor;
	ty_free(DSC_NAME(thedsc));

	if(theobj->offset != -1){  /* If this object owns a disk chunk... */

		/* Give the chunk back to the system */

		DSC_CHUNK(thedsc) = theobj->offset;
		DSC_SIZE(thedsc) = theobj->chunk_size;
		free_chunk(thedsc);
	} else {
		free_descriptor(thedsc);
	}
	ty_free( (char *) theobj);
	actual_objects--;
	garbage_count++;
	main_index[num] = (struct dsc *) NULL;
	return;
}

/*
	Create a new object. Returns the object number.
*/

create_obj(type)

int type;

{
	struct obj_data *theobj;
	struct dsc *thedsc;
	int num;

	thedsc = get_descriptor();
	theobj = (struct obj_data *) ty_malloc(sizeof(struct obj_data)
		,"create_obj");

	DSC_DATA(thedsc) = theobj;
	DSC_SIZE(thedsc) = INITIAL_SIZE;
	DSC_NAME(thedsc) = NULL;
	DSC_FLAGS(thedsc) = type | IN_MEMORY | DIRTY;
	thedsc->list_next = -1;

	theobj->offset = -1;  /* This object has NO on-disk chunk yet */

	/* Make it a grey box */

	theobj->suc = NULL;
	theobj->osuc = NULL;
	theobj->fail = NULL;
	theobj->ofail = NULL;
	theobj->desc = NULL;
	theobj->lock = NULL;
	theobj->contents = -1;
	theobj->exits = -1;
	theobj->pennies = 0;
	theobj->owner = -1;
	theobj->loc = 0;
	theobj->home_drpto = -1;
	theobj->descriptor = thedsc;

	/* Stuff it in cache */

	cache_insert(theobj);

	/* Now get this thing an object number. Yow! */

	if(garbage_count == 0){ /* Gotta get a new number */

		if(slack == 0){
			grow_index();
		}
		slack--;
		main_index[total_objects] = thedsc;
		num = total_objects;
		total_objects++;

	} else {  /* Find a garbage slot */
		for(num = total_objects-1 ; num > 0 ; num--){
			if(main_index[num] == NULL)
			break;
		}
		if(num == 0 && main_index[num] != NULL){
			warning("create_obj","garbage count out if synch");
			return(-1);
		}
		main_index[num] = thedsc;
		garbage_count--;
	}
	actual_objects++;
	return(num); /* Done! */
}

/*
	Match a player's name. This is expensive. Use it rarely. Returns a
player object number if found, otherwise -1. This lives here because it screws
around with DB internals.

*/

int match_player(name)
char *name;
{
	int i,j;
	char *player;

	for(i = 0; i < total_objects ; i++){
		if(main_index[i] == NULL || !PlayerP(main_index[i]))
			continue;

		player = DSC_NAME(main_index[i]);
		if(player == NULL){
			warning("match_player","player with NULL name");
			continue;
		}

		for(j = 0; DOWNCASE(name[j]) == DOWNCASE(player[j]) 
				&& !isspace(name[j]) && name[j] && player[j];
				j++)
			{}

		if(isspace(player[j]) && name[j] == '\0'){
			return(i);
		}
	}
	return(-1);
}

/*
	Look up an object by number. Snarf it off disk and shove it in cache if
necessary. The DB is *complicated*, so don't even THINK about trying to
reference object data any way other than through this function, OK?

*/

struct obj_data *lookup_obj(num)

int num;

{
	struct dsc *thedsc;
	struct obj_data *theobj;

	/* If there is no such object (number too large, or garbage) */

	if(num >= total_objects || num < 0
			|| (thedsc = main_index[num]) == NULL){
		return(NULL);
	}

	if( !ResidentP(thedsc)){

		/* Grab it off disk */

		if( (theobj = disk_thaw(thedsc)) == NULL){
			warning("lookup_obj","thaw failed");
			return(NULL);
		}

		/* Stuff it into the cache, too */

		cache_insert(theobj);
	} else {
		theobj = DSC_DATA(thedsc);
		touch(theobj);
	}

	/* Now it's been gotten, and is in cache. Yay. */

	return(theobj);
}

/*
	Returns 1 if and only if the specified object number refers to a valid
object.

*/

exists_object(num)
int num;
{
	return(num >= 0 && num < total_objects && main_index[num] != NULL);
}

/*
	Sets an object alive. Something of an anachronism.
*/

void animate(num)

int num;

{
	struct dsc *thedsc;

	if(num < 0 || num >= total_objects || (thedsc = main_index[num]) == NULL){
		warning("animate","attempt to animate a non-existant object");
		return;
	}
	(thedsc->flags) |= ALIVE;
	return;
}

/*
	Deanimates an animate object.

*/
deanimate(num)

int num;

{
	struct dsc *thedsc;

	if(num < 0 || num >= total_objects || (thedsc = main_index[num]) == NULL){
		warning("deanimate"
			,"attempt to deanimate a non-existant object");
		return;
	}
	thedsc->flags &= ~ALIVE;
	return;
}

/*
	This sets up a blank DB from scratch. Allocates an initial set of free
descriptors and an initial index (so if you know how big the DB is to start
with, you can be fairly efficient, and allocate all the descriptors you'll
need to start with).

*/

initialize_db(dsc_cnt, idx_siz)

int dsc_cnt,idx_siz;

{
	alloc_dsc(dsc_cnt);
	main_index = (struct dsc **) ty_malloc(sizeof(int) * idx_siz
		, "initialize_db");

	for(idx_siz-- ;idx_siz > 0 ; idx_siz--){
		main_index[idx_siz] = NULL;
	}
	growth_increment = GROWTH_INCREMENT;
}

/*
	Writes out the descriptor file.
	Returns -1 if failure, 0 otherwise.
*/

write_descriptors(name)
char *name;

{
	int fd;
	int i,len;
	int zerofrags,smallfrags,medfrags,bigfrags;
	char fragstr[32],*p;
	struct dsc *thedsc;
	extern long chunk_eof;
	extern struct dsc *free_chunks;

	/* Space for obj#, chunk offset, flags, next, and a name */

	char work[(sizeof(long) * 5) + 512];

	if((fd = open(name,O_WRONLY | O_CREAT,0755)) == -1){
		warning("write_descriptors","could not open output file");
		return(-1);
	}

	/* Write out the basic record at the beginning */
	/* Actual number of objects, and number of garbage descriptors */

	((long *) work)[0] = actual_objects;
	((long *) work)[1] = garbage_count;
	((long *) work)[2] = total_objects;
	((long *) work)[3] = chunk_eof;

	write(fd,work,sizeof(long) * 4);

	/* Write out all the real object descriptors */

	for(i = 0 ; i < total_objects ; i++){
		if((thedsc = main_index[i]) != NULL){
			/* Write a single descriptor */
			((long *) work)[0] = i; /* Obj # */
			((long *) work)[1] = DSC_FLAGS(thedsc);
			((long *) work)[2] = DSC_CHUNK(thedsc);
			((long *) work)[3] = DSC_SIZE(thedsc);
			((long *) work)[4] = thedsc->list_next;

			if(DSC_NAME(thedsc) == NULL){
				work[sizeof(long) * 5] = '\0';
				len = 1;
			} else {
				strcpy( &(work[sizeof(long)*5])
					,DSC_NAME(thedsc));
				len = strlen(DSC_NAME(thedsc)) + 1;
			}
			write(fd,work,(sizeof(long) * 5) + len);
		}
	}
	/* Write out all the free chunk descriptors */

	thedsc = free_chunks;
	zerofrags = smallfrags = medfrags = bigfrags = 0;
	while(thedsc != NULL){
		if(DSC_SIZE(thedsc) <= 0){
			zerofrags++;
		} else if(DSC_SIZE(thedsc) < INITIAL_SIZE){
			smallfrags++;
		} else if(DSC_SIZE(thedsc) < 512){
			medfrags++;
		} else {
			bigfrags++;
		}
		((long *) work)[0] = DSC_SIZE(thedsc);
		((long *) work)[1] = DSC_CHUNK(thedsc);
		write(fd,work,sizeof(long) * 2);
		thedsc = (thedsc->ptr).next;
	}
	/* Write fragmentation data out */

	p = fragstr;
	p = ty_itoa(p,zerofrags);
	*p++ = ' ';
	p = ty_itoa(p,smallfrags);
	*p++ = ' ';
	p = ty_itoa(p,medfrags);
	*p++ = ' ';
	p = ty_itoa(p,bigfrags);
	*p++ = '\0';

	warning("Fragmentation data",fragstr);

	/* close it all down */

	((long *) work)[0] = -1L;
	((long *) work)[1] = -1L;
	write(fd,work,sizeof(long) * 2);
	(void)close(fd);
	return(0);
}

/*
	reads in a descriptor file by name. Use this when you are initializing
things, otherwise all hell will break loose.

	This is basically what should be used to bring an existing DB back up.
Play with the numbers in the initialize_db() call if your MUD grows very slowly
or very quickly.

*/

read_descriptors(name)
char *name;

{
	int fd;
	int i,j;
	long objnum;
	struct dsc *thedsc;
	extern long chunk_eof;

	/* Space for obj#, chunk offset, flags, OR a name */

	char work[512];

	if((fd = open(name,O_RDONLY,0)) == -1){
		fatal("read_descriptors","could not open descriptor file");
	}

	/* Read in the initial stuff */

	i = read(fd,work,sizeof(long) * 4);
	if(i != (sizeof(long) * 4)){
		fatal("read_descriptors","error reading descriptor file");
	}

	actual_objects = ((long *) work)[0];
	garbage_count = ((long *) work)[1];
	total_objects = ((long *) work)[2];
	chunk_eof = ((long *) work)[3];

	/* Set up the DB for enough descriptors for everything, and enough
		index space too, plus 512 on both for luck. */

	initialize_db(actual_objects+garbage_count+SLACK,total_objects+SLACK);
	slack = SLACK;

	/* Now charge along reading in descriptors like crazy. */

	for(i = actual_objects; i > 0; i--){

		thedsc = get_descriptor();

		/* Start by reading in the five longs at the front. */

		if(read(fd,work,sizeof(long) * 5) != (sizeof(long) * 5)){
			fatal("read_descriptors"
				,"error reading descriptor file");
		}

		objnum = ((long *) work)[0];
		main_index[objnum] = thedsc;
		DSC_FLAGS(thedsc) = ((long *) work)[1];
		DSC_CHUNK(thedsc) = ((long *) work)[2];
		DSC_SIZE(thedsc) = ((long *) work)[3];
		thedsc->list_next = ((long *) work)[4];

		/* Now get the string, one char at a crack. Hee! */

		j = 0;
		do {
			if(read(fd,&(work[j]),1) != 1){
				fatal("read_descriptors"
					,"error reading descriptor file");
			}

		} while(work[j++] != '\0');

		DSC_NAME(thedsc) = ty_malloc(j,"read_descriptors");
		strcpy(DSC_NAME(thedsc) , work);

#ifdef LOADDEBUG
		printf("Object %s lives at %dl, size == %dl\n",DSC_NAME(thedsc)
			,DSC_CHUNK(thedsc),DSC_SIZE(thedsc));
#endif

	}

	/* Snarf in the free chunks too */

	while((read(fd,work,sizeof(long) * 2)) == (sizeof(long) * 2)
			&& ((long *) work)[0] != -1){

		thedsc = get_descriptor();
		DSC_SIZE(thedsc) = ((long *) work)[0];
		DSC_CHUNK(thedsc) = ((long *) work)[1];
#ifdef LOADDEBUG
		printf("Free chunk at %d, size == %d\n",DSC_CHUNK(thedsc),DSC_SIZE(thedsc));
#endif
		free_chunk(thedsc);
	}
	/* close it all down. */

	if(close(fd) == -1){
		warning("read_descriptors","error closing descriptor file");
	}
}

/*
	This grows the index by the basic increment.
*/

grow_index()
{
	/* slack *should* be zero, but might not be */

	main_index = (struct dsc **) realloc((char *)main_index,
		(unsigned) (sizeof(int)*(total_objects+growth_increment+slack)));

	if(main_index == NULL){
		fatal("grow_index", "could not grow index!");
	}
}