untermud/DOC/
untermud/DOC/U/
untermud/DOC/U/U-examples/
untermud/DOC/internals/
untermud/DOC/wizard/
untermud/MISC/
untermud/MISC/dbchk/
untermud/RWHO/
untermud/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    "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);
}