tinymush-3.1p2/game/backups/
tinymush-3.1p2/game/bin/
tinymush-3.1p2/game/data/
tinymush-3.1p2/game/modules/
tinymush-3.1p2/game/modules/old/
tinymush-3.1p2/src/modules/comsys/
tinymush-3.1p2/src/modules/hello/
tinymush-3.1p2/src/modules/mail/
tinymush-3.1p2/src/tools/
/* udb_ocache.c - LRU caching */ 
/* $Id: udb_ocache.c,v 1.80 2003/11/18 23:58:28 dpassmor Exp $ */

#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_defs.h"	/* required by code */
#include "ansi.h"	/* required by code */

extern void VDECL(logf, (char *, ...));
extern void VDECL(fatal, (char *, ...));
extern void FDECL(log_db_err, (int, int, const char *));
extern void FDECL(dddb_setsync, (int));

extern void FDECL(raw_notify, (dbref, const char *));

#define NAMECMP(a,b,c,d,e)	((d == e) && !memcmp(a,b,c))

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

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

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

#define F_DEQUEUE(q, e)	if(e->nxtfree == (Cache *)0) { \
				if (e->prvfree != (Cache *)0) { \
					e->prvfree->nxtfree = (Cache *)0; \
				} \
				q->tail = e->prvfree; \
			} \
			if(e->prvfree == (Cache *)0) { \
				q->head = e->nxtfree; \
				if (e->nxtfree) \
					e->nxtfree->prvfree = (Cache *) 0; \
			} else { \
				e->prvfree->nxtfree = e->nxtfree; \
				if (e->nxtfree) \
					e->nxtfree->prvfree = e->prvfree; \
			}

#define F_INSHEAD(q, e)	if (q->head == (Cache *)0) { \
				q->tail = e; \
				e->nxtfree = (Cache *)0; \
			} else { \
				e->nxtfree = q->head; \
				e->nxtfree->prvfree = e; \
			} \
			e->prvfree = (Cache *)0; \
			q->head = e;

#define F_INSTAIL(q, e)	if (q->head == (Cache *)0) { \
				q->head = e; \
			} else { \
				q->tail->nxtfree = e; \
			} \
			e->prvfree = q->tail; \
			q->tail = e; \
			e->nxtfree = (Cache *)0;

static Cache * FDECL(get_free_entry, (int));
static int     FDECL(cache_write, (Cache *));

/* initial settings for cache sizes */

static int cwidth = CACHE_WIDTH;

/* sys_c points to all cache lists */

Chain *sys_c;

/* freelist points to an alternate linked list kept in LRU order */

Chain *freelist;

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

/* cache stats */

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_syncs = 0;		/* total cache syncs */
int cs_size = 0;		/* total cache size */

int cachehash(keydata, keylen, type)
void *keydata;
int keylen;
unsigned int type;
{
	unsigned int hash = 0;
	char *sp;

        if (keydata == NULL)
                return 0;
        for (sp = (char *)keydata; (sp - (char *)keydata) < keylen; sp++)
                hash = (hash << 5) + hash + *sp;
        return ((hash + type) % cwidth);
}

void cache_repl(cp, new, len, type, flags)
Cache *cp;
void *new;
int len;
unsigned int type;
unsigned int flags;
{
	cs_size -= cp->datalen;
	if (cp->data != NULL) 
		RAW_FREE(cp->data, "cache_repl");
	cp->data = new;
	cp->datalen = len;
	cp->type = type;
	cp->flags = flags;
	cs_size += cp->datalen;
}

int cache_init(width)
int width;
{
	int x;
	Chain *sp;
	static char *ncmsg = "cache_init: cannot allocate cache: ";

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

	/* If width is specified as non-zero, change it to that,
	 * otherwise use default. */

	if (width)
		cwidth = width;

	sp = sys_c = (Chain *) RAW_MALLOC((unsigned)cwidth * sizeof(Chain), "cache_init");
	if (sys_c == (Chain *) 0) {
		logf(ncmsg, (char *)-1, "\n", (char *)0);
		return (-1);
	}
	freelist = (Chain *) RAW_MALLOC(sizeof(Chain), "cache_init");

	/* Allocate the initial cache entries */

	for (x = 0; x < cwidth; x++, sp++) {
		sp->head = (Cache *) 0;
		sp->tail = (Cache *) 0;
	}

	/* Init the LRU freelist */

	freelist->head = (Cache *) 0;
	freelist->tail = (Cache *) 0;
	
	/* Initialize the object pipelines */
	
	for (x = 0; x < NUM_OBJPIPES; x++) {
		mudstate.objpipes[x] = NULL;
	}
	
	/* Initialize the object access counter */
	
	mudstate.objc = 0;
	
	/* mark caching system live */
	cache_initted++;

	cs_ltime = time(NULL);

	return (0);
}

void cache_reset()
{
	int x;
	Cache *cp, *nxt;
	Chain *sp;
	DBData key, data;
	
	/* Clear the cache after startup and reset stats */

	db_lock();
	for (x = 0; x < cwidth; x++, sp++) {
		sp = &sys_c[x];
	
		/* traverse the chain */
		for (cp = sp->head; cp != NULL; cp = nxt) {
			nxt = cp->nxt;

			if (cp->flags & CACHE_DIRTY) {
				if (cp->data == NULL) {
					switch(cp->type) {
					case DBTYPE_ATTRIBUTE:
						pipe_del_attrib(((Aname *)cp->keydata)->attrnum, 
							      ((Aname *)cp->keydata)->object);
						break;
					default:
						key.dptr = cp->keydata;
						key.dsize = cp->keylen;
						db_del(key, cp->type);
					}
					cs_dels++;
				} else {
					switch(cp->type) {
					case DBTYPE_ATTRIBUTE:
						pipe_set_attrib(((Aname *)cp->keydata)->attrnum, 
							   ((Aname *)cp->keydata)->object,
							   (char *)cp->data);
						break;
					default:
						key.dptr = cp->keydata;
						key.dsize = cp->keylen;
						data.dptr = cp->data;
						data.dsize = cp->datalen;
						db_put(key, data, cp->type);
					}
					cs_dbwrites++;
				}
			}

			cache_repl(cp, NULL, 0, DBTYPE_EMPTY, 0);
			RAW_FREE(cp->keydata, "cache_get");
			RAW_FREE(cp, "get_free_entry");
		}
		
		sp->head = (Cache *) 0;
		sp->tail = (Cache *) 0;
	}
	freelist->head = (Cache *) 0;
	freelist->tail = (Cache *) 0;

	db_unlock();
	
	/* Clear the counters after startup, or they'll be skewed */
	
	cs_writes = 0;		/* total writes */
	cs_reads = 0;		/* total reads */
	cs_dbreads = 0;		/* total read-throughs */
	cs_dbwrites = 0;	/* total write-throughs */
	cs_dels = 0;		/* total deletes */
	cs_checks = 0;		/* total checks */
	cs_rhits = 0;		/* total reads filled from cache */
	cs_ahits = 0;		/* total reads filled active cache */
	cs_whits = 0;		/* total writes to dirty cache */
	cs_fails = 0;		/* attempts to grab nonexistent */
	cs_syncs = 0;		/* total cache syncs */
	cs_size = 0;		/* size of cache in bytes */
}


/* list dbrefs of objects in the cache. */

void list_cached_objs(player)
    dbref player;
{
    Chain *sp;
    Cache *cp;
    int x;
    int aco, maco, asize, msize, oco, moco;
    int *count_array, *size_array;
    
    aco = maco = asize = msize = oco = moco = 0;

    count_array = (int *) XCALLOC(mudstate.db_top, sizeof(int),
				  "list_cached_objs.count");
    size_array = (int *) XCALLOC(mudstate.db_top, sizeof(int),
				 "list_cached_objs.size");

    for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
        for (cp = sp->head; cp != NULL; cp = cp->nxt) {
            if (cp->data && (cp->type == DBTYPE_ATTRIBUTE) &&
                !(cp->flags & CACHE_DIRTY)) {
                aco++;
                asize += cp->datalen;
		count_array[((Aname *)cp->keydata)->object] += 1;
		size_array[((Aname *)cp->keydata)->object] += cp->datalen;
            }
        }
    }

    raw_notify(player, "Active Cache:");
    raw_notify(player,
	       "Name                            Dbref    Attrs      Size");
    raw_notify(player,
	       "========================================================");

    for (x = 0; x < mudstate.db_top; x++) {
	if (count_array[x] > 0) {
	    raw_notify(player,
		       tprintf("%-30.30s  #%-6d  %5d  %8d",
			       strip_ansi(Name(x)), x, count_array[x], size_array[x]));
	    oco++;
	    count_array[x] = 0;
	    size_array[x] = 0;
	}
    }

    raw_notify(player, "\nModified Active Cache:");
    raw_notify(player,
	       "Name                            Dbref    Attrs      Size");
    raw_notify(player,
	       "========================================================");

    for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
        for (cp = sp->head; cp != NULL; cp = cp->nxt) {
            if (cp->data && (cp->type == DBTYPE_ATTRIBUTE) &&
                (cp->flags & CACHE_DIRTY)) {
                maco++;
                msize += cp->datalen;
		count_array[((Aname *)cp->keydata)->object] += 1;
		size_array[((Aname *)cp->keydata)->object] += cp->datalen;
            }
        }
    }

    for (x = 0; x < mudstate.db_top; x++) {
	if (count_array[x] > 0) {
	    raw_notify(player,
		       tprintf("%-30.30s  #%-6d  %5d  %8d",
			       strip_ansi(Name(x)), x, count_array[x], size_array[x]));
	    moco++;
	}
    }

    raw_notify(player,
               tprintf("\nTotals: active %d (%d attrs), modified active %d (%d attrs), total attrs %d", oco, aco, moco, maco, aco + maco));
    raw_notify(player,
               tprintf("Size: active %d bytes, modified active %d bytes", asize, msize));

    XFREE(count_array, "list_cached_objs.count");
    XFREE(size_array, "list_cached_objs.size");
}

void list_cached_attrs(player)
    dbref player;
{
    Chain *sp;
    Cache *cp;
    int x;
    int aco, maco, asize, msize;
    ATTR *atr;
    
    aco = maco = asize = msize = 0;

    raw_notify(player, "Active Cache:");
    raw_notify(player, "Name                    Attribute                       Dbref   Size");
    raw_notify(player, "====================================================================");

    for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
        for (cp = sp->head; cp != NULL; cp = cp->nxt) {
            if (cp->data && (cp->type == DBTYPE_ATTRIBUTE) &&
                !(cp->flags & CACHE_DIRTY)) {
                aco++;
                asize += cp->datalen;
                atr = atr_num(((Aname *)cp->keydata)->attrnum);
		raw_notify(player, 
			tprintf("%-23.23s %-31.31s #%-6d %6d", PureName(((Aname *)cp->keydata)->object),
			(atr ? atr->name : "(Unknown)"), ((Aname *)cp->keydata)->object, 
			cp->datalen));
            }
        }
    }

    raw_notify(player, "\nModified Active Cache:");
    raw_notify(player, "Name                    Attribute                       Dbref   Size");
    raw_notify(player, "====================================================================");

    for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
        for (cp = sp->head; cp != NULL; cp = cp->nxt) {
            if (cp->data && (cp->type == DBTYPE_ATTRIBUTE) &&
                (cp->flags & CACHE_DIRTY)) {
                maco++;
                msize += cp->datalen;
                atr = atr_num(((Aname *)cp->keydata)->attrnum);
		raw_notify(player, 
			tprintf("%-23.23s %-31.31s #%-6d %6d", PureName(((Aname *)cp->keydata)->object),
			(atr ? atr->name : "(Unknown)"), ((Aname *)cp->keydata)->object, 
			cp->datalen));
            }
        }
    }

    raw_notify(player,
               tprintf("\nTotals: active %d, modified active %d, total attributes %d", aco, maco, aco + maco));
    raw_notify(player,
               tprintf("Size: active %d bytes, modified active %d bytes", asize, msize));
}

/* Search the cache for an entry of a specific type, if found, copy the data
 * and length into pointers provided by the caller, if not, fetch from DB.
 * You do not need to free data returned by this call. */

DBData cache_get(key, type)
DBData key;
unsigned int type;
{
	Cache *cp;
	Chain *sp;
	int hv = 0;
	DBData data;
#ifdef MEMORY_BASED
	char *cdata;
#endif

	if (!key.dptr || !cache_initted) {
		data.dptr = NULL;
		data.dsize = 0;
		return data;
	}

	/* If we're dumping, ignore stats - activity during a dump skews the
	 * working set. We make sure in get_free_entry that any activity
	 * resulting from a dump does not push out entries that are already
	 * in the cache */

#ifndef MEMORY_BASED
	if (!mudstate.standalone && !mudstate.dumping)
		cs_reads++;
#endif

#ifdef MEMORY_BASED
	if (type == DBTYPE_ATTRIBUTE)
		goto skipcacheget;
#endif


	hv = cachehash(key.dptr, key.dsize, type);
	sp = &sys_c[hv];

	for (cp = sp->head; cp != NULL; cp = cp->nxt) {
		if (NAMECMP(key.dptr, cp->keydata, key.dsize, type, cp->type)) {
			if (!mudstate.standalone && !mudstate.dumping) {
				cs_rhits++;
				cs_ahits++;
			}
			F_DEQUEUE(freelist, cp);
			F_INSTAIL(freelist, cp);


			data.dptr = cp->data;
			data.dsize = cp->datalen;

			return data;
		}
	}

#ifdef MEMORY_BASED
skipcacheget:
#endif

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

	/* Grab the data from wherever */
	
	switch(type) {
	case DBTYPE_ATTRIBUTE:
#ifdef MEMORY_BASED
		cdata = obj_get_attrib(((Aname *)key.dptr)->attrnum,
			&(db[((Aname *)key.dptr)->object].attrtext));
		if (cdata) {
			data.dptr = cdata;
			data.dsize = strlen(cdata) + 1;
			return data;
		}
#endif		
		data.dptr = (void *)pipe_get_attrib(((Aname *)key.dptr)->attrnum,
			((Aname *)key.dptr)->object); 
		if (data.dptr == NULL) {
			data.dsize = 0;
		} else {
			data.dsize = strlen(data.dptr) + 1;
		}

#ifdef MEMORY_BASED
		if (!mudstate.standalone && !mudstate.dumping)
			cs_dbreads++;
		if (data.dptr) {
			data.dsize = strlen(data.dptr) + 1;
			cdata = XMALLOC(data.dsize, "cache_get.membased");
			memcpy((void *)cdata, (void *)data.dptr, data.dsize);
			
			obj_set_attrib(((Aname *)key.dptr)->attrnum,
				&(db[((Aname *)key.dptr)->object].attrtext),
				cdata);
			data.dptr = cdata;
			return data;
		} else {
			data.dptr = NULL;
			data.dsize = 0;
			return data;
		}
#endif			
		break;
	default:
		data = db_get(key, type);
	}

	if (!mudstate.standalone && !mudstate.dumping)
		cs_dbreads++;
	
	if (data.dptr == NULL) {
		return data;
	}

	if ((cp = get_free_entry(data.dsize)) == NULL) {
		return data;
	}

	cp->keydata = (void *)RAW_MALLOC(key.dsize, "cache_get");
	memcpy(cp->keydata, key.dptr, key.dsize);
	cp->keylen = key.dsize;
	
	cp->data = data.dptr;
	cp->datalen = data.dsize;
	cp->type = type;
	cp->flags = 0;

	/* If we're dumping, we'll put everything we fetch that is not
	   already in cache at the end of the chain and set its last
	   referenced time to zero. This will ensure that we won't blow away
	   what's already in cache, since get_free_entry will just reuse
	   these entries. */
	
	cs_size += cp->datalen;

	if (mudstate.dumping) {
		/* Link at head of chain */
		INSHEAD(sp, cp);
		/* Link at head of LRU freelist */
		F_INSHEAD(freelist, cp);
	} else {
		/* Link at tail of chain */
		INSTAIL(sp, cp);
		/* Link at tail of LRU freelist */
		F_INSTAIL(freelist, cp);
	}

	return data;
}

/*
 * put an attribute 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 move it to the modified chain. 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!!!!
 * 
 */

int cache_put(key, data, type)
DBData key;
DBData data;
unsigned int type;
{
	Cache *cp;
	Chain *sp;
	int hv = 0;
#ifdef MEMORY_BASED
	char *cdata;
#endif
	
	if (!key.dptr || !data.dptr || !cache_initted) {
		return (1);
	}

	/* Call module API hook */
	
	CALL_ALL_MODULES(cache_put_notify, (key, type))
	
#ifndef MEMORY_BASED
	if (mudstate.standalone) {
#endif
		/* Bypass the cache when standalone or memory based for writes */
		if (data.dptr == NULL) {
			switch(type) {
			case DBTYPE_ATTRIBUTE:
				pipe_del_attrib(((Aname *)key.dptr)->attrnum, 
					      ((Aname *)key.dptr)->object);
#ifdef MEMORY_BASED
				obj_del_attrib(((Aname *)key.dptr)->attrnum,
					   &(db[((Aname *)key.dptr)->object].attrtext));
#endif
				break;
			default:
				db_lock();
				db_del(key, type);
				db_unlock();
			}
		} else {
			switch(type) {
			case DBTYPE_ATTRIBUTE:
				pipe_set_attrib(((Aname *)key.dptr)->attrnum, 
					   ((Aname *)key.dptr)->object,
					   (char *)data.dptr);
#ifdef MEMORY_BASED
				cdata = XMALLOC(data.dsize, "cache_get.membased");
				memcpy((void *)cdata, (void *)data.dptr, data.dsize);
			
				obj_set_attrib(((Aname *)key.dptr)->attrnum,
					&(db[((Aname *)key.dptr)->object].attrtext),
					cdata);
#endif
				/* Don't forget to free data.dptr when standalone */
				XFREE(data.dptr, "cache_put");
				
				break;
			default:
				db_lock();
				db_put(key, data, type);
				db_unlock();
			}
		}
		return(0);
#ifndef MEMORY_BASED
	}
#endif

	cs_writes++;

	/* generate hash */
	
	hv = cachehash(key.dptr, key.dsize, type);
	sp = &sys_c[hv];

	/* step one, search chain, and if we find the obj, dirty it */

	for (cp = sp->head; cp != NULL; cp = cp->nxt) {
		if (NAMECMP(key.dptr, cp->keydata, key.dsize, type, cp->type)) {
			if (!mudstate.dumping) {
				cs_whits++;
			}
			if(cp->data != data.dptr) {
				cache_repl(cp, data.dptr, data.dsize, type, CACHE_DIRTY);
			}

			F_DEQUEUE(freelist, cp);
			F_INSTAIL(freelist, cp);

			return (0);
		}
	}
	
	/* Add a new entry to the cache */

	if ((cp = get_free_entry(data.dsize)) == NULL)
		return (1);

	cp->keydata = (void *)RAW_MALLOC(key.dsize, "cache_put");
	memcpy(cp->keydata, key.dptr, key.dsize);
	cp->keylen = key.dsize;
	
	cp->data = data.dptr;
	cp->datalen = data.dsize;
	cp->type = type;
	cp->flags = CACHE_DIRTY;
	
	cs_size += cp->datalen;

	/* link at tail of chain */
	
	INSTAIL(sp, cp);

	/* link at tail of LRU freelist */
	
	F_INSTAIL(freelist, cp);
	
	return (0);
}

static Cache *get_free_entry(atrsize)
int atrsize;
{
	DBData key, data;
	Chain *sp;
	Cache *cp, *p, *prv;
	int hv;
	
	/* Flush entries from the cache until there's enough room for
	 * this one. The max size can be dynamically changed-- if it is too
	 * small, the MUSH will flush entries until the cache fits within
	 * this size and if it is too large, we'll fill it up before we
	 * start flushing */
	
	while ((cs_size + atrsize) > 
		(mudconf.cache_size ? mudconf.cache_size : CACHE_SIZE)) {

		cp = freelist->head;

		if (cp) {
			F_DEQUEUE(freelist, cp);
		}

		if (cp && (cp->flags & CACHE_DIRTY)) {
			/* Flush the modified attributes to disk */
		
			if (cp->data == NULL) {
				switch(cp->type) {
				case DBTYPE_ATTRIBUTE:
					pipe_del_attrib(((Aname *)cp->keydata)->attrnum, 
						      ((Aname *)cp->keydata)->object);
					break;
				default:
					key.dptr = cp->keydata;
					key.dsize = cp->keylen;
					db_lock();
					db_del(key, cp->type);
					db_unlock();
				}
				cs_dels++;
			} else {
				switch(cp->type) {
				case DBTYPE_ATTRIBUTE:
					pipe_set_attrib(((Aname *)cp->keydata)->attrnum, 
						   ((Aname *)cp->keydata)->object,
						   (char *)cp->data);
					break;
				default:
					key.dptr = cp->keydata;
					key.dsize = cp->keylen;
					data.dptr = cp->data;
					data.dsize = cp->datalen;
					db_lock();
					db_put(key, data, cp->type);
					db_unlock();
				}
				cs_dbwrites++;
			}
		}
		
		/* Take the attribute off of its chain and nuke the
		   attribute's memory */
		
		if (cp) {
			/* Find the cache entry inside the real cache */
			hv = cachehash(cp->keydata, cp->keylen, cp->type);
			sp = &sys_c[hv];
                	
                	prv = NULL;
                	for (p = sp->head; p != NULL; p = p->nxt) {
                		if (cp == p) {
                			break;
                		}
                		prv = p;
                	}

			/* Remove the cache entry */
			
			DEQUEUE(sp, cp);
			                	
			cache_repl(cp, NULL, 0, DBTYPE_EMPTY, 0);
			
			RAW_FREE(cp->keydata, "cache_reset.actkey");
			RAW_FREE(cp, "get_free_entry");
		}
		cp = NULL;
	}		

	/* No valid cache entries to flush, allocate a new one */

	if ((cp = (Cache *) RAW_MALLOC(sizeof(Cache), "get_free_entry")) == NULL)
		fatal("cache get_free_entry: malloc failed", (char *)-1, (char *)0);

	cp->keydata = NULL;
	cp->keylen = 0;
	cp->data = NULL;
	cp->datalen = 0;
	cp->type = DBTYPE_EMPTY;
	cp->flags = 0;
	return (cp);
}

static int cache_write(cp)
Cache *cp;
{
	DBData key, data;
	
	/* Write a single cache chain to disk */

	while (cp != NULL) {
	        if (!(cp->flags & CACHE_DIRTY)) {
			cp = cp->nxt;
			continue;
		}
		if (cp->data == NULL) {
			switch(cp->type) {
			case DBTYPE_ATTRIBUTE:
				pipe_del_attrib(((Aname *)cp->keydata)->attrnum, 
					      ((Aname *)cp->keydata)->object);
				break;
			default:
				key.dptr = cp->keydata;
				key.dsize = cp->keylen;
				db_del(key, cp->type);
			}
			cs_dels++;
		} else {
			switch(cp->type) {
			case DBTYPE_ATTRIBUTE:
				pipe_set_attrib(((Aname *)cp->keydata)->attrnum, 
					   ((Aname *)cp->keydata)->object,
					   (char *)cp->data);
				break;
			default:
				key.dptr = cp->keydata;
				key.dsize = cp->keylen;
				data.dptr = cp->data;
				data.dsize = cp->datalen;
				db_put(key, data, cp->type);
			}
			cs_dbwrites++;
		}
		cp->flags = 0;
		cp = cp->nxt;
	}
	
	return (0);
}

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

	cs_syncs++;

	if (!cache_initted)
		return (1);

	if (cache_frozen)
		return (0);

	if (mudstate.standalone || mudstate.restarting) {
		/* If we're restarting or standalone, having DBM wait for
		 * each write is a performance no-no; run asynchronously */

		dddb_setsync(0);
	}

	/* Lock the database */

	db_lock();
	
	for (x = 0, sp = sys_c; x < cwidth; x++, sp++) {
		if (cache_write(sp->head))
			return (1);
	}

	/* Also sync the read and write object structures if they're dirty */
	
	attrib_sync();

	/* Unlock the database */
	
	db_unlock();

	if (mudstate.standalone || mudstate.restarting) {
		dddb_setsync(1);
	}
	
	return (0);
}

void cache_del(key, type)
DBData key;
unsigned int type;
{
	Cache *cp;
	Chain *sp;
	int hv = 0;
	
	if (!key.dptr || !cache_initted)
		return;

	/* Call module API hook */
	
	CALL_ALL_MODULES(cache_del_notify, (key, type))

#ifdef MEMORY_BASED
	if (type == DBTYPE_ATTRIBUTE) {
		pipe_del_attrib(((Aname *)key.dptr)->attrnum, 
			      ((Aname *)key.dptr)->object);
		obj_del_attrib(((Aname *)key.dptr)->attrnum,
			   &(db[((Aname *)key.dptr)->object].attrtext));
		return;
	}
#endif

	cs_dels++;

	hv = cachehash(key.dptr, key.dsize, type);
	sp = &sys_c[hv];

	/* mark dead in cache */
	
	for (cp = sp->head; cp != NULL; cp = cp->nxt) {
		if (NAMECMP(key.dptr, cp->keydata, key.dsize, type, cp->type)) {
			F_DEQUEUE(freelist, cp);
			F_INSHEAD(freelist, cp);
			cache_repl(cp, NULL, 0, type, CACHE_DIRTY);
			return;
		}
	}
	
	if ((cp = get_free_entry(0)) == NULL)
		return;

	cp->keydata = (void *)RAW_MALLOC(key.dsize, "cache_del");
	memcpy(cp->keydata, key.dptr, key.dsize);
	cp->keylen = key.dsize;
	cp->type = type;
	cp->flags = CACHE_DIRTY;

	INSHEAD(sp, cp);
	F_INSHEAD(freelist, cp);
	return;
}