/* 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!"); } }