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