pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file db.c
 *
 * \brief Loading and saving the PennMUSH object database.
 *
 *
 */

#include "copyrite.h"
#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#include <stdlib.h>
#include "conf.h"
#include "dbio.h"
#include "externs.h"
#include "mushdb.h"
#include "attrib.h"
#include "mymalloc.h"
#include "game.h"
#include "flags.h"
#include "lock.h"
#include "dbdefs.h"
#include "log.h"
#include "strtree.h"
#include "parse.h"
#include "privtab.h"
#include "htab.h"
#include "extmail.h"
#include "confmagic.h"

#ifdef WIN32
#pragma warning( disable : 4761)	/* disable warning re conversion */
#endif

#ifdef WIN32SERVICES
void shutdown_checkpoint(void);
#endif

/** Get a ref out of the database if a given db flag is set */
#define MAYBE_GET(f,x) \
        (globals.indb_flags & (x)) ? getref(f) : 0


int loading_db = 0;   /**< Are we loading the database? */

char db_timestamp[100];	/**< Time the read database was saved. */

struct object *db = NULL; /**< The object db array */
dbref db_top = 0;	  /**< The number of objects in the db array */

dbref errobj;		  /**< Dbref of object on which an error has occurred */

int dbline = 0;		  /**< Line of the database file being read */

/** String that markes the end of dumps */
const char *EOD = "***END OF DUMP***\n";

#ifndef DB_INITIAL_SIZE
#define DB_INITIAL_SIZE 5000   /**< Initial size for db array */
#endif				/* DB_INITIAL_SIZE */

dbref db_size = DB_INITIAL_SIZE;  /**< Current size of db array */

HASHTAB htab_objdata;	      /**< Object data hash table */
HASHTAB htab_objdata_keys;    /**< Object data keys hash table */

static void db_grow(dbref newtop);

static void db_write_obj_basic(FILE * f, dbref i, struct object *o);
int db_paranoid_write_object(FILE * f, dbref i, int flag);
int db_write_object(FILE * f, dbref i);
void putlocks(FILE * f, lock_list *l);
void getlocks(dbref i, FILE * f);
void get_new_locks(dbref i, FILE * f, int c);
void db_read_attrs(FILE * f, dbref i, int c);
int get_list(FILE * f, dbref i);
void db_free(void);
static void init_objdata_htab(int size);
static void db_write_flags(FILE * f);
static dbref db_read_oldstyle(FILE * f);

StrTree object_names;	    /**< String tree of object names */
extern StrTree atr_names;

void init_names(void);

void create_minimal_db(void);

extern struct db_stat_info current_state;

/** Initialize the name strtree.
 */
void
init_names(void)
{
  st_init(&object_names);
}

/** Set an object's name through the name strtree.
 * We maintain object names in a strtree because many objects have
 * the same name (cardinal exits, weapons and armor, etc.)
 * This function is used to set an object's name; if the name's already
 * in the strtree, we just get a pointer to it, saving memory.
 * (If not, we add it to the strtree and use that pointer).
 * \param obj dbref of object whose name is to be set.
 * \param newname name to set on the object, or NULL to clear the name.
 * \return object's new name, or NULL if none is given.
 */
const char *
set_name(dbref obj, const char *newname)
{
  /* if pointer not null unalloc it */
  if (Name(obj))
    st_delete(Name(obj), &object_names);
  if (!newname || !*newname)
    return NULL;
  Name(obj) = st_insert(newname, &object_names);
  return Name(obj);
}

int db_init = 0;  /**< Has the db array been initialized yet? */

static void
db_grow(dbref newtop)
{
  struct object *newdb;
  dbref initialized;
  struct object *o;

  if (newtop > db_top) {
    initialized = db_top;
    current_state.total = newtop;
    current_state.garbage += newtop - db_top;
    db_top = newtop;
    if (!db) {
      /* make the initial one */
      db_size = (db_init) ? db_init : DB_INITIAL_SIZE;
      while (db_top > db_size)
	db_size *= 2;
      if ((db = (struct object *)
	   malloc(db_size * sizeof(struct object))) == NULL) {
	do_rawlog(LT_ERR, "ERROR: out of memory while creating database!");
	abort();
      }
    }
    /* maybe grow it */
    if (db_top > db_size) {
      /* make sure it's big enough */
      while (db_top > db_size)
	db_size *= 2;
      if ((newdb = (struct object *)
	   realloc(db, db_size * sizeof(struct object))) == NULL) {
	do_rawlog(LT_ERR, "ERROR: out of memory while extending database!");
	abort();
      }
      db = newdb;
    }
    while (initialized < db_top) {
      o = db + initialized;
      o->name = 0;
      o->list = 0;
      o->location = NOTHING;
      o->contents = NOTHING;
      o->exits = NOTHING;
      o->next = NOTHING;
      o->parent = NOTHING;
      o->locks = NULL;
      o->owner = GOD;
      o->zone = NOTHING;
      o->penn = 0;
      o->type = TYPE_GARBAGE;
      o->flags = NULL;
      o->powers = NULL;
      o->warnings = 0;
      o->modification_time = o->creation_time = mudtime;
      o->attrcount = 0;
      initialized++;
    }
  }
}

/** Allocate a new object structure.
 * This function allocates and returns a new object structure.
 * The caller must see that it gets appropriately typed and otherwise
 * initialized.
 * \return dbref of newly allocated object.
 */
dbref
new_object(void)
{
  dbref newobj;
  struct object *o;
  /* if stuff in free list use it */
  if ((newobj = free_get()) == NOTHING) {
    /* allocate more space */
    newobj = db_top;
    db_grow(db_top + 1);
  }
  /* clear it out */
  o = db + newobj;
  o->name = 0;
  o->list = 0;
  o->location = NOTHING;
  o->contents = NOTHING;
  o->exits = NOTHING;
  o->next = NOTHING;
  o->parent = NOTHING;
  o->locks = NULL;
  o->owner = GOD;
  o->zone = NOTHING;
  o->penn = 0;
  o->type = TYPE_GARBAGE;
  o->flags = new_flag_bitmask("FLAG");
  o->powers = new_flag_bitmask("POWER");
  o->warnings = 0;
  o->modification_time = o->creation_time = mudtime;
  o->attrcount = 0;
  if (current_state.garbage)
    current_state.garbage--;
  return newobj;
}

/** Output a long int to a file.
 * \param f file pointer to write to.
 * \param ref value to write.
 */
void
putref(FILE * f, long int ref)
{
  OUTPUT(fprintf(f, "%ld\n", ref));
}

/** Output a string to a file.
 * This function writes a string to a file, double-quoted, 
 * appropriately escaping quotes and backslashes (the escape character).
 * \param f file pointer to write to.
 * \param s value to write.
 */
void
putstring(FILE * f, const char *s)
{
  OUTPUT(putc('"', f));
  while (*s) {
    switch (*s) {
    case '\\':
    case '"':
      OUTPUT(putc('\\', f));
      /* FALL THROUGH */
    default:
      OUTPUT(putc(*s, f));
    }
    s++;
  }
  OUTPUT(putc('"', f));
  OUTPUT(putc('\n', f));
}

/** Read a labeled entry from a database.
 * Labeled entries look like 'label entry', and are used
 * extensively in the current database format, and to a lesser
 * extent in older versions.
 * \param f the file to read from
 * \param label pointer to update to the address of a static
 * buffer containing the label that was read.
 * \param value pointer to update to the address of a static
 * buffer containing the value that was read.
 */
void
db_read_labeled_string(FILE * f, char **label, char **value)
{
  static char lbuf[BUFFER_LEN], vbuf[BUFFER_LEN];
  int c;
  char *p;

  *label = lbuf;
  *value = vbuf;

  /* invariant: we start at the beginning of a line. */

  dbline++;

  do {
    c = getc(f);
    while (isspace(c)) {
      if (c == '\n')
	dbline++;
      c = getc(f);
    }
    if (c == '#') {
      while ((c = getc(f)) != '\n' && c != EOF) {
	/* nothing */
      }
      if (c == '\n')
	dbline++;
    }
  } while (c != EOF && isspace(c));

  if (c == EOF) {
    do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
    longjmp(db_err, 1);
  }

  /* invariant: we should have the first character of a label in 'c'. */

  p = lbuf;
  do {
    if (c != '_' && c != '-' && c != '!' && c != '.' && c != '>' && c != '<' && c != '#' &&	/* these really should only be first time */
	!isalnum(c)) {
      do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in label, line %d",
		c, c, dbline);
      longjmp(db_err, 1);
    }
    safe_chr(c, lbuf, &p);
    c = getc(f);
  } while (c != EOF && !isspace(c));
  *p++ = '\0';
  if (p >= lbuf + BUFFER_LEN)
    do_rawlog(LT_ERR, "DB: warning: very long label, line %d", dbline);

  /* suck up separating whitespace */
  while (c != '\n' && c != EOF && isspace(c))
    c = getc(f);

  /* check for presence of a value, which we must have. */
  if (c == EOF || c == '\n') {
    if (c == EOF)
      do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
    else
      do_rawlog(LT_ERR, T("DB: Missing value for '%s' at line %d"), lbuf,
		dbline);
    longjmp(db_err, 1);
  }

  /* invariant: we should have the first character of a value in 'c'. */

  p = vbuf;
  if (c == '"') {
    /* quoted string */
    int sline;
    sline = dbline;
    for (;;) {
      c = getc(f);
      if (c == '"')
	break;
      if (c == '\\')
	c = getc(f);
      if (c == EOF) {
	do_rawlog(LT_ERR, "DB: Unclosed quoted string starting on line %d",
		  sline);
	longjmp(db_err, 1);
      }
      if (c == '\0')
	do_rawlog(LT_ERR,
		  "DB: warning: null in quoted string, remainder lost, line %d",
		  dbline);
      if (c == '\n')
	dbline++;
      safe_chr(c, vbuf, &p);
    }
    do {
      c = getc(f);
      if (c != EOF && !isspace(c)) {
	do_rawlog(LT_ERR, "DB: Garbage after quoted string, line %d", dbline);
	longjmp(db_err, 1);
      }
    } while (c != '\n' && c != EOF);
  } else {
    /* non-quoted value */
    do {
      if (c != '_' && c != '-' && c != '!' && c != '.' &&
	  c != '#' && !isalnum(c) && !isspace(c)) {
	do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in value, line %d",
		  c, c, dbline);
	longjmp(db_err, 1);
      }
      safe_chr(c, vbuf, &p);
      c = getc(f);
    } while (c != EOF && c != '\n');
    if (c == '\n' && (p - vbuf >= 2) && (*(p - 2) == '\r')) {
      /* Oops, we read in \r\n at the end of this value. Drop the \r */
      p--;
      *(p - 1) = '\n';
    }
  }
  *p++ = '\0';
  if (p >= vbuf + BUFFER_LEN)
    do_rawlog(LT_ERR, "DB: warning: very long value, line %d", dbline);

  /* note no line increment for final newline because of initial increment */
}

/** Read a string with a given label.
 * If the label read is different than the one being checked, the 
 * database load will abort with an error.
 * \param f the file to read from.
 * \param label the label that should be read.
 * \param value pointer to update to the address of a static
 * buffer containing the value that was read.
 */
void
db_read_this_labeled_string(FILE * f, const char *label, char **value)
{
  char *readlabel;

  db_read_labeled_string(f, &readlabel, value);

  if (strcmp(readlabel, label)) {
    do_rawlog(LT_ERR,
	      T("DB: error: Got label '%s', expected label '%s' at line %d"),
	      readlabel, label, dbline);
    longjmp(db_err, 1);
  }
}

/** Read an integer with a given label.
 * If the label read is different than the one being checked, the 
 * database load will abort with an error.
 * \param f the file to read from.
 * \param label the label that should be read.
 * \param value pointer to update to the number that was read.
 */
void
db_read_this_labeled_number(FILE * f, const char *label, int *value)
{
  char *readlabel;
  char *readvalue;

  db_read_labeled_string(f, &readlabel, &readvalue);

  if (strcmp(readlabel, label)) {
    do_rawlog(LT_ERR,
	      T("DB: error: Got label '%s', expected label '%s' at line %d"),
	      readlabel, label, dbline);
    longjmp(db_err, 1);
  }

  *value = parse_integer(readvalue);
}

/** Read an integer and label.
 * \param f the file to read from.
 * \param label pointer to update to the address of a static
 * buffer containing the label that was read.
 * \param value pointer to update to the number that was read.
 */
void
db_read_labeled_number(FILE * f, char **label, int *value)
{
  char *readvalue;
  db_read_labeled_string(f, label, &readvalue);
  *value = parse_integer(readvalue);
}

/** Read a dbref with a given label.
 * If the label read is different than the one being checked, the 
 * database load will abort with an error.
 * \param f the file to read from.
 * \param label the label that should be read.
 * \param value pointer to update to the dbref that was read.
 */
void
db_read_this_labeled_dbref(FILE * f, const char *label, dbref *val)
{
  char *readlabel;
  char *readvalue;

  db_read_labeled_string(f, &readlabel, &readvalue);

  if (strcmp(readlabel, label)) {
    do_rawlog(LT_ERR,
	      T("DB: error: Got label '%s', expected label '%s' at line %d"),
	      readlabel, label, dbline);
    longjmp(db_err, 1);
  }
  *val = qparse_dbref(readvalue);
}

/** Read a dbref and label.
 * \param f the file to read from.
 * \param label pointer to update to the address of a static
 * buffer containing the label that was read.
 * \param value pointer to update to the dbref that was read.
 */
void
db_read_labeled_dbref(FILE * f, char **label, dbref *val)
{
  char *readvalue;
  db_read_labeled_string(f, label, &readvalue);
  *val = qparse_dbref(readvalue);
}

static void
db_write_label(FILE * f, char const *l)
{
  OUTPUT(fputs(l, f));
  OUTPUT(putc(' ', f));
}

void
db_write_labeled_string(FILE * f, char const *label, char const *value)
{
  db_write_label(f, label);
  putstring(f, value);
}

void
db_write_labeled_number(FILE * f, char const *label, int value)
{
  OUTPUT(fprintf(f, "%s %d\n", label, value));
}

void
db_write_labeled_dbref(FILE * f, char const *label, dbref value)
{
  OUTPUT(fprintf(f, "%s #%d\n", label, value));
}

/** Write a boolexp to a file in unparsed (text) form.
 * \param f file pointer to write to.
 * \param b pointer to boolexp to write.
 */
void
putboolexp(FILE * f, boolexp b)
{
  db_write_labeled_string(f, "  key", unparse_boolexp(GOD, b, UB_DBREF));
}

/** Write a list of locks to a file.
 * \param f file pointer to write to.
 * \param l pointer to lock_list to write.
 */
void
putlocks(FILE * f, lock_list *l)
{
  lock_list *ll;
  int count = 0;
  for (ll = l; ll; ll = ll->next)
    count++;
  db_write_labeled_number(f, "lockcount", count);
  for (ll = l; ll; ll = ll->next) {
    db_write_labeled_string(f, " type", ll->type);
    db_write_labeled_dbref(f, "  creator", L_CREATOR(ll));
    db_write_labeled_string(f, "  flags", lock_flags_long(ll));
    db_write_labeled_number(f, "  derefs", chunk_derefs(L_KEY(ll)));
    putboolexp(f, ll->key);
    /* putboolexp adds a '\n', so we won't. */
  }
}


/** Write out the basics of an object.
 * This function writes out the basic information associated with an
 * object - just about everything but the attributes.
 * \param f file pointer to write to.
 * \param i dbref of object to write.
 * \param o pointer to object to write.
 */
static void
db_write_obj_basic(FILE * f, dbref i, struct object *o)
{
  db_write_labeled_string(f, "name", o->name);
  db_write_labeled_dbref(f, "location", o->location);
  db_write_labeled_dbref(f, "contents", o->contents);
  db_write_labeled_dbref(f, "exits", o->exits);
  db_write_labeled_dbref(f, "next", o->next);
  db_write_labeled_dbref(f, "parent", o->parent);
  putlocks(f, Locks(i));
  db_write_labeled_dbref(f, "owner", o->owner);
  db_write_labeled_dbref(f, "zone", o->zone);
  db_write_labeled_number(f, "pennies", Pennies(i));
  db_write_labeled_number(f, "type", Typeof(i));
  db_write_labeled_string(f, "flags",
			  bits_to_string("FLAG", o->flags, GOD, NOTHING));
  db_write_labeled_string(f, "powers",
			  bits_to_string("POWER", o->powers, GOD, NOTHING));
  db_write_labeled_string(f, "warnings", unparse_warnings(o->warnings));
  db_write_labeled_number(f, "created", (int) o->creation_time);
  db_write_labeled_number(f, "modified", (int) o->modification_time);
}

/** Write out an object.
 * This function writes a single object out to a file.
 * \param f file pointer to write to.
 * \param i dbref of object to write.
 */
int
db_write_object(FILE * f, dbref i)
{
  struct object *o;
  ALIST *list;
  int count = 0;

  o = db + i;
  db_write_obj_basic(f, i, o);

  /* write the attribute list */

  /* Don't trust AttrCount(thing) for number of attributes to write. */
  for (list = o->list; list; list = AL_NEXT(list)) {
    if (AF_Nodump(list))
      continue;
    count++;
  }
  db_write_labeled_number(f, "attrcount", count);

  for (list = o->list; list; list = AL_NEXT(list)) {
    if (AF_Nodump(list))
      continue;
    db_write_labeled_string(f, " name", AL_NAME(list));
    db_write_labeled_dbref(f, "  owner", Owner(AL_CREATOR(list)));
    db_write_labeled_string(f, "  flags", atrflag_to_string(AL_FLAGS(list)));
    db_write_labeled_number(f, "  derefs", AL_DEREFS(list));
    db_write_labeled_string(f, "  value", atr_value(list));
  }
  return 0;
}

/** Write out the object database to disk.
 * \verbatim
 * This function writes the databsae out to disk. The database
 * structure currently looks something like this:
 * +V<header line>
 * +FLAGS LIST
 * <flag data>
 * +POWERS LIST
 * <flag data>
 * ~<number of objects>
 * <object data>
 * \endverbatim
 * \param f file pointer to write to.
 * \param flag 0 for normal dump, DBF_PANIC for panic dumps.
 * \return the number of objects in the database (db_top)
 */
dbref
db_write(FILE * f, int flag)
{
  dbref i;
  int dbflag;

  /* print a header line to make a later conversion to 2.0 easier to do.
   * the odd choice of numbers is based on 256*x + 2 offset
   * The original PennMUSH had x=5 (chat) or x=6 (nochat), and Tiny expects
   * to deal with that. We need to use some extra flags as well, so
   * we may be adding to 5/6 as needed, using successive binary numbers.
   */
  dbflag = 5 + flag;
  dbflag += DBF_NO_CHAT_SYSTEM;
  dbflag += DBF_WARNINGS;
  dbflag += DBF_CREATION_TIMES;
  dbflag += DBF_SPIFFY_LOCKS;
  dbflag += DBF_NEW_STRINGS;
  dbflag += DBF_TYPE_GARBAGE;
  dbflag += DBF_SPLIT_IMMORTAL;
  dbflag += DBF_NO_TEMPLE;
  dbflag += DBF_LESS_GARBAGE;
  dbflag += DBF_AF_VISUAL;
  dbflag += DBF_VALUE_IS_COST;
  dbflag += DBF_LINK_ANYWHERE;
  dbflag += DBF_NO_STARTUP_FLAG;
  dbflag += DBF_AF_NODUMP;
  dbflag += DBF_NEW_FLAGS;
  dbflag += DBF_NEW_POWERS;
  dbflag += DBF_POWERS_LOGGED;
  dbflag += DBF_LABELS;

  OUTPUT(fprintf(f, "+V%d\n", dbflag * 256 + 2));

  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));

  db_write_flags(f);

  OUTPUT(fprintf(f, "~%d\n", db_top));

  for (i = 0; i < db_top; i++) {
#ifdef WIN32SERVICES
    /* Keep the service manager happy */
    if (shutdown_flag && (i & 0xFF) == 0)
      shutdown_checkpoint();
#endif
    if (IsGarbage(i))
      continue;
    OUTPUT(fprintf(f, "!%d\n", i));
    db_write_object(f, i);
  }
  OUTPUT(fputs(EOD, f));
  return db_top;
}

static void
db_write_flags(FILE * f)
{
  OUTPUT(fprintf(f, "+FLAGS LIST\n"));
  flag_write_all(f, "FLAG");
  OUTPUT(fprintf(f, "+POWER LIST\n"));
  flag_write_all(f, "POWER");
}


/** Write out an object, in paranoid fashion.
 * This function writes a single object out to a file in paranoid
 * mode, which warns about several potential types of corruption,
 * and can fix some of them.
 * \param f file pointer to write to.
 * \param i dbref of object to write.
 * \param flag 1 = debug, 0 = normal
 */
int
db_paranoid_write_object(FILE * f, dbref i, int flag)
{
  struct object *o;
  ALIST *list, *next;
  char name[BUFFER_LEN];
  char tbuf1[BUFFER_LEN];
  int err = 0;
  char *p;
  char lastp;
  dbref owner;
  int flags;
  int fixmemdb = 0;
  int count = 0;
  int attrcount = 0;

  o = db + i;
  db_write_obj_basic(f, i, o);
  /* fflush(f); */

  /* write the attribute list, scanning */
  for (list = o->list; list; list = AL_NEXT(list)) {
    if (AF_Nodump(list))
      continue;
    attrcount++;
  }

  db_write_labeled_number(f, "attrcount", count);

  for (list = o->list; list; list = next) {
    next = AL_NEXT(list);
    if (AF_Nodump(list))
      continue;
    fixmemdb = err = 0;
    /* smash unprintable characters in the name, replace with ! */
    strcpy(name, AL_NAME(list));
    for (p = name; *p; p++) {
      if (!isprint((unsigned char) *p) || isspace((unsigned char) *p)) {
	*p = '!';
	fixmemdb = err = 1;
      }
    }
    if (err) {
      /* If name already exists on this object, try adding a
       * number to the end. Give up if we can't find one < 10000
       */
      if (atr_get_noparent(i, name)) {
	count = 0;
	do {
	  name[BUFFER_LEN - 6] = '\0';
	  sprintf(tbuf1, "%s%d", name, count);
	  count++;
	} while (count < 10000 && atr_get_noparent(i, tbuf1));
	strcpy(name, tbuf1);
      }
      do_rawlog(LT_CHECK,
		T(" * Bad attribute name on #%d. Changing name to %s.\n"),
		i, name);
      err = 0;
    }
    /* check the owner */
    owner = AL_CREATOR(list);
    if (!GoodObject(owner)) {
      do_rawlog(LT_CHECK, T(" * Bad owner on attribute %s on #%d.\n"), name, i);
      owner = GOD;
      fixmemdb = 1;
    } else {
      owner = Owner(owner);
    }

    /* write that info out */
    db_write_labeled_string(f, "name", name);
    db_write_labeled_dbref(f, "owner", owner);
    db_write_labeled_string(f, "flags", atrflag_to_string(AL_FLAGS(list)));
    db_write_labeled_number(f, "derefs", AL_DEREFS(list));

    /* now check the attribute */
    strcpy(tbuf1, atr_value(list));
    /* get rid of unprintables and hard newlines */
    lastp = '\0';
    for (p = tbuf1; *p; p++) {
      if (!isprint((unsigned char) *p)) {
	if (!isspace((unsigned char) *p)) {
	  *p = '!';
	  err = 1;
	}
      }
      lastp = *p;
    }
    if (err) {
      fixmemdb = 1;
      do_rawlog(LT_CHECK,
		T(" * Bad text in attribute %s on #%d. Changed to:\n"), name,
		i);
      do_rawlog(LT_CHECK, "%s\n", tbuf1);
    }
    db_write_labeled_string(f, "value", tbuf1);
    if (flag && fixmemdb) {
      /* Fix the db in memory */
      flags = AL_FLAGS(list);
      atr_clr(i, AL_NAME(list), owner);
      (void) atr_add(i, name, tbuf1, owner, flags);
      list = atr_get_noparent(i, name);
      AL_FLAGS(list) = flags;
    }
  }
  return 0;
}



/** Write out the object database to disk, in paranoid mode.
 * \verbatim
 * This function writes the databsae out to disk, in paranoid mode. 
 * The database structure currently looks something like this:
 * +V<header line>
 * +FLAGS LIST
 * <flag data>
 * ~<number of objects>
 * <object data>
 * \endverbatim
 * \param f file pointer to write to.
 * \param flag 0 for normal paranoid dump, 1 for debug paranoid dump.
 * \return the number of objects in the database (db_top)
 */
dbref
db_paranoid_write(FILE * f, int flag)
{
  dbref i;
  int dbflag;

/* print a header line to make a later conversion to 2.0 easier to do.
 * the odd choice of numbers is based on 256*x + 2 offset
 */
  dbflag = 5;
  dbflag += DBF_NO_CHAT_SYSTEM;
  dbflag += DBF_WARNINGS;
  dbflag += DBF_CREATION_TIMES;
  dbflag += DBF_SPIFFY_LOCKS;
  dbflag += DBF_NEW_STRINGS;
  dbflag += DBF_TYPE_GARBAGE;
  dbflag += DBF_SPLIT_IMMORTAL;
  dbflag += DBF_NO_TEMPLE;
  dbflag += DBF_LESS_GARBAGE;
  dbflag += DBF_AF_VISUAL;
  dbflag += DBF_VALUE_IS_COST;
  dbflag += DBF_LINK_ANYWHERE;
  dbflag += DBF_NO_STARTUP_FLAG;
  dbflag += DBF_AF_NODUMP;
  dbflag += DBF_NEW_FLAGS;
  dbflag += DBF_NEW_POWERS;
  dbflag += DBF_POWERS_LOGGED;
  dbflag += DBF_LABELS;

  do_rawlog(LT_CHECK, "PARANOID WRITE BEGINNING...\n");

  OUTPUT(fprintf(f, "+V%d\n", dbflag * 256 + 2));

  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));
  db_write_flags(f);
  OUTPUT(fprintf(f, "~%d\n", db_top));

  /* write out each object */
  for (i = 0; i < db_top; i++) {
#ifdef WIN32SERVICES
    /* Keep the service manager happy */
    if (shutdown_flag && (i & 0xFF) == 0)
      shutdown_checkpoint();
#endif
    if (IsGarbage(i))
      continue;
    OUTPUT(fprintf(f, "!%d\n", i));
    db_paranoid_write_object(f, i, flag);
    /* print out a message every so many objects */
    if (i % globals.paranoid_checkpt == 0)
      do_rawlog(LT_CHECK, T("\t...wrote up to object #%d\n"), i);
  }
  OUTPUT(fputs(EOD, f));
  do_rawlog(LT_CHECK, T("\t...finished at object #%d\n"), i - 1);
  do_rawlog(LT_CHECK, "END OF PARANOID WRITE.\n");
  return db_top;
}


/** Read in a long int.
 * \param f file pointer to read from.
 * \return long int read.
 */
long int
getref(FILE * f)
{
  static char buf[BUFFER_LEN];
  if (!fgets(buf, sizeof(buf), f)) {
    do_rawlog(LT_ERR, T("Unexpected EOF at line %d"), dbline);
    longjmp(db_err, 1);
  }
  dbline++;
  return strtol(buf, NULL, 10);
}


/** Read in a string, into a static buffer.
 * This function reads a double-quoted escaped string of the form
 * written by putstring. The string is read into a static buffer
 * that is not allocated, so the return value must usually be copied
 * elsewhere.
 * \param f file pointer to read from.
 * \return pointer to static buffer containing string read.
 */
const char *
getstring_noalloc(FILE * f)
{
  static char buf[BUFFER_LEN];
  char *p;
  int c;

  p = buf;
  c = fgetc(f);
  if (c == EOF) {
    do_rawlog(LT_ERR, T("Unexpected EOF at line %d"), dbline);
    longjmp(db_err, 1);
  } else if (c != '"') {
    for (;;) {
      if ((c == '\0') || (c == EOF) ||
	  ((c == '\n') && ((p == buf) || (p[-1] != '\r')))) {
	*p = '\0';
	if (c == '\n')
	  dbline++;
	return buf;
      }
      safe_chr(c, buf, &p);
      c = fgetc(f);
    }
  } else {
    for (;;) {
      c = fgetc(f);
      if (c == '"') {
	/* It's a closing quote if it's followed by \r or \n */
	c = fgetc(f);
	if (c == '\r') {
	  /* Get a possible \n, too */
	  if ((c = fgetc(f)) != '\n')
	    ungetc(c, f);
	  else
	    dbline++;
	} else if (c != '\n')
	  ungetc(c, f);
	*p = '\0';
	return buf;
      } else if (c == '\\') {
	c = fgetc(f);
      }
      if ((c == '\0') || (c == EOF)) {
	*p = '\0';
	return buf;
      }
      safe_chr(c, buf, &p);
    }
  }
}

/** Read a boolexp from a file.
 * This function reads a boolexp from a file. It expects the format that
 * put_boolexp writes out.
 * \param f file pointer to read from.
 * \param type pointer to lock type being read.
 * \return pointer to boolexp read.
 */
boolexp
getboolexp(FILE * f, const char *type)
{
  char *val;
  db_read_this_labeled_string(f, "key", &val);
  return parse_boolexp(GOD, val, type);
}

extern PRIV lock_privs[];

/** Read locks for an object.
 * This function is used for DBF_SPIFFY_LOCKS to read a whole list
 * of locks from an object and set them.
 * \param i dbref of the object.
 * \param f file pointer to read from.
 * \param c number of locks, or -1 if not yet known.
 */
void
get_new_locks(dbref i, FILE * f, int c)
{
  char *val, *key;
  dbref creator;
  int flags;
  char type[BUFFER_LEN];
  boolexp b;
  int count = c, n, derefs = 0;

  if (c < 0) {
    db_read_this_labeled_string(f, "lockcount", &val);
    count = parse_integer(val);
  }

  for (n = 0; n < count; n++) {
    /* Name of the lock */
    db_read_this_labeled_string(f, "type", &val);
    strcpy(type, val);
    if (globals.indb_flags & DBF_LABELS) {
      db_read_this_labeled_dbref(f, "creator", &creator);
      db_read_this_labeled_string(f, "flags", &val);
      flags = string_to_privs(lock_privs, val, 0);
      db_read_this_labeled_number(f, "derefs", &derefs);
    } else {
      db_read_this_labeled_number(f, "creator", &creator);
      db_read_this_labeled_number(f, "flags", &flags);
    }
    /* boolexp */
    db_read_this_labeled_string(f, "key", &key);
    b = parse_boolexp_d(GOD, key, type, derefs);
    add_lock_raw(creator, i, type, b, flags);
  }
}


/** Read locks for an object.
 * This function is used for DBF_NEW_LOCKS to read a whole list
 * of locks from an object and set them. DBF_NEW_LOCKS aren't really
 * new any more, and get_new_locks() is probably being used instead of
 * this function.
 * \param i dbref of the object.
 * \param f file pointer to read from.
 */
void
getlocks(dbref i, FILE * f)
{
  /* Assumes it begins at the beginning of a line. */
  int c;
  boolexp b;
  char buf[BUFFER_LEN], *p;
  while ((c = getc(f)), c != EOF && c == '_') {
    p = buf;
    while ((c = getc(f)), c != EOF && c != '|') {
      *p++ = c;
    }
    *p = '\0';
    if (c == EOF || (p - buf == 0)) {
      do_rawlog(LT_ERR, T("ERROR: Invalid lock format on object #%d"), i);
      return;
    }
    b = getboolexp(f, buf);	/* Which will clobber a '\n' */
    if (b == TRUE_BOOLEXP) {
      /* getboolexp() would already have complained. */
      return;
    } else {
      add_lock_raw(Owner(i), i, buf, b, -1);
    }
  }
  ungetc(c, f);
  return;
}

/** Free the entire database.
 * This function frees the name, attributes, and locks on every object
 * in the database, and then free the entire database structure and
 * resets db_top.
 */
void
db_free(void)
{
  dbref i;

  if (db) {

    for (i = 0; i < db_top; i++) {
      set_name(i, NULL);
      atr_free(i);
      free_locks(Locks(i));
    }

    free((char *) db);
    db = NULL;
    db_init = db_top = 0;
  }
}

/** Read an attribute list for an object from a file
 * \param f file pointer to read from.
 * \param i dbref for the attribute list.
 */
int
get_list(FILE * f, dbref i)
{
  int c;
  char *p, *q;
  char tbuf1[BUFFER_LEN + 150];
  int flags;
  int count = 0;
  unsigned char derefs;

  List(i) = NULL;
  tbuf1[0] = '\0';
  while (1)
    switch (c = getc(f)) {
    case ']':			/* new style attribs, read name then value */
      /* Using getstring_noalloc here will cause problems with attribute
         names starting with ". This is probably a better fix than just
         disallowing " in attribute names. */
      fgets(tbuf1, BUFFER_LEN + 150, f);
      if (!(p = strchr(tbuf1, '^'))) {
	do_rawlog(LT_ERR, T("ERROR: Bad format on new attributes. object #%d"),
		  i);
	return -1;
      }
      *p++ = '\0';
      if (!(q = strchr(p, '^'))) {
	do_rawlog(LT_ERR,
		  T("ERROR: Bad format on new attribute %s. object #%d"),
		  tbuf1, i);
	return -1;
      }
      *q++ = '\0';
      flags = atoi(q);
      /* Remove obsolete AF_NUKED flag and AF_STATIC, just in case */
      flags &= ~AF_NUKED;
      flags &= ~AF_STATIC;
      if (!(globals.indb_flags & DBF_AF_VISUAL)) {
	/* Remove AF_ODARK flag. If it wasn't there, set AF_VISUAL */
	if (!(flags & AF_ODARK))
	  flags |= AF_VISUAL;
	flags &= ~AF_ODARK;
      }
      /* Read in the deref count for the attribute, or set it to 0 if not
         present. */
      q = strchr(q, '^');
      if (q++)
	derefs = atoi(q);
      else
	derefs = 0;
      /* We add the attribute assuming that atoi(p) is an ok dbref
       * since we haven't loaded the whole db and can't really tell
       * if it is or not. We'll fix this up at the end of the load 
       */
      atr_new_add(i, tbuf1, getstring_noalloc(f), atoi(p), flags, derefs);
      count++;
      /* Check removed for atoi(q) == 0  (which results in NOTHING for that
       * parameter, and thus no flags), since this eliminates 'visual'
       * attributes (which, if not built-in attrs, have a flag val of 0.)
       */
      break;
    case '>':			/* old style attribs, die noisily */
      do_rawlog(LT_ERR, T("ERROR: old-style attribute format in object %d"), i);
      return -1;
      break;
    case '<':			/* end of list */
      if ('\n' != getc(f)) {
	do_rawlog(LT_ERR, T("ERROR: no line feed after < on object %d"), i);
	return -1;
      }
      return count;
    default:
      if (c == EOF) {
	do_rawlog(LT_ERR, T("ERROR: Unexpected EOF on file."));
	return -1;
      }
      do_rawlog(LT_ERR,
		T
		("ERROR: Bad character %c (%d) in attribute list on object %d"),
		c, c, i);
      do_rawlog(LT_ERR,
		T("  (expecting ], >, or < as first character of the line.)"));
      if (*tbuf1)
	do_rawlog(LT_ERR, T("  Last attribute read was: %s"), tbuf1);
      else
	do_rawlog(LT_ERR, T("  No attributes had been read yet."));
      return -1;
    }
}

extern PRIV attr_privs[];

/** Read an attribute list for an object from a file
 * \param f file pointer to read from.
 * \param i dbref for the attribute list.
 * \param count the number of attributes to read.
 */
void
db_read_attrs(FILE * f, dbref i, int count)
{
  char name[ATTRIBUTE_NAME_LIMIT + 1];
  char value[BUFFER_LEN + 1];
  dbref owner;
  int derefs;
  int flags;
  char *tmp;

  List(i) = NULL;

  for (; count > 0; count--) {
    db_read_this_labeled_string(f, "name", &tmp);
    strcpy(name, tmp);
    db_read_this_labeled_dbref(f, "owner", &owner);
    db_read_this_labeled_string(f, "flags", &tmp);
    flags = string_to_privs(attr_privs, tmp, 0);
    db_read_this_labeled_number(f, "derefs", &derefs);
    db_read_this_labeled_string(f, "value", &tmp);
    strcpy(value, tmp);
    atr_new_add(i, name, value, owner, flags, derefs);
  }
}

/** Read a non-labeled database from a file. 
 * \param f the file to read from
 * \return number of objects in the database
 */
static dbref
db_read_oldstyle(FILE * f)
{
  int c;
  dbref i;
  struct object *o;
  int temp = 0;
  time_t temp_time = 0;

  for (i = 0;; i++) {
    /* Loop invariant: we always begin at the beginning of a line. */
    errobj = i;
    c = getc(f);
    switch (c) {
      /* make sure database is at least this big *1.5 */
    case '~':
      db_init = (getref(f) * 3) / 2;
      init_objdata_htab(db_init);
      break;
      /* Use the MUSH 2.0 header stuff to see what's in this db */
    case '+':
      c = getc(f);		/* Skip the V */
      if (c == 'F') {
	(void) getstring_noalloc(f);
	flag_read_all(f, "FLAG");
      } else if (c == 'P') {
	(void) getstring_noalloc(f);
	flag_read_all(f, "POWER");
      } else {
	do_rawlog(LT_ERR, T("Unrecognized database format!"));
	return -1;
      }
      break;
      /* old fashioned database */
    case '#':
    case '&':			/* zone oriented database */
      do_rawlog(LT_ERR, T("ERROR: old style database."));
      return -1;
      break;
      /* new database */
    case '!':			/* non-zone oriented database */
      /* make space */
      i = getref(f);
      db_grow(i + 1);
      /* read it in */
      o = db + i;
      set_name(i, getstring_noalloc(f));
      o->location = getref(f);
      o->contents = getref(f);
      o->exits = getref(f);
      o->next = getref(f);
      o->parent = getref(f);
      o->locks = NULL;
      get_new_locks(i, f, -1);
      o->owner = getref(f);
      o->zone = getref(f);
      s_Pennies(i, getref(f));
      if (globals.indb_flags & DBF_NEW_FLAGS) {
	o->type = getref(f);
	o->flags = string_to_bits("FLAG", getstring_noalloc(f));
      } else {
	int old_flags, old_toggles;
	old_flags = getref(f);
	old_toggles = getref(f);
	if ((o->type = type_from_old_flags(old_flags)) < 0) {
	  do_rawlog(LT_ERR, T("Unable to determine type of #%d\n"), i);
	  return -1;
	}
	o->flags =
	  flags_from_old_flags("FLAG", old_flags, old_toggles, o->type);
      }

      /* We need to have flags in order to do this right, which is why
       * we waited until now
       */
      switch (Typeof(i)) {
      case TYPE_PLAYER:
	current_state.players++;
	current_state.garbage--;
	break;
      case TYPE_THING:
	current_state.things++;
	current_state.garbage--;
	break;
      case TYPE_EXIT:
	current_state.exits++;
	current_state.garbage--;
	break;
      case TYPE_ROOM:
	current_state.rooms++;
	current_state.garbage--;
	break;
      }

      if (IsPlayer(i) && (strlen(o->name) > (Size_t) PLAYER_NAME_LIMIT)) {
	char buff[BUFFER_LEN + 1];	/* The name plus a NUL */
	strncpy(buff, o->name, PLAYER_NAME_LIMIT);
	buff[PLAYER_NAME_LIMIT] = '\0';
	set_name(i, buff);
	do_rawlog(LT_CHECK,
		  T(" * Name of #%d is longer than the maximum, truncating.\n"),
		  i);
      } else if (!IsPlayer(i) && (strlen(o->name) > OBJECT_NAME_LIMIT)) {
	char buff[OBJECT_NAME_LIMIT + 1];	/* The name plus a NUL */
	strncpy(buff, o->name, OBJECT_NAME_LIMIT);
	buff[OBJECT_NAME_LIMIT] = '\0';
	set_name(i, buff);
	do_rawlog(LT_CHECK,
		  T(" * Name of #%d is longer than the maximum, truncating.\n"),
		  i);
      }

      if (!(globals.indb_flags & DBF_VALUE_IS_COST) && IsThing(i))
	s_Pennies(i, (Pennies(i) + 1) * 5);

      if (globals.indb_flags & DBF_NEW_POWERS) {
	o->powers = string_to_bits("POWER", getstring_noalloc(f));
      } else {
	int old_powers;
	old_powers = getref(f);
	o->powers = flags_from_old_flags("POWER", old_powers, 0, o->type);
      }

      /* If we've got a variable exit predating the link_anywhere power,
       * give it the link_anywhere power now.
       */
      if (!(globals.indb_flags & DBF_LINK_ANYWHERE)) {
	if (IsExit(i) && (Destination(i) == AMBIGUOUS))
	  set_power_internal(i, "LINK_ANYWHERE");
      }

      /* Remove the STARTUP and ACCESSED flags */
      if (!(globals.indb_flags & DBF_NO_STARTUP_FLAG)) {
	clear_flag_internal(i, "STARTUP");
	clear_flag_internal(i, "ACCESSED");
      }

      /* Clear the GOING flags. If it was scheduled for destruction
       * when the db was saved, it gets a reprieve.
       */
      clear_flag_internal(i, "GOING");
      clear_flag_internal(i, "GOING_TWICE");

      /* If there are channels in the db, read 'em in */
      /* We don't support this anymore, so we just discard them */
      if (!(globals.indb_flags & DBF_NO_CHAT_SYSTEM))
	temp = getref(f);
      else
	temp = 0;

      /* If there are warnings in the db, read 'em in */
      temp = MAYBE_GET(f, DBF_WARNINGS);
      o->warnings = temp;
      /* If there are creation times in the db, read 'em in */
      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
      if (temp_time)
	o->creation_time = (time_t) temp_time;
      else
	o->creation_time = mudtime;
      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
      if (temp_time || IsPlayer(i))
	o->modification_time = (time_t) temp_time;
      else
	o->modification_time = o->creation_time;

      /* read attribute list for item */
      if ((o->attrcount = get_list(f, i)) < 0) {
	do_rawlog(LT_ERR, T("ERROR: bad attribute list object %d"), i);
	return -1;
      }
      if (!(globals.indb_flags & DBF_AF_NODUMP)) {
	/* Clear QUEUE and SEMAPHORE attributes */
	atr_clr(i, "QUEUE", GOD);
	atr_clr(i, "SEMAPHORE", GOD);
      }
      /* check to see if it's a player */
      if (IsPlayer(i)) {
	add_player(i, NULL);
	clear_flag_internal(i, "CONNECTED");
      }
      break;

    case '*':
      {
	char buff[80];
	ungetc('*', f);
	fgets(buff, sizeof buff, f);
	if (strcmp(buff, EOD) != 0) {
	  do_rawlog(LT_ERR, T("ERROR: No end of dump after object #%d"), i - 1);
	  return -1;
	} else {
	  do_rawlog(LT_ERR, "READING: done");
	  loading_db = 0;
	  fix_free_list();
	  dbck();
	  log_mem_check();
	  return db_top;
	}
      }
    default:
      do_rawlog(LT_ERR, T("ERROR: failed object %d"), i);
      return -1;
    }
  }
}

/** Read the object database from a file.
 * This function reads the entire database from a file. See db_write()
 * for some notes about the expected format.
 * \param f file pointer to read from.
 * \return number of objects in the database.
 */
dbref
db_read(FILE * f)
{
  int c;
  dbref i = 0;
  char *tmp;
  struct object *o;
  int minimum_flags =
    DBF_NEW_STRINGS | DBF_TYPE_GARBAGE | DBF_SPLIT_IMMORTAL | DBF_NO_TEMPLE |
    DBF_SPIFFY_LOCKS;

  log_mem_check();

  loading_db = 1;

  clear_players();
  db_free();
  globals.indb_flags = 1;

  c = fgetc(f);
  if (c != '+') {
    do_rawlog(LT_ERR, T("Database does not start with a version string"));
    return -1;
  }
  c = fgetc(f);
  if (c != 'V') {
    do_rawlog(LT_ERR, T("Database does not start with a version string"));
    return -1;
  }
  globals.indb_flags = ((getref(f) - 2) / 256) - 5;
  /* if you want to read in an old-style database, use an earlier
   * patchlevel to upgrade.
   */
  if (((globals.indb_flags & minimum_flags) != minimum_flags) ||
      (globals.indb_flags & DBF_NO_POWERS)) {
    do_rawlog(LT_ERR, T("ERROR: Old database without required dbflags."));
    return -1;
  }

  if (!(globals.indb_flags & DBF_LABELS))
    return db_read_oldstyle(f);

  db_read_this_labeled_string(f, "savedtime", &tmp);
  strcpy(db_timestamp, tmp);

  do_rawlog(LT_ERR, T("Loading database saved on %s UTC"), db_timestamp);

  while ((c = fgetc(f)) != EOF) {
    switch (c) {
    case '+':
      c = fgetc(f);
      if (c == 'F') {
	(void) getstring_noalloc(f);
	flag_read_all(f, "FLAG");
      } else if (c == 'P') {
	(void) getstring_noalloc(f);
	flag_read_all(f, "POWER");
      } else {
	do_rawlog(LT_ERR, T("Unrecognized database format!"));
	return -1;
      }
      break;
    case '~':
      db_init = (getref(f) * 3) / 2;
      init_objdata_htab(db_init);
      break;
    case '!':
      /* Read an object */
      {
	char *label, *value;
	/* Thre should be an entry in the enum and following table and
	   switch for each top-level label associated with an
	   object. Not finding a label is not an error; the default
	   set in new_object() is used. Finding a label not listed
	   below is an error. */
	enum known_labels {
	  LBL_NAME, LBL_LOCATION, LBL_CONTENTS, LBL_EXITS,
	  LBL_NEXT, LBL_PARENT, LBL_LOCKS, LBL_OWNER, LBL_ZONE,
	  LBL_PENNIES, LBL_TYPE, LBL_FLAGS, LBL_POWERS, LBL_WARNINGS,
	  LBL_CREATED, LBL_MODIFIED, LBL_ATTRS, LBL_ERROR
	};
	struct label_table {
	  const char *label;
	  enum known_labels tag;
	};
	struct label_table fields[] = {
	  {"name", LBL_NAME},
	  {"location", LBL_LOCATION},
	  {"contents", LBL_CONTENTS},
	  {"exits", LBL_EXITS},
	  {"next", LBL_NEXT},
	  {"parent", LBL_PARENT},
	  {"lockcount", LBL_LOCKS},
	  {"owner", LBL_OWNER},
	  {"zone", LBL_ZONE},
	  {"pennies", LBL_PENNIES},
	  {"type", LBL_TYPE},
	  {"flags", LBL_FLAGS},
	  {"powers", LBL_POWERS},
	  {"warnings", LBL_WARNINGS},
	  {"created", LBL_CREATED},
	  {"modified", LBL_MODIFIED},
	  {"attrcount", LBL_ATTRS},
	  /* Add new label types here. */
	  {NULL, LBL_ERROR}
	}, *entry;
	enum known_labels the_label;

	i = getref(f);
	db_grow(i + 1);
	o = db + i;
	while (1) {
	  c = fgetc(f);
	  ungetc(c, f);
	  /* At the start of another object or the EOD marker */
	  if (c == '!' || c == '*')
	    break;
	  db_read_labeled_string(f, &label, &value);
	  the_label = LBL_ERROR;
	  /* Look up the right enum value in the label table */
	  for (entry = fields; entry->label; entry++) {
	    if (strcmp(entry->label, label) == 0) {
	      the_label = entry->tag;
	      break;
	    }
	  }
	  switch (the_label) {
	  case LBL_NAME:
	    set_name(i, value);
	    break;
	  case LBL_LOCATION:
	    o->location = qparse_dbref(value);
	    break;
	  case LBL_CONTENTS:
	    o->contents = qparse_dbref(value);
	    break;
	  case LBL_EXITS:
	    o->exits = qparse_dbref(value);
	    break;
	  case LBL_NEXT:
	    o->next = qparse_dbref(value);
	    break;
	  case LBL_PARENT:
	    o->parent = qparse_dbref(value);
	    break;
	  case LBL_LOCKS:
	    get_new_locks(i, f, parse_integer(value));
	    break;
	  case LBL_OWNER:
	    o->owner = qparse_dbref(value);
	    break;
	  case LBL_ZONE:
	    o->zone = qparse_dbref(value);
	    break;
	  case LBL_PENNIES:
	    s_Pennies(i, parse_integer(value));
	    break;
	  case LBL_TYPE:
	    o->type = parse_integer(value);
	    switch (Typeof(i)) {
	    case TYPE_PLAYER:
	      current_state.players++;
	      current_state.garbage--;
	      break;
	    case TYPE_THING:
	      current_state.things++;
	      current_state.garbage--;
	      break;
	    case TYPE_EXIT:
	      current_state.exits++;
	      current_state.garbage--;
	      break;
	    case TYPE_ROOM:
	      current_state.rooms++;
	      current_state.garbage--;
	      break;
	    }
	    break;
	  case LBL_FLAGS:
	    o->flags = string_to_bits("FLAG", value);
	    /* Clear the GOING flags. If it was scheduled for destruction
	     * when the db was saved, it gets a reprieve.
	     */
	    clear_flag_internal(i, "GOING");
	    clear_flag_internal(i, "GOING_TWICE");
	    break;
	  case LBL_POWERS:
	    o->powers = string_to_bits("POWER", value);
	    break;
	  case LBL_WARNINGS:
	    o->warnings = parse_warnings(NOTHING, value);
	    break;
	  case LBL_CREATED:
	    o->creation_time = (time_t) parse_integer(value);
	    break;
	  case LBL_MODIFIED:
	    o->modification_time = (time_t) parse_integer(value);
	    break;
	  case LBL_ATTRS:
	    {
	      int attrcount = parse_integer(value);
	      db_read_attrs(f, i, attrcount);
	    }
	    break;
	  case LBL_ERROR:
	  default:
	    do_rawlog(LT_ERR, T("Unrecognized field '%s' in object #%d"),
		      label, i);
	    return -1;
	  }
	}
	if (IsPlayer(i) && (strlen(o->name) > (size_t) PLAYER_NAME_LIMIT)) {
	  char buff[BUFFER_LEN + 1];	/* The name plus a NUL */
	  strncpy(buff, o->name, PLAYER_NAME_LIMIT);
	  buff[PLAYER_NAME_LIMIT] = '\0';
	  set_name(i, buff);
	  do_rawlog(LT_CHECK,
		    T
		    (" * Name of #%d is longer than the maximum, truncating.\n"),
		    i);
	} else if (!IsPlayer(i) && (strlen(o->name) > OBJECT_NAME_LIMIT)) {
	  char buff[OBJECT_NAME_LIMIT + 1];	/* The name plus a NUL */
	  strncpy(buff, o->name, OBJECT_NAME_LIMIT);
	  buff[OBJECT_NAME_LIMIT] = '\0';
	  set_name(i, buff);
	  do_rawlog(LT_CHECK,
		    T
		    (" * Name of #%d is longer than the maximum, truncating.\n"),
		    i);
	}
	if (IsPlayer(i)) {
	  add_player(i, NULL);
	  clear_flag_internal(i, "CONNECTED");
	}
      }
      break;
    case '*':
      {
	char buff[80];
	ungetc('*', f);
	fgets(buff, sizeof buff, f);
	if (strcmp(buff, EOD) != 0) {
	  do_rawlog(LT_ERR, T("ERROR: No end of dump after object #%d"), i - 1);
	  return -1;
	} else {
	  do_rawlog(LT_ERR, "READING: done");
	  loading_db = 0;
	  fix_free_list();
	  dbck();
	  log_mem_check();
	  return db_top;
	}
      }
    default:
      do_rawlog(LT_ERR, T("ERROR: failed object %d"), i);
      return -1;
    }
  }
  return -1;
}

static void
init_objdata_htab(int size)
{
  hashinit(&htab_objdata, size, 4);
  hashinit(&htab_objdata_keys, 8, 32);
}

/** Add data to the object data hashtable. 
 * This hash table is typically used to store transient object data
 * that is built at database load and isn't saved to disk, but it
 * can be used for other purposes as well - it's a good general
 * tool for hackers who want to add their own data to objects.
 * This function adds data to the hashtable.
 * \param thing dbref of object to associate the data with.
 * \param keybase base string for type of data.
 * \param data pointer to the data to store.
 * \return data passed in.
 */
void *
set_objdata(dbref thing, const char *keybase, void *data)
{
  hashdelete(tprintf("%s_#%d", keybase, thing), &htab_objdata);
  if (data) {
    if (hashadd(tprintf("%s_#%d", keybase, thing), data, &htab_objdata) < 0)
      return NULL;
    if (hash_find(&htab_objdata_keys, keybase) == NULL) {
      char *newkey = strdup(keybase);
      hashadd(keybase, (void *) &newkey, &htab_objdata_keys);
    }
  }
  return data;
}

/** Retrieve data from the object data hashtable.
 * \param thing dbref of object data is associated with.
 * \param keybase base string for type of data.
 * \return data stored for that object and keybase, or NULL.
 */
void *
get_objdata(dbref thing, const char *keybase)
{
  return hashfind(tprintf("%s_#%d", keybase, thing), &htab_objdata);
}

/** Clear all of an object's data from the object data hashtable.
 * This function clears any data associated with a given object
 * that's in the object data hashtable (under any keybase).
 * It's used before we free the object.
 * \param thing dbref of object data is associated with.
 */
void
clear_objdata(dbref thing)
{
  char *p;
  for (p = (char *) hash_firstentry(&htab_objdata_keys);
       p; p = (char *) hash_nextentry(&htab_objdata_keys)) {
    set_objdata(thing, p, NULL);
  }
}

/** Create a basic 3-object (Start Room, God, Master Room) database. */
void
create_minimal_db(void)
{
  dbref start_room, god, master_room;

  int desc_flags = AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH;

  start_room = new_object();	/* #0 */
  god = new_object();		/* #1 */
  master_room = new_object();	/* #2 */

  init_objdata_htab(DB_INITIAL_SIZE);

  set_name(start_room, "Room Zero");
  Type(start_room) = TYPE_ROOM;
  Flags(start_room) = string_to_bits("FLAG", "LINK_OK");
  atr_new_add(start_room, "DESCRIBE", "You are in Room Zero.", GOD, desc_flags,
	      1);
  CreTime(start_room) = ModTime(start_room) = mudtime;
  current_state.rooms++;

  set_name(god, "One");
  Type(god) = TYPE_PLAYER;
  Flags(god) = string_to_bits("FLAG", "WIZARD");
  Location(god) = start_room;
  Home(god) = start_room;
  Owner(god) = god;
  CreTime(god) = mudtime;
  ModTime(god) = (time_t) 0;
  add_lock(god, god, Basic_Lock, parse_boolexp(god, "=me", Basic_Lock), -1);
  add_lock(god, god, Enter_Lock, parse_boolexp(god, "=me", Enter_Lock), -1);
  add_lock(god, god, Use_Lock, parse_boolexp(god, "=me", Use_Lock), -1);
  atr_new_add(god, "DESCRIBE", "You see Number One.", god, desc_flags, 1);
  atr_new_add(god, "MAILCURF", "0", god, AF_LOCKED | AF_NOPROG | AF_WIZARD, 1);
  add_folder_name(god, 0, "inbox");
  PUSH(god, Contents(start_room));
  add_player(god, NULL);
  s_Pennies(god, START_BONUS);
  local_data_create(god);
  current_state.players++;

  set_name(master_room, "Master Room");
  Type(master_room) = TYPE_ROOM;
  Flags(master_room) = string_to_bits("FLAG", "FLOATING");
  Owner(master_room) = god;
  CreTime(master_room) = ModTime(master_room) = mudtime;
  atr_new_add(master_room, "DESCRIBE",
	      "This is the master room. Any exit in here is considered global. The same is true for objects with $-commands placed here.",
	      god, desc_flags, 1);
  current_state.rooms++;
}