/* db.c */
#include "copyright.h"
#include "config.h"
#include <stdio.h>
#ifdef STRING_H
#include <string.h>
#else
#include <strings.h>
#endif /* STRING_H */
#include <fcntl.h>
#include <sys/types.h>
#ifndef NO_UIO_H
#include <sys/uio.h>
#endif /* NO_UIO_H */
#ifdef STDDEF_H
#include <stddef.h>
#else
#define size_t unsigned
#endif /* STDDEF_H */
#include "teeny.h"
#include "db.h"
/*
* 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.
*
* db_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();
#ifdef COMPRESS
/*
* NOTEs on compression: if you use COMPRESS, a pointer returned by
* get_str_elt() will become very invalid upon another call to get_str_elt()
* in too many cases... i will *try* to get it debugged before release.
* --jason
*/
extern char *compress();
extern char *uncompress();
#endif
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;
#ifdef DROP_FIELDS
case DROP:
*ret = theobj->drop;
break;
case ODROP:
*ret = theobj->odrop;
break;
#endif
case DESC:
*ret = theobj->desc;
break;
case GENDER:
*ret = theobj->gender;
break;
default:
warning("get_str_elt", "invalid element code");
return (-1);
break;
}
#ifdef COMPRESS
/* this is spoooky */
if (elt != GENDER)
{
*ret = uncompress(*ret);
}
#endif
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 || elt == OWNER || elt == HOME)
&& 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;
case OWNER:
*ret = DSC_OWNER(thedsc);
break;
case HOME:
/* case DESTINATION: */
/* case DROPTO: */
*ret = thedsc->home_dropto;
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 SITE:
*ret = theobj->pennies;
break;
case LOC:
*ret = theobj->loc;
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 than 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;
#ifdef DROP_FIELDS
case DROP:
thestr = &(theobj->drop);
break;
case ODROP:
thestr = &(theobj->odrop);
break;
#endif
case DESC:
thestr = &(theobj->desc);
break;
case GENDER:
thestr = &(theobj->gender);
break;
default:
warning("set_str_elt", "invalid element code");
return (-1);
break;
}
/* Recompute the on disk size. */
#ifdef COMPRESS
if ((value != NULL) && (elt != NAME) && (elt != GENDER))
{
value = compress(value);
}
#endif
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 || elt == OWNER || elt == HOME)
&& 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;
case OWNER:
DSC_OWNER(thedsc) = value;
break;
case HOME:
/* case DESTINATION: */
/* case DROPTO: */
thedsc->home_dropto = 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 SITE:
theobj->pennies = value;
break;
case LOC:
theobj->loc = 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);
#ifdef DROP_FIELDS
ty_free(theobj->drop);
ty_free(theobj->odrop);
#endif
ty_free(theobj->desc);
ty_free(theobj->gender);
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;
DSC_OWNER(thedsc) = -1;
thedsc->list_next = -1;
thedsc->home_dropto = -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;
#ifdef DROP_FIELDS
theobj->drop = NULL;
theobj->odrop = NULL;
#endif
theobj->desc = NULL;
theobj->gender = NULL;
theobj->lock = NULL;
theobj->contents = -1;
theobj->exits = -1;
theobj->pennies = 0;
theobj->loc = 0;
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! */
}
/*
* 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, owner, etc., and a name */
char work[(sizeof(long) * 7) + 512];
if ((fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 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;
((long *) work)[5] = DSC_OWNER(thedsc);
((long *) work)[6] = thedsc->home_dropto;
if (DSC_NAME(thedsc) == NULL)
{
work[sizeof(long) * 7] = '\0';
len = 1;
} else
{
strcpy(&(work[sizeof(long) * 7])
,DSC_NAME(thedsc));
len = strlen(DSC_NAME(thedsc)) + 1;
}
write(fd, work, (sizeof(long) * 7) + 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, owner, etc., 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 seven longs at the front. */
if (read(fd, work, sizeof(long) * 7) != (sizeof(long) * 7))
{
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];
DSC_OWNER(thedsc) = ((long *) work)[5];
thedsc->home_dropto = ((long *) work)[6];
/* 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,
(size_t) (sizeof(int) * (total_objects + growth_increment + slack)));
if (main_index == NULL)
{
fatal("grow_index", "could not grow index!");
}
}