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