/* udb_obj.c - Binary object handling gear. */ /* $Id: udb_obj.c,v 1.22 2002/03/31 06:07:26 dpassmor Exp $ */ /* Originally written by Andrew Molitor, amolitor@nmsu.edu 1992 */ /* Why not just write the attributes individually to disk? Well, when you're * running on a platform that does synchronous writes with a large database, * thousands of I/O operations tend to be expensive. When you 'coalesce' * many attribute writes onto a single object and do only one I/O operation, * you can get an order of magnitude speed difference, especially on * loading/unloading to flatfile. It also has the side effect of * pre-fetching on reads, since you often have sequential attribute reads * off of the same object. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "alloc.h" /* required by mudconf */ #include "flags.h" /* required by mudconf */ #include "htab.h" /* required by mudconf */ #include "mudconf.h" /* required by code */ #include "db.h" /* required by externs */ #include "externs.h" /* required by code */ #include "udb.h" /* required by code */ /* Sizes, on disk, of Object and (within the object) Attribute headers */ #define OBJ_HEADER_SIZE (sizeof(Objname) + sizeof(int)) #define ATTR_HEADER_SIZE (sizeof(int) * 2) /* Take a chunk of data which contains an object, and parse it into an * object structure. */ Obj *unroll_obj(data) char *data; { int i, j; Obj *o; Attrib *a; char *dptr; dptr = data; /* Get a new Obj struct */ if ((o = (Obj *) XMALLOC(sizeof(Obj), "unroll_obj.o")) == NULL) return (NULL); /* Read in the header */ if (memcpy((void *)&(o->name), (void *)dptr, sizeof(Objname)) == NULL) { XFREE(o, "unroll_obj.o"); return (NULL); } dptr += sizeof(Objname); if (memcpy((void *)&i, (void *)dptr, sizeof(int)) == NULL) { XFREE(o, "unroll_obj.o"); return (NULL); } dptr += sizeof(int); o->at_count = i; /* Now get an array of Attrs */ a = o->atrs = (Attrib *) XMALLOC(i * sizeof(Attrib), "unroll_obj.a"); if (!o->atrs) { XFREE(o, "unroll_obj.o"); return (NULL); } /* Now go get the attrs, one at a time. */ for (j = 0; j < i;) { /* Attribute size */ if (memcpy((void *)&(a[j].size), (void *)dptr, sizeof(int)) == NULL) goto bail; dptr += sizeof(int); /* Attribute number */ if (memcpy((void *)&(a[j].attrnum), (void *)dptr, sizeof(int)) == NULL) goto bail; dptr += sizeof(int); /* get some memory for the data */ if ((a[j].data = (char *)XMALLOC(a[j].size, "unroll_obj.data")) == NULL) goto bail; /* Preincrement j, so we know how many to free if this next * bit fails. */ j++; /* Now get the data */ if (memcpy((void *)a[j - 1].data, (void *)dptr, a[j - 1].size) == NULL) goto bail; dptr += a[j - 1].size; } /* Should be all done... */ o->dirty = 0; return (o); /* Oh shit. We gotta free up all these little bits of memory. */ bail: /* j points one attribute *beyond* what we need to free up */ for (i = 0; i < j; i++) XFREE(a[i].data, "unroll_obj.data"); XFREE(a, "unroll_obj.a"); XFREE(o, "unroll_obj.o"); return (NULL); } /* Rollup an object structure into a single buffer for a write to disk. */ char *rollup_obj(o) Obj *o; { int i; Attrib *a; char *dptr, *data; dptr = data = (char *)XMALLOC(obj_siz(o), "rollup_obj.data"); /* Mark the object as clean */ o->dirty = 0; /* Write out the object header */ if (memcpy((void *)dptr, (void *)&(o->name), sizeof(Objname)) == NULL) return NULL; dptr += sizeof(Objname); if (memcpy((void *)dptr, (void *)&(o->at_count), sizeof(int)) == NULL) return NULL; dptr += sizeof(int); /* Now do the attributes, one at a time. */ a = o->atrs; for (i = 0; i < o->at_count; i++) { /* Attribute size. */ if (memcpy((void *)dptr, (void *)&(a[i].size), sizeof(int)) == NULL) return NULL; dptr += sizeof(int); /* Attribute number */ if (memcpy((void *)dptr, (void *)&(a[i].attrnum), sizeof(int)) == NULL) return NULL; dptr += sizeof(int); /* Attribute data */ if (memcpy((void *)dptr, (void *)a[i].data, a[i].size) == NULL) return NULL; dptr += a[i].size; } /* That's all she wrote. */ return data; } /* Return the size, on disk, the thing is going to take up.*/ int obj_siz(o) Obj *o; { int i; int siz; siz = OBJ_HEADER_SIZE; for (i = 0; i < o->at_count; i++) siz += (((o->atrs)[i]).size + ATTR_HEADER_SIZE); return (siz); } /* And something to free all the goo on an Obj, as well as the Obj.*/ static void objfree(o) Obj *o; { int i; Attrib *a; if (!o->atrs) { XFREE(o, "objfree.o"); return; } a = o->atrs; for (i = 0; i < o->at_count; i++) { XFREE(a[i].data, "objfree.data"); } XFREE(a, "objfree.a"); XFREE(o, "objfree.o"); } /* Routines to manipulate attributes within the object structure */ char *obj_get_attrib(anam, obj) int anam; Obj *obj; { int lo, mid, hi; Attrib *a; /* Binary search for the attribute */ lo = 0; hi = obj->at_count - 1; a = obj->atrs; while (lo <= hi) { mid = ((hi - lo) >> 1) + lo; if (a[mid].attrnum == anam) { return (char *) a[mid].data; break; } else if (a[mid].attrnum > anam) { hi = mid - 1; } else { lo = mid + 1; } } return (NULL); } void obj_set_attrib(anam, obj, value) int anam; Obj *obj; char *value; { int hi, lo, mid; Attrib *a; /* Demands made elsewhere insist that we cope with the case of an * empty object. */ if (obj->atrs == NULL) { a = (Attrib *) XMALLOC(sizeof(Attrib), "obj_set_attrib.a"); obj->atrs = a; obj->at_count = 1; a[0].attrnum = anam; a[0].data = (char *)value; a[0].size = strlen(value) + 1; return; } /* Binary search for the attribute. */ lo = 0; hi = obj->at_count - 1; a = obj->atrs; while (lo <= hi) { mid = ((hi - lo) >> 1) + lo; if (a[mid].attrnum == anam) { XFREE(a[mid].data, "obj_set_attrib"); a[mid].data = (char *)value; a[mid].size = strlen(value) + 1; return; } else if (a[mid].attrnum > anam) { hi = mid - 1; } else { lo = mid + 1; } } /* If we got here, we didn't find it, so lo = hi + 1, and the * attribute should be inserted between them. */ a = (Attrib *) XREALLOC(obj->atrs, (obj->at_count + 1) * sizeof(Attrib), "obj_set_attrib.a"); /* Move the stuff upwards one slot. */ if (lo < obj->at_count) memmove((void *)(a + lo + 1), (void *)(a + lo), (obj->at_count - lo) * sizeof(Attrib)); a[lo].data = value; a[lo].attrnum = anam; a[lo].size = strlen(value) + 1; obj->at_count++; obj->atrs = a; } void obj_del_attrib(anam, obj) int anam; Obj *obj; { int hi, lo, mid; Attrib *a; if (!obj->at_count || !obj->atrs) return; if (obj->at_count < 0) abort(); /* Binary search for the attribute. */ lo = 0; hi = obj->at_count - 1; a = obj->atrs; while (lo <= hi) { mid = ((hi - lo) >> 1) + lo; if (a[mid].attrnum == anam) { XFREE(a[mid].data, "obj_del_attrib.data"); obj->at_count--; if (mid != obj->at_count) memcpy((void *)(a + mid), (void *)(a + mid + 1), (obj->at_count - mid) * sizeof(Attrib)); if (obj->at_count == 0) { XFREE(obj->atrs, "del_attrib.atrs"); obj->atrs = NULL; } return; } else if (a[mid].attrnum > anam) { hi = mid - 1; } else { lo = mid + 1; } } } /* Now let's define the functions that the cache actually uses. These * 'pipeline' attribute writes using a pair of object structures. When * the object which is being read or written to changes, we have to * dump the current object, and bring in the new one. */ /* get_free_objpipe: return an object pipeline */ Obj *get_free_objpipe(obj) int obj; { DBData key, data; int i, j = 0; /* Try to see if it's already in a pipeline first */ for (i = 0; i < NUM_OBJPIPES; i++) { if (mudstate.objpipes[i] && mudstate.objpipes[i]->name == obj) { mudstate.objpipes[i]->counter = mudstate.objc; mudstate.objc++; return mudstate.objpipes[i]; } } /* Look for an empty pipeline */ for (i = 0; i < NUM_OBJPIPES; i++) { if (!mudstate.objpipes[i]) { /* If there's no object there, read one in */ key.dptr = &obj; key.dsize = sizeof(int); data = db_get(key, DBTYPE_ATTRIBUTE); if (data.dptr) { mudstate.objpipes[i] = unroll_obj(data.dptr); XFREE(data.dptr, "get_free_objpipe"); } else { /* New object */ if ((mudstate.objpipes[i] = (Obj *) XMALLOC(sizeof(Obj), "unroll_obj.o")) == NULL) return (NULL); mudstate.objpipes[i]->name = obj; mudstate.objpipes[i]->at_count = 0; mudstate.objpipes[i]->dirty = 0; mudstate.objpipes[i]->atrs = NULL; } mudstate.objpipes[i]->counter = mudstate.objc; mudstate.objc++; return mudstate.objpipes[i]; } if (mudstate.objpipes[i]->counter < mudstate.objpipes[j]->counter) { j = i; } } /* We got here, so we have to push the oldest object out. If it's * dirty, write it first */ if (mudstate.objpipes[j]->dirty) { data.dptr = rollup_obj(mudstate.objpipes[j]); data.dsize = obj_siz(mudstate.objpipes[j]); key.dptr = &mudstate.objpipes[j]->name; key.dsize = sizeof(int); db_lock(); db_put(key, data, DBTYPE_ATTRIBUTE); db_unlock(); XFREE(data.dptr, "get_free_objpipe.2"); } objfree(mudstate.objpipes[j]); /* Bring in the object from disk if it exists there */ key.dptr = &obj; key.dsize = sizeof(int); data = db_get(key, DBTYPE_ATTRIBUTE); if (data.dptr) { mudstate.objpipes[j] = unroll_obj(data.dptr); XFREE(data.dptr, "get_free_objpipe.3"); if (mudstate.objpipes[j] == NULL) { STARTLOG(LOG_PROBLEMS, "ERR", "CACHE") log_printf("Null returned on unroll of object #%d", j); ENDLOG return (NULL); } } else { /* New object */ if ((mudstate.objpipes[j] = (Obj *) XMALLOC(sizeof(Obj), "unroll_obj.o")) == NULL) return (NULL); mudstate.objpipes[j]->name = obj; mudstate.objpipes[j]->at_count = 0; mudstate.objpipes[j]->dirty = 0; mudstate.objpipes[j]->atrs = NULL; } mudstate.objpipes[j]->counter = mudstate.objc; mudstate.objc++; return mudstate.objpipes[j]; } char *pipe_get_attrib(anum, obj) int anum; int obj; { Obj *object; char *value, *tmp; object = get_free_objpipe(obj); value = obj_get_attrib(anum, object); if (value) { tmp = XSTRDUP(value, "pipe_get_attrib"); return tmp; } else { return NULL; } } void pipe_set_attrib(anum, obj, value) int anum; int obj; char *value; { Obj *object; char *newvalue; /* Write the damn thing */ object = get_free_objpipe(obj); object->dirty = 1; newvalue = XSTRDUP(value, "pipe_set_attrib"); obj_set_attrib(anum, object, newvalue); return; } void pipe_del_attrib(anum, obj) int anum; int obj; { Obj *object; /* Write the damn thing */ object = get_free_objpipe(obj); object->dirty = 1; obj_del_attrib(anum, object); return; } void attrib_sync() { DBData key, data; int i; /* Make sure dirty objects are written to disk */ for (i = 0; i < NUM_OBJPIPES; i++) { if (mudstate.objpipes[i] && mudstate.objpipes[i]->dirty) { data.dptr = rollup_obj(mudstate.objpipes[i]); data.dsize = obj_siz(mudstate.objpipes[i]); key.dptr = &mudstate.objpipes[i]->name; key.dsize = sizeof(int); db_put(key, data, DBTYPE_ATTRIBUTE); XFREE(data.dptr, "attrib_sync.1"); mudstate.objpipes[i]->dirty = 0; } } }