/
umud/DOC/
umud/DOC/examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
 * Check an Unter database for internal consistency
 * BUGS: our idea of "consistency" is a bit odd, to say the least.
 *	 We should be able to use ../vars.c instead of hardcoded variable
 *	 names, but vars.c wants to drag in too much baggage.
*/

#include	"config.h"
#include	<stdio.h>
#include	<ctype.h>
#ifdef	NOSYSTYPES_H
#include	<types.h>
#else
#include	<sys/types.h>
#endif
#include	"mud.h"

extern	char	*optarg;
extern	int	optind;
extern	int	opterr;

int	pflg = 0;	/* The picky flag; warn about things which are
			   dubious, but not outright errors. Can be specified
			   multiple times for more pickyness. */
char	*mudname = 0;	/* Our MUD name; optional but useful. */

/* The list of all the OIDs in the database. Yes, this gets big... */
char	**oids = 0;
int	curoids = 0;	/* How many OIDs we have. */
int	maxoids = 0;	/* If curoids == maxoids, we need to grow oids. */

/* This should be strdup(), but not everyone has that. */
char *
dupstr(s)
char	*s;
{
	int	l = strlen(s) + 1;
	char	*t;

	t = (char *) malloc(l);
	if (t != (char *) 0)
		(void) strcpy(t, s);
	return t;
}

int
oidcompare(a, b)
char	**a, **b;
{
	return (atoi(*a) - atoi(*b));
}

main(argc, argv)
int	argc;
char	**argv;
{
	char	obuf[MAXOID];
	Obj	*op;
	char	*fdir = (char *)0;
	char	*t;
	int	vflg = 0;
	int	bflg = 0;
	int	x, i;

	while((x = getopt(argc,argv,"n:vpb:f:")) != EOF) {
		switch(x) {
		case 'b':
			bflg = atoi(optarg);
			if(bflg <= 0) {
				fprintf(stderr,"%s not a valid size\n",optarg);
				exit(1);
			}
			break;
		case 'f':
			fdir = optarg;
			break;
		case 'v':
			vflg++;
			break;
		case 'p':
			pflg++;
			break;
		case 'n':
			mudname = optarg;
			break;
		default:
			fprintf(stderr, "%s: [-v] [-b size] [-f file] [-n name]\n",
				argv[0]);
			exit(1);
			/*NOTREACHED*/
		}
	}
	/* BUGS: we should be able to fold this into a macro somehow;
	   this way is a hack. */
	if (fdir) {
#ifdef	STORAGE_IS_HASHDIR
		dhdb_sethpath(fdir);
		if(bflg)
			dhdb_sethsiz(bflg);
#endif
#ifdef	STORAGE_IS_DBMCHUNK
		dddb_setfile(fdir);
		if(bflg)
			dddb_setbsiz(bflg);
#endif
#ifdef	STORAGE_IS_GDBMCHUNK
		dgdb_setfile(fdir);
#endif
	}

	/* Allocate an inital chunk of OIDs. */
	oids = (char **) malloc(1024 * sizeof(char *));
	if (oids == (char **) 0) {
		fprintf(stderr, "%s: cannot allocate initial oids array.\n",
			argv[0]);
		exit(1);
	}
	maxoids = 1024;

	if (cache_init()) {
		fprintf(stderr, "%s: cache init failed\n", argv[0]);
		exit(1);
	}
	if (DB_INIT()) {
		fprintf(stderr, "%s: cannot initialize db layer\n", argv[0]);
		exit(1);
	}

	/* 'Prime' things with the system object. */
	op = DB_GET("sysobj");
	if (op == (Obj *) 0) {
		fprintf(stderr, "%s: cannot read the system object, giving up.\n",
			argv[0]);
		exit(1);
	}
	highwater(op, "sysobj");
	objfree(op);
	tmp_sync();

	if (DB_TRAVSTART()) {
		fprintf(stderr, "%s: cannot start db traverse\n", argv[0]);
		exit(1);
	}
	while (DB_TRAVERSE(obuf)) {
		cache_reset();
		
		if ((op = cache_get(obuf)) == (Obj *) 0) {
			fprintf(stderr, "%s: cannot read object '%s'\n", argv[0],
				obuf);
			exit(1);
		}
		/* Save this OID. */
		t = dupstr(obuf);
		if (t == (char *) 0) {
			fprintf(stderr, "%s: cannot save OID %s\n", argv[0]);
			exit(1);
		}
		/* Now add it to the array. */
		if (!(curoids < maxoids)) {
			/* Drat. We have to grow the array. */
			maxoids = maxoids * 2;
			oids = (char **) realloc(oids, maxoids *
						 sizeof(char *));
			if (oids == (char **) 0) {
				fprintf(stderr,
					"%s: cannot realloc oids to %d, death.\n",
					argv[0], maxoids);
				exit(1);
			}
		}
		oids[curoids++] = t;

		/* objfree(op); */
		tmp_sync();	/* really free it, yes. */
	}
	if (DB_TRAVEND()) {
		fprintf(stderr, "%s: cannot cleanly close down db traverse\n",
			argv[0]);
		exit(1);
	}

	/* If we are good little citizens, this will improve locality and
	   thus cache hit rate. You can work out why, I'm sure. */
	qsort((void *) oids, (size_t) curoids, (size_t) sizeof(char *), oidcompare);
	
	/* Now, walk the database checking stuff. */
	for (i = 0; i < curoids; i++) {
		/* Curiously enough, this is in fact the right place for
		   this. */
		cache_reset();

		if ((op = cache_get(oids[i])) == (Obj *) 0) {
			fprintf(stderr,
				"%s: inconsistency: cannot reread object '%s'\n",
				argv[0], obuf);
			exit(1);
		}
		if (vflg)
			fprintf(stdout, "-> %s\n", oids[i]);
		/* check object. */
		checkobj(op, oids[i]);

		tmp_sync();	/* really free it, yes. */
	}
		
	if (DB_CLOSE()) {
		fprintf(stderr, "%s: cannot cleanly close db\n", argv[0]);
		exit(1);
	}

	/* Like neat compulsive people, free the oids array. */
	for (i = 0; i < curoids; i++)
		(void) free(oids[i]);
	(void) free(oids);

	cache_stats();
	exit(0);
}

/* Maxlocal holds the value of sysobj._objcnt (or -1 if we couldn't get
   the value for some reason). We check the object numbers of all objects
   known to be local against it to make sure sysobj._objcnt is reasonable. */
int	maxlocal = -1;

/* Does object OP have element ELEM? Does object OP have element ELEM with
   type TP? Both macros set the value of the element ELEM into the variable
   attr as a side effect; this variable must exist (and be a char *) for them
   to work. */
#define	HAS(op,elem)		((attr = objattr((op), (elem), (int *) 0)) != 0)
#define	HAST(op,elem,tp)	(HAS((op),(elem)) && attistype(attr, (tp)))

/* Given an object (assumed to be sysobj), set maxlocal to the value of
   the _objcnt field on that object if the field exists and is an int. */
highwater(op, nm)
Obj	*op;
char	*nm;
{
	char	*attr;

	if (!HAST(op, "_objcnt", "int"))
		return;		/* Don't bother screaming now, we'll do
				   it later. */
	maxlocal = atoi(attdata(attr));
}

/* Check object OP for validity; OP has object ID NM. */
checkobj(op, nm)
Obj	*op;
char	*nm;
{
	char	*attr;
	int	ply = 0, rm = 0, xit = 0, thing = 0, num;

	if (!HAST(op, "nam", "str"))
		printf("%s : has no name field or the name field isn't a string\n", 
			nm);
	if (HAS(op, "_pl"))
		ply = 1;
	if (HAS(op, "_rm"))
		rm = 1;
	if (HAS(op, "dst"))
		xit = 1;

	/* Check to see if the object number is too large. */
	num = localnum(nm);
	if (num != -1 && maxlocal != -1 && maxlocal < num)
		printf("%s : object number is greater than sysobj _objcnt of %d, something's broken\n", 
			nm, maxlocal);

	/* Check for being Mr. God. */
	if (HAS(op, "_wz") && pflg)
		printf("%s is set wizard\n", nm);

	/* Check system object consistency. */
	if (!strcmp(nm, "sysobj")) {
		checksysobj(op, nm);
		return;	/* System object is never anything else. We hope... */
	}
	/* Maybe it's an object? */
	if (!ply && !rm && !xit)
		checkobject(op, nm);
	/* We could make a switch or something, but in Unter it's
	   possible for an object to be more than one sort of object, 
	   and we must handle this. */
	if (ply)
		checkplayer(op, nm);
	if (xit)
		checkexit(op, nm);
	if (rm)
		checkroom(op, nm);
}

/* Check the system object; we check for _objcnt and for _syslimbo, and
   validate _syslimbo being a room. Checking for _objcnt looking good is
   done elsewhere, in highwater() and checkobj(). */
checksysobj(op, nm)
Obj	*op;
char	*nm;
{
	Obj	*scr;
	char	*attr, *t;

	if (!HAST(op, "_objcnt", "int"))
		printf("%s : no _objcnt field on sysobj, or field isn't an integer\n", 
			nm);
	if (!HAST(op, "_syslimbo", "obj")) {
		printf("%s : no _syslimbo field on sysobj, or field isn't a location\n", 
			nm);
		return;
	}
	t = attr;
	scr = cache_get(attdata(attr));
	if (!scr || !HAS(scr, "_rm")) {
		printf("%s : system limbo %s non-existent or not a room\n",
			nm, attdata(t));
	}
}

/* Check an otherwise unidentifiable object out, under the assumption
   that it is a normal TinyMUD "object". We validate the loc field;
   it must exist and point somewhere, and that somewhere must either
   be a player or a room and it must have us on it's .con list. */
checkobject(op, nm)
Obj	*op;
char	*nm;
{
	Obj	*scr;
	char	*attr, *t;

	if (!HAST(op, "loc", "obj")) {
		printf("%s : Thing has no location, or it's loc field isn't an obj\n", 
			nm);
		return;
	}
	t = attr;
	scr = cache_get(attdata(attr));
	if (!scr || !(HAS(scr, "_rm") || HAS(scr, "_pl"))) {
		printf("%s : object location %s is nonexistant or not a room or a player\n",
			nm, attdata(t));
	}
	else {
		/* If it's a player, it could have us in use instead. */
		if (HAS(scr, "_pl") && HAST(scr, "use", "obj")) {
			if (strcmp(attdata(attr), nm))
				listhas(scr, "con", nm, attdata(t));
		}
		else
			listhas(scr, "con", nm, attdata(t));
	}
}

/* Validate a player object. We check
	- password: a warning is given if it's non-existent
	- loc: if it exists, it must be a room and must have us on
	  it's .ply list.
	- con: All the objects on our contents list must claim to
	  be located in us.
	- dark: if we're being picky, we warn about dark players.
   Wizardry has already been mentioned in checkobj().
*/
checkplayer(op, nm)
Obj	*op;
char	*nm;
{
	Obj	*scr;
	char	*attr, *t;

	if (HAS(op, "dark") && pflg)
		printf("warning: player %s is dark\n", nm);

	if (!HAST(op, "pass", "str"))
		printf("%s : no password or the password field is not a string\n", nm);
	if (HAS(op, "loc")) {
		if (attistype("loc", "obj")) {
			printf("%s : player has location field, but it isn't of type obj\n",
				nm);
		}
		else {
			t = attr;
			scr = cache_get(attdata(attr));
			if (!scr || !HAS(scr, "_rm")) {
				printf("%s : player location %s is nonexistant or not a room\n",
					nm, attdata(t));
			}
			else
				/* Make sure the room has us in it. */
				listhas(scr, "ply", nm, attdata(t));
		}
	}
	else if (pflg > 1)
		printf("warning: player %s has no location\n", nm);
	if (HAS(op, "con") && !attistype(attr, "lst"))
		printf("%s : has contents field but it's not a list\n",
			nm);
	if (HAST(op, "con", "lst"))
		locatedhere(attdata(attr), nm);
	if (HAST(op, "use", "obj"))
		locatedhere(attdata(attr), nm);
}

/* Verify an exit; we check dst and loc. If dst is local, it must exist
   and be a room. We insist on having a loc which is a valid room and has
   us in its xit list, although old UnterMUD databases didn't put loc
   fields on exits; they can upgrade. With the output from dbchk it's
   a fairly simple job to write an automatic script to do it. */
checkexit(op, nm)
Obj	*op;
char	*nm;
{
	Obj	*scr;
	char	*attr, *t;

	/* It must have a dst element, since that's how we found it
	   in the first place. */
	if (!HAST(op, "dst", "obj"))
		printf("%s : exit has a dst field, but it isn't of type obj\n", 
			nm);
	else {
		t = attr;
		if (strcmp(attdata(t), "home") != 0) {
			scr = cache_get(attdata(attr));
			if ((!scr && islocal(attdata(attr))) ||
			    (scr && !HAS(scr, "_rm")))
				printf("%s : exit destination %s is non-existent or not a room\n",
					nm, attdata(t));
		}
	}

	if (!HAST(op, "loc", "obj")) {
		printf("%s : exit has no location or the loc field isn't an obj\n",
				nm);
	}
	else {
		t = attr;
		scr = cache_get(attdata(attr));
		if (!scr || !HAS(scr, "_rm"))
			printf("%s : exit location %s is nonexistant or not a room\n", 
				nm, attdata(t));
		else
			listhas(scr, "xit", nm, attdata(t));
	}
}

/* Validate rooms. We make sure everything on the con, xit, or ply lists
   exists and claims to be located in us. */
checkroom(op, nm)
Obj	*op;
char	*nm;
{
	Obj	*scr;
	char	*attr;

	if (HAS(op, "con") && !attistype(attr, "lst"))
		printf("%s : has contents field but it's not a list\n",
			nm);
	if (HAS(op, "xit") && !attistype(attr, "lst"))
		printf("%s : has exits field but it's not a list\n",
			nm);
	if (HAS(op, "ply") && !attistype(attr, "lst"))
		printf("%s : has players field but it's not a list\n",
			nm);
	if (HAST(op, "con", "lst"))
		locatedhere(attdata(attr), nm, "con");
	if (HAST(op, "xit", "lst"))
		locatedhere(attdata(attr), nm, "xit");
	if (HAST(op, "ply", "lst"))
		locatedhere(attdata(attr), nm, "ply");
}

/* Check and warn about list lnam on object ob not containing object
   nam. obnam is the object ID of object ob. We print out any warning
   messages, instead of returning an error indicator to our caller. */
listhas(ob, lnam, nam, obnam)
Obj	*ob;
char	*lnam, *nam, *obnam;
{
	char	*attr;

	if (!HAST(ob, lnam, "lst")) {
		printf("%s : location %s has no list element %s\n", 
			nam, obnam, lnam);
		return;
	}

	if (!lstlook(attdata(attr), nam))
		printf("%s : location %s doesn't have %s on the %s list\n", 
			nam, obnam, nam, lnam);
}

/* Verify that every element of LST is located in NM, printing out error
   messages for those elements that aren't. */
locatedhere(lst, nm, what)
char	*lst, *nm;
char	*what;
{
	Obj	*op = (Obj *) 0;
	char	*attr;
	char	obuf[MAXOID];

	while (lst) {
		lst = lstnext(lst, obuf);
		if (obuf[0] == 0)
			continue;
		op = cache_get(obuf);
		if (op == (Obj *) 0)
			printf("%s : claimed to contain %s on %s but it doesn't exist\n",
				nm, obuf, what);
		else if (!HAS(op, "loc"))
			printf("%s : claimed to contain %s on %s which doesn't have a location\n", 
				nm, obuf, what);
		else if (!attistype(attr, "obj"))
			printf("%s : claimed to contain %s on %s which has a bad type on its loc field\n",
				nm, obuf, what);
		else if (strcmp(attdata(attr), nm))
			printf("%s : claimed to contain %s on %s which in turn claims to be in %s\n",
				nm, obuf, what, attdata(attr));
		/* else we're fine. */
	}
}

/* return non-zero if nam is a "local" name, one lacking an @MUD
   qualifier. Really checks only for the presence of an '@' in the name.
   BUGS: would probably be much faster if we could use strchr() or index(). */
islocal(nam)
register char	*nam;
{
	register char	*t;

	while (*nam != '@' && *nam)
		nam++;
	return (*nam == 0);
}

/* Return the object # of NAM, if NAM is an object local to this MUD, or
   -1 if the object either has no object # (sysobj, wizard, etc) or isn't
   from this MUD. Uses the mudname variable if it's been set to catch
   #@ThisMUD and handle them correctly. */
localnum(nam)
char	*nam;
{
	char	*t;

	/* Oh well, doesn't start with a digit. */
	if (!isdigit(*nam))
		return -1;
	if (islocal(nam))
		return atoi(nam);
	/* Is our name set? */
	if (mudname == (char *) 0)
		return -1;

	t = nam;
	while (*t != '@')
		t++;
	t++;
	if (strcmp(t, mudname))
		return -1;
	else
		return atoi(nam);
}