/
umud/DOC/
umud/DOC/examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
	Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /usr/users/mjr/hacks/umud/RCS/cache.c,v 1.3 91/08/19 01:14:44 mjr Exp $";
#endif

/* configure all options BEFORE including system stuff. */
#include	"config.h"


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

#include	"mud.h"

/*
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	{
	char	*onm;
	Obj	*op;
	int	flg;
	struct	cache	*nxt;
	struct	cache	*prv;
} Cache;


/* flag states */
#define	C_NOFLG	00
#define	C_DIRTY	01
#define	C_DEAD	02


typedef	struct	{
	Cache	*ahead;
	Cache	*ohead;
	Cache	*atail;
	Cache	*otail;
} CacheLst;

/* 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;

/* cache stats gathering stuff. you don't like it? comment it out */
static	time_t	cs_ltime;
static	int	cs_writes = 0;	/* total writes */
static	int	cs_reads = 0;	/* total reads */
static	int	cs_dbreads = 0;	/* total read-throughs */
static	int	cs_dbwrites = 0;	/* total write-throughs */
static	int	cs_dels = 0;		/* total deletes */
static	int	cs_checks = 0;	/* total checks */
static	int	cs_rhits = 0;	/* total reads filled from cache */
static	int	cs_ahits = 0;	/* total reads filled active cache */
static	int	cs_whits = 0;	/* total writes to dirty cache */
static	int	cs_fails = 0;	/* attempts to grab nonexistent */
static	int	cs_resets = 0;	/* total cache resets */
static	int	cs_syncs = 0;	/* total cache syncs */
static	int	cs_objects = 0;	/* total cache size */



int
cache_init()
{
	int	x;
	int	y;
	Cache	*np;
	static char	*ncmsg = "cache_init: cannot allocate cache: ";

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

	sys_c = (CacheLst *)malloc((unsigned)cwidth * sizeof(CacheLst));
	if(sys_c == (CacheLst *)0) {
		logf(ncmsg,(char *)-1,"\n",(char *)0);
		return(-1);
	}

	for(x = 0; x < cwidth; x++) {
		sys_c[x].ahead = sys_c[x].ohead = (Cache *)0;
		sys_c[x].atail = sys_c[x].otail = (Cache *)0;

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

			np = (Cache *)malloc(sizeof(Cache));
			if(np == (Cache *)0) {
				logf(ncmsg,(char *)-1,"\n",(char *)0);
				return(-1);
			}

			if((np->nxt = sys_c[x].ohead) != (Cache *)0)
				np->nxt->prv = np;
			else
				sys_c[x].otail = np;

			np->prv = (Cache *)0;
			np->flg = C_NOFLG;
			np->onm = (char *)0;
			np->op = (Obj *)0;

			sys_c[x].ohead = np;
			cs_objects++;
		}
	}

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

	time(&cs_ltime);

	return(0);
}





void
cache_reset()
{
	int	x;

	if(!cache_initted)
		return;

	/* unchain and rechain each active list at head of old chain */
	for(x = 0; x < cwidth; x++) {
		if(sys_c[x].ahead != (Cache *)0) {
			sys_c[x].atail->nxt = sys_c[x].ohead;

			if(sys_c[x].ohead != (Cache *)0)
				sys_c[x].ohead->prv = sys_c[x].atail;
			if (sys_c[x].otail == (Cache *)0)
				sys_c[x].otail = sys_c[x].atail;

			sys_c[x].ohead = sys_c[x].ahead;
			sys_c[x].ahead = (Cache *)0;
		}
	}
	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.
*/
Obj	*
cache_get(nam)
char	*nam;
{
	Cache		*cp;
	int		hv = 0;
	Obj		*ret;
	static char	*nmmesg = "cache_get: cannot allocate memory ";

	/* firewall */
	if(nam == (char *)0 || *nam == '\0' || !cache_initted) {
#ifdef	CACHE_VERBOSE
		logf("cache_get: NULL object name - programmer error\n",(char *)0);
#endif
		return((Obj *)0);
	}

#ifdef	CACHE_DEBUG
	printf("get %s\n",nam);
#endif

	/* is some fool trying to get "nowhere"? */
	if(!strcmp(nam,"nowhere"))
		return((Obj *)0);

	cs_reads++;

	hv = objid_hash(nam,cwidth);

	/* search active chain first */
	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam)) {
			cs_rhits++;
			cs_ahits++;
#ifdef	CACHE_DEBUG
			printf("return %s active cache %d\n",cp->onm,cp->op);
#endif
			return(cp->op);
		}
	}

	/* search in-active chain second */
	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam)) {

			/* dechain from in-active chain */
			if(cp->nxt == (Cache *)0)
				sys_c[hv].otail = cp->prv;
			else
				cp->nxt->prv = cp->prv;
			if(cp->prv == (Cache *)0)
				sys_c[hv].ohead = cp->nxt;
			else
				cp->prv->nxt = cp->nxt;

			/* insert at head of active chain */
			cp->nxt = sys_c[hv].ahead;
			if(sys_c[hv].ahead == (Cache *)0)
				sys_c[hv].atail = cp;
			cp->prv = (Cache *)0;
			if(cp->nxt != (Cache *)0)
				cp->nxt->prv = cp;
			sys_c[hv].ahead = cp;

			/* done */
			cs_rhits++;
#ifdef	CACHE_DEBUG
			printf("return %s old cache %d\n",cp->onm,cp->op);
#endif
			return(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)) == (Obj *)0) {
		cs_fails++;
#ifdef	CACHE_DEBUG
		printf("%s not in db\n",nam);
#endif
		return((Obj *)0);
	}
	cs_dbreads++;

	/* if there are no old cache object holders left, allocate one */
	if(sys_c[hv].otail == (Cache *)0) {
		int	xnm;

		if((cp = (Cache *)malloc(sizeof(Cache))) == (Cache *)0)
			fatal(nmmesg,(char *)-1,(char *)0);

		xnm = strlen(nam) + 1;
		if((cp->onm = (char *)malloc((unsigned)xnm)) == (char *)0)
			fatal(nmmesg,(char *)-1,(char *)0);

		/* sanity chex */
		if(xnm > MAXOID)
			logf("cache_get: name ",nam," too long!!\n",(char *)0);

		(void)strcpy(cp->onm,nam);
		cp->flg = C_NOFLG;

		/* linkit at head of active chain */
		cp->nxt = sys_c[hv].ahead;
		if(sys_c[hv].ahead == (Cache *)0)
			sys_c[hv].atail = cp;
		cp->prv = (Cache *)0;
		if(cp->nxt != (Cache *)0)
			cp->nxt->prv = cp;
		sys_c[hv].ahead = cp;
		cs_objects++;
#ifdef	CACHE_DEBUG
		printf("return %s loaded into cache %d\n",cp->onm,cp->op);
#endif
		return(cp->op = ret);
	}

	/* unlink old cache chain tail */
	cp = sys_c[hv].otail;
	if(cp->prv != (Cache *)0) {
		sys_c[hv].otail = cp->prv;
		cp->prv->nxt = cp->nxt;
	} else	/* took last one */
		sys_c[hv].ohead = sys_c[hv].otail = (Cache *)0;

	/* if there is a dirty object still in memory, write it */
	if((cp->flg & C_DIRTY) && cp->onm != (char *)0 && cp->op != (Obj *)0) {
#ifdef	CACHE_DEBUG
		printf("clean %s from cache %d\n",cp->onm,cp->op);
#endif
		if(DB_PUT(cp->op,cp->onm))
			return((Obj *)0);
		cs_dbwrites++;
	}

	/* free object's data */
	if(cp->op != (Obj *)0)
		objfree(cp->op);
	cp->op = ret;

	/* free old name */
	if(cp->onm != (char *)0)
		deferfree((mall_t)cp->onm);

	/* if this fails, boy are we in trouble! */
	if((cp->onm = (char *)malloc((unsigned)strlen(nam) + 1)) == (char *)0)
		fatal(nmmesg,(char *)-1,(char *)0);

	/* sanity chex */
	if(strlen(nam) + 1 > MAXOID)
		logf("cache_get: name ",nam," too long!!\n",(char *)0);

	(void)strcpy(cp->onm,nam);
	cp->flg = C_NOFLG;

	/* relink at head of active chain */
	cp->nxt = sys_c[hv].ahead;
	if(sys_c[hv].ahead == (Cache *)0)
		sys_c[hv].atail = cp;
	cp->prv = (Cache *)0;
	if(cp->nxt != (Cache *)0)
		cp->nxt->prv = cp;
	sys_c[hv].ahead = cp;

#ifdef	CACHE_DEBUG
	printf("return %s loaded into cache %d\n",cp->onm,cp->op);
#endif
	return(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
cache_put(obj,nam)
Obj	*obj;
char	*nam;
{
	Cache	*cp;
	int	hv = 0;
	static char	*nmmesg = "cache_put: cannot allocate memory ";

	/* firewall */
	if(obj == (Obj *)0 || nam == (char *)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 = objid_hash(nam,cwidth);

	/* step one, search active chain, and if we find the obj, dirty it */
	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->op == obj) {

			/* already dirty? */
			if(cp->flg & C_DIRTY)
				cs_whits++;

			/* OWIE! - bait & switch */
			if(obj != cp->op) {
#ifdef	CACHE_DEBUG
				printf("cache_put bait&switch %s %d\n",cp->onm,cp->op);
#endif
				if(cp->op != (Obj *)0)
					objfree(cp->op);
				cp->op = obj;
			}

#ifdef	CACHE_DEBUG
			printf("cache_put %s %d\n",cp->onm,cp->op);
#endif
			cp->flg |= C_DIRTY;
			return(0);
		}
	}

	/* step two, search in-active chain */
	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->op == obj) {

			/* already dirty? */
			if(cp->flg & C_DIRTY)
				cs_whits++;

			/* OWIE! - bait & switch */
			if(obj != cp->op) {
#ifdef	CACHE_DEBUG
				printf("cache_put bait&switch %s %d\n",cp->onm,cp->op);
#endif
				if(cp->op != (Obj *)0)
					objfree(cp->op);
				cp->op = obj;
			}

#ifdef	CACHE_DEBUG
			printf("cache_put %s %d\n",cp->onm,cp->op);
#endif
			cp->flg |= C_DIRTY;
			return(0);
		}
	}

	/* a totally new object. make a slot in the cache for it. */
	if((cp = sys_c[hv].otail) == (Cache *)0) {

		/* no room on the chain!! chain must grow! */
		if((cp = (Cache *)malloc(sizeof(Cache))) == (Cache *)0)
			fatal(nmmesg,(char *)-1,(char *)0);
		cp->onm = (char *)0;
		cp->op = (Obj *)0;
		cs_objects++;
	} else {

		/* there is a dirty object still in memory? write. */
		if((cp->flg & C_DIRTY) && cp->onm != (char *)0 && cp->op != (Obj *)0) {

#ifdef	CACHE_DEBUG
			printf("cache_put %s clean-out %d\n",cp->onm,cp->op);
#endif
			/* if this fails we are a hurting puppy. */
			if(DB_PUT(cp->op,cp->onm))
				return(1);
			cs_dbwrites++;
		}

		/* unlink from inactive chain */
		if(cp->prv != (Cache *)0) {
			sys_c[hv].otail = cp->prv;
			cp->prv->nxt = cp->nxt;
		} else {
			sys_c[hv].ohead = sys_c[hv].otail = (Cache *)0;
		}
	}


	/* point it at the new object */
	if(cp->op != (Obj *)0)
		objfree(cp->op);
	cp->op = obj;


	/* set up name */
	if(cp->onm != (char *)0)
		deferfree((mall_t)cp->onm);
	if((cp->onm = (char *)malloc((unsigned)strlen(nam) + 1)) == (char *)0)
		fatal(nmmesg,(char *)-1,(char *)0);
	(void)strcpy(cp->onm,nam);
	if(strlen(nam) + 1 > MAXOID)
		logf("cache_put: name ",nam," too long!!\n",(char *)0);


	/* mark new dodad as dirty */
	cp->flg = C_NOFLG | C_DIRTY;


	/* link at head of active chain */
	cp->nxt = sys_c[hv].ahead;
	if(sys_c[hv].ahead == (Cache *)0)
		sys_c[hv].atail = cp;
	cp->prv = (Cache *)0;
	if(cp->nxt != (Cache *)0)
		cp->nxt->prv = cp;
	sys_c[hv].ahead = cp;


#ifdef	CACHE_DEBUG
	printf("cache_put %s new in cache %d\n",cp->onm,cp->op);
#endif

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




int
cache_sync()
{
	int	x;
	Cache	*cp;

	cs_syncs++;

	if(!cache_initted)
		return(1);

	for(x = 0; x < cwidth; x++) {
		for(cp = sys_c[x].ahead; cp != (Cache *)0; cp = cp->nxt) {
			if(cp->flg & C_DIRTY) {
#ifdef	CACHE_DEBUG
				printf("sync %s\n",cp->onm);
#endif
				if(DB_PUT(cp->op,cp->onm))
					return(1);
				cs_dbwrites++;
				cp->flg &= ~C_DIRTY;
			}
		}
		for(cp = sys_c[x].ohead; cp != (Cache *)0; cp = cp->nxt) {
			if(cp->flg & C_DIRTY) {
#ifdef	CACHE_DEBUG
				printf("sync %s\n",cp->onm);
#endif
				if(DB_PUT(cp->op,cp->onm))
					return(1);
				cs_dbwrites++;
				cp->flg &= ~C_DIRTY;
			}
		}
	}
	return(0);
}




/*
probe the cache and the database (if needed) for the existence of an
object. return nonzero if the object is in cache or database
*/
int
cache_check(nam)
char	*nam;
{
	Cache	*cp;
	int	hv = 0;

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

	cs_checks++;

	hv = objid_hash(nam,cwidth);

	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt)
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam))
			return(1);

	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt)
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam))
			return(1);

	/* no ? */
	return(DB_CHECK(nam));
}




/*
delete something from the cache/db
*/
int
cache_del(nam,flg)
char	*nam;
int	flg;
{
	Cache	*cp;
	int	hv = 0;

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

	cs_dels++;

	hv = objid_hash(nam,cwidth);

	/* mark dead in cache */
	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !strcmp(cp->onm,nam)) {
#ifdef	CACHE_DEBUG
			printf("kill %s\n",cp->onm);
#endif
			cp->flg = C_DEAD;
			break;
		}
	}

	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !strcmp(cp->onm,nam)) {
#ifdef	CACHE_DEBUG
			printf("kill %s\n",cp->onm);
#endif
			cp->flg = C_DEAD;
			break;
		}
	}

	return(DB_DEL(nam,flg));
}




/* ARGSUSED */
cmd__cacheconfig(argc,argv,who,aswho)
int	argc;
char	*argv[];
char	*who;
char	*aswho;
{
	/* configure cache depth */
	if(!strcmp(argv[1],"depth")) {
		if(cache_initted) {
			logf("cache depth already fixed.\n",(char *)0);
			return(1);
		}
		if(argc < 3 || (cdepth = atoi(argv[2])) <= 0) {
			logf("bad cache depth value.\n",(char *)0);
			return(1);
		}
		logf("cache depth starts at ",argv[2]," slots.\n",(char *)0);
		return(0);
	}

	/* configure cache width */
	if(!strcmp(argv[1],"width")) {
		if(cache_initted) {
			logf("cache width already fixed.\n",(char *)0);
			return(1);
		}
		if(argc < 3 || (cwidth = atoi(argv[2])) <= 0) {
			logf("bad cache width value.\n",(char *)0);
			return(1);
		}
		logf("cache width is ",argv[2]," slots.\n",(char *)0);
		return(0);
	}

	/* sync */
	if(!strcmp(argv[1],"sync"))
		return(cache_sync());

	if(!strcmp(argv[1],"stats")) {
		char	vuf[128];

		sprintf(vuf,"cache stats, last %d sec.:",time(0) - cs_ltime);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"writes=%d reads=%d",cs_writes,cs_reads);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"dbwrites=%d dbreads=%d",cs_dbwrites,cs_dbreads);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"read hits=%d active hits=%d",cs_rhits,cs_ahits);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"write hits to dirty cache=%d",cs_whits);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"deletes=%d checks=%d",cs_dels,cs_checks);
		say(who,vuf,"\n",(char *)0);

		sprintf(vuf,"resets=%d syncs=%d objects=%d",
			cs_resets,cs_syncs,cs_objects);
		say(who,vuf,"\n",(char *)0);

		if(argc > 2 && !strcmp(argv[2],"clear")) {
			time(&cs_ltime);
			cs_writes = 0;
			cs_reads = 0;
			cs_dbreads = 0;
			cs_dbwrites = 0;
			cs_rhits = 0;
			cs_ahits = 0;
			cs_whits = 0;
			cs_fails = 0;
			cs_dels = 0;
			cs_checks = 0;
			cs_resets = 0;
			cs_syncs = 0;
		}
		return(0);
	}

	if(!strcmp(argv[1],"help")) {
		say(who,argv[0]," depth number-of-slots\n",(char *)0);
		say(who,argv[0]," width number-of-slots\n",(char *)0);
		say(who,argv[0]," sync\n",(char *)0);
		say(who,argv[0]," stats [clear]\n",(char *)0);
		return(0);
	}

	logf("I don't understand ",argv[1],"\n",(char *)0);
	return(0);
}