/* game.c -- initialization stuff */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char *RCSid = "$Id: game.c,v 1.9 1995/03/21 00:00:27 ambar Exp $";
USE(RCSid);
#endif

#include "interface.h"
#include "file_c.h"
#include "match.h"
#include "flags.h"
#include "attrs.h"
#include "rwho_clilib.h"

extern void NDECL(init_attrtab);
extern void NDECL(init_cmdtab);
extern void NDECL(cf_init);
extern void NDECL(pcache_init);
extern int FDECL(cf_read, (char *fn));
extern void NDECL(init_functab);
extern void FDECL(close_sockets, (int emergency, char *message));
extern void NDECL(init_version);
extern void NDECL(init_logout_cmdtab);
extern void NDECL(init_timer);
extern void FDECL(raw_notify, (dbref, const char *));
extern void NDECL(do_second);
extern void FDECL(do_dbck, (dbref, dbref, int));

void FDECL(fork_and_dump, (int));
void NDECL(dump_database);
void NDECL(pcache_sync);
static void FDECL(dump_database_internal, (int));
static void NDECL(init_rlimit);

int reserved;

/*
 * used to allocate storage for temporary stuff, cleared before command
 * execution
 */

void 
do_dump(player, cause, key)
    dbref player, cause;
    int key;
{
    notify(player, "Dumping...");
    fork_and_dump(key);
}

/* print out stuff into error file */

void 
NDECL(report)
{
    STARTLOG(LOG_BUGS, "BUG", "INFO")
    log_text((char *) "Command: '");
    log_text(mudstate.debug_cmd);
    log_text((char *) "'");
    ENDLOG
	if (Good_obj(mudstate.curr_player)) {
	STARTLOG(LOG_BUGS, "BUG", "INFO")
	    log_text((char *) "Player: ");
	log_name_and_loc(mudstate.curr_player);
	if ((mudstate.curr_enactor != mudstate.curr_player) &&
	    Good_obj(mudstate.curr_enactor)) {
	    log_text((char *) " Enactor: ");
	    log_name_and_loc(mudstate.curr_enactor);
	}
	ENDLOG
    }
}

/* ----------------------------------------------------------------------
 * atr_match: Check attribute list for wild card matches and queue them.
 */

static int 
atr_match1(thing, parent, player, type, str, raw_str, check_exclude,
	   hash_insert)
    dbref thing, parent, player;
    char type, *str, *raw_str;
    int check_exclude, hash_insert;
{
    dbref aowner;
    int match, attr, aflags, i;
    char *buff, *s, *as;
    char *args[10];
    ATTR *ap;

    /* See if we can do it.  Silently fail if we can't. */

    if (!could_doit(player, parent, A_LUSE))
	return -1;

    match = 0;
    buff = alloc_lbuf("atr_match1");
    atr_push();
    for (attr = atr_head(parent, &as); attr; attr = atr_next(&as)) {
	ap = atr_num(attr);

	/* Never check NOPROG attributes. */

	if (!ap || (ap->flags & AF_NOPROG))
	    continue;

	/* If we aren't the bottom level check if we saw this attr
	 * before.  Also exclude it if the attribute type is PRIVATE.
	 */

	if (check_exclude &&
	    ((ap->flags & AF_PRIVATE) ||
	     nhashfind(ap->number, &mudstate.parent_htab))) {
	    continue;
	}
	atr_get_str(buff, parent, attr, &aowner, &aflags);

	/* Skip if private and on a parent */

	if (check_exclude && (aflags & AF_PRIVATE)) {
	    continue;
	}
	/* If we aren't the top level remember this attr so we exclude
	 * it from now on.
	 */

	if (hash_insert)
	    nhashadd(ap->number, (int *) &attr,
		     &mudstate.parent_htab);

	/* Check for the leadin character after excluding the attrib
	 * This lets non-command attribs on the child block commands
	 * on the parent.
	 */

	if ((buff[0] != type) || (aflags & AF_NOPROG))
	    continue;

	/* decode it: search for first un escaped : */

	for (s = buff + 1; *s && (*s != ':'); s++);
	if (!*s)
	    continue;
	*s++ = 0;
	if (wild_match(buff + 1, 
		       ((aflags & AF_NOPARSE) ? raw_str : str),
		       args, 10, 0)) {
	    match = 1;
	    wait_que(thing, player, 0, NOTHING, s, args, 10,
		     mudstate.global_regs);
	    for (i = 0; i < 10; i++) {
		if (args[i])
		    free_lbuf(args[i]);
	    }
	}
    }
    atr_pop();
    free_lbuf(buff);
    return (match);
}

int 
atr_match(thing, player, type, str, raw_str, check_parents)
    dbref thing, player;
    char type, *str, *raw_str;
    int check_parents;
{
    int match, lev, result, exclude, insert;
    dbref parent;

    /* If thing is halted, or it doesn't have a COMMANDS flag and we're
     * we're doing a $-match, don't check it.
     */

    if (((type == AMATCH_CMD) && 
	 !Has_Commands(thing) && mudconf.req_cmds_flag) || Halted(thing))
	return 0;

    /* If not checking parents, just check the thing */

    match = 0;
    if (!check_parents)
	return atr_match1(thing, thing, player, type, str, raw_str, 0, 0);

    /* Check parents, ignoring halted objects */

    exclude = 0;
    insert = 1;
    nhashflush(&mudstate.parent_htab, 0);
    ITER_PARENTS(thing, parent, lev) {
	if (!Good_obj(Parent(parent)))
	    insert = 0;
	result = atr_match1(thing, parent, player, type, str, raw_str,
			    exclude, insert);
	if (result > 0) {
	    match = 1;
	} else if (result < 0) {
	    return match;
	}
	exclude = 1;
    }

    return match;
}

/* ---------------------------------------------------------------------------
 * notify_check: notifies the object #target of the message msg, and
 * optionally notify the contents, neighbors, and location also.
 */

int 
check_filter(object, player, filter, msg)
    dbref object, player;
    int filter;
    const char *msg;
{
    int aflags;
    dbref aowner;
    char *buf, *nbuf, *cp, *dp, *preserve[MAX_GLOBAL_REGS];

    buf = atr_pget(object, filter, &aowner, &aflags);
    if (!*buf) {
	free_lbuf(buf);
	return (1);
    }

    save_global_regs("check_filter_save", preserve);
    dp = nbuf = exec(object, player, EV_FIGNORE | EV_EVAL | EV_TOP, buf,
		     (char **) NULL, 0);
    free_lbuf(buf);
    restore_global_regs("check_filter_restore", preserve);

    do {
	cp = parse_to(&dp, ',', EV_STRIP);
	if (quick_wild(cp, (char *) msg)) {
	    free_lbuf(nbuf);
	    return (0);
	}
    } while (dp != NULL);
    free_lbuf(nbuf);
    return (1);
}

static char *
add_prefix(object, player, prefix, msg, dflt)
    dbref object, player;
    int prefix;
    const char *msg, *dflt;
{
    int aflags;
    dbref aowner;
    char *buf, *nbuf, *cp, *preserve[MAX_GLOBAL_REGS];

    buf = atr_pget(object, prefix, &aowner, &aflags);
    if (!*buf) {
	cp = buf;
	safe_str((char *) dflt, buf, &cp);
    } else {
	save_global_regs("add_prefix_save", preserve);
	nbuf = exec(object, player, EV_FIGNORE | EV_EVAL | EV_TOP, buf,
		    (char **) NULL, 0);
	free_lbuf(buf);
	restore_global_regs("add_prefix_restore", preserve);
	buf = nbuf;
	cp = &buf[strlen(buf)];
    }
    if (cp != buf)
	safe_str((char *) " ", buf, &cp);
    safe_str((char *) msg, buf, &cp);
    return (buf);
}

static char *
dflt_from_msg(sender, sendloc)
    dbref sender, sendloc;
{
    char *tp, *tbuff;

    tp = tbuff = alloc_lbuf("notify_check.fwdlist");
    safe_str((char *) "From ", tbuff, &tp);
    if (Good_obj(sendloc))
	safe_str(Name(sendloc), tbuff, &tp);
    else
	safe_str(Name(sender), tbuff, &tp);
    safe_chr(',', tbuff, &tp);
    return tbuff;
}

#ifdef PUEBLO_SUPPORT

/* Do HTML escaping, converting < to &lt;, etc.  'dest' needs to be
 * allocated & freed by the caller.
 *
 * If you're using this to append to a string, you can pass in the
 * safe_{str|chr} (char **) so we can just do the append directly, 
 * saving you an alloc_lbuf()...free_lbuf().  If you want us to append
 * from the start of 'dest', just pass in a 0 for 'destp'.
 * 
 * Returns 0 if the copy succeeded, 1 if it failed.
 */
int
html_escape(src, dest, destp)
     const char *src;
     char *dest;
     char **destp;
{
    static char new_buf[LBUF_SIZE * 5];
    const char *msg_orig;
    char *mp_escaped;
    char *temp, *bufp;

    if (destp == 0) {
	temp = dest;
	destp = &temp;
    }

    for (*new_buf = '\0', bufp = new_buf, msg_orig = src; 
	 msg_orig && *msg_orig;
	 msg_orig++) {
	switch (*msg_orig) {
	  case '<':
	    *bufp = '\0';
	    strcat(new_buf, (char *) "&lt;");
	    bufp += 4;
	    break;
	  case '>':
	    *bufp = '\0';
	    strcat(new_buf, (char *) "&gt;"); 
	    bufp += 4;
	    break;
	  case '&':
	    *bufp = '\0';
	    strcat(new_buf, (char *) "&amp;"); 
	    bufp += 5;
	    break;
	  case '\"':
	    *bufp = '\0';
	    strcat(new_buf, (char *) "&quot;"); 
	    bufp += 6;
	    break;
	  default:
	    *bufp++ = *msg_orig;
	    break;
	}
    }
    *bufp = '\0';
    safe_str(new_buf, dest, destp);
    destp = '\0';
    return;
}

#endif /* PUEBLO_SUPPORT */

void 
notify_check(target, sender, msg, key)
    dbref target, sender;
    int key;
    const char *msg;
{
    char *msg_ns, *mp, *tbuff, *tp, *buff;
    char *args[10];
    dbref aowner, targetloc, recip, obj;
    int i, nargs, aflags, has_neighbors, pass_listen;
    int check_listens, pass_uselock, is_audible;
    FWDLIST *fp;

    /* If speaker is invalid or message is empty, just exit */

    if (!Good_obj(target) || !msg || !*msg)
	return;

    /* Enforce a recursion limit */

    mudstate.ntfy_nest_lev++;
    if (mudstate.ntfy_nest_lev >= mudconf.ntfy_nest_lim) {
	mudstate.ntfy_nest_lev--;
	return;
    }
    /* If we want NOSPOOF output, generate it.  It is only needed if
     * we are sending the message to the target object */

    if (key & MSG_ME) {
	mp = msg_ns = alloc_lbuf("notify_check");
	if (Nospoof(target) &&
	    (target != sender) &&
	    (target != mudstate.curr_enactor) &&
	    (target != mudstate.curr_player)) {

	    /* I'd really like to use tprintf here but I can't
	     * because the caller may have.
	     * notify(target, tprintf(...)) is quite common in
	     * the code.
	     */

	    tbuff = alloc_sbuf("notify_check.nospoof");
	    safe_chr('[', msg_ns, &mp);
	    safe_str(Name(sender), msg_ns, &mp);
	    *tbuff = '(';
	    tbuff[1] = '#';
	    ltos(&tbuff[2], sender);
	    safe_str(tbuff, msg_ns, &mp);
	    safe_chr(')', msg_ns, &mp);

	    if (sender != Owner(sender)) {
		safe_chr('{', msg_ns, &mp);
		safe_str(Name(Owner(sender)), msg_ns, &mp);
		safe_chr('}', msg_ns, &mp);
	    }
	    if (sender != mudstate.curr_enactor) {
		strcpy(tbuff, (const char *) "<-(#");
		ltos(&tbuff[4], mudstate.curr_enactor);
		safe_str(tbuff, msg_ns, &mp);
		safe_chr(')', msg_ns, &mp);
	    }
	    safe_str((char *) "] ", msg_ns, &mp);
	    free_sbuf(tbuff);
	}
	safe_long_str((char *) msg, msg_ns, &mp);
    } else {
	msg_ns = NULL;
    }

    /* msg contains the raw message, msg_ns contains the NOSPOOFed msg */

    check_listens = Halted(target) ? 0 : 1;
    switch (Typeof(target)) {
    case TYPE_PLAYER:

#ifndef PUEBLO_SUPPORT
	if (key & MSG_ME)
	    raw_notify(target, msg_ns);
#else
	if (key & MSG_ME) {

	    if (key & MSG_HTML) {
		raw_notify_html(target, msg_ns);
	    } else {
		if (Html(target)) {
		char *msg_ns_escaped;

		msg_ns_escaped = alloc_lbuf("notify_check_escape");
		html_escape(msg_ns, msg_ns_escaped, 0);
		raw_notify(target, msg_ns_escaped);
		free_lbuf(msg_ns_escaped);
		} else {
		    raw_notify(target, msg_ns);
		}
	    }
	}
#endif /* ! PUEBLO_SUPPORT */
	if (!mudconf.player_listen)
	    check_listens = 0;
    case TYPE_THING:
    case TYPE_ROOM:

	/* Forward puppet message if it is for me */

	has_neighbors = Has_location(target);
	targetloc = where_is(target);
	is_audible = Audible(target);

	if ((key & MSG_ME) &&
	    Puppet(target) &&
	    (target != Owner(target)) &&
	    ((key & MSG_PUP_ALWAYS) ||
	     ((targetloc != Location(Owner(target))) &&
	      (targetloc != Owner(target))))) {

	    tp = tbuff = alloc_lbuf("notify_check.puppet");
	    safe_str(Name(target), tbuff, &tp);
	    safe_str((char *) "> ", tbuff, &tp);
	    safe_str(msg_ns, tbuff, &tp);
	    raw_notify(Owner(target), tbuff);
	    free_lbuf(tbuff);
	}
	/* Check for @Listen match if it will be useful */

	pass_listen = 0;
	nargs = 0;
	if (check_listens && (key & (MSG_ME | MSG_INV_L)) &&
	    H_Listen(target)) {
	    tp = atr_get(target, A_LISTEN, &aowner, &aflags);
	    if (*tp && wild_match(tp, (char *) msg, args, 10, 0)) {
		for (nargs = 10;
		     nargs &&
		     (!args[nargs - 1] || !(*args[nargs - 1]));
		     nargs--);
		pass_listen = 1;
	    }
	    free_lbuf(tp);
	}
	/* If we matched the @listen or are monitoring, check the
	 * USE lock
	 */

	pass_uselock = 0;
	if ((key & MSG_ME) && check_listens &&
	    (pass_listen || Monitor(target)))
	    pass_uselock = could_doit(sender, target, A_LUSE);

	/* Process AxHEAR if we pass LISTEN, USElock and it's for me */

	if ((key & MSG_ME) && pass_listen && pass_uselock) {
	    if (sender != target)
		did_it(sender, target, 0, NULL, 0, NULL,
		       A_AHEAR, args, nargs);
	    else
		did_it(sender, target, 0, NULL, 0, NULL,
		       A_AMHEAR, args, nargs);
	    did_it(sender, target, 0, NULL, 0, NULL,
		   A_AAHEAR, args, nargs);
	}
	/* Get rid of match arguments. We don't need them anymore */

	if (pass_listen) {
	    for (i = 0; i < nargs; i++)
		if (args[i] != NULL)
		    free_lbuf(args[i]);
	}
	/* Process ^-listens if for me, MONITOR, and we pass USElock */

	if ((key & MSG_ME) && pass_uselock && (sender != target) &&
	    Monitor(target)) {
	    (void) atr_match(target, sender,
			     AMATCH_LISTEN, (char *) msg, (char *) msg, 0);
	}
	/* Deliver message to forwardlist members */

	if ((key & MSG_FWDLIST) && Audible(target) &&
	    check_filter(target, sender, A_FILTER, msg)) {
	    tbuff = dflt_from_msg(sender, target);
	    buff = add_prefix(target, sender, A_PREFIX,
			      msg, tbuff);
	    free_lbuf(tbuff);

	    fp = fwdlist_get(target);
	    if (fp) {
		for (i = 0; i < fp->count; i++) {
		    recip = fp->data[i];
		    if (!Good_obj(recip) ||
			(recip == target))
			continue;
		    notify_check(recip, sender, buff,
		       (MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE));
		}
	    }
	    free_lbuf(buff);
	}
	/* Deliver message through audible exits */

	if (key & MSG_INV_EXITS) {
	    DOLIST(obj, Exits(target)) {
		recip = Location(obj);
		if (Audible(obj) && ((recip != target) &&
				check_filter(obj, sender, A_FILTER, msg))) {
		    buff = add_prefix(obj, target,
				      A_PREFIX, msg,
				      "From a distance,");
		    notify_check(recip, sender, buff,
			 MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE);
		    free_lbuf(buff);
		}
	    }
	}
	/* Deliver message through neighboring audible exits */

	if (has_neighbors &&
	    ((key & MSG_NBR_EXITS) ||
	     ((key & MSG_NBR_EXITS_A) && is_audible))) {

	    /* If from inside, we have to add the prefix string of
	     * the container.
	     */

	    if (key & MSG_S_INSIDE) {
		tbuff = dflt_from_msg(sender, target);
		buff = add_prefix(target, sender, A_PREFIX,
				  msg, tbuff);
		free_lbuf(tbuff);
	    } else {
		buff = (char *) msg;
	    }

	    DOLIST(obj, Exits(Location(target))) {
		recip = Location(obj);
		if (Good_obj(recip) && Audible(obj) &&
		    (recip != targetloc) &&
		    (recip != target) &&
		    check_filter(obj, sender, A_FILTER, msg)) {
		    tbuff = add_prefix(obj, target,
				       A_PREFIX, buff,
				       "From a distance,");
		    notify_check(recip, sender, tbuff,
			 MSG_ME | MSG_F_UP | MSG_F_CONTENTS | MSG_S_INSIDE);
		    free_lbuf(tbuff);
		}
	    }
	    if (key & MSG_S_INSIDE) {
		free_lbuf(buff);
	    }
	}

	if (Bouncer(target))
	    pass_listen = 1;

	/* Deliver message to contents */

	if (((key & MSG_INV) || ((key & MSG_INV_L) && pass_listen)) &&
	    (check_filter(target, sender, A_INFILTER, msg))) {

	    /* Don't prefix the message if we were given the
	     * MSG_NOPREFIX key.
	     */

	    if (key & MSG_S_OUTSIDE) {
		buff = add_prefix(target, sender, A_INPREFIX,
				  msg, "");
	    } else {
		buff = (char *) msg;
	    }
	    DOLIST(obj, Contents(target)) {
		if (obj != target) {
#ifdef PUEBLO_SUPPORT
		    notify_check(obj, sender, buff,
				 MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE | (key & MSG_HTML));
#else
		    notify_check(obj, sender, buff,
				 MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE);
#endif /* PUEBLO_SUPPORT */

		}
	    }
	    if (key & MSG_S_OUTSIDE)
		free_lbuf(buff);
	}
	/* Deliver message to neighbors */

	if (has_neighbors &&
	    ((key & MSG_NBR) ||
	     ((key & MSG_NBR_A) && is_audible &&
	      check_filter(target, sender, A_FILTER, msg)))) {
	    if (key & MSG_S_INSIDE) {
		tbuff = dflt_from_msg(sender, target);
		buff = add_prefix(target, sender, A_PREFIX,
				  msg, "");
		free_lbuf(tbuff);
	    } else {
		buff = (char *) msg;
	    }
	    DOLIST(obj, Contents(targetloc)) {
		if ((obj != target) && (obj != targetloc)) {
		    notify_check(obj, sender, buff,
				 MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE);
		}
	    }
	    if (key & MSG_S_INSIDE) {
		free_lbuf(buff);
	    }
	}
	/* Deliver message to container */

	if (has_neighbors &&
	    ((key & MSG_LOC) ||
	     ((key & MSG_LOC_A) && is_audible &&
	      check_filter(target, sender, A_FILTER, msg)))) {
	    if (key & MSG_S_INSIDE) {
		tbuff = dflt_from_msg(sender, target);
		buff = add_prefix(target, sender, A_PREFIX,
				  msg, tbuff);
		free_lbuf(tbuff);
	    } else {
		buff = (char *) msg;
	    }
	    notify_check(targetloc, sender, buff,
			 MSG_ME | MSG_F_UP | MSG_S_INSIDE);
	    if (key & MSG_S_INSIDE) {
		free_lbuf(buff);
	    }
	}
    }
    if (msg_ns)
	free_lbuf(msg_ns);
    mudstate.ntfy_nest_lev--;
}

void 
notify_except(loc, player, exception, msg)
    dbref loc, player, exception;
    const char *msg;
{
    dbref first;

    if (loc != exception)
	notify_check(loc, player, msg,
		  (MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A));
    DOLIST(first, Contents(loc)) {
	if (first != exception) {
	    notify_check(first, player, msg,
			 (MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE));
	}
    }
}

void 
notify_except2(loc, player, exc1, exc2, msg)
    dbref loc, player, exc1, exc2;
    const char *msg;
{
    dbref first;

    if ((loc != exc1) && (loc != exc2))
	notify_check(loc, player, msg,
		  (MSG_ME_ALL | MSG_F_UP | MSG_S_INSIDE | MSG_NBR_EXITS_A));
    DOLIST(first, Contents(loc)) {
	if (first != exc1 && first != exc2) {
	    notify_check(first, player, msg,
			 (MSG_ME | MSG_F_DOWN | MSG_S_OUTSIDE));
	}
    }
}

void 
do_shutdown(player, cause, key, message)
    dbref player, cause;
    int key;
    char *message;
{
    int fd;

    if (key & SHUTDN_COREDUMP) {
	if (player != NOTHING) {
	    raw_broadcast(0, "Game: Aborted by %s", Name(Owner(player)));
	    STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN")
		log_text((char *) "Abort and coredump by ");
	    log_name(player);
	    ENDLOG
	}
	/* Don't bother to even shut down the network or dump. Die. Die now. */
	abort();
    }

    if (player != NOTHING) {
	raw_broadcast(0, "Game: Shutdown by %s", Name(Owner(player)));
	STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN")
	    log_text((char *) "Shutdown by ");
	log_name(player);
	ENDLOG
    } else {
	raw_broadcast(0, "Game: Fatal Error: %s", message);
	STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN")
	    log_text((char *) "Fatal error: ");
	log_text(message);
	ENDLOG
    }
    STARTLOG(LOG_ALWAYS, "WIZ", "SHTDN")
	log_text((char *) "Shutdown status: ");
    log_text(message);
    ENDLOG
	fd = tf_open(mudconf.status_file, O_RDWR | O_CREAT | O_TRUNC);
    (void) write(fd, message, strlen(message));
    (void) write(fd, (char *) "\n", 1);
    tf_close(fd);

    /* Do we perform a normal or an emergency shutdown?  Normal shutdown
     * is handled by exiting the main loop in shovechars, emergency
     * shutdown is done here.
     */

    if (key & SHUTDN_PANIC) {

	/* Close down the network interface */

	emergency_shutdown();

	/* Close the attribute text db and dump the header db */

	pcache_sync();
	SYNC;
	CLOSE;
	STARTLOG(LOG_ALWAYS, "DMP", "PANIC")
	    log_text((char *) "Panic dump: ");
	log_text(mudconf.crashdb);
	ENDLOG
	    dump_database_internal(1);
	STARTLOG(LOG_ALWAYS, "DMP", "DONE")
	    log_text((char *) "Panic dump complete: ");
	log_text(mudconf.crashdb);
	ENDLOG
    }
    /* Set up for normal shutdown */

    mudstate.shutdown_flag = 1;
    return;
}

static void 
dump_database_internal(panic_dump)
    int panic_dump;
{
    char tmpfile[256], prevfile[256];
    FILE *f;

    if (panic_dump) {
	unlink(mudconf.crashdb);
	f = tf_fopen(mudconf.crashdb, O_WRONLY | O_CREAT | O_TRUNC);
	if (f != NULL) {
	    db_write(f, F_MUSH, OUTPUT_VERSION | OUTPUT_FLAGS);
	    tf_fclose(f);
	} else {
	    log_perror("DMP", "FAIL", "Opening crash file",
		       mudconf.crashdb);
	}
	return;
    }
    sprintf(prevfile, "%s.prev", mudconf.outdb);
#ifdef VMS
    sprintf(tmpfile, "%s.-%d-", mudconf.outdb, mudstate.epoch - 1);
    unlink(tmpfile);		/* nuke our predecessor */
    sprintf(tmpfile, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#else
    sprintf(tmpfile, "%s.#%d#", mudconf.outdb, mudstate.epoch - 1);
    unlink(tmpfile);		/* nuke our predecessor */
    sprintf(tmpfile, "%s.#%d#", mudconf.outdb, mudstate.epoch);

#endif /* VMS */

    f = tf_fopen(tmpfile, O_WRONLY | O_CREAT | O_TRUNC);
    if (f) {
	db_write(f, F_MUSH, OUTPUT_VERSION | OUTPUT_FLAGS);
	tf_fclose(f);
	rename(mudconf.outdb, prevfile);
	if (rename(tmpfile, mudconf.outdb) < 0)
	    log_perror("SAV", "FAIL",
		       "Renaming output file to DB file", tmpfile);
    } else {
	log_perror("SAV", "FAIL", "Opening", tmpfile);
    }
}

void 
NDECL(dump_database)
{
    char
        *buff;

    mudstate.epoch++;
    buff = alloc_mbuf("dump_database");
#ifdef VMS
    sprintf(buff, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#else
    sprintf(buff, "%s.#%d#", mudconf.outdb, mudstate.epoch);
#endif /* VMS */
    STARTLOG(LOG_DBSAVES, "DMP", "DUMP")
	log_text((char *) "Dumping: ");
    log_text(buff);
    ENDLOG
	pcache_sync();
    SYNC;
    dump_database_internal(0);
    STARTLOG(LOG_DBSAVES, "DMP", "DONE")
	log_text((char *) "Dump complete: ");
    log_text(buff);
    ENDLOG
	free_mbuf(buff);
}

void 
fork_and_dump(key)
    int key;
{
    int child;
    char *buff;

    if (*mudconf.dump_msg)
	raw_broadcast(0, "%s", mudconf.dump_msg);
    mudstate.epoch++;
    buff = alloc_mbuf("fork_and_dump");
#ifdef VMS
    sprintf(buff, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#else
    sprintf(buff, "%s.#%d#", mudconf.outdb, mudstate.epoch);
#endif /* VMS */
    STARTLOG(LOG_DBSAVES, "DMP", "CHKPT")
	if (!key || (key & DUMP_TEXT)) {
	log_text((char *) "SYNCing");
	if (!key || (key & DUMP_STRUCT))
	    log_text((char *) " and ");
    }
    if (!key || (key & DUMP_STRUCT)) {
	log_text((char *) "Checkpointing: ");
	log_text(buff);
    }
    ENDLOG
	free_mbuf(buff);
    al_store();			/* Save cached modified attribute list */
    if (!key || (key & DUMP_TEXT))
	pcache_sync();
    SYNC;
    if (!key || (key & DUMP_STRUCT)) {
#ifndef VMS
	if (mudconf.fork_dump) {
	    if (mudconf.fork_vfork) {
		child = vfork();
	    } else {
		child = fork();
	    }
	} else {
	    child = 0;
	}
#else
	child = 0;
#endif /* VMS */
	if (child == 0) {
	    dump_database_internal(0);
#ifndef VMS
	    if (mudconf.fork_dump)
		_exit(0);
#endif /* VMS */
	} else if (child < 0) {
	    log_perror("DMP", "FORK", NULL, "fork()");
	}
    }
    if (*mudconf.postdump_msg)
	raw_broadcast(0, "%s", mudconf.postdump_msg);
}

static int 
NDECL(load_game)
{
    FILE *f = NULL;
    char infile[256];
    int db_format, db_version, db_flags;

    strcpy(infile, mudconf.indb);
    if ((f = tf_fopen(mudconf.indb, O_RDONLY)) == NULL)
	return -1;

    /* ok, read it in */

    STARTLOG(LOG_STARTUP, "INI", "LOAD")
	log_text((char *) "Loading: ");
    log_text(infile);
    ENDLOG

	if (db_read(f, &db_format, &db_version, &db_flags) < 0) {
	STARTLOG(LOG_ALWAYS, "INI", "FATAL")
	    log_text((char *) "Error loading ");
	log_text(infile);
	ENDLOG
	    return -1;
    }
    STARTLOG(LOG_STARTUP, "INI", "LOAD")
	log_text((char *) "Load complete.");
    ENDLOG

    /* everything ok */

	tf_fclose(f);
    return (0);
}

/* match a list of things */

int 
list_check(thing, player, type, str, raw_str, check_parent, stop_status)
    dbref thing, player;
    char type, *str, *raw_str;
    int check_parent;
    int *stop_status;
{
    int match;

    match = 0;
    while (thing != NOTHING) {
	if ((thing != player) &&
	    (atr_match(thing, player, type, str, raw_str, check_parent) > 0)) {
	    match = 1;
	    if (Stop_Match(thing)) {
		*stop_status = 1;
		return match;
	    }
	}
	thing = Next(thing);
    }
    return match;
}

int 
Hearer(thing)
    dbref thing;
{
    char *as, *buff, *s;
    dbref aowner;
    int attr, aflags;
    ATTR *ap;

    if (Connected(thing) || Puppet(thing))
	return 1;

    if (Monitor(thing))
	buff = alloc_lbuf("Hearer");
    else
	buff = NULL;
    atr_push();
    for (attr = atr_head(thing, &as); attr; attr = atr_next(&as)) {
	if (attr == A_LISTEN) {
	    if (buff)
		free_lbuf(buff);
	    atr_pop();
	    return 1;
	}
	if (Monitor(thing)) {
	    ap = atr_num(attr);
	    if (!ap || (ap->flags & AF_NOPROG))
		continue;

	    atr_get_str(buff, thing, attr, &aowner, &aflags);

	    /* Make sure we can execute it */

	    if ((buff[0] != AMATCH_LISTEN) || (aflags & AF_NOPROG))
		continue;

	    /* Make sure there's a : in it */

	    for (s = buff + 1; *s && (*s != ':'); s++);
	    if (s) {
		free_lbuf(buff);
		atr_pop();
		return 1;
	    }
	}
    }
    if (buff)
	free_lbuf(buff);
    atr_pop();
    return 0;
}

void 
do_rwho(player, cause, key)
    dbref player, cause;
    int key;
{
#ifdef RWHO_IN_USE
    if (key == RWHO_START) {
	if (!mudstate.rwho_on) {
	    rwhocli_setup(mudconf.rwho_host,
			  mudconf.rwho_info_port,
			  mudconf.rwho_pass, mudconf.mud_name,
			  mudstate.short_ver);
	    rwho_update();
	    if (!Quiet(player))
		notify(player, "RWHO transmission started.");
	    mudstate.rwho_on = 1;
	} else {
	    notify(player, "RWHO transmission already on.");
	}
    } else if (key == RWHO_STOP) {
	if (mudstate.rwho_on) {
	    rwhocli_shutdown();
	    if (!Quiet(player))
		notify(player, "RWHO transmission stopped.");
	    mudstate.rwho_on = 0;
	} else {
	    notify(player, "RWHO transmission already off.");
	}
    } else {
	notify(player, "Illegal combination of switches.");
    }
#else
    notify(player, "RWHO support has not been compiled in to the server.");
#endif

}

void 
do_readcache(player, cause, key)
    dbref player, cause;
    int key;
{
    helpindex_load(player);
    fcache_load(player);
}

static void 
NDECL(process_preload)
{
    dbref thing, parent, aowner;
    int aflags, lev, i;
    char *tstr;
    FWDLIST *fp;

    fp = (FWDLIST *) alloc_lbuf("process_preload.fwdlist");
    tstr = alloc_lbuf("process_preload.string");
    i = 0;
    DO_WHOLE_DB(thing) {

	/* Ignore GOING objects */

	if (Going(thing))
	    continue;

	do_top(10);

	/* Look for a STARTUP attribute in parents */

	ITER_PARENTS(thing, parent, lev) {
	    if (Flags(thing) & HAS_STARTUP) {
		did_it(Owner(thing), thing, 0, NULL, 0, NULL,
		       A_STARTUP, (char **) NULL, 0);
		/* Process queue entries as we add them */

		do_second();
		do_top(10);
		cache_reset(0);
		break;
	    }
	}

	/* Look for a FORWARDLIST attribute */

	if (H_Fwdlist(thing)) {
	    (void) atr_get_str(tstr, thing, A_FORWARDLIST,
			       &aowner, &aflags);
	    if (*tstr) {
		fwdlist_load(fp, GOD, tstr);
		if (fp->count > 0)
		    fwdlist_set(thing, fp);
		cache_reset(0);
	    }
	}
    }
    free_lbuf(fp);
    free_lbuf(tstr);
}

#ifndef VMS
void
#endif /* VMS */
main(argc, argv)
    int argc;
    char *argv[];
{
    int mindb;

    if ((argc > 2) && (!strcmp(argv[1], "-s") && (argc > 3))) {
	fprintf(stderr, "Usage: %s [-s] [config-file]\n", argv[0]);
	exit(1);
    }
/*
    fclose(stdin);
    fclose(stdout);
*/
    tf_init();
#ifdef IN_MEM_COMPRESSION
    init_string_compress();
#endif
    mindb = 0;			/* Are we creating a new db? */
    time(&mudstate.start_time);
    pool_init(POOL_LBUF, LBUF_SIZE);
    pool_init(POOL_MBUF, MBUF_SIZE);
    pool_init(POOL_SBUF, SBUF_SIZE);
    pool_init(POOL_BOOL, sizeof(struct boolexp));

    pool_init(POOL_DESC, sizeof(DESC));
    pool_init(POOL_QENTRY, sizeof(BQUE));
    tcache_init();
    pcache_init();
    cf_init();
    init_rlimit();
    init_cmdtab();
    init_logout_cmdtab();
    init_flagtab();
    init_functab();
    init_attrtab();
    init_version();
    hashinit(&mudstate.player_htab, 511);
    nhashinit(&mudstate.fwdlist_htab, 57);
    nhashinit(&mudstate.parent_htab, 57);
    nhashinit(&mudstate.desc_htab, 37);

    if (argc > 1 && !strcmp(argv[1], "-s")) {
	mindb = 1;
	if (argc == 3)
	    cf_read(argv[2]);
	else
	    cf_read((char *) CONF_FILE);
    } else if (argc == 2) {
	cf_read(argv[1]);
    } else {
	cf_read((char *) CONF_FILE);
    }

    fcache_init();
    helpindex_init();

    if (mindb)
	unlink(mudconf.gdbm);
    if (init_gdbm_db(mudconf.gdbm) < 0) {
	STARTLOG(LOG_ALWAYS, "INI", "LOAD")
	    log_text((char *) "Couldn't load text database: ");
	log_text(mudconf.gdbm);
	ENDLOG
	    exit(2);
    }
    if (mindb)
	db_make_minimal();
    else if (load_game() < 0) {
	STARTLOG(LOG_ALWAYS, "INI", "LOAD")
	    log_text((char *) "Couldn't load: ");
	log_text(mudconf.indb);
	ENDLOG
	    exit(2);
    }
    srandom(getpid());
    set_signals();

    /* Do a consistency check and set up the freelist */

    do_dbck(NOTHING, NOTHING, 0);

    /* Reset all the hash stats */

    hashreset(&mudstate.command_htab);
    hashreset(&mudstate.logout_cmd_htab);
    hashreset(&mudstate.func_htab);
    hashreset(&mudstate.flags_htab);
    hashreset(&mudstate.attr_name_htab);
    nhashreset(&mudstate.attr_num_htab);
    hashreset(&mudstate.player_htab);
    nhashreset(&mudstate.fwdlist_htab);
    hashreset(&mudstate.news_htab);
    hashreset(&mudstate.help_htab);
    hashreset(&mudstate.wizhelp_htab);
    nhashreset(&mudstate.desc_htab);

    for (mindb = 0; mindb < MAX_GLOBAL_REGS; mindb++)
	mudstate.global_regs[mindb] = alloc_lbuf("main.global_reg");
    mudstate.now = time(NULL);

    process_preload();
    STARTLOG(LOG_STARTUP, "INI", "LOAD")
	log_text((char *) "Startup processing complete.");
    ENDLOG

    if (mudconf.rwho_transmit)
	do_rwho(NOTHING, NOTHING, RWHO_START);

    /* go do it */

    mudstate.now = time(NULL);
    init_timer();
    shovechars(mudconf.port);

    close_sockets(0, (char *) "Going down - Bye");
    dump_database();
    CLOSE;
    exit(0);
}

static void 
NDECL(init_rlimit)
{
#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
    struct rlimit *rlp;

    rlp = (struct rlimit *) alloc_lbuf("rlimit");

    if (getrlimit(RLIMIT_NOFILE, rlp)) {
	log_perror("RLM", "FAIL", NULL, "getrlimit()");
	free_lbuf(rlp);
	return;
    }
    rlp->rlim_cur = rlp->rlim_max;
    if (setrlimit(RLIMIT_NOFILE, rlp))
	log_perror("RLM", "FAIL", NULL, "setrlimit()");
    free_lbuf(rlp);
#else
#if defined(_SEQUENT_) && defined(NUMFDS_LIMIT)
    setdtablesize(NUMFDS_LIMIT);
#endif /* Sequent and unlimiting #define'd */
#endif /* HAVE_SETRLIMIT */
}
