/* * 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 "mud.h" #ifdef WIN32 #include "getopt.h" #endif 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. */ extern void cache_stats (); void highwater (Obj * op, char *nm); void checkobj (Obj * op, char *nm); void checksysobj (Obj * op, char *nm); void checkobject (Obj * op, char *nm); void checkplayer (Obj * op, char *nm); void checkexit (Obj * op, char *nm); void checkroom (Obj * op, char *nm); void listhas (Obj * ob, char *lnam, char *nam, char *obnam); void locatedhere (char *lst, char *nm, char *what); int localnum (char *nam); int islocal (char *nam); int oidcompare (a, b) char **a, **b; { return (atoi (*a) - atoi (*b)); } int main (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 = strdup (obuf); if (t == (char *) 0) { fprintf (stderr, "%s: cannot save OID %s\n", argv[0], obuf); 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. */ void highwater (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. */ void checkobj (Obj * op, char *nm) { char *attr; int ply = 0, rm = 0, xit = 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(). */ void checksysobj (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. */ void checkobject (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(). */ void checkplayer (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, "con"); if (HAST (op, "use", "obj")) locatedhere (attdata (attr), nm, "use"); } /* 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. */ void checkexit (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. */ void checkroom (Obj * op, char *nm) { 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. */ void listhas (Obj * ob, char *lnam, char *nam, char *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. */ void locatedhere (char *lst, char *nm, char *what) { Obj *op = (Obj *) 0; char *attr; char obuf[MAXOID]; while (lst) { lst = lstnext (lst, obuf, sizeof (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(). */ int islocal (char *nam) { 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. */ int localnum (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); }