/* 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.
*
* 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
int db_top()
{
return (total_objects);
}
/*
* Routines to dig elements out of objects. They return an error status of 0
* if OK, -1 if not.
*
*/
int 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 DROP:
*ret = theobj->drop;
break;
case ODROP:
*ret = theobj->odrop;
break;
case DESC:
*ret = theobj->desc;
break;
case ENTER:
*ret = theobj->enter;
break;
case OENTER:
*ret = theobj->oenter;
break;
case EFAIL:
*ret = theobj->efail;
break;
case OEFAIL:
*ret = theobj->oefail;
break;
case OXENTER:
*ret = theobj->oxent;
break;
case LEAVE:
*ret = theobj->leave;
break;
case OLEAVE:
*ret = theobj->oleave;
break;
case OXLEAVE:
*ret = theobj->oxlea;
break;
case IDESC:
*ret = theobj->idesc;
break;
case ODESC:
*ret = theobj->odesc;
break;
case SITE:
*ret = theobj->site;
break;
case PASSWORD:
*ret = theobj->password;
break;
case KILL:
*ret = theobj->kill;
break;
case OKILL:
*ret = theobj->okill;
break;
case OTELEPORT:
*ret = theobj->otel;
break;
case OXTELEPORT:
*ret = theobj->oxtel;
break;
default:
warning("get_str_elt", "invalid element code");
return (-1);
break;
}
#ifdef COMPRESS
/* this is spoooky */
if (elt != PASSWORD) {
*ret = uncompress(*ret);
}
#endif
return (0);
}
int get_int_elt(num, elt, ret)
int num, elt;
long *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 QUOTA:
*ret = (long) theobj->pennies;
break;
case LOC:
*ret = theobj->loc;
break;
case CONTENTS:
*ret = theobj->contents;
break;
case EXITS:
*ret = theobj->exits;
break;
case ROOMS:
*ret = theobj->rooms;
break;
case TIMESTAMP:
*ret = theobj->timestamp;
break;
default:
warning("get_int_elt", "invalid element code");
return (-1);
break;
}
return (0);
}
int get_lock_elt(num, elt, ret)
int num, elt;
int **ret;
{
struct obj_data *theobj;
if ((elt != LOCK) && (elt != ELOCK) && (elt != DESTINATIONS)) {
warning("get_lock_elt", "bad elt code");
return (-1);
}
if ((theobj = lookup_obj(num)) == NULL) {
return (-1);
}
switch (elt) {
case LOCK:
*ret = theobj->lock;
break;
case ELOCK:
*ret = theobj->elock;
break;
case DESTINATIONS:
*ret = theobj->destinations;
break;
}
return (0);
}
/*
* These set the values of elements. They return 0 is OK, -1 if not.
*/
int 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 DROP:
thestr = &(theobj->drop);
break;
case ODROP:
thestr = &(theobj->odrop);
break;
case DESC:
thestr = &(theobj->desc);
break;
case ENTER:
thestr = &(theobj->enter);
break;
case OENTER:
thestr = &(theobj->oenter);
break;
case EFAIL:
thestr = &(theobj->efail);
break;
case OEFAIL:
thestr = &(theobj->oefail);
break;
case OXENTER:
thestr = &(theobj->oxent);
break;
case LEAVE:
thestr = &(theobj->leave);
break;
case OLEAVE:
thestr = &(theobj->oleave);
break;
case OXLEAVE:
thestr = &(theobj->oxlea);
break;
case IDESC:
thestr = &(theobj->idesc);
break;
case ODESC:
thestr = &(theobj->odesc);
break;
case SITE:
thestr = &(theobj->site);
break;
case PASSWORD:
thestr = &(theobj->password);
break;
case KILL:
thestr = &(theobj->kill);
break;
case OKILL:
thestr = &(theobj->okill);
break;
case OTELEPORT:
thestr = &(theobj->otel);
break;
case OXTELEPORT:
thestr = &(theobj->oxtel);
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 != PASSWORD)) {
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;
}
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);
}
int set_int_elt(num, elt, value)
int num, elt;
long 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 QUOTA:
theobj->pennies = (int) value;
break;
case LOC:
theobj->loc = value;
break;
case CONTENTS:
theobj->contents = value;
break;
case EXITS:
theobj->exits = value;
break;
case ROOMS:
theobj->rooms = value;
break;
case TIMESTAMP:
theobj->timestamp = value;
break;
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.
*
*/
int 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) && (elt != ELOCK) && (elt != DESTINATIONS)) {
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);
}
switch (elt) {
case LOCK:
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;
break;
case ELOCK:
if (theobj->elock == NULL) {
oldsize = 0;
} else {
oldsize = (theobj->elock)[0] * sizeof(int);
}
DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;
ty_free((char *) theobj->elock);
theobj->elock = value;
break;
case DESTINATIONS:
if (theobj->destinations == NULL) {
oldsize = 0;
} else {
oldsize = (theobj->destinations)[0] * sizeof(int);
}
DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;
ty_free((char *) theobj->destinations);
theobj->destinations = value;
break;
}
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((char *) theobj->elock);
ty_free((char *) theobj->destinations);
ty_free(theobj->suc);
ty_free(theobj->osuc);
ty_free(theobj->fail);
ty_free(theobj->ofail);
ty_free(theobj->drop);
ty_free(theobj->odrop);
ty_free(theobj->kill);
ty_free(theobj->okill);
ty_free(theobj->desc);
ty_free(theobj->enter);
ty_free(theobj->oenter);
ty_free(theobj->leave);
ty_free(theobj->oleave);
ty_free(theobj->idesc);
ty_free(theobj->odesc);
ty_free(theobj->site);
ty_free(theobj->password);
ty_free(theobj->otel);
ty_free(theobj->oxtel);
ty_free(theobj->oxent);
ty_free(theobj->oxlea);
ty_free(theobj->efail);
ty_free(theobj->oefail);
thedsc = theobj->descriptor;
ty_free(DSC_NAME(thedsc));
/* we don't KNOW if it has been stored yet, but gdbm is smart. i hope. */
disk_delete(num);
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.
*/
int 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;
/* Make it a grey box */
theobj->suc = NULL;
theobj->osuc = NULL;
theobj->fail = NULL;
theobj->ofail = NULL;
theobj->drop = NULL;
theobj->odrop = NULL;
theobj->kill = NULL;
theobj->okill = NULL;
theobj->desc = NULL;
theobj->enter = NULL;
theobj->oenter = NULL;
theobj->leave = NULL;
theobj->oleave = NULL;
theobj->idesc = NULL;
theobj->odesc = NULL;
theobj->site = NULL;
theobj->password = NULL;
theobj->otel = NULL;
theobj->oxtel = NULL;
theobj->oxent = NULL;
theobj->oxlea = NULL;
theobj->efail = NULL;
theobj->oefail = NULL;
theobj->lock = NULL;
theobj->elock = NULL;
theobj->destinations = NULL;
theobj->contents = -1;
theobj->exits = -1;
theobj->rooms = -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--;
}
DSC_NUMBER(thedsc) = num;
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;
extern int cache_hits, cache_errors, cache_misses;
/* If there is no such object (number too large, or garbage) */
if (num >= total_objects || num < 0
|| (thedsc = main_index[num]) == NULL) {
cache_errors++;
return (NULL);
}
if (!ResidentP(thedsc)) {
cache_misses++;
/* Grab it off disk */
DSC_NUMBER(thedsc) = num;
if ((theobj = disk_thaw(thedsc)) == NULL) {
warning("lookup_obj", "thaw failed");
cache_errors++;
return (NULL);
}
/* Stuff it into the cache, too */
cache_insert(theobj);
} else {
cache_hits++;
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.
*
*/
int exists_object(num)
int num;
{
return (num >= 0 && num < total_objects && main_index[num] != NULL);
}
/*
* 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).
*
*/
void initialize_db(idx_siz)
int idx_siz;
{
alloc_dsc(idx_siz);
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.
*/
int write_descriptors(name)
char *name;
{
int fd;
int i, len;
struct dsc *thedsc;
/* Space for obj#, chunk offset, flags, owner, etc., and a name */
char work[(sizeof(long) * 6) + 512];
if ((fd = open(name, O_WRONLY | O_CREAT, 0600)) == -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;
write(fd, work, sizeof(long) * 3);
/* 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_SIZE(thedsc);
((long *) work)[3] = thedsc->list_next;
((long *) work)[4] = DSC_OWNER(thedsc);
((long *) work)[5] = thedsc->home_dropto;
if (DSC_NAME(thedsc) == NULL) {
work[sizeof(long) * 6] = '\0';
len = 1;
} else {
strcpy(&(work[sizeof(long) * 6])
,DSC_NAME(thedsc));
len = strlen(DSC_NAME(thedsc)) + 1;
}
write(fd, work, (sizeof(long) * 6) + len);
}
}
/* close it all down */
(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.
*
*/
int read_descriptors(name)
char *name;
{
int fd;
int i, j;
long objnum;
struct dsc *thedsc;
/* 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) * 3);
if (i != (sizeof(long) * 3)) {
fatal("read_descriptors", "error reading descriptor file");
}
actual_objects = ((long *) work)[0];
garbage_count = ((long *) work)[1];
total_objects = ((long *) work)[2];
/*
* Set up the DB for enough descriptors for everything, and enough index
* space too, plus 512 on both for luck.
*/
initialize_db(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) * 6) != (sizeof(long) * 6)) {
fatal("read_descriptors"
,"error reading descriptor file");
}
objnum = ((long *) work)[0];
main_index[objnum] = thedsc;
DSC_NUMBER(thedsc) = objnum;
DSC_FLAGS(thedsc) = ((long *) work)[1];
DSC_SIZE(thedsc) = ((long *) work)[2];
thedsc->list_next = ((long *) work)[3];
DSC_OWNER(thedsc) = ((long *) work)[4];
thedsc->home_dropto = ((long *) work)[5];
/* 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
}
/* close it all down. */
if (close(fd) == -1) {
warning("read_descriptors", "error closing descriptor file");
}
}
/*
* This grows the index by the basic increment.
*/
void 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!");
}
}