pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* db.c */

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

#include <stdio.h>
#include <ctype.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#ifdef I_STDLIB
#include <stdlib.h>
#endif

#include "conf.h"
#include "intrface.h"
#include "mushdb.h"
#include "attrib.h"
#include "externs.h"
#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#ifdef ADD_POWERS
#include "convdb.c"
#endif
#include "mymalloc.h"
#include "confmagic.h"

#ifdef WIN32
void shutdown_checkpoint _((void));
#endif

#define QUOTE_STRINGS

#define MAYBE_GET(f,x) \
	(indb_flags & (x)) ? getref(f) : 0

extern int paranoid_checkpt;	/* from game.c */

static long indb_flags;		/* What flags are in the db we read at startup? */

struct object *db = 0;
dbref db_top = 0;

dbref errobj;

#ifndef DB_INITIAL_SIZE
#define DB_INITIAL_SIZE 5000
#endif				/* DB_INITIAL_SIZE */

dbref db_size = DB_INITIAL_SIZE;

extern char ccom[];

struct boolexp *alloc_bool _((void));
void free_bool _((struct boolexp * b));
const char *set_string _((const char **ptr, const char *new));
static void db_grow _((dbref newtop));
dbref new_object _((void));
void putref _((FILE * f, long int ref));
void putstring _((FILE * f, const char *s));
static void putbool_subexp _((FILE * f, struct boolexp * b));
void putboolexp _((FILE * f, struct boolexp * b));
void putlocks _((FILE * f, struct lock_list *l));
void db_write_obj_basic _((FILE * f, dbref i, struct object * o));
int db_write_object _((FILE * f, dbref i));
dbref db_write _((FILE * f));
int db_paranoid_write_object _((FILE * f, dbref i, int flag));
dbref db_paranoid_write _((FILE * f, int flag));
long int getref _((FILE * f));
const char *getstring_noalloc _((FILE * f));
static struct boolexp *getboolexp1 _((FILE * f));
struct boolexp *getboolexp _((FILE * f));
void getlocks _((dbref i, FILE * f));
void get_old_locks _((dbref i, FILE * f));
static void tweak_locks _((void));
void free_boolexp _((struct boolexp * b));
struct boolexp *dup_bool _((struct boolexp * b));
void db_free _((void));
int get_list _((FILE * f, dbref i));
dbref db_read _((FILE * f));


/* manage boolean expression free list */
struct boolexp *
alloc_bool()
{
  struct boolexp *b = (struct boolexp *) mush_malloc(sizeof(struct boolexp), "boolexp");
  b->type = BOOLEXP_NULL;
  b->sub1 = NULL;
  b->sub2 = NULL;
  b->thing = NOTHING;
  b->atr_lock = NULL;
  return b;
}

void
free_bool(b)
    struct boolexp *b;
{
  if (b != TRUE_BOOLEXP && b) {
    mush_free((Malloc_t) b, "boolexp");
  }
}

const char *
set_string(ptr, new)
    const char **ptr;
    const char *new;
{
  /* if pointer not null unalloc it */
  if (*ptr) {
    mush_free((Malloc_t) * ptr, "object_name");
  }
  if (!new || !*new)
    return (*ptr = NULL);
  *ptr = strdup(new);
#ifdef MEM_CHECK
  add_check("object_name");
#endif
  return (*ptr);
}

int db_init = 0;

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

  if (newtop > db_top) {
    initialized = 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) {
	fprintf(stderr, "ERROR: out of memory!\n");
	fflush(stderr);
	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) {
	fprintf(stderr, "ERROR: out of memory!\n");
	fflush(stderr);
	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->flags = TYPE_GARBAGE;
      o->toggles = 0;
      o->powers = 0;
#ifdef CHAT_SYSTEM
      /* initialize channels here, since it's not going to get done otherwise */
      o->channels = NULL;
#endif
#ifdef USE_WARNINGS
      o->warnings = 0;
#endif
#ifdef CREATION_TIMES
      o->modification_time = o->creation_time = time((time_t *) 0);
#endif
#ifdef LOCAL_DATA
      o->local_data = NULL;
#endif
      initialized++;
    }
  }
}

dbref
new_object()
{
  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->flags = TYPE_GARBAGE;
  o->toggles = 0;
  o->powers = 0;
#ifdef CHAT_SYSTEM
  /* initialize channels here, since it's not going to get done otherwise */
  o->channels = NULL;
#endif
#ifdef USE_WARNINGS
  o->warnings = 0;
#endif
#ifdef CREATION_TIMES
  o->modification_time = o->creation_time = time((time_t *) 0);
#endif
#ifdef LOCAL_DATA
  o->local_data = NULL;
#endif
  /* flags you must initialize yourself */
  return newobj;
}

void
putref(f, ref)
    FILE *f;
    long int ref;
{
  fprintf(f, "%ld\n", ref);
}

#ifdef QUOTE_STRINGS
void
putstring(f, s)
    FILE *f;
    const char *s;
{
  putc('"', f);
  while (*s) {
    switch (*s) {
    case '\\':
    case '"':
      putc('\\', f);
      /* FALL THROUGH */
    default:
      putc(*s, f);
    }
    s++;
  }
  putc('"', f);
  putc('\n', f);
}
#else				/* !QUOTE_STRINGS */
void
putstring(f, s)
    FILE *f;
    const char *s;
{
  if (s) {
    fputs(s, f);
  }
  putc('\n', f);
}
#endif				/* !QUOTE_STRINGS */

static void
putbool_subexp(f, b)
    FILE *f;
    struct boolexp *b;
{
  switch (b->type) {
  case BOOLEXP_IS:
    putc('(', f);
    putc(IS_TOKEN, f);
    putbool_subexp(f, b->sub1);
    putc(')', f);
    break;
  case BOOLEXP_CARRY:
    putc('(', f);
    putc(IN_TOKEN, f);
    putbool_subexp(f, b->sub1);
    putc(')', f);
    break;
  case BOOLEXP_OWNER:
    putc('(', f);
    putc(OWNER_TOKEN, f);
    putbool_subexp(f, b->sub1);
    putc(')', f);
    break;
  case BOOLEXP_IND:
    putc('(', f);
    putc(AT_TOKEN, f);
    putbool_subexp(f, b->sub1);
    putc(')', f);
    break;
  case BOOLEXP_AND:
    putc('(', f);
    putbool_subexp(f, b->sub1);
    putc(AND_TOKEN, f);
    putbool_subexp(f, b->sub2);
    putc(')', f);
    break;
  case BOOLEXP_OR:
    putc('(', f);
    putbool_subexp(f, b->sub1);
    putc(OR_TOKEN, f);
    putbool_subexp(f, b->sub2);
    putc(')', f);
    break;
  case BOOLEXP_NOT:
    putc('(', f);
    putc(NOT_TOKEN, f);
    putbool_subexp(f, b->sub1);
    putc(')', f);
    break;
  case BOOLEXP_CONST:
    fprintf(f, "%d", b->thing);
    break;
#ifdef QUOTE_STRINGS
  case BOOLEXP_ATR:
    putstring(f, b->atr_lock->name);
    putc(':', f);
    putstring(f, uncompress(b->atr_lock->text));
    break;
  case BOOLEXP_EVAL:
    putstring(f, b->atr_lock->name);
    putc('/', f);
    putstring(f, uncompress(b->atr_lock->text));
    break;
#else				/* !QUOTE_STRINGS */
  case BOOLEXP_ATR:
    fprintf(f, "%s:%s", b->atr_lock->name, uncompress(b->atr_lock->text));
    break;
  case BOOLEXP_EVAL:
    fprintf(f, "%s/%s", b->atr_lock->name, uncompress(b->atr_lock->text));
    break;
#endif				/* !QUOTE_STRINGS */
  default:
    break;
  }
}

void
putboolexp(f, b)
    FILE *f;
    struct boolexp *b;
{
  if (b != TRUE_BOOLEXP) {
    putbool_subexp(f, b);
  }
  putc('\n', f);
}

void
putlocks(f, l)
    FILE *f;
    struct lock_list *l;
{
  struct lock_list *ll;
  for (ll = l; ll; ll = ll->next) {
    putc('_', f);
    fputs(ll->type, f);
    putc('|', f);
    putboolexp(f, ll->key);
    /* putboolexp adds a '\n', so we won't. */
  }
}

void
db_write_obj_basic(f, i, o)
    FILE *f;
    dbref i;
    struct object *o;
{
  putstring(f, o->name);
  putref(f, o->location);
  putref(f, o->contents);
  putref(f, o->exits);
  putref(f, o->next);
  putref(f, o->parent);
  putlocks(f, Locks(i));
  putref(f, o->owner);
  putref(f, o->zone);
  putref(f, Pennies(i));
  putref(f, o->flags);
  putref(f, o->toggles);
#ifndef DELETE_POWERS
  putref(f, o->powers);
#endif
  /* Original chat system wrote chat channels to the db here. 
   * That's no longer done, they're written to chatdb 
   */
#ifdef USE_WARNINGS
  putref(f, o->warnings);
#endif
#ifdef CREATION_TIMES
  putref(f, o->creation_time);
  putref(f, o->modification_time);
#endif
}

int
db_write_object(f, i)
    FILE *f;
    dbref i;
{
  struct object *o;
  ALIST *list;
  o = db + i;
  db_write_obj_basic(f, i, o);

  /* write the attribute list */
  for (list = o->list; list; list = AL_NEXT(list)) {
#ifndef DUMP_EMPTY_ATTRS
    if (!*AL_STR(list))
      continue;
#endif
    fprintf(f, "]%s^%d^%d\n", AL_NAME(list), db[AL_CREATOR(list)].owner,
	    AL_FLAGS(list));
    putstring(f, uncompress(AL_STR(list)));
  }
  fprintf(f, "<\n");
  return 0;
}

dbref
db_write(f)
    FILE *f;
{
  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;
  dbflag += DBF_NO_CHAT_SYSTEM;
#ifdef USE_WARNINGS
  dbflag += DBF_WARNINGS;
#endif
#ifdef CREATION_TIMES
  dbflag += DBF_CREATION_TIMES;
#endif
#ifndef ADD_POWERS
#ifdef DELETE_POWERS
  dbflag += DBF_NO_POWERS;
#endif
#endif
  dbflag += DBF_NEW_LOCKS;
#ifdef QUOTE_STRINGS
  dbflag += DBF_NEW_STRINGS;
#endif
  dbflag += DBF_TYPE_GARBAGE;
  dbflag += DBF_SPLIT_IMMORTAL;
  dbflag += DBF_NO_TEMPLE;
#ifdef DUMP_LESS_GARBAGE
  dbflag += DBF_LESS_GARBAGE;
#endif
  dbflag += DBF_AF_VISUAL;

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

  fprintf(f, "~%d\n", db_top);
  for (i = 0; i < db_top; i++) {
#ifdef WIN32
    /* Keep the service manager happy */
    if (shutdown_flag && (i & 0xFF) == 0)
      shutdown_checkpoint();
#endif
#ifdef DUMP_LESS_GARBAGE
    if (Destroyed(i))
      continue;
#endif
    fprintf(f, "!%d\n", i);
    db_write_object(f, i);
  }
  fputs("***END OF DUMP***\n", f);
/*  fflush(f); */
  return (db_top);
}

int
db_paranoid_write_object(f, i, flag)
    FILE *f;
    dbref i;
    int flag;			/* 1 = debug, 0 = normal */
{
  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;

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

  /* write the attribute list, scanning */
  for (list = o->list; list; list = next) {
    next = AL_NEXT(list);
#ifndef DUMP_EMPTY_ATTRS
    if (!*AL_STR(list))
      continue;
#endif
    fixmemdb = err = 0;
    /* smash unprintable characters in the name, replace with ! */
    strcpy(name, AL_NAME(list));
    for (p = name; *p; p++) {
      if (!isprint(*p) || isspace(*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);
      }
      fprintf(checklog_fp,
	      " * Bad attribute name on #%d. Changing name to %s.\n",
	      i, name);
      err = 0;
    }
    /* check the owner */
    owner = AL_CREATOR(list);
    if (!GoodObject(owner)) {
      fprintf(checklog_fp,
	      " * Bad owner on attribute %s on #%d.\n", name, i);
      owner = GOD;
      fixmemdb = 1;
    } else {
      owner = db[owner].owner;
    }

    /* write that info out */
    fprintf(f, "]%s^%d^%d\n", name, (int) owner, AL_FLAGS(list));

    /* now check the attribute */
    strcpy(tbuf1, uncompress(AL_STR(list)));
    /* get rid of unprintables and hard newlines */
    lastp = '\0';
    for (p = tbuf1; *p; p++) {
      if (!isascii(*p)) {
	*p = '!';
	err = 1;
      } else if (!isprint(*p)) {
#ifdef QUOTE_STRINGS
	if (!isspace(*p)) {
	  *p = '!';
	  err = 1;
	}
#else				/* !QUOTE_STRINGS */
	/* If we've got a \n that's not preceded by a \r, stomp it. */
	if (!isspace(*p) || ((*p == '\n') && (lastp != '\r'))) {
	  *p = '!';
	  err = 1;
	}
#endif				/* !QUOTE_STRINGS */
      }
      lastp = *p;
    }
    if (err) {
      fixmemdb = 1;
      fprintf(checklog_fp,
	   " * Bad text in attribute %s on #%d. Changed to:\n", name, i);
      fprintf(checklog_fp, "%s\n", tbuf1);
    }
    putstring(f, tbuf1);
    if (flag && fixmemdb) {
      /* Fix the db in memory */
      flags = AL_FLAGS(list);
      atr_clr(i, AL_NAME(list), owner);
      atr_add(i, name, tbuf1, owner, flags);
      list = atr_get_noparent(i, name);
      AL_FLAGS(list) = flags;
    }
  }
  fprintf(f, "<\n");
  return 0;
}

dbref
db_paranoid_write(f, flag)
    FILE *f;
    int flag;			/* 1 = debug, 0 = normal */
{
  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;
#ifdef USE_WARNINGS
  dbflag += DBF_WARNINGS;
#endif
#ifdef CREATION_TIMES
  dbflag += DBF_CREATION_TIMES;
#endif
#ifndef ADD_POWERS
#ifdef DELETE_POWERS
  dbflag += DBF_NO_POWERS;
#endif
#endif
  dbflag += DBF_NEW_LOCKS;
#ifdef QUOTE_STRINGS
  dbflag += DBF_NEW_STRINGS;
#endif
  dbflag += DBF_TYPE_GARBAGE;
  dbflag += DBF_SPLIT_IMMORTAL;
  dbflag += DBF_NO_TEMPLE;
#ifdef DUMP_LESS_GARBAGE
  dbflag += DBF_LESS_GARBAGE;
#endif
  dbflag += DBF_AF_VISUAL;

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

  fprintf(checklog_fp, "PARANOID WRITE BEGINNING...\n");
  fflush(checklog_fp);

  /* print total number of objects at top of file */
  fprintf(f, "~%d\n", db_top);

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


long
getref(f)
    FILE *f;
{
  static char buf[BUFFER_LEN];
  fgets(buf, sizeof(buf), f);
  return (atol(buf));
}

#ifdef QUOTE_STRINGS

const char *
getstring_noalloc(f)
    FILE *f;
{
  static char buf[BUFFER_LEN];
  char *p;
  char c;

  p = buf;
  c = fgetc(f);
  if (!(indb_flags & DBF_NEW_STRINGS) || (c != '"')) {
    for (;;) {
      if ((c == '\0') || (c == EOF) ||
	  ((c == '\n') && ((p == buf) || (p[-1] != '\r')))) {
	*p = '\0';
	return buf;
      }
      safe_chr(c, buf, &p);
      c = fgetc(f);
    }
  } else {
    for (;;) {
      c = fgetc(f);
      if (c == '"') {
	if ((c = fgetc(f)) != '\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);
    }
  }
}
#else				/* !QUOTE_STRINGS */
#ifndef OLD_NEWLINES
const char *
getstring_noalloc(f)
    FILE *f;
{
  static char buf[BUFFER_LEN];
  char *p;
  char c, lastc;
  int starting_quote = 0;

  p = buf;
  c = '\0';

  if (indb_flags & DBF_NEW_STRINGS) {
    c = fgetc(f);		/* Get the initial quote */
    if (c == '"')
      starting_quote = 1;
    else
      ungetc(c, f);
  }
  for (;;) {

    lastc = c;
    c = fgetc(f);

    /* if EOF or null, return */
    if ((c == '\0') || (c == EOF)) {
      *p = '\0';
      return buf;
    }
    /* if it's a newline, return if prior char is not a carriage return.
     * "\r\n" sequences are created by using the %r substitution, and
     * thus may occur in the middle of a buffer.
     */
    if (starting_quote) {
      if (c == '"') {
	fgetc(f);		/* Get the newline */
	*p = '\0';
	return buf;
      } else if (c == '\\') {
	c = fgetc(f);
      }
    } else {
      if ((c == '\n') && (lastc != '\r')) {
	*p = '\0';
	return buf;
      }
    }
    /* copy it in */
    safe_chr(c, buf, &p);
  }
}

#else
const char *
getstring_noalloc(f)
    FILE *f;
{
  static char buf[2 * BUFFER_LEN];
  char *p;
  fgets(buf, sizeof(buf), f);
  for (p = buf; *p; p++) {
    if (*p == '\n') {
      *p = '\0';
      break;
    }
  }
  return buf;
}
#endif				/* OLD_NEWLINES */
#endif				/* !QUOTE_STRINGS */

#define getstring(x,p) {p=NULL; SET(p,getstring_noalloc(x));}
#define getstring_compress(x,p)  {p=NULL; SETC(p,getstring_noalloc(x));}

static struct boolexp *
getboolexp1(f)
    FILE *f;
{
  struct boolexp *b;
  int c;
  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];

  c = getc(f);
  switch (c) {
  case '\n':
    ungetc(c, f);
    return TRUE_BOOLEXP;
    /* break; */
  case EOF:
    fprintf(stderr, "ERROR: Unexpected EOF in boolexp.  Object #%d\n",
	    errobj);
    return TRUE_BOOLEXP;
    /*NOTREACHED */
    break;
  case '(':
    b = alloc_bool();
    if ((c = getc(f)) == NOT_TOKEN) {
      b->type = BOOLEXP_NOT;
      b->sub1 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    } else if (c == IS_TOKEN) {
      b->type = BOOLEXP_IS;
      b->sub1 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    } else if (c == IN_TOKEN) {
      b->type = BOOLEXP_CARRY;
      b->sub1 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    } else if (c == OWNER_TOKEN) {
      b->type = BOOLEXP_OWNER;
      b->sub1 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    } else if (c == AT_TOKEN) {
      b->type = BOOLEXP_IND;
      b->sub1 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    } else {
      ungetc(c, f);
      b->sub1 = getboolexp1(f);
      switch (c = getc(f)) {
      case AND_TOKEN:
	b->type = BOOLEXP_AND;
	break;
      case OR_TOKEN:
	b->type = BOOLEXP_OR;
	break;
      default:
	goto error;
	/* break */
      }
      b->sub2 = getboolexp1(f);
      if (getc(f) != ')')
	goto error;
      return b;
    }
    /* break; */
  case '-':
    /* obsolete NOTHING key */
    /* eat it */
    while ((c = getc(f)) != '\n')
      if (c == EOF) {
	fprintf(stderr, "ERROR: Unexpected EOF in boolexp. Object #%d\n",
		errobj);
	return TRUE_BOOLEXP;
      }
    ungetc(c, f);
    return TRUE_BOOLEXP;
    /* break */
#ifdef QUOTE_STRINGS
  case '"':
    /* Either a BOOLEXP_ATR or a BOOLEXP_EVAL */
    ungetc(c, f);
    strcpy(tbuf1, getstring_noalloc(f));
    c = fgetc(f);
    if (c == EOF) {
      fprintf(stderr, "ERROR: Unexpected EOF in boolexp. Object #%d\n",
	      errobj);
      return TRUE_BOOLEXP;
    }
    b = alloc_bool();
    b->type = (c == ':') ? BOOLEXP_ATR : BOOLEXP_EVAL;
    b->atr_lock = alloc_atr(tbuf1, (char *) getstring_noalloc(f));
    return b;
#endif
  default:
    /* can be either a dbref or : seperated string */
    ungetc(c, f);
    b = alloc_bool();
    b->type = BOOLEXP_CONST;
    b->thing = 0;

    /* constant dbref */
    if (isdigit(c = getc(f))) {
      while (isdigit(c)) {
	b->thing = b->thing * 10 + c - '0';
	c = getc(f);
      }
      switch (c) {
      case ':':		/* old style boolexp lock */
	{
	  char *p;

	  for (p = tbuf1;
	       ((c = getc(f)) != EOF) && (c != '\n') && (c != ')');
	       *p++ = c) ;
	  *p = '\0';
	  if (c == EOF)
	    goto error;
	  b->atr_lock = alloc_atr(convert_atr(b->thing), tbuf1);
	  b->thing = 0;
	  b->type = BOOLEXP_ATR;
	  /* this is only needed because of the braindeath of the previous
	   * version of atrlocks.. bleah.
	   */
	  if (c == '\n')
	    return (b);
	}
      default:
	ungetc(c, f);
	break;
      }
      return (b);
    } else {
      /* we indulge in a bit of a kludge. We either have a colon
       * separated string (attribute lock) or a slash-separate string
       * (eval lock).
       */
      char *p = tbuf1, *s;
      char savec;

      *p++ = c;
      for (;
       ((c = getc(f)) != EOF) && (c != '\n') && (c != ':') && (c != '/');
	   *p++ = c) ;
      *p = '\0';
      if ((c == EOF) || (c == '\n'))
	goto error;
      if ((c != ':') && (c != '/'))
	goto error;
      savec = c;
      for (s = tbuf2;
	   ((c = getc(f)) != EOF) && (c != '\n') && (c != ')' &&
				    (c != OR_TOKEN) && (c != AND_TOKEN));
	   *s++ = c) ;
      if (c == EOF)
	goto error;
      *s++ = 0;
      ungetc(c, f);
      b->atr_lock = alloc_atr(tbuf1, tbuf2);
      if (savec == ':')
	b->type = BOOLEXP_ATR;
      else
	b->type = BOOLEXP_EVAL;
      return (b);
    }
  }
error:
  fprintf(stderr, "ERROR: Unknown error in boolexp. Object #%d\n", errobj);
  return TRUE_BOOLEXP;
}

struct boolexp *
getboolexp(f)
    FILE *f;
{
  struct boolexp *b;
  b = getboolexp1(f);
  if (getc(f) != '\n') {
    fprintf(stderr, "ERROR: Invalid boolexp format on object #%d\n", errobj);
    return TRUE_BOOLEXP;
  }
  return b;
}

void
getlocks(i, f)
    dbref i;
    FILE *f;
{
  /* Assumes it begins at the beginning of a line. */
  int c;
  struct 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)) {
      fprintf(stderr, "ERROR: Invalid lock format on object #%d\n", i);
      return;
    }
    b = getboolexp(f);		/* Which will clobber a '\n' */
    if (b == TRUE_BOOLEXP) {
      /* getboolexp() would already have complained. */
      return;
    } else {
      add_lock(i, buf, b);
    }
  }
  ungetc(c, f);
  return;
}

void
get_old_locks(i, f)
    dbref i;
    FILE *f;
{
  /* To review the format: There are three boolexps in order, with no
   * identifiers. They are these:
   * Basic Lock
   * Enter/Tport/Zone Lock
   * Use/Page Lock.
   * Most of the subtlety of this routine comes from deciding what locks
   * to set when locks have been overloaded.
   */
  /* Oops. When we read the locks in, we do not know what the flags of
   * the object are, so we have no grounds for subtle deduction about
   * what locks are appropriate. Therefore, all the subtlety is relegated
   * to the tweak_locks() routine.
   */
  struct boolexp *basic, *enter, *use;
  basic = getboolexp(f);
  if (basic != TRUE_BOOLEXP) {
    add_lock(i, Basic_Lock, basic);
  }
  use = getboolexp(f);
  if (use != TRUE_BOOLEXP) {
    add_lock(i, Use_Lock, use);
  }
  enter = getboolexp(f);
  if (enter != TRUE_BOOLEXP) {
    add_lock(i, Enter_Lock, enter);
  }
}

  /* This should be called after a whole database with DBF_NEW_LOCKS not
   * set is read in, to do some post-processing on the locks.
   */
static void
tweak_locks()
{
  int i;
  struct boolexp *b1, *b2;
  for (i = 0; i < db_top; i++) {
    switch (Typeof(i)) {
    case TYPE_ROOM:
      /* Turn elocks into tport locks on rooms. */
      b1 = getlock(i, Enter_Lock);
      if (b1 != TRUE_BOOLEXP) {
	b2 = dup_bool(b1);
	delete_lock(i, Enter_Lock);
	add_lock(i, Tport_Lock, b2);
      }
      break;
    case TYPE_PLAYER:
      /* Clone the uselock to get a pagelock for players. */
      b1 = getlock(i, Use_Lock);
      if (b1 != TRUE_BOOLEXP) {
	b2 = dup_bool(b1);
	/* Do not delete the uselock. */
	add_lock(i, Page_Lock, b2);
      }
      if (ZMaster(i)) {
	/* Copy the elock to get a Zone Lock. */
	b1 = getlock(i, Enter_Lock);
	if (b1 != TRUE_BOOLEXP) {
	  b2 = dup_bool(b1);
	  add_lock(i, Zone_Lock, b2);
	}
      }
      break;
    default:
      break;
    }
    if (Zone(i) != NOTHING) {
      /* If Zone(i) has an enter lock but does not have a zone lock,
       * clone the enter lock to get a zone lock.
       */
      if (getlock(Zone(i), Enter_Lock) != TRUE_BOOLEXP &&
	  getlock(Zone(i), Zone_Lock) == TRUE_BOOLEXP) {
	b1 = getlock(Zone(i), Enter_Lock);
	b2 = dup_bool(b1);
	add_lock(Zone(i), Zone_Lock, b2);
      }
    }
  }
}

void
free_boolexp(b)
    struct boolexp *b;
{
  if (b != TRUE_BOOLEXP && b) {
    switch (b->type) {
    case BOOLEXP_AND:
    case BOOLEXP_OR:
      free_boolexp(b->sub1);
      free_boolexp(b->sub2);
      free_bool(b);
      break;
    case BOOLEXP_NOT:
    case BOOLEXP_CARRY:
    case BOOLEXP_IND:
    case BOOLEXP_IS:
      free_boolexp(b->sub1);
      free_bool(b);
      break;
    case BOOLEXP_CONST:
      free_bool(b);
      break;
    case BOOLEXP_ATR:
    case BOOLEXP_EVAL:
      if (b->atr_lock->name)
	mush_free((Malloc_t) b->atr_lock->name, "boolatr_name");
      if (b->atr_lock->text)
	mush_free((Malloc_t) b->atr_lock->text, "boolatr_text");
      if (b->atr_lock)
	mush_free((Malloc_t) b->atr_lock, "boolatr");
      free_bool(b);
      break;
    }
  }
}

struct boolexp *
dup_bool(b)
    struct boolexp *b;
{
  struct boolexp *r;
  if (b == TRUE_BOOLEXP)
    return (TRUE_BOOLEXP);
  r = alloc_bool();
  switch (r->type = b->type) {
  case BOOLEXP_AND:
  case BOOLEXP_OR:
    r->sub2 = dup_bool(b->sub2);
  case BOOLEXP_NOT:
  case BOOLEXP_IND:
  case BOOLEXP_IS:
  case BOOLEXP_CARRY:
  case BOOLEXP_OWNER:
    r->sub1 = dup_bool(b->sub1);
  case BOOLEXP_CONST:
    r->thing = b->thing;
    break;
  case BOOLEXP_ATR:
  case BOOLEXP_EVAL:
    r->atr_lock = alloc_atr(b->atr_lock->name, uncompress(b->atr_lock->text));
    break;
  default:
    fprintf(stderr, "ERROR: bad bool type in dup_bool!\n");
    return (TRUE_BOOLEXP);
  }
  return (r);
}

void
db_free()
{
  dbref i;
  struct object *o;

  if (db) {

    for (i = 0; i < db_top; i++) {
      o = &db[i];
      SET(o->name, NULL);
      atr_free(i);
      free_locks(Locks(i));
    }

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

/* read attribute list */
int
get_list(f, i)
    FILE *f;
    dbref i;
{
  int c;
  char *p, *q;
  char tbuf1[BUFFER_LEN];
  int flags;

#ifdef OLD_NEWLINES
  char nextc;
#endif				/* OLD_NEWLINES */

  db[i].list = NULL;
  tbuf1[0] = '\0';
  while (1)
    switch (c = getc(f)) {
    case ']':			/* new style attribs, read name then value */
      strcpy(tbuf1, getstring_noalloc(f));
      if (!(p = (char *) index(tbuf1, '^'))) {
	fprintf(stderr, "ERROR: Bad format on new attributes. object #%d\n",
		i);
	return 0;
      }
      *p++ = '\0';
      if (!(q = (char *) index(p, '^'))) {
	fprintf(stderr, "ERROR: Bad format on new attribute %s. object #%d\n",
		tbuf1, i);
	return 0;
      }
      *q++ = '\0';
      flags = atoi(q);
      if (!(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;
      }
      atr_new_add(i, tbuf1, (char *) getstring_noalloc(f), (GoodObject(atoi(p))) ?
		  atoi(p) : GOD, flags);
      /* 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.)
       */
#ifdef OLD_NEWLINES
      /* hack to check if there's an unexpected '\n' that got in */
      nextc = getc(f);
      if ((nextc != ']') && (nextc != '>') && (nextc != '<') &&
	  (nextc != '!')) {
	/* we have a problem. print error */
	fprintf(stderr,
	   "** WARNING **  Hard newline in attribute %s on object #%d\n",
		tbuf1, i);
	/* throw out everything until next good attrib or object.
	 * we can tell a good attrib or object by looking for a \n
	 * followed by a ], >, <, or !. Otherwise, we can be "spoofed"
	 */
	do {
	  while ((nextc = getc(f)) != '\n') ;
	  nextc = getc(f);	/* go past EOL */
	} while ((nextc != ']') && (nextc != '>') && (nextc != '<') &&
		 (nextc != '!'));
      }
      /* we've eaten a character, so go back. */
      ungetc(nextc, f);
#endif				/* OLD_NEWLINES */
      break;
    case '>':			/* old style attribs, read # then value */
      strcpy(tbuf1, convert_atr(getref(f)));
      atr_new_add(i, tbuf1, (char *) getstring_noalloc(f), db[i].owner, NOTHING);
      break;
    case '<':			/* end of list */
      if ('\n' != getc(f)) {
	fprintf(stderr, "ERROR: no line feed after < on object %d\n", i);
	return (0);
      }
      return (1);
    default:
      if (c == EOF) {
	fprintf(stderr, "ERROR: Unexpected EOF on file.\n");
	return (0);
      }
      fprintf(stderr, "ERROR: Bad character %c (%d) in attribute list on object %d\n", c, c, i);
      fprintf(stderr, "  (expecting ], >, or < as first character of the line.)\n");
      if (*tbuf1)
	fprintf(stderr, "  Last attribute read was: %s\n", tbuf1);
      else
	fprintf(stderr, "  No attributes had been read yet.\n");
      return (0);
    }
}



dbref
db_read(f)
    FILE *f;
{
  int c;
  dbref i;
  struct object *o;
  const char *end;
#ifdef ADD_POWERS
  int flags, toggles, powers;
#endif
  int temp = 0;
  time_t temp_time = 0;

  clear_players();
  db_free();
  indb_flags = 1;
  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;
      break;
      /* Use the MUSH 2.0 header stuff to see what's in this db */
    case '+':
      c = getc(f);		/* Skip the V */
      indb_flags = ((getref(f) - 2) / 256) - 5;
      break;
      /* old fashioned database */
    case '#':
      fprintf(stderr, "ERROR: old style database.\n");
      /* if you want to read in an old-style database, use an earlier
       * patchlevel to upgrade.
       */
      return -1;
      break;
      /* new database */
    case '!':			/* non-zone oriented database */
    case '&':			/* zone oriented database */
      /* make space */
      i = getref(f);
      db_grow(i + 1);
      /* read it in */
      o = db + i;
      getstring(f, o->name);
      o->location = getref(f);
      /* --- get zone number --- */
      (c == '&') ? (int) getref(f) : 0;
      /* ----------------------- */
      o->contents = getref(f);
      o->exits = getref(f);
      o->next = getref(f);
      o->parent = getref(f);
      if (indb_flags & DBF_NEW_LOCKS) {
	o->locks = NULL;
	getlocks(i, f);
      } else {
	o->locks = NULL;
	get_old_locks(i, f);
      }
      o->owner = getref(f);
      o->zone = getref(f);
      s_Pennies(i, getref(f));
      o->flags = getref(f);
      o->toggles = getref(f);
      if (!(indb_flags & DBF_NO_POWERS))
	o->powers = getref(f);
      else
	o->powers = 0;
      /* Split the immortal power into NO_PAY, NO_QUOTA, and UNKILLABLE
       * Remove the CAN_DEBUG power, too.
       */
      if (!(indb_flags & DBF_SPLIT_IMMORTAL)) {
	o->powers &= ~CAN_DEBUG;
	if (o->powers & IMMORTAL) {
	  o->powers &= ~IMMORTAL;
	  o->powers |= NO_PAY | NO_QUOTA | UNKILLABLE;
	}
      }
      /* Munge the representation of Garbage objects. */
      if (!(indb_flags & DBF_TYPE_GARBAGE)) {
	if ((o->flags & ~ACCESSED) == (TYPE_THING | GOING)) {
	  /* Old-style garbage object. Convert to new-style. */
	  o->flags = TYPE_GARBAGE;
	}
      }
      /* Remove the TEMPLE flag */
      if (!(indb_flags & DBF_NO_TEMPLE)) {
	if ((o->flags & TYPE_MASK) == TYPE_ROOM)
	  o->toggles &= ~ROOM_TEMPLE;
      }
      /* Clear the GOING flags. If it was scheduled for destruction
       * when the db was saved, it gets a reprieve.
       */
      o->flags &= ~GOING;
      o->flags &= ~GOING_TWICE;

      /* If there are channels in the db, read 'em in */
      /* We don't support this anymore, so we just discard them */
      if (!(indb_flags & DBF_NO_CHAT_SYSTEM))
	temp = getref(f);
      else
	temp = 0;
#ifdef CHAT_SYSTEM
      o->channels = NULL;
#endif				/* CHAT_SYSTEM */

      /* If there are warnings in the db, read 'em in */
      temp = MAYBE_GET(f, DBF_WARNINGS);
#ifdef USE_WARNINGS
      o->warnings = temp;
#endif
      /* If there are creation times in the db, read 'em in */
      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
#ifdef CREATION_TIMES
      if (temp_time)
	o->creation_time = (time_t) temp_time;
      else
	o->creation_time = time((time_t *) 0);
#endif
      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
#ifdef CREATION_TIMES
      if (temp_time || (Typeof(i) == TYPE_PLAYER))
	o->modification_time = (time_t) temp_time;
      else
	o->modification_time = o->creation_time;
#endif

      /* read attribute list for item */
      if (!get_list(f, i)) {
	fprintf(stderr, "ERROR: bad attribute list object %d\n", i);
	return -1;
      }
      /* check to see if it's a player */
      if (Typeof(i) == TYPE_PLAYER) {
	add_player(i, NULL);
	Toggles(i) &= ~PLAYER_CONNECT;
#ifdef ADD_POWERS
	Flags(i) &= ~0x200;	/* zap old connect flag, too */
#endif
      }
#ifdef LOCAL_DATA
      o->local_data = NULL;
#endif
      break;

    case '*':
      end = getstring_noalloc(f);
      if (!strcmp(end, "***END OF DUMP***")) {
	fprintf(stderr, "ERROR: No end of dump %d\n", i);
	return -1;
      } else {
	{
	  fprintf(stderr, "READING: done\n");
	  fix_free_list();
	  if (!(indb_flags & DBF_NEW_LOCKS))
	    tweak_locks();
	  return db_top;
	}
      }
    default:
      fprintf(stderr, "ERROR: failed object %d\n", i);
      return -1;
    }
  }
}