tinymush-3.1p1/game/backups/
tinymush-3.1p1/game/bin/
tinymush-3.1p1/game/data/
tinymush-3.1p1/game/modules/
tinymush-3.1p1/game/modules/old/
tinymush-3.1p1/src/modules/comsys/
tinymush-3.1p1/src/modules/hello/
tinymush-3.1p1/src/modules/mail/
tinymush-3.1p1/src/tools/
/* db_rw.c - flatfile implementation */
/* $Id: db_rw.c,v 1.96 2002/09/22 05:48:34 rmg Exp $ */

#include "copyright.h"
#include "autoconf.h"
#include "config.h"

#include "alloc.h"	/* required by mudconf */
#include "flags.h"	/* required by mudconf */
#include "htab.h"	/* required by mudconf */
#include "mudconf.h"	/* required by code */
#include "udb.h"	/* required by code */
#include "db.h"		/* required by externs */

#include "externs.h"	/* required by code */

#include "vattr.h"	/* required by code */
#include "attrs.h"	/* required by code */

#include "powers.h"	/* required by code */

extern void FDECL(db_grow, (dbref));

extern struct object *db;

static int g_version;
static int g_format;
static int g_flags;

extern int anum_alc_top;
static int *used_attrs_table;

/* ---------------------------------------------------------------------------
 * getboolexp1: Get boolean subexpression from file.
 */

BOOLEXP *getboolexp1(f)
FILE *f;
{
	BOOLEXP *b;
	char *buff, *s;
	int c, d, anum;

	c = getc(f);
	switch (c) {
	case '\n':
		ungetc(c, f);
		return TRUE_BOOLEXP;
		/* break; */
	case EOF:
	        fprintf(mainlog_fp, "ABORT! db_rw.c, unexpected EOF in boolexp in getboolexp1().\n");
		abort();
		break;
	case '(':
		b = alloc_bool("getboolexp1.openparen");
		switch (c = getc(f)) {
		case NOT_TOKEN:
			b->type = BOOLEXP_NOT;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		case INDIR_TOKEN:
			b->type = BOOLEXP_INDIR;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		case IS_TOKEN:
			b->type = BOOLEXP_IS;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		case CARRY_TOKEN:
			b->type = BOOLEXP_CARRY;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		case OWNER_TOKEN:
			b->type = BOOLEXP_OWNER;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		default:
			ungetc(c, f);
			b->sub1 = getboolexp1(f);
			if ((c = getc(f)) == '\n')
				c = getc(f);
			switch (c) {
			case AND_TOKEN:
				b->type = BOOLEXP_AND;
				break;
			case OR_TOKEN:
				b->type = BOOLEXP_OR;
				break;
			default:
				goto error;
			}
			b->sub2 = getboolexp1(f);
			if ((d = getc(f)) == '\n')
				d = getc(f);
			if (d != ')')
				goto error;
			return b;
		}
	case '-':		/* obsolete NOTHING key, eat it */
	        while ((c = getc(f)) != '\n') {
		    if (c == EOF) {
			fprintf(mainlog_fp, "ABORT! db_rw.c, unexpected EOF in getboolexp1().\n");
			abort();
		    }
		}
		ungetc(c, f);
		return TRUE_BOOLEXP;
	case '"':
		ungetc(c, f);
		buff = alloc_lbuf("getboolexp.quoted");
		StringCopy(buff, getstring_noalloc(f, 1));
		c = fgetc(f);
		if (c == EOF) {
			free_lbuf(buff);
			return TRUE_BOOLEXP;
		}

		b = alloc_bool("getboolexp1.quoted");
		anum = mkattr(buff);
		if (anum <= 0) {
			free_bool(b);
			free_lbuf(buff);
			goto error;
		}
		free_lbuf(buff);
		b->thing = anum;

		/* if last character is : then this is an attribute lock. A 
		 * last character of / means an eval lock 
		 */

		if ((c == ':') || (c == '/')) {
			if (c == '/')
				b->type = BOOLEXP_EVAL;
			else
				b->type = BOOLEXP_ATR;
			b->sub1 = (BOOLEXP *) XSTRDUP(getstring_noalloc(f, 1), "getboolexp1.attr_lock");
		}
		return b;
	default:		/* dbref or attribute */
		ungetc(c, f);
		b = alloc_bool("getboolexp1.default");
		b->type = BOOLEXP_CONST;
		b->thing = 0;

		/* This is either an attribute, eval, or constant lock.
		 * Constant locks are of the form <num>, while attribute and 
		 * eval locks are of the form <anam-or-anum>:<string> or
		 * <aname-or-anum>/<string> respectively. The
		 * characters <nl>, |, and & terminate the string. 
		 */

		if (isdigit(c)) {
			while (isdigit(c = getc(f))) {
				b->thing = b->thing * 10 + c - '0';
			}
		} else if (isalpha(c)) {
			buff = alloc_lbuf("getboolexp1.atr_name");
			for (s = buff;
			     ((c = getc(f)) != EOF) && (c != '\n') &&
			     (c != ':') && (c != '/');
			     *s++ = c) ;
			if (c == EOF) {
				free_lbuf(buff);
				free_bool(b);
				goto error;
			}
			*s = '\0';

			/* Look the name up as an attribute.  If not found,
			 * create a new attribute. 
			 */

			anum = mkattr(buff);
			if (anum <= 0) {
				free_bool(b);
				free_lbuf(buff);
				goto error;
			}
			free_lbuf(buff);
			b->thing = anum;
		} else {
			free_bool(b);
			goto error;
		}

		/* if last character is : then this is an attribute lock. A 
		 * last character of / means an eval lock 
		 */

		if ((c == ':') || (c == '/')) {
			if (c == '/')
				b->type = BOOLEXP_EVAL;
			else
				b->type = BOOLEXP_ATR;
			buff = alloc_lbuf("getboolexp1.attr_lock");
			for (s = buff;
			     ((c = getc(f)) != EOF) && (c != '\n') && (c != ')') &&
			     (c != OR_TOKEN) && (c != AND_TOKEN);
			     *s++ = c) ;
			if (c == EOF)
				goto error;
			*s++ = 0;
			b->sub1 = (BOOLEXP *) XSTRDUP(buff, "getboolexp1.attr_lock");
			free_lbuf(buff);
		}
		ungetc(c, f);
		return b;
	}

      error:
	fprintf(mainlog_fp,
		"ABORT! db_rw.c, reached error case in getboolexp1().\n");
	abort();		/* bomb out */
	return TRUE_BOOLEXP;	/* NOTREACHED */
}

/* ---------------------------------------------------------------------------
 * getboolexp: Read a boolean expression from the flat file.
 */

static BOOLEXP *getboolexp(f)
FILE *f;
{
	BOOLEXP *b;
	char c;

	b = getboolexp1(f);
	if (getc(f) != '\n') {
	    fprintf(mainlog_fp, "ABORT! db_rw.c, parse error in getboolexp().\n");
	    abort();	/* parse error, we lose */
	}

	if ((c = getc(f)) != '\n')
		ungetc(c, f);

	return b;
}

/* ---------------------------------------------------------------------------
 * unscramble_attrnum: Fix up attribute numbers from foreign muds
 */

static int unscramble_attrnum(attrnum)
int attrnum;
{
	switch (g_format) {
	case F_MUSH:
		/* TinyMUSH 2.2:  Deal with different attribute numbers. */

		switch (attrnum) {
		    case 208:
			return A_NEWOBJS;
			break;
		    case 209:
			return A_LCON_FMT;
			break;
		    case 210:
			return A_LEXITS_FMT;
			break;
		    case 211:
			return A_PROGCMD;
			break;
		    default:
			return attrnum;
		}
	default:
		return attrnum;
	}
}

/* ---------------------------------------------------------------------------
 * get_list: Read attribute list from flat file.
 */

static int get_list(f, i, new_strings)
FILE *f;
dbref i;
int new_strings;
{
	dbref atr;
	int c;
	char *buff;

	buff = alloc_lbuf("get_list");
	while (1) {
		switch (c = getc(f)) {
		case '>':	/* read # then string */
			if (mudstate.standalone) 
				atr = unscramble_attrnum(getref(f));
			else
				atr = getref(f);
			if (atr > 0) {
				/* Store the attr */

				atr_add_raw(i, atr,
					 (char *)getstring_noalloc(f, new_strings));
			} else {
				/* Silently discard */

				getstring_noalloc(f, new_strings);
			}
			break;
		case '\n':	/* ignore newlines. They're due to v(r). */
			break;
		case '<':	/* end of list */
			free_lbuf(buff);
			c = getc(f);
			if (c != '\n') {
				ungetc(c, f);
				fprintf(mainlog_fp,
					"No line feed on object %d\n", i);
				return 1;
			}
			return 1;
		default:
			fprintf(mainlog_fp,
				"Bad character '%c' when getting attributes on object %d\n",
				c, i);
			/* We've found a bad spot.  I hope things aren't
			 * too bad. 
			 */

			(void)getstring_noalloc(f, new_strings);
		}
	}
	return 1;		/* NOTREACHED */
}

/* ---------------------------------------------------------------------------
 * putbool_subexp: Write a boolean sub-expression to the flat file.
 */
static void putbool_subexp(f, b)
FILE *f;
BOOLEXP *b;
{
	ATTR *va;

	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(CARRY_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_INDIR:
		putc('(', f);
		putc(INDIR_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_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;
	case BOOLEXP_ATR:
		va = atr_num(b->thing);
		if (va) {
			fprintf(f, "%s:%s", va->name, (char *)b->sub1);
		} else {
			fprintf(f, "%d:%s\n", b->thing, (char *)b->sub1);
		}
		break;
	case BOOLEXP_EVAL:
		va = atr_num(b->thing);
		if (va) {
			fprintf(f, "%s/%s\n", va->name, (char *)b->sub1);
		} else {
			fprintf(f, "%d/%s\n", b->thing, (char *)b->sub1);
		}
		break;
	default:
		fprintf(mainlog_fp, "Unknown boolean type in putbool_subexp: %d\n",
			b->type);
	}
}

/* ---------------------------------------------------------------------------
 * putboolexp: Write boolean expression to the flat file.
 */

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

/* ---------------------------------------------------------------------------
 * upgrade_flags: Convert foreign flags to MUSH format.
 */

static void upgrade_flags(flags1, flags2, flags3, thing, db_format, db_version)
FLAG *flags1, *flags2, *flags3;
dbref thing;
int db_format, db_version;
{
	FLAG f1, f2, f3, newf1, newf2, newf3;

	f1 = *flags1;
	f2 = *flags2;
	f3 = *flags3;
	newf1 = 0;
	newf2 = 0;
	newf3 = 0;
	if ((db_format == F_MUSH) && (db_version >= 3)) {
		newf1 = f1;
		newf2 = f2;
		newf3 = 0;

		/* Then we have to do the 2.2 to 3.0 flag conversion */

		if (newf1 & ROYALTY) {
		    newf1 &= ~ROYALTY;
		    newf2 |= CONTROL_OK;
		}
		if (newf2 & HAS_COMMANDS) {
		    newf2 &= ~HAS_COMMANDS;
		    newf2 |= NOBLEED;
		}
		if (newf2 & AUDITORIUM) {
		    newf2 &= ~AUDITORIUM;
		    newf2 |= ZONE_PARENT;
		}
		if (newf2 & ANSI) {
		    newf2 &= ~ANSI;
		    newf2 |= STOP_MATCH;
		}
		if (newf2 & HEAD_FLAG) {
		    newf2 &= ~HEAD_FLAG;
		    newf2 |= HAS_COMMANDS;
		}
		if (newf2 & FIXED) {
		    newf2 &= ~FIXED;
		    newf2 |= BOUNCE;
		}
		if (newf2 & STAFF) {
		    newf2 &= STAFF;
		    newf2 |= HTML;
		}
		if (newf2 & HAS_DAILY) {
		    newf2 &= ~HAS_DAILY;
		    /* This is the unimplemented TICKLER flag. */
		}
		if (newf2 & GAGGED) {
		    newf2 &= ~GAGGED;
		    newf2 |= ANSI;
		}
		if (newf2 & WATCHER) {
		    newf2 &= ~WATCHER;
		    s_Powers(thing, Powers(thing) | POW_BUILDER);
		}
	} else if (db_format == F_MUX) {

	    /* TinyMUX to 3.0 flag conversion */

	    newf1 = f1;
	    newf2 = f2;
	    newf3 = f3;

	    if (newf2 & ZONE_PARENT) {
		/* This used to be an object set NO_COMMAND. We unset the
		 * flag.
		 */
		newf2 &= ~ZONE_PARENT;
	    } else {
		/* And if it wasn't NO_COMMAND, then it should be COMMANDS. */
		newf2 |= HAS_COMMANDS;
	    }

	    if (newf2 & WATCHER) {
		/* This used to be the COMPRESS flag, which didn't do
		 * anything.
		 */
		newf2 &= ~WATCHER;
	    }

	    if ((newf1 & MONITOR) && ((newf1 & TYPE_MASK) == TYPE_PLAYER)) {
		/* Players set MONITOR should be set WATCHER as well. */
		newf2 |= WATCHER;
	    }

	} else if (db_format == F_TINYMUSH) {
		/* Native TinyMUSH 3.0 database.
		 * The only thing we have to do is clear the redirection
		 * flag, as nothing is ever redirected at startup.
		 */
		newf1 = f1;
		newf2 = f2;
		newf3 = f3 & ~HAS_REDIRECT;
	}

	newf2 = newf2 & ~FLOATING; /* this flag is now obsolete */

	*flags1 = newf1;
	*flags2 = newf2;
	*flags3 = newf3;
	return;
}

/* ---------------------------------------------------------------------------
 * efo_convert: Fix things up for Exits-From-Objects
 */

void NDECL(efo_convert)
{
	int i;
	dbref link;

	DO_WHOLE_DB(i) {
		switch (Typeof(i)) {
		case TYPE_PLAYER:
		case TYPE_THING:

			/* swap Exits and Link */

			link = Link(i);
			s_Link(i, Exits(i));
			s_Exits(i, link);
			break;
		}
	}
}

/* ---------------------------------------------------------------------------
 * fix_mux_zones: Convert MUX-style zones to 3.0-style zones.
 */

static void fix_mux_zones()
{
    /* For all objects in the database where Zone(thing) != NOTHING,
     * set the CONTROL_OK flag on them.
     *
     * For all objects in the database that are ZMOs (that have other
     * objects zoned to them), copy the EnterLock of those objects to
     * the ControlLock.
     */

    int i;
    int *zmarks;
    char *astr;

    zmarks = (int *) XCALLOC(mudstate.db_top, sizeof(int), "fix_mux_zones");

    DO_WHOLE_DB(i) {
	if (Zone(i) != NOTHING) {
	    s_Flags2(i, Flags2(i) | CONTROL_OK);
	    zmarks[Zone(i)] = 1;
	}
    }

    DO_WHOLE_DB(i) {
	if (zmarks[i]) {
	    astr = atr_get_raw(i, A_LENTER);
	    if (astr) {
		atr_add_raw(i, A_LCONTROL, astr);
	    }
	}
    }

    XFREE(zmarks, "fix_mux_zones");
}

/* ---------------------------------------------------------------------------
 * fix_typed_quotas: Explode standard quotas into typed quotas
 */

static void fix_typed_quotas()
{
	/* If we have a pre-2.2 or MUX database, only the QUOTA and RQUOTA
	 * attributes  exist. For simplicity's sake, we assume that players
	 * will have the  same quotas for all types, equal to the current
	 * value. This is  going to produce incorrect values for RQUOTA;
	 * this is easily fixed  by a @quota/fix done from within-game. 
	 *
	 * If we have a early beta 2.2 release, we have quotas which are
	 * spread out over ten attributes. We're going to have to grab
	 * those, make the new quotas, and then delete the old attributes.
	 */

	int i;
	char *qbuf, *rqbuf;

	DO_WHOLE_DB(i) {
		if (isPlayer(i)) {
			qbuf = atr_get_raw(i, A_QUOTA);
			rqbuf = atr_get_raw(i, A_RQUOTA);
			if (!qbuf || !*qbuf)
				qbuf = (char *)"1";
			if (!rqbuf || !*rqbuf)
				rqbuf = (char *)"0";
			atr_add_raw(i, A_QUOTA,
				    tprintf("%s %s %s %s %s",
					    qbuf, qbuf, qbuf, qbuf, qbuf));
			atr_add_raw(i, A_RQUOTA,
				    tprintf("%s %s %s %s %s",
					rqbuf, rqbuf, rqbuf, rqbuf, rqbuf));
		}
	}
}

dbref db_read_flatfile(f, db_format, db_version, db_flags)
FILE *f;
int *db_format, *db_version, *db_flags;
{
	dbref i, anum;
	char ch;
	const char *tstr;
	int header_gotten, size_gotten, nextattr_gotten;
	int read_attribs, read_name, read_zone, read_link, read_key, read_parent;
	int read_extflags, read_3flags, read_money, read_timestamps, read_new_strings;
	int read_powers, read_powers_player, read_powers_any;
	int has_typed_quotas, has_visual_attrs;
	int deduce_version, deduce_name, deduce_zone, deduce_timestamps;
	int aflags, f1, f2, f3;
	BOOLEXP *tempbool;
	time_t tmptime;
#ifndef NO_TIMECHECKING
	struct timeval obj_time;
#endif

	header_gotten = 0;
	size_gotten = 0;
	nextattr_gotten = 0;
	g_format = F_UNKNOWN;
	g_version = 0;
	g_flags = 0;
	read_attribs = 1;
	read_name = 1;
	read_zone = 0;
	read_link = 0;
	read_key = 1;
	read_parent = 0;
	read_money = 1;
	read_extflags = 0;
	read_3flags = 0;
	has_typed_quotas = 0;
	has_visual_attrs = 0;
	read_timestamps = 0;
	read_new_strings = 0;
	read_powers = 0;
	read_powers_player = 0;
	read_powers_any = 0;
	deduce_version = 1;
	deduce_zone = 1;
	deduce_name = 1;
	deduce_timestamps = 1;
	fprintf(mainlog_fp, "Reading ");
	db_free();
	for (i = 0;; i++) {

		if (!(i % 100)) {
			fputc('.', mainlog_fp);
		}

		switch (ch = getc(f)) {
		case '-':	/* Misc tag */
			switch (ch = getc(f)) {	
			case 'R':	/* Record number of players */
				mudstate.record_players = getref(f);
				break;
			default:
				(void)getstring_noalloc(f, 0);
			}
			break;
		case '+':	/* MUX and MUSH header */

		    ch = getc(f); /* 2nd char selects type */

		    if ((ch == 'V') || (ch == 'X') || (ch == 'T')) {

			/* The following things are common across 2.x, MUX,
			 * and 3.0.
			 */
		        
			 if (header_gotten) {
			     fprintf(mainlog_fp,
				     "\nDuplicate MUSH version header entry at object %d, ignored.\n",
				     i);
			     tstr = getstring_noalloc(f, 0);
			     break;
			 }
			 header_gotten = 1;
			 deduce_version = 0;
			 g_version = getref(f);
			 
			 /* Otherwise extract feature flags */

			 if (g_version & V_GDBM) {
			     read_attribs = 0;
			     read_name = !(g_version & V_ATRNAME);
			 }
			 read_zone = (g_version & V_ZONE);
			 read_link = (g_version & V_LINK);
			 read_key = !(g_version & V_ATRKEY);
			 read_parent = (g_version & V_PARENT);
			 read_money = !(g_version & V_ATRMONEY);
			 read_extflags = (g_version & V_XFLAGS);
			 has_typed_quotas = (g_version & V_TQUOTAS);
			 read_timestamps = (g_version & V_TIMESTAMPS);
			 has_visual_attrs = (g_version & V_VISUALATTRS);
			 g_flags = g_version & ~V_MASK;

			 deduce_name = 0;
			 deduce_version = 0;
			 deduce_zone = 0;
		    }

		    /* More generic switch. */

		    switch (ch) {
			case 'T':	/* 3.0 VERSION */
			    g_format = F_TINYMUSH;
			    read_3flags = (g_version & V_3FLAGS);
			    read_powers = (g_version & V_POWERS);
			    read_new_strings = (g_version & V_QUOTED);
			    g_version &= V_MASK;
			    break;

			case 'V':	/* 2.0 VERSION */
			    g_format = F_MUSH;
			    g_version &= V_MASK;
			    break;

			case 'X':	/* MUX VERSION */
			    g_format = F_MUX;
			    read_3flags = (g_version & V_3FLAGS);
			    read_powers = (g_version & V_POWERS);
			    read_new_strings = (g_version & V_QUOTED);
			    g_version &= V_MASK;
			    break;

			case 'S':	/* SIZE */
				if (size_gotten) {
					fprintf(mainlog_fp,
						"\nDuplicate size entry at object %d, ignored.\n",
						i);
					tstr = getstring_noalloc(f, 0);
				} else {
					mudstate.min_size = getref(f);
				}
				size_gotten = 1;
				break;
			case 'A':	/* USER-NAMED ATTRIBUTE */
				anum = getref(f);
				tstr = getstring_noalloc(f, read_new_strings);
				if (isdigit(*tstr)) {
					aflags = 0;
					while (isdigit(*tstr))
						aflags = (aflags * 10) +
							(*tstr++ - '0');
					tstr++;		/* skip ':' */
					if (!has_visual_attrs) {
					    /* If not AF_ODARK, is AF_VISUAL.
					     * Strip AF_ODARK.
					     */
					    if (aflags & AF_ODARK)
						aflags &= ~AF_ODARK;
					    else
						aflags |= AF_VISUAL;
					}
				} else {
					aflags = mudconf.vattr_flags;
				}
				vattr_define((char *)tstr, anum, aflags);
				break;
			case 'F':	/* OPEN USER ATTRIBUTE SLOT */
				anum = getref(f);
				break;
			case 'N':	/* NEXT ATTR TO ALLOC WHEN NO
					 * FREELIST 
					 */
				if (nextattr_gotten) {
					fprintf(mainlog_fp,
						"\nDuplicate next free vattr entry at object %d, ignored.\n",
						i);
					tstr = getstring_noalloc(f, 0);
				} else {
					mudstate.attr_next = getref(f);
					nextattr_gotten = 1;
				}
				break;
			default:
				fprintf(mainlog_fp,
					"\nUnexpected character '%c' in MUSH header near object #%d, ignored.\n",
					ch, i);
				tstr = getstring_noalloc(f, 0);
			}
			break;
		case '!':	/* MUX entry/MUSH entry */
			if (deduce_version) {
				g_format = F_TINYMUSH;
				g_version = 1;
				deduce_name = 0;
				deduce_zone = 0;
				deduce_version = 0;
			} else if (deduce_zone) {
				deduce_zone = 0;
				read_zone = 0;
			}
			i = getref(f);
			db_grow(i + 1);

#ifndef NO_TIMECHECKING
			obj_time.tv_sec = obj_time.tv_usec = 0;
			s_Time_Used(i, obj_time);
#endif
			s_StackCount(i, 0);
			s_VarsCount(i, 0);
			s_StructCount(i, 0);
			s_InstanceCount(i, 0);

			if (read_name) {
				tstr = getstring_noalloc(f, read_new_strings);
				if (deduce_name) {
					if (isdigit(*tstr)) {
						read_name = 0;
						s_Location(i, atoi(tstr));
					} else {
						s_Name(i, (char *)tstr);
						s_Location(i, getref(f));
					}
					deduce_name = 0;
				} else {
					s_Name(i, (char *)tstr);
					s_Location(i, getref(f));
				}
			} else {
				s_Location(i, getref(f));
			}

			if (read_zone)
				s_Zone(i, getref(f));
/* else s_Zone(i, NOTHING); */

			/* CONTENTS and EXITS */

			s_Contents(i, getref(f));
			s_Exits(i, getref(f));

			/* LINK */

			if (read_link) {
				s_Link(i, getref(f));
			} else {
				s_Link(i, NOTHING);
			}

			/* NEXT */

			s_Next(i, getref(f));

			/* LOCK */

			if (read_key) {
				tempbool = getboolexp(f);
				atr_add_raw(i, A_LOCK,
				unparse_boolexp_quiet(1, tempbool));
				free_boolexp(tempbool);
			}
			/* OWNER */

			s_Owner(i, getref(f));

			/* PARENT */

			if (read_parent) {
				s_Parent(i, getref(f));
			} else {
				s_Parent(i, NOTHING);
			}

			/* PENNIES */

			if (read_money)		
				s_Pennies(i, getref(f));

			/* FLAGS */

			f1 = getref(f);
			if (read_extflags)
				f2 = getref(f);
			else
				f2 = 0;
				
			if (read_3flags)
				f3 = getref(f);
			else
				f3 = 0;

			upgrade_flags(&f1, &f2, &f3, i, g_format, g_version);
			s_Flags(i, f1);
			s_Flags2(i, f2);
			s_Flags3(i, f3);

			if (read_powers) {
				f1 = getref(f);
				f2 = getref(f);
				s_Powers(i, f1);
				s_Powers2(i, f2);
			}

			if (read_timestamps) {
			    tmptime = (time_t) getlong(f);
			    s_AccessTime(i, tmptime);
			    tmptime = (time_t) getlong(f);
			    s_ModTime(i, tmptime);
			} else {
			    AccessTime(i) = ModTime(i) = time(NULL);
			}
				
			/* ATTRIBUTES */

			if (read_attribs) {
				if (!get_list(f, i, read_new_strings)) {
					fprintf(mainlog_fp,
						"\nError reading attrs for object #%d\n",
						i);
					return -1;
				}
			}

			/* check to see if it's a player */

			if (Typeof(i) == TYPE_PLAYER) {
				c_Connected(i);
			}
			break;
		case '*':	/* EOF marker */
			tstr = getstring_noalloc(f, 0);
			if (strcmp(tstr, "**END OF DUMP***")) {
				fprintf(mainlog_fp,
					"\nBad EOF marker at object #%d\n",
					i);
				return -1;
			} else {
				fprintf(mainlog_fp, "\n");
				*db_version = g_version;
				*db_format = g_format;
				*db_flags = g_flags;
				if (!has_typed_quotas)
					fix_typed_quotas();
				if (g_format == F_MUX)
					fix_mux_zones();
				return mudstate.db_top;
			}
		default:
			fprintf(mainlog_fp, "\nIllegal character '%c' near object #%d\n",
				ch, i);
			return -1;
		}


	}
}

int db_read()
{
	DBData key, data;
	int *c, vattr_flags, i, j, blksize, num;
	char *s;

#ifndef NO_TIMECHECKING
	struct timeval obj_time;
#endif	

	/* Fetch the database info */
	
	key.dptr = "TM3";
	key.dsize = strlen("TM3") + 1;
	data = db_get(key, DBTYPE_DBINFO);
	
	if (!data.dptr) {
		fprintf(mainlog_fp, "\nCould not open main record");
		return -1;
	}
	
	/* Unroll the data returned */
	
	c = data.dptr;
	memcpy((void *)&mudstate.min_size, (void *)c, sizeof(int));
	c++;
	memcpy((void *)&mudstate.attr_next, (void *)c, sizeof(int));
	c++;
	memcpy((void *)&mudstate.record_players, (void *)c, sizeof(int));
	c++;
	memcpy((void *)&mudstate.moduletype_top, (void *)c, sizeof(int));
	RAW_FREE(data.dptr, "db_get");
	
	/* Load the attribute numbers */
	
	blksize = ATRNUM_BLOCK_SIZE;
	
	for (i = 0; i <= ENTRY_NUM_BLOCKS(mudstate.attr_next, blksize); i++) {
		key.dptr = &i;
		key.dsize = sizeof(int);
		data = db_get(key, DBTYPE_ATRNUM);
		
		if (data.dptr) {
			/* Unroll the data into flags and name */
			
			s = data.dptr;	

			while ((s - (char *)data.dptr) < data.dsize) {
				memcpy((void *)&j, (void *)s, sizeof(int));
				s += sizeof(int);
				memcpy((void *)&vattr_flags, (void *)s, sizeof(int));
				s += sizeof(int);
				vattr_define(s, j, vattr_flags);
				s = strchr((const char *)s, '\0');
				
				if (!s) {
					/* Houston, we have a problem */
					fprintf(mainlog_fp,
						"\nError reading attribute number %d\n",
						j + ENTRY_BLOCK_STARTS(i, blksize));
				}
				
				s++;
			}
			RAW_FREE(data.dptr, "db_get");
		}
	
	}
	
	/* Load the object structures */

	if (mudstate.standalone) 
		fprintf(mainlog_fp, "Reading ");
	
	blksize = OBJECT_BLOCK_SIZE;
	
	for (i = 0; i <= ENTRY_NUM_BLOCKS(mudstate.min_size, blksize); i++) {
		key.dptr = &i;
		key.dsize = sizeof(int);
		data = db_get(key, DBTYPE_OBJECT);
		
		if (data.dptr) {
			/* Unroll the data into objnum and object */
			
			s = data.dptr;
			
			while ((s - (char *)data.dptr) < data.dsize) {
				memcpy((void *)&num, (void *)s, sizeof(int));
				s += sizeof(int);
				db_grow(num + 1);

				if (mudstate.standalone && !(num % 100)) {
					fputc('.', mainlog_fp);
				}
				
				/* We read the entire object structure in
				 * and copy it into place */
			
				memcpy((void *)&(db[num]), (void *)s, sizeof(DUMPOBJ));
				s += sizeof(DUMPOBJ);
#ifndef NO_TIMECHECKING
				obj_time.tv_sec = obj_time.tv_usec = 0;
				s_Time_Used(num, obj_time);
#endif
				s_StackCount(num, 0);
				s_VarsCount(num, 0);
				s_StructCount(num, 0);
				s_InstanceCount(num, 0);
#ifdef MEMORY_BASED
				db[num].attrtext.at_count = 0;
				db[num].attrtext.atrs = NULL;
#endif
				/* Check to see if it's a player */

				if (Typeof(num) == TYPE_PLAYER) {
					c_Connected(num);
				}
				
				s_Clean(num);
			}
			
			RAW_FREE(data.dptr, "db_get");
		}
	}	

	if (!mudstate.standalone)
		load_player_names();

	if (mudstate.standalone)
		fprintf(mainlog_fp, "\n");

	return (0);
}			

static int db_write_object_out(f, i, db_format, flags, n_atrt)
FILE *f;
dbref i;
int db_format, flags;
int *n_atrt;
{
	ATTR *a;

	char *got, *as;
	dbref aowner;
	int ca, aflags, alen, save, j, changed;
	BOOLEXP *tempbool;

	if (Going(i)) {
		return (0);
	}
	
	fprintf(f, "!%d\n", i);
	if (!(flags & V_ATRNAME))
		putstring(f, Name(i));
	putref(f, Location(i));
	if (flags & V_ZONE)
		putref(f, Zone(i));
	putref(f, Contents(i));
	putref(f, Exits(i));
	if (flags & V_LINK)
		putref(f, Link(i));
	putref(f, Next(i));
	if (!(flags & V_ATRKEY)) {
		got = atr_get(i, A_LOCK, &aowner, &aflags, &alen);
		tempbool = parse_boolexp(GOD, got, 1);
		free_lbuf(got);
		putboolexp(f, tempbool);
		if (tempbool)
			free_boolexp(tempbool);
	}
	putref(f, Owner(i));
	if (flags & V_PARENT)
		putref(f, Parent(i));
	if (!(flags & V_ATRMONEY))
		putref(f, Pennies(i));
	putref(f, Flags(i));
	if (flags & V_XFLAGS)
		putref(f, Flags2(i));
	if (flags & V_3FLAGS)
		putref(f, Flags3(i));
	if (flags & V_POWERS) {
		putref(f, Powers(i));
		putref(f, Powers2(i));
	}
	if (flags & V_TIMESTAMPS) {
		putlong(f, AccessTime(i));
		putlong(f, ModTime(i));
	}

	/* write the attribute list */

	changed = 0;

	for (ca = atr_head(i, &as); ca; ca = atr_next(&as)) {
		save = 0;
		if (!mudstate.standalone) {
			a = atr_num(ca);
			if (a)
				j = a->number;
			else
				j = -1;
		} else {
			j = ca;
		}

		if (j > 0) {
			switch (j) {
			case A_NAME:
				if (flags & V_ATRNAME)
					save = 1;
				break;
			case A_LOCK:
				if (flags & V_ATRKEY)
					save = 1;
				break;
			case A_LIST:
			case A_MONEY:
				break;
			default:
				save = 1;
			}
		}
		if (save) {
			got = atr_get_raw(i, j);
			if (used_attrs_table != NULL) {
			    fprintf(f, ">%d\n", used_attrs_table[j]);
			    if (used_attrs_table[j] != j) {
				changed = 1;
				*n_atrt += 1;
			    }
			} else {
			    fprintf(f, ">%d\n", j);
			}
			putstring(f, got);
		}
	}
	fprintf(f, "<\n");
	return (changed);
}

dbref db_write_flatfile(f, format, version)
FILE *f;
int format, version;
{
	dbref i;
	int flags;
	VATTR *vp;
	int n, end, ca, n_oldtotal, n_oldtop, n_deleted, n_renumbered;
	int n_objt, n_atrt, anxt, dbclean;
	int *old_attrs_table;
	char *as;

	al_store();

	dbclean = (version & V_DBCLEAN) ? 1 : 0;
	version &= ~V_DBCLEAN;

	switch (format) {
	case F_TINYMUSH:
		flags = version;
		break;
	default:
		fprintf(mainlog_fp, "Can only write TinyMUSH 3 format.\n");
		return -1;
	}
	if (mudstate.standalone)
		fprintf(mainlog_fp, "Writing ");

	/* Attribute cleaning, if standalone. */

	if (mudstate.standalone && dbclean) {

	    used_attrs_table = (int *) XCALLOC(mudstate.attr_next,
					       sizeof(int),
					       "flatfile.used_attrs_table");
	    old_attrs_table = (int *) XCALLOC(mudstate.attr_next,
					       sizeof(int),
					       "flatfile.old_attrs_table");
	    n_oldtotal = mudstate.attr_next;
	    n_oldtop = anum_alc_top;
	    n_deleted = n_renumbered = n_objt = n_atrt = 0;

	    /* Non-user defined attributes are always considered used. */

	    for (n = 0; n < A_USER_START; n++)
		used_attrs_table[n] = n;

	    /* Walk the database. Mark all the attribute numbers in use. */

	    atr_push();
	    DO_WHOLE_DB(i) {
		for (ca = atr_head(i, &as); ca; ca = atr_next(&as))
		    used_attrs_table[ca] = old_attrs_table[ca] = ca;
	    }
	    atr_pop();

	    /* Count up how many attributes we're deleting. */

	    vp = vattr_first();
	    while (vp) {
		if (used_attrs_table[vp->number] == 0)
		    n_deleted++;
		vp = vattr_next(vp);
	    }

	    /* Walk the table we've created of used statuses. When we
	     * find free slots, walk backwards to the first used slot
	     * at the end of the table. Write the number of the free
	     * slot into that used slot.
	     * Keep a mapping of what things used to be. 
	     */

	    for (n = A_USER_START, end = mudstate.attr_next - 1;
		 (n < mudstate.attr_next) && (n < end); n++) {
		if (used_attrs_table[n] == 0) {
		    while ((end > n) && (used_attrs_table[end] == 0))
			end--;
		    if (end > n) {
			old_attrs_table[n] = end;
			used_attrs_table[end] = used_attrs_table[n] = n;
			end--;
		    }
		}
	    }

	    /* Count up our renumbers. */

	    for (n = A_USER_START; n < mudstate.attr_next; n++) {
		if ((used_attrs_table[n] != n) && (used_attrs_table[n] != 0)) {
		    vp = (VATTR *) anum_get(n);
		    if (vp)
			n_renumbered++;
		}
	    }

	    /* The new end of the attribute table is the first thing
	     * we've renumbered. 
	     */

	    for (anxt = A_USER_START;
		 ((anxt == used_attrs_table[anxt]) &&
		  (anxt < mudstate.attr_next));
		 anxt++)
		;

	} else {
	    used_attrs_table = NULL;
	    anxt = mudstate.attr_next;
	}

	/* Write database information.
	 * TinyMUSH 2 wrote '+V', MUX wrote '+X', 3.0 writes '+T'.
	 */
	fprintf(f, "+T%d\n+S%d\n+N%d\n", flags, mudstate.db_top, anxt);
	fprintf(f, "-R%d\n", mudstate.record_players);

	/* Dump user-named attribute info */

	if (mudstate.standalone && dbclean) {
	    for (i = A_USER_START; i < anxt; i++) {
		if (used_attrs_table[i] == 0)
		    continue;
		vp = (VATTR *) anum_get(old_attrs_table[i]);
		if (vp) {
		    if (!(vp->flags & AF_DELETED)) {
			fprintf(f, "+A%d\n\"%d:%s\"\n",
				i, vp->flags, vp->name);
		    }
		}
	    }
	} else {
	    vp = vattr_first();
	    while (vp != NULL) {
		if (!(vp->flags & AF_DELETED)) {
		    fprintf(f, "+A%d\n\"%d:%s\"\n",
			    vp->number, vp->flags, vp->name);
		}
                vp = vattr_next(vp);
	    }
	}

	/* Dump object and attribute info */

	n_objt = n_atrt = 0;

	DO_WHOLE_DB(i) {

		if (mudstate.standalone && !(i % 100)) {
			fputc('.', mainlog_fp);
		}

		n_objt += db_write_object_out(f, i, format, flags, &n_atrt);
	}

	fputs("***END OF DUMP***\n", f);
	fflush(f);
	
	if (mudstate.standalone) {
		fprintf(mainlog_fp, "\n");
		if (dbclean) {
		    if (n_objt) {
			fprintf(mainlog_fp, "Cleaned %d attributes (now %d): %d deleted, %d renumbered (%d objects and %d individual attrs touched).\n",
				n_oldtotal, anxt, n_deleted, n_renumbered,
				n_objt, n_atrt);
		    } else if (n_deleted || n_renumbered) {
			fprintf(mainlog_fp, "Cleaned %d attributes (now %d): %d deleted, %d renumbered (no objects touched).\n",
				n_oldtotal, anxt, n_deleted, n_renumbered);
		    }
		    XFREE(used_attrs_table, "flatfile.used_attrs_table");
		    XFREE(old_attrs_table, "flatfile.old_attrs_table");
		}
	}

	return (mudstate.db_top);
}

dbref db_write()
{
	VATTR *vp;
	DBData key, data;
	int *c, blksize, num, i, j, k, dirty, len;
	char *s;
	
	al_store();

	if (mudstate.standalone)
		fprintf(mainlog_fp, "Writing ");

	/* Lock the database */
	
	db_lock();

	/* Write database information */

	i = mudstate.attr_next;

	/* Roll up various paramaters needed for startup into one record.
	 * This should be the only data record of its type */
		
	c = data.dptr = (int *)XMALLOC(4 * sizeof(int), "db_write");
	memcpy((void *)c, (void *)&mudstate.db_top, sizeof(int));
	c++;
	memcpy((void *)c, (void *)&i, sizeof(int));
	c++;
	memcpy((void *)c, (void *)&mudstate.record_players, sizeof(int));
	c++;
	memcpy((void *)c, (void *)&mudstate.moduletype_top, sizeof(int));
			
	/* "TM3" is our unique key */
		
	key.dptr = "TM3";
	key.dsize = strlen("TM3") + 1;
	data.dsize = 4 * sizeof(int);
	db_put(key, data, DBTYPE_DBINFO);
	XFREE(data.dptr, "db_write");
		
	/* Dump user-named attribute info */

	/* First, calculate the number of attribute entries we can fit in 
	 * a block, allowing for some minor DBM key overhead. This should
	 * not change unless the size of VNAME_SIZE or LBUF_SIZE changes,
	 * in which case you'd have to reload anyway */
	 
	blksize = ATRNUM_BLOCK_SIZE;
	
	/* Step through the attribute number array, writing stuff in 'num'
	 * sized chunks */
	 
	data.dptr = (char *)XMALLOC(ATRNUM_BLOCK_BYTES, "db_write.cdata");
	
	for (i = 0; i <= ENTRY_NUM_BLOCKS(mudstate.attr_next, blksize); i++) {
		dirty = 0;
		num = 0;
		s = data.dptr;
				
		for (j = ENTRY_BLOCK_STARTS(i, blksize);
		     (j <= ENTRY_BLOCK_ENDS(i, blksize)) &&
		     (j < mudstate.attr_next);
		     j++) {

			if (j < A_USER_START) {
				continue;
			}
			
			vp = (VATTR *)anum_table[j];

			if (vp && !(vp->flags & AF_DELETED)) {
				if (!mudstate.standalone) {
					if (vp->flags & AF_DIRTY) {
						/* Only write the dirty
						 * attribute numbers and
						 * clear the flag */
					 
						vp->flags &= ~AF_DIRTY;
						dirty = 1;
					}
				} else {
					dirty = 1;
				}
				
				num++;
			}
		}
		
		if (!num) {
			/* No valid attributes in this block, delete it */
			key.dptr = &i;
			key.dsize = sizeof(int);
			db_del(key, DBTYPE_ATRNUM);
		}
		
		if (dirty) {
			/* Something is dirty in this block, write all of
			 * the attribute numbers in this block */
			
			for (j = 0; (j < blksize) &&
			    ((ENTRY_BLOCK_STARTS(i, blksize) + j) < mudstate.attr_next);
			    j++) {
				/* j is an offset of attribute
				 * numbers into the current block */
					
				if ((ENTRY_BLOCK_STARTS(i, blksize) + j) < A_USER_START) {
					continue;
				}

				vp = (VATTR *)anum_table[ENTRY_BLOCK_STARTS(i, blksize) + j];
				
				if (vp && !(vp->flags & AF_DELETED)) {
					len = strlen(vp->name) + 1;
					memcpy((void *)s, (void *)&vp->number, sizeof(int));
					s += sizeof(int);
					memcpy((void *)s, (void *)&vp->flags, sizeof(int));
					s += sizeof(int);
					memcpy((void *)s, (void *)vp->name, len); 
					s += len;
				}
			}
			
			/* Write the block: Block number is our key */
			
			key.dptr = &i;
			key.dsize = sizeof(int);
			data.dsize = s - (char *)data.dptr;
			db_put(key, data, DBTYPE_ATRNUM);
		}
	}
	XFREE(data.dptr, "db_write.cdata");

	/* Dump object structures using the same block-based method we use
	 * to dump attribute numbers */

	blksize = OBJECT_BLOCK_SIZE;

	/* Step through the object structure array, writing stuff in 'num'
	 * sized chunks */

	data.dptr = (char *)XMALLOC(OBJECT_BLOCK_BYTES, "db_write.cdata");

	for (i = 0; i <= ENTRY_NUM_BLOCKS(mudstate.db_top, blksize); i++) {
		dirty = 0;
		num = 0;
		s = data.dptr;
		
		for (j = ENTRY_BLOCK_STARTS(i, blksize);
		     (j <= ENTRY_BLOCK_ENDS(i, blksize)) &&
		     (j < mudstate.db_top);
		     j++) {

			if (mudstate.standalone && !(j % 100)) {
				fputc('.', mainlog_fp);
			}

			/* We assume you always do a dbck before dump, and
			 * Going objects are really destroyed! */
	 
			if (!Going(j)) {
				if (!mudstate.standalone) {
					if (Flags3(j) & DIRTY) {
						/* Only write the dirty
						 * objects and clear the
						 * flag */
					
						s_Clean(j);
						dirty = 1;
					}
				} else {
					dirty = 1;
				}
				num++;
			}
		}	

		if (!num) {
			/* No valid objects in this block, delete it */
			
			key.dptr = &i;
			key.dsize = sizeof(int);
			db_del(key, DBTYPE_OBJECT);
		}
		
		if (dirty) {
			/* Something is dirty in this block, write all of the
			 * objects in this block */
			 
			 for (j = 0; (j < blksize) &&
			      ((ENTRY_BLOCK_STARTS(i, blksize) + j) < mudstate.db_top);
			      j++) {
				/* j is an offset of object numbers into the
				 * current block */
				
				k = ENTRY_BLOCK_STARTS(i, blksize) + j;
				
				if (!Going(k)) {
					memcpy((void *)s, (void *)&k, sizeof(int));
					s += sizeof(int);
					memcpy((void *)s, (void *)&(db[k]), sizeof(DUMPOBJ));
					s += sizeof(DUMPOBJ);
				}
			}
			
			/* Write the block: Block number is our key */
			
			key.dptr = &i;
			key.dsize = sizeof(int);
			data.dsize = s - (char *)data.dptr;
			db_put(key, data, DBTYPE_OBJECT);
		}
	}
		
	XFREE(data.dptr, "db_write.cdata");

	/* Unlock the database */
	
	db_unlock();
	
	if (mudstate.standalone)
		fprintf(mainlog_fp, "\n");
	return (mudstate.db_top);
}

/* Open a file pointer for a module to use when writing a flatfile */

FILE *db_module_flatfile(modname, wrflag)
char *modname;
int wrflag;
{
	char filename[256];
	FILE *f = NULL;
	
	sprintf(filename, "%s/mod_%s.db", mudconf.dbhome, modname);
	
	if (wrflag) { 
		f = tf_fopen(filename, O_WRONLY | O_CREAT | O_TRUNC);
		STARTLOG(LOG_ALWAYS, "DMP", "DUMP")
			log_printf("Writing db: %s", filename);
		ENDLOG
	} else {
		f = tf_fopen(filename, O_RDONLY);
		STARTLOG(LOG_ALWAYS, "INI", "LOAD")
			log_printf("Loading db: %s", filename);
		ENDLOG
	}
	
	if (f != NULL) {
		return f;
	} else {
		log_perror("DMP", "FAIL", "Opening flatfile", filename);
		return NULL;
	}
}