game/bin/
game/data/
/*
 * Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
 * Slightly whacked by Andrew Molitor. My apologies to the author..
 * 
 * Also banged on by dcm@somewhere.or.other, Jellan on the muds,
 * to be more complex and more efficient.
 * 
 * This version then banged on by Andrew Molitor, 1992, to
 * do object-by-object, hiding the object part inside
 * the cache layer. Upper layers think it's an attribute cache.
 * 
 * $Id: udb_ocache.c,v 1.3 1995/11/22 23:33:17 root Exp $
 */

/*
 * #define CACHE_DEBUG
 * #define CACHE_VERBOSE
 */
/*
 * First 
 */
#include	"autoconf.h"
#include	"udb_defs.h"
#include	"mudconf.h"

#ifdef	NOSYSTYPES_H
#include	<types.h>
#else
#include	<sys/types.h>
#endif

extern struct Obj *dddb_get();
extern void logf();

/*
 * This is by far the most complex and kinky code in UnterMUD. You should
 * never need to mess with anything in here - if you value your sanity.
 */


typedef struct cache {
	Obj *op;
	struct cache *nxt;
	struct cache *prv;
} Cache;

typedef struct {
	Cache *head;
	Cache *tail;
} Chain;

typedef struct {
	Chain active;
	Chain mactive;
	Chain old;
	Chain mold;
	int count;
} CacheLst;

/*
 * This is a MUX specific definition, depending on Objname being an
 * * integer type of some sort. 
 */

#define NAMECMP(a,b) ((a)->op) && (((a)->op)->name == (b)->object)

#define DEQUEUE(q, e)	if(e->nxt == (Cache *)0) \
				q.tail = e->prv; \
			else \
				e->nxt->prv = e->prv; \
			if(e->prv == (Cache *)0) \
				q.head = e->nxt; \
			else \
				e->prv->nxt = e->nxt;

#define INSHEAD(q, e)	if (q.head == (Cache *)0) { \
				q.tail = e; \
				e->nxt = (Cache *)0; \
			} else { \
				q.head->prv = e; \
				e->nxt = q.head; \
			} \
			q.head = e; \
			e->prv = (Cache *)0;

#define INSTAIL(q, e)	if (q.head == (Cache *)0) { \
				q.head = q.tail = e; \
				e->prv = (Cache *)0; \
			} else { \
				q.tail->nxt = e; \
				e->prv = q.tail; \
			} \
			q.tail = e; \
			e->nxt = (Cache *)0;

static Cache *get_free_entry();
static int cache_write();
static void cache_clean();

static Attr *get_attrib();
static void set_attrib();
static void del_attrib();
static void FDECL(objfree, (Obj *));

/*
 * initial settings for cache sizes 
 */
static int cwidth = CACHE_WIDTH;
static int cdepth = CACHE_DEPTH;


/*
 * ntbfw - main cache pointer and list of things to kill off 
 */
static CacheLst *sys_c;

static int cache_initted = 0;
static int cache_frozen = 0;
static int cache_busy = 0;

/*
 * cache stats gathering stuff. you don't like it? comment it out 
 */
time_t cs_ltime;
int cs_writes = 0;		/*

				 * total writes 
				 */
int cs_reads = 0;		/*

				 * total reads 
				 */
int cs_dbreads = 0;		/*

				 * total read-throughs 
				 */
int cs_dbwrites = 0;		/*

				 * total write-throughs 
				 */
int cs_dels = 0;		/*

				 * total deletes 
				 */
int cs_checks = 0;		/*

				 * total checks 
				 */
int cs_rhits = 0;		/*

				 * total reads filled from cache 
				 */
int cs_ahits = 0;		/*

				 * total reads filled active cache 
				 */
int cs_whits = 0;		/*

				 * total writes to dirty cache 
				 */
int cs_fails = 0;		/*

				 * attempts to grab nonexistent 
				 */
int cs_resets = 0;		/*

				 * total cache resets 
				 */
int cs_syncs = 0;		/*

				 * total cache syncs 
				 */
int cs_objects = 0;		/*

				 * total cache size 
				 */


void cache_repl(cp, new)
Cache *cp;
Obj *new;
{
	if (cp->op != ONULL)
		objfree(cp->op);
	cp->op = new;
}

int cache_init(width, depth)
int width, depth;
{
	int x;
	int y;
	Cache *np, *cp;
	CacheLst *sp;
	static char *ncmsg = "cache_init: cannot allocate cache: ";

	if (cache_initted || sys_c != (CacheLst *) 0)
		return (0);

	/*
	 * If either dimension is specified as non-zero, change it to 
	 */
	/*
	 * that, otherwise use default. Treat dimensions deparately.  
	 */

	if (width)
		cwidth = width;
	if (depth)
		cdepth = depth;

	sp = sys_c = (CacheLst *) malloc((unsigned)cwidth * sizeof(CacheLst));
	if (sys_c == (CacheLst *) 0) {
		logf(ncmsg, (char *)-1, "\n", (char *)0);
		return (-1);
	}
	/*
	 * Pre-allocate the initial cache entries in one chunk 
	 */
	cp = (Cache *) malloc(cwidth * cdepth * sizeof(Cache));
	if (cp == (Cache *) 0) {
		logf(ncmsg, (char *)-1, "\n", (char *)0);
		return (-1);
	}
	for (x = 0; x < cwidth; x++, sp++) {
		sp->active.head = sp->old.head = (Cache *) 0;
		sp->old.tail = sp->old.tail = (Cache *) 0;
		sp->mactive.head = sp->mold.head = (Cache *) 0;
		sp->mactive.tail = sp->mold.tail = (Cache *) 0;
		sp->count = cdepth;

		for (y = 0; y < cdepth; y++) {

			np = cp++;
			if ((np->nxt = sp->old.head) != (Cache *) 0)
				np->nxt->prv = np;
			else
				sp->old.tail = np;

			np->prv = (Cache *) 0;
			np->op = (Obj *) 0;

			sp->old.head = np;
			cs_objects++;
		}
	}

	/*
	 * mark caching system live 
	 */
	cache_initted++;

	time(&cs_ltime);

	return (0);
}



static void cache_trim(sp)
CacheLst *sp;
{
	Cache *cp;

	while ((sp->count > cdepth) && ((cp = sp->old.tail) != CNULL)) {
		/*
		 * unlink old cache chain tail 
		 */
		if (cp->prv != CNULL) {
			sp->old.tail = cp->prv;
			cp->prv->nxt = cp->nxt;
		} else		/*
				 * took last one 
				 */
			sp->old.head = sp->old.tail = CNULL;

		/*
		 * free object's data 
		 */

		cache_repl(cp, ONULL);
		free(cp);
		sp->count--;
		cs_objects--;
	}
}

void cache_reset(trim)
int trim;
{
	int x;
	CacheLst *sp;

	if (!cache_initted)
		return;

	/*
	 * unchain and rechain each active list at head of old chain 
	 */
	sp = sys_c;
	for (x = 0; x < cwidth; x++, sp++) {
		if (sp->active.head != CNULL) {
			sp->active.tail->nxt = sp->old.head;

			if (sp->old.head != CNULL)
				sp->old.head->prv = sp->active.tail;
			if (sp->old.tail == CNULL)
				sp->old.tail = sp->active.tail;

			sp->old.head = sp->active.head;
			sp->active.head = CNULL;
		}
		if (!cache_busy && sp->mactive.head != CNULL) {
			sp->mactive.tail->nxt = sp->mold.head;

			if (sp->mold.head != CNULL)
				sp->mold.head->prv = sp->mactive.tail;
			if (sp->mold.tail == CNULL)
				sp->mold.tail = sp->mactive.tail;

			sp->mold.head = sp->mactive.head;
			sp->mactive.head = CNULL;
		}
		if (trim && mudconf.cache_trim)
			cache_trim(sp);
	}
	cs_resets++;
}




/*
 * search the cache for an object, and if it is not found, thaw it.
 * this code is probably a little bigger than it needs be because I
 * had fun and unrolled all the pointer juggling inline.
 */
Attr *
 cache_get(nam)
Aname *nam;
{
	Cache *cp;
	CacheLst *sp;
	int hv = 0;
	Obj *ret;

	/*
	 * firewall 
	 */
	if (nam == (Aname *) 0 || !cache_initted) {
#ifdef	CACHE_VERBOSE
		logf("cache_get: NULL object name - programmer error\n", (char *)0);
#endif
		return ((Attr *) 0);
	}
#ifdef	CACHE_DEBUG
	printf("get %d/%d\n", nam->object, nam->attrnum);
#endif

	cs_reads++;

	/*
	 * We search the cache for the right Obj, then find the Attrib inside
	 * * * * that. 
	 */

	hv = nam->object % cwidth;
	sp = &sys_c[hv];

	/*
	 * search active chain first 
	 */
	for (cp = sp->active.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			cs_rhits++;
			cs_ahits++;
#ifdef	CACHE_DEBUG
			printf("return active cache -- %d\n", cp->op);
#endif
			/*
			 * Found the Obj, return the Attr within it 
			 */

			return (get_attrib(nam, cp->op));
		}
	}

	/*
	 * search modified active chain next. 
	 */
	for (cp = sp->mactive.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			cs_rhits++;
			cs_ahits++;

#ifdef	CACHE_DEBUG
			printf("return modified active cache -- %d\n", cp->op);
#endif
			return (get_attrib(nam, cp->op));
		}
	}

	/*
	 * search in-active chain now 
	 */
	for (cp = sp->old.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * dechain from in-active chain 
			 */
			DEQUEUE(sp->old, cp);
			/*
			 * insert at head of active chain 
			 */
			INSHEAD(sp->active, cp);

			/*
			 * done 
			 */
			cs_rhits++;
#ifdef	CACHE_DEBUG
			printf("return old cache -- %d\n", cp->op);
#endif
			return (get_attrib(nam, cp->op));
		}
	}

	/*
	 * search modified in-active chain now 
	 */
	for (cp = sp->mold.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * dechain from modified in-active chain 
			 */
			DEQUEUE(sp->mold, cp);

			/*
			 * insert at head of modified active chain 
			 */
			INSHEAD(sp->mactive, cp);

			/*
			 * done 
			 */
			cs_rhits++;

#ifdef	CACHE_DEBUG
			printf("return modified old cache -- %d\n", cp->op);
#endif
			return (get_attrib(nam, cp->op));
		}
	}

	/*
	 * DARN IT - at this point we have a certified, type-A cache miss 
	 */

	/*
	 * thaw the object from wherever. 
	 */

	if ((ret = DB_GET(&(nam->object))) == (Obj *) 0) {
		cs_fails++;
		cs_dbreads++;
#ifdef	CACHE_DEBUG
		printf("Object %d not in db\n", nam->object);
#endif
		return ((Attr *) 0);
	} else
		cs_dbreads++;

	if ((cp = get_free_entry(sp)) == CNULL)
		return ((Attr *) 0);

	cp->op = ret;

	/*
	 * relink at head of active chain 
	 */
	INSHEAD(sp->active, cp);

#ifdef	CACHE_DEBUG
	printf("returning attr %d, object %d loaded into cache -- %d\n",
	       nam->attrnum, nam->object, cp->op);
#endif
	return (get_attrib(nam, ret));
}




/*
 * put an object back into the cache. this is complicated by the
 * fact that when a function calls this with an object, the object
 * is *already* in the cache, since calling functions operate on
 * pointers to the cached objects, and may or may not be actively
 * modifying them. in other words, by the time the object is handed
 * to cache_put, it has probably already been modified, and the cached
 * version probably already reflects those modifications!
 * 
 * so - we do a couple of things: we make sure that the cached
 * object is actually there, and set its dirty bit. if we can't
 * find it - either we have a (major) programming error, or the
 * *name* of the object has been changed, or the object is a totally
 * new creation someone made and is inserting into the world.
 * 
 * in the case of totally new creations, we simply accept the pointer
 * to the object and add it into our environment. freeing it becomes
 * the responsibility of the cache code. DO NOT HAND A POINTER TO
 * CACHE_PUT AND THEN FREE IT YOURSELF!!!!
 * 
 * There are other sticky issues about changing the object pointers
 * of MUDs and their names. This is left as an exercise for the
 * reader.
 */
int
#ifdef RADIX_COMPRESSION
 cache_put(nam, obj, len)
int len;

#else
 cache_put(nam, obj)
#endif				/*
				 * RADIX_COMPRESSION 
				 */
Aname *nam;
Attr *obj;
{
	Cache *cp;
	CacheLst *sp;
	Obj *newobj;
	int hv = 0;

	/*
	 * firewall 
	 */
	if (obj == (Attr *) 0 || nam == (Aname *) 0 || !cache_initted) {
#ifdef	CACHE_VERBOSE
		logf("cache_put: NULL object/name - programmer error\n", (char *)0);
#endif
		return (1);
	}
	cs_writes++;

	/*
	 * generate hash 
	 */
	hv = nam->object % cwidth;
	sp = &sys_c[hv];

	/*
	 * step one, search active chain, and if we find the obj, dirty it 
	 */
	for (cp = sp->active.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * Right object, set the attribute 
			 */

#ifdef RADIX_COMPRESSION
			set_attrib(nam, cp->op, obj, len);
#else
			set_attrib(nam, cp->op, obj);
#endif /*
        * RADIX_COMPRESSION 
        */

#ifdef	CACHE_DEBUG
			printf("cache_put object %d, attr %d -- %d\n",
			       nam->object, nam->attrnum, cp->op);
#endif
			DEQUEUE(sp->active, cp);
			INSHEAD(sp->mactive, cp);
			cs_whits++;
			return (0);
		}
	}

	/*
	 * step two, search modified active chain, and if we find the obj, *
	 * * * we're done 
	 */
	for (cp = sp->mactive.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * Right object, set the attribute 
			 */

#ifdef RADIX_COMPRESSION
			set_attrib(nam, cp->op, obj, len);
#else
			set_attrib(nam, cp->op, obj);
#endif /*
        * RADIX_COMPRESSION 
        */

#ifdef	CACHE_DEBUG
			printf("cache_put object %d,attr %d -- %d\n",
			       nam->object, nam->attrnum, cp->op);
#endif
			cs_whits++;
			return (0);
		}
	}

	/*
	 * step three, search in-active chain.  If we find it, move it to the 
	 * 
	 * *  * *  * * mod active 
	 */
	for (cp = sp->old.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * Right obj. Set the attrib. 
			 */

#ifdef RADIX_COMPRESSION
			set_attrib(nam, cp->op, obj, len);
#else
			set_attrib(nam, cp->op, obj);
#endif

#ifdef	CACHE_DEBUG
			printf("cache_put object %d, attr %d -- %d\n",
			       nam->object, nam->attrnum, cp->op);
#endif
			DEQUEUE(sp->old, cp);
			INSHEAD(sp->mactive, cp);
			cs_whits++;
			return (0);
		}
	}

	/*
	 * step four, search modified in-active chain 
	 */
	for (cp = sp->mold.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {

			/*
			 * Right object. Set the attribute. 
			 */

#ifdef RADIX_COMPRESSION
			set_attrib(nam, cp->op, obj, len);
#else
			set_attrib(nam, cp->op, obj);
#endif /*
        * RADIX_COMPRESSION 
        */

#ifdef	CACHE_DEBUG
			printf("cache_put %d,%d %d\n",
			       nam->object, nam->attrnum, cp->op);
#endif
			DEQUEUE(sp->mold, cp);
			INSHEAD(sp->mactive, cp);
			cs_whits++;
			return (0);
		}
	}

	/*
	 * Ok, now the fact that we're caching objects, and pretending to * * 
	 * 
	 * * cache attributes starts to *hurt*. We gotta try to get the
	 * object * * * in to cache, see.. 
	 */

	newobj = DB_GET(&(nam->object));
	if (newobj == (Obj *) 0) {

		/*
		 * SHIT! Totally NEW object! 
		 */

		newobj = (Obj *) malloc(sizeof(Obj));
		if (newobj == (Obj *) 0)
			return (1);

		newobj->atrs = ATNULL;
		newobj->name = nam->object;
	}
	/*
	 * Now we got the thing, hang the new version of the attrib on it. 
	 */

#ifdef RADIX_COMPRESSION
	set_attrib(nam, newobj, obj, len);
#else
	set_attrib(nam, newobj, obj);
#endif /*
        * RADIX_COMPRESSION 
        */

	/*
	 * add it to the cache 
	 */
	if ((cp = get_free_entry(sp)) == CNULL)
		return (1);

	cp->op = newobj;

	/*
	 * link at head of modified active chain 
	 */
	INSHEAD(sp->mactive, cp);

#ifdef	CACHE_DEBUG
	printf("cache_put %d/%d new in cache %d\n", nam->object, nam->attrnum, cp->op);
#endif

	/*
	 * e finito ! 
	 */
	return (0);
}

static Cache *
 get_free_entry(sp)
CacheLst *sp;
{
	Cache *cp;

	/*
	 * if there are no old cache object holders left, allocate one 
	 */
	if ((cp = sp->old.tail) == CNULL) {

		if (!cache_busy && !cache_frozen &&
		    (cp = sp->mold.tail) != CNULL) {
			/*
			 * unlink old cache chain tail 
			 */
			if (cp->prv != CNULL) {
				sp->mold.tail = cp->prv;
				cp->prv->nxt = cp->nxt;
			} else	/*
				 * took last one 
				 */
				sp->mold.head = sp->mold.tail = CNULL;

#ifdef	CACHE_DEBUG
			printf("clean object %d from cache %d\n",
			       (cp->op)->name, cp->op);
#endif
			if ((cp->op)->at_count == 0) {
				if (DB_DEL(&((cp->op)->name), x))
					return (CNULL);
				cs_dels++;
			} else {
				if (DB_PUT(cp->op, &((cp->op)->name)))
					return (CNULL);
				cs_dbwrites++;
			}
		} else {
			if ((cp = (Cache *) malloc(sizeof(Cache))) == CNULL)
				fatal("cache get_free_entry: malloc failed", (char *)-1, (char *)0);

			cp->op = (Obj *) 0;
			sp->count++;
			cs_objects++;
		}
	} else {

		/*
		 * unlink old cache chain tail 
		 */
		if (cp->prv != CNULL) {
			sp->old.tail = cp->prv;
			cp->prv->nxt = cp->nxt;
		} else		/*
				 * took last one 
				 */
			sp->old.head = sp->old.tail = CNULL;
	}

	/*
	 * free object's data 
	 */

	cache_repl(cp, ONULL);
	return (cp);
}

static int cache_write(cp)
Cache *cp;
{
	while (cp != CNULL) {
#ifdef	CACHE_DEBUG
		printf("sync %d -- %d\n", cp->op->name, cp->op);
#endif
		if (cp->op->at_count == 0) {
			if (DB_DEL(&((cp->op)->name), x)) {
				log_db_err(cp->op->name, 0, "delete");
				return (1);
			}
			cs_dels++;
		} else {
			if (DB_PUT(cp->op, &((cp->op)->name))) {
				log_db_err(cp->op->name, 0, "write");
				return (1);
			}
			cs_dbwrites++;
		}
		cp = cp->nxt;
	}
	return (0);
}

static void cache_clean(sp)
CacheLst *sp;
{
	/*
	 * move the modified inactive chain to the inactive chain 
	 */
	if (sp->mold.head != NULL) {
		if (sp->old.head == NULL) {
			sp->old.head = sp->mold.head;
			sp->old.tail = sp->mold.tail;
		} else {
			sp->old.tail->nxt = sp->mold.head;
			sp->mold.head->prv = sp->old.tail;
			sp->old.tail = sp->mold.tail;
		}
		sp->mold.head = sp->mold.tail = CNULL;
	}
	/*
	 * same for the modified active chain 
	 */
	if (sp->mactive.head != CNULL) {
		if (sp->active.head == CNULL) {
			sp->active.head = sp->mactive.head;
			sp->active.tail = sp->mactive.tail;
		} else {
			sp->active.tail->nxt = sp->mactive.head;
			sp->mactive.head->prv = sp->active.tail;
			sp->active.tail = sp->mactive.tail;
		}
		sp->mactive.head = sp->mactive.tail = CNULL;
	}
}

int NDECL(cache_sync)
{
	int x;
	CacheLst *sp;

	cs_syncs++;

	if (!cache_initted)
		return (1);

	if (cache_frozen)
		return (0);

	cache_reset(0);

	for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
		if (cache_write(sp->mold.head))
			return (1);
		if (cache_write(sp->mactive.head))
			return (1);
		cache_clean(sp);
		if (mudconf.cache_trim)
			cache_trim(sp);
	}
	return (0);
}

/*
 * Mark this attr as deleted in the cache. The object will flush back to
 * disk without it, eventually.
 */
void cache_del(nam)
Aname *nam;
{
	Cache *cp;
	CacheLst *sp;
	Obj *obj;
	int hv = 0;

	if (nam == (Aname *) 0 || !cache_initted)
		return;

#ifdef CACHE_DEBUG
	printf("cache_del: object %d, attr %d\n", nam->object, nam->attrnum);
#endif

	cs_dels++;

	hv = nam->object % cwidth;
	sp = &sys_c[hv];

	/*
	 * mark dead in cache 
	 */
	for (cp = sp->active.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			DEQUEUE(sp->active, cp);
			INSHEAD(sp->mactive, cp);
			del_attrib(nam, cp->op);
			return;
		}
	}
	for (cp = sp->mactive.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			del_attrib(nam, cp->op);
			return;
		}
	}

	for (cp = sp->old.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			DEQUEUE(sp->old, cp);
			INSHEAD(sp->mactive, cp);
			del_attrib(nam, cp->op);
			return;
		}
	}
	for (cp = sp->mold.head; cp != CNULL; cp = cp->nxt) {
		if (NAMECMP(cp, nam)) {
			DEQUEUE(sp->mold, cp);
			INSHEAD(sp->mactive, cp);
			del_attrib(nam, cp->op);
			return;
		}
	}

	/*
	 * If we got here, the object the attribute's on isn't in cache 
	 */
	/*
	 * At all.  So we get to fish it off of disk, nuke the attrib,  
	 */
	/*
	 * and shove the object in cache, so it'll be flushed back later 
	 */

	obj = DB_GET(&(nam->object));
	if (obj == (Obj *) 0)
		return;

	if ((cp = get_free_entry(sp)) == CNULL)
		return;

	del_attrib(nam, obj);

	cp->op = obj;

	INSHEAD(sp->mactive, cp);
	return;
}

/*
 * And now a totally new suite of functions for manipulating
 * attributes within an Obj. Woo. Woo. Andrew.
 * 
 */

static Attr *
 get_attrib(anam, obj)
Aname *anam;
Obj *obj;
{
	int lo, mid, hi;
	Attrib *a;

#ifdef CACHE_DEBUG
	printf("get_attrib: object %d, attr %d, obj ptr %d\n",
	       anam->object, anam->attrnum, obj);
#endif

	/*
	 * 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->attrnum) {
			return (Attr *) a[mid].data;
			break;
		} else if (a[mid].attrnum > anam->attrnum) {
			hi = mid - 1;
		} else {
			lo = mid + 1;
		}
	}
#ifdef CACHE_DEBUG
	printf("get_attrib: not found.\n");
#endif
	return ((Attr *) 0);	/*
				 * Not found 
				 */
}

static void
#ifdef RADIX_COMPRESSION
 set_attrib(anam, obj, value, len)
Aname *anam;
Obj *obj;
Attr *value;
int len;

#else
 set_attrib(anam, obj, value)
Aname *anam;
Obj *obj;
Attr *value;

#endif /*
        * RADIX_COMPRESSION 
        */
{
	int hi, lo, mid;
	Attrib *a;

	/*
	 * Demands made elsewhere insist that we cope with the case of an * * 
	 * 
	 * * empty object. 
	 */

	if (obj->atrs == ATNULL) {
		a = (Attrib *) malloc(sizeof(Attrib));
		if (a == ATNULL)	/*
					 * Fail silently. It's a game. 
					 */
			return;

		obj->atrs = a;
		obj->at_count = 1;
		a[0].attrnum = anam->attrnum;
		a[0].data = (char *)value;
#ifdef RADIX_COMPRESSION
		a[0].size = len;
#else
		a[0].size = ATTR_SIZE(value);
#endif /*
        * RADIX_COMPRESSION 
        */
		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->attrnum) {
			free(a[mid].data);
			a[mid].data = (char *)value;
#ifdef RADIX_COMPRESSION
			a[mid].size = len;
#else
			a[mid].size = ATTR_SIZE(value);
#endif /*
        * RADIX_COMPRESSION 
        */
			return;
		} else if (a[mid].attrnum > anam->attrnum) {
			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 *) realloc(obj->atrs, (obj->at_count + 1) * sizeof(Attrib));

	if (!a) {
		/*
		 * Silently fail. It's just a game. 
		 */

		return;
	}
	/*
	 * Move the stuff upwards one slot. 
	 */
	if (lo < obj->at_count)
		bcopy((char *)(a + lo), (char *)(a + lo + 1),
		      (obj->at_count - lo) * sizeof(Attrib));

	a[lo].data = value;
	a[lo].attrnum = anam->attrnum;
#ifdef RADIX_COMPRESSION
	a[lo].size = len;
#else
	a[lo].size = ATTR_SIZE(value);
#endif /*
        * RADIX_COMPRESSION 
        */
	obj->at_count++;
	obj->atrs = a;

}

static void del_attrib(anam, obj)
Aname *anam;
Obj *obj;
{
	int hi, lo, mid;
	Attrib *a;

#ifdef CACHE_DEBUG
	printf("del_attrib: deleteing attr %d on object %d (%d)\n", anam->attrnum,
	       obj->name, anam->object);
#endif

	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->attrnum) {
			free(a[mid].data);
			obj->at_count--;
			if (mid != obj->at_count)
				bcopy((char *)(a + mid + 1), (char *)(a + mid),
				    (obj->at_count - mid) * sizeof(Attrib));

			if (obj->at_count == 0) {
				free(obj->atrs);
				obj->atrs = ATNULL;
			}
			return;
		} else if (a[mid].attrnum > anam->attrnum) {
			hi = mid - 1;
		} else {
			lo = mid + 1;
		}
	}
}

/*
 * 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) {
		free(o);
		return;
	}
	a = o->atrs;
	for (i = 0; i < o->at_count; i++)
		free(a[i].data);

	free(a);
	free(o);
}