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