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

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

#include <ctype.h>
#include <fcntl.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#include <signal.h>
#ifdef I_SYS_WAIT
#include <sys/wait.h>
#endif
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#ifdef WIN32
#include <process.h>
void Win32MUSH_setup _((void));
#endif
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#ifdef HAS_GETRUSAGE
#include <sys/resource.h>
#endif
#ifdef I_STDLIB
#include <stdlib.h>
#endif
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <stdio.h>

#include "conf.h"
#include "mushdb.h"
#include "game.h"
#include "externs.h"
#include "intrface.h"
#include "match.h"
#include "globals.h"
#ifdef USE_MAILER
#include "extmail.h"
#endif
#ifdef CHAT_SYSTEM
#include "extchat.h"
#endif
#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#include "getpgsiz.h"
#include "parse.h"
#include "access.h"
#include "version.h"

#include "command.h"

#ifdef hpux
#include <sys/syscall.h>
#define getrusage(x,p)   syscall(SYS_GETRUSAGE,x,p)
#endif				/* fix to HP-UX getrusage() braindamage */

#include "confmagic.h"

#ifdef HAS_WAITPID
#define WAIT_TYPE int
#else
#ifdef UNION_WAIT
#define WAIT_TYPE union wait
#else
#define WAIT_TYPE int
#endif
#endif

/* declarations */
char dumpfile[200];
time_t start_time;		/* MUSH start time */
extern time_t mudtime;		/* MUSH current time */
static int epoch = 0;
int reserved;
int depth = 0;			/* excessive recursion prevention */
extern int invok_counter;	/* function recursion prevention */
extern dbref cplr;
extern char ccom[];

int paranoid_dump = 0;		/* if paranoid, scan before dumping */
int paranoid_checkpt = 0;	/* write out an okay message every x objs */
static void dump_database_internal _((void));
static FILE *db_open _((const char *filename));
static FILE *db_open_write _((const char *filename));
static void db_close _((FILE * f));
Signal_t reapear _((int));
#ifdef CHAT_SYSTEM
int parse_chat _((dbref player, char *command));
#endif
void do_readcache _((dbref player));
void set_interp _((dbref player, dbref cause, char const *obj,
		   char const *attrib, char const *val, int from_port));
int test_set _((dbref player, dbref cause, char const *command,
		char *arg1, char *arg2, int from_port));
char **argv_hack _((dbref player, dbref cause, char const *arg,
		    char *fargs[], int eflags));
int check_alias _((const char *command, const char *list));
int list_check _((dbref thing, dbref player, char type,
		  char end, char *str, int just_match));
int alias_list_check _((dbref thing, const char *command,
			const char *type));
int loc_alias_check _((dbref loc, const char *command,
		       const char *type));
void do_poor _((dbref player, char *arg1));
void do_writelog _((dbref player, char *str, int ltype));
void bind_and_queue _((dbref player, dbref cause,
		       char *action, char *arg));
void do_scan _((dbref player, char *command, int flag));
void do_list _((dbref player, char *arg));
void do_dolist _((dbref player, char *list, char *command,
		  dbref cause, int flag, int notify_flag));
void do_uptime _((dbref player));
char *make_new_epoch_file _((const char *basename, int the_epoch));
void fork_and_dump _((int forking));
void dest_info _((dbref thing, dbref tt));
#ifdef HAS_GETRUSAGE
void rusage_stats _((void));
#endif
void do_restart _((void));
extern void charge_action _((dbref player, dbref thing, const char *awhat));
extern int filter_found _((dbref thing, const char *msg, int flag));
extern void local_startup _((void));
extern void do_reboot _((dbref player, int flag));
extern void local_dump_database _((void));

extern dbref first_free;	/* head of free object list, destroy.c */

dbref orator = NOTHING;

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

void
do_dump(player, num, flag)
    dbref player;
    char *num;
    int flag;
{
  /* flag: 0 = normal, 1 = paranoid, 2 = debug */

  time_t tt;
  if (Wizard(player)) {
    if (options.daytime) {
      notify(player, "Sorry, CPU intensive commands are currently disabled.");
      return;
    }
    tt = time((time_t *) 0);
#ifdef ALWAYS_PARANOID
    if (1) {
#else
    if (flag) {
#endif
      /* want to do a scan before dumping each object */
      paranoid_dump = flag;
      if (num && *num) {
	/* checkpoint interval given */
	paranoid_checkpt = atoi(num);
	if ((paranoid_checkpt < 1) || (paranoid_checkpt >= db_top)) {
	  notify(player, "Permission denied. Invalid checkpoint interval.");
	  paranoid_dump = 0;
	  return;
	}
      } else {
	/* use a default interval */
	paranoid_checkpt = db_top / 5;
	if (paranoid_checkpt < 1)
	  paranoid_checkpt = 1;
      }
      if (flag == 1) {
	notify(player, tprintf("Paranoid dumping, checkpoint interval %d.",
			       paranoid_checkpt));
	fprintf(checklog_fp,
		"*** PARANOID DUMP *** done by %s(#%d),\n",
		Name(player), player);
      } else {
	notify(player, tprintf("Debug dumping, checkpoint interval %d.",
			       paranoid_checkpt));
	fprintf(checklog_fp,
		"*** DEBUG DUMP *** done by %s(#%d),\n",
		Name(player), player);
      }
      fprintf(checklog_fp, "\tcheckpoint interval %d, at %s",
	      paranoid_checkpt, ctime(&tt));
    } else {
      /* normal dump */
      paranoid_dump = 0;	/* just to be safe */
      notify(player, "Dumping...");
      fprintf(checklog_fp, "** DUMP ** done by %s(#%d) at %s",
	      db[player].name, player, ctime(&tt));
    }
    fflush(checklog_fp);
    fork_and_dump(1);
    paranoid_dump = 0;
  } else {
    notify(player, "Sorry, you are in a no dumping zone.");
  }
}


/* print out stuff into error file */
void
report()
{
  if (GoodObject(cplr))
    do_rawlog(LT_TRACE, "TRACE: Cmd:%s\tdepth:%d\tby #%d at #%d", ccom,
	      depth, cplr, Location(cplr));
  else
    do_rawlog(LT_TRACE, "TRACE: Cmd:%s\tdepth:%d\tby #%d", ccom,
	      depth, cplr);
  fflush(tracelog_fp);
}

#ifdef HAS_GETRUSAGE
void
rusage_stats()
{
  struct rusage usage;
  int pid;
#ifndef hpux
  int psize;
#endif

  pid = getpid();
#ifndef hpux
  psize = getpagesize();
#endif
  getrusage(RUSAGE_SELF, &usage);

  fprintf(stderr, "\nProcess statistics:\n");
  fprintf(stderr, "Time used:   %10ld user   %10ld sys\n",
	  usage.ru_utime.tv_sec, usage.ru_stime.tv_sec);
#ifndef hpux
  fprintf(stderr, "Max res mem: %10ld pages  %10ld bytes\n",
	  usage.ru_maxrss, (usage.ru_maxrss * psize));
#else
  fprintf(stderr, "Max res mem: %10 pages\n", usage.ru_maxrss);
#endif				/* hpux */
  fprintf(stderr, "Integral mem:%10ld shared %10ld private %10ld stack\n",
	  usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss);
  fprintf(stderr, "Page faults: %10ld hard   %10ld soft    %10ld swapouts\n",
	  usage.ru_majflt, usage.ru_minflt, usage.ru_nswap);
  fprintf(stderr, "Disk I/O:    %10ld reads  %10ld writes\n",
	  usage.ru_inblock, usage.ru_oublock);
  fprintf(stderr, "Network I/O: %10ld in     %10ld out\n",
	  usage.ru_msgrcv, usage.ru_msgsnd);
  fprintf(stderr, "Context swi: %10ld vol    %10ld forced\n",
	  usage.ru_nvcsw, usage.ru_nivcsw);
  fprintf(stderr, "Signals:     %10ld\n", usage.ru_nsignals);
}

#endif				/* HAS_GETRUSAGE */

void
do_shutdown(player, flag)
    dbref player;
    int flag;			/* -1 = panic shutdown, 0 = normal, 1 = paranoid */
{
  if (flag == -1 && !God(player)) {
    notify(player, "It takes a God to make me panic.");
    return;
  }
  if (Wizard(player)) {
    flag_broadcast(0, 0, "GAME: Shutdown by %s", db[player].name);
    do_log(LT_ERR, player, NOTHING, "SHUTDOWN by %s\n",
	   real_unparse(player, player, 1));

    /* This will create a file used to check if a restart should occur */
#ifdef AUTORESTART
    system("touch NORESTART");
#endif

    if (flag == -1) {
      panic("@shutdown/panic");
    } else {
      if (flag == 1) {
	paranoid_checkpt = db_top / 5;
	if (paranoid_checkpt < 1)
	  paranoid_checkpt = 1;
	paranoid_dump = 1;
      }
      shutdown_flag = 1;
    }
  } else {
    notify(player, "Your delusions of grandeur have been duly noted.");
  }
}


static void
dump_database_internal()
{
  char realdumpfile[2048];
  char realtmpfl[2048];
  char tmpfl[2048];
  FILE *f;

  local_dump_database();

#ifdef ALWAYS_PARANOID
  paranoid_checkpt = db_top / 5;
  if (paranoid_checkpt < 1)
    paranoid_checkpt = 1;
#endif

  sprintf(realdumpfile, "%s%s", dumpfile, options.compresssuff);
  strcpy(tmpfl, make_new_epoch_file(dumpfile, epoch));
  sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);

  if ((f = db_open_write(tmpfl)) != NULL) {
    switch (paranoid_dump) {
    case 0:
#ifdef ALWAYS_PARANOID
      db_paranoid_write(f, 0);
#else
      db_write(f);
#endif
      break;
    case 1:
      db_paranoid_write(f, 0);
      break;
    case 2:
      db_paranoid_write(f, 1);
      break;
    }
    db_close(f);
#ifdef WIN32
    /* Win32 systems can't rename over an existing file, so unlink first */
    unlink(realdumpfile);
#endif
    if (rename(realtmpfl, realdumpfile) < 0)
      perror(realtmpfl);
  } else
    perror(realtmpfl);
#ifdef USE_MAILER
  sprintf(realdumpfile, "%s%s", options.mail_db, options.compresssuff);
  strcpy(tmpfl, make_new_epoch_file(options.mail_db, epoch));
  sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
  if (mdb_top >= 0) {
    if ((f = db_open_write(tmpfl)) != NULL) {
      dump_mail(f);
      db_close(f);
#ifdef WIN32
      unlink(realdumpfile);
#endif
      if (rename(realtmpfl, realdumpfile) < 0)
	perror(realtmpfl);
    } else
      perror(realtmpfl);
  }
#endif				/* USE_MAILER */
#ifdef ALLOW_RPAGE
  strcpy(tmpfl, make_new_epoch_file("rpage.db.Z", epoch));
  if ((f = db_open_write(tmpfl)) != NULL) {
    dump_server_database(f);
    db_close(f);
#ifdef WIN32
    unlink("rpage.db.Z");
#endif
    if (rename(tmpfl, "rpage.db.Z") < 0)
      perror(tmpfl);
  } else
    perror(tmpfl);
#endif				/* ALLOW_RPAGE */
#ifdef CHAT_SYSTEM
  sprintf(realdumpfile, "%s%s", options.chatdb, options.compresssuff);
  strcpy(tmpfl, make_new_epoch_file(options.chatdb, epoch));
  sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
  if ((f = db_open_write(tmpfl)) != NULL) {
    save_chatdb(f);
    db_close(f);
#ifdef WIN32
    unlink(realdumpfile);
#endif
    if (rename(realtmpfl, realdumpfile) < 0)
      perror(realtmpfl);
  } else
    perror(realtmpfl);
#endif
}

void
panic(message)
    const char *message;
{
  const char *panicfile = options.crash_db;
  FILE *f;
  int i;

  fprintf(stderr, "PANIC: %s\n", message);
  report();
  flag_broadcast(0, 0, "EMERGENCY SHUTDOWN: %s", message);

  /* turn off signals */
  for (i = 0; i < NSIG; i++) {
    signal(i, SIG_IGN);
  }

  /* shut down interface */
  emergency_shutdown();

  /* dump panic file */
  if ((f = fopen(panicfile, "w")) == NULL) {
    perror("CANNOT OPEN PANIC FILE, YOU LOSE");
    _exit(135);
  } else {
    fprintf(stderr, "DUMPING: %s\n", panicfile);
    db_write(f);
    fclose(f);
    fprintf(stderr, "DUMPING: %s (done)\n", panicfile);
    _exit(136);
  }
}

void
dump_database()
{
  epoch++;

  fprintf(stderr, "DUMPING: %s.#%d#\n", dumpfile, epoch);
  dump_database_internal();
  fprintf(stderr, "DUMPING: %s.#%d# (done)\n", dumpfile, epoch);
}

#ifndef WIN32
Signal_t reaper _((int sig));
Signal_t
reaper(sig)
    int sig;
{
  WAIT_TYPE my_stat;

#ifdef HAS_WAITPID
  while (waitpid(-1, &my_stat, WNOHANG) > 0) ;
#else
  while (wait3(&my_stat, WNOHANG, 0) > 0) ;
#endif

#ifndef HAS_SIGACTION
/* If we have sigaction, the signals are reliable */
#ifndef SIGNALS_KEPT
  signal(SIGCLD, (Sigfunc) reaper);	/* do this last or it'll loop */
#endif
#endif
#ifndef VOIDSIG
  return 0;
#endif
}
#endif				/* !WIN32 */

void
fork_and_dump(forking)
    int forking;
{
  int child, nofork;
  epoch++;

  fprintf(checklog_fp, "CHECKPOINTING: %s.#%d#\n", dumpfile, epoch);
  fflush(checklog_fp);
  if (NO_FORK)
    nofork = 1;
  else
    nofork = !forking || (paranoid_dump == 2);	/* Don't fork for dump/debug */
#ifdef WIN32
  nofork = 1;
#endif
  if (!nofork) {
#ifndef WIN32
    child = fork();
    if (child < 0) {
      /* Oops, fork failed. Let's do a nofork dump */
      do_log(LT_ERR, 0, 0, "fork_and_dump: fork() failed! Dumping nofork instead.");
      if (DUMP_NOFORK_MESSAGE && *DUMP_NOFORK_MESSAGE)
	flag_broadcast(0, 0, DUMP_NOFORK_MESSAGE);
      child = 0;
      nofork = 1;
    }
#ifdef HAS_SETPRIORITY
    else {
      /* Lower the priority of the child to make parent more responsive */
#ifdef HAS_GETPRIORITY
      setpriority(PRIO_PROCESS, child, getpriority(PRIO_PROCESS, child) + 4);
#else
      setpriority(PRIO_PROCESS, child, 8);
#endif
    }
#endif
#endif
  } else {
    if (DUMP_NOFORK_MESSAGE && *DUMP_NOFORK_MESSAGE)
      flag_broadcast(0, 0, DUMP_NOFORK_MESSAGE);
    child = 0;
  }
  if (nofork || (!nofork && child == 0)) {
    /* in the child */
#ifndef WIN32
    close(reserved);		/* get that file descriptor back */
#endif
#ifdef CONCENTRATOR
    signal(SIGCLD, SIG_DFL);
#endif				/* CONCENTRATOR */
    dump_database_internal();
    if (!nofork) {
      _exit(0);			/* !!! */
    } else {
#ifndef WIN32
      reserved = open("/dev/null", O_RDWR);
#endif
#ifdef CONCENTRATOR
      signal(SIGCLD, (void *) reaper);
#endif				/* CONCENTRATOR */
      if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE)
	flag_broadcast(0, 0, DUMP_NOFORK_COMPLETE);
    }
  }
}

void
do_restart()
{
  dbref thing;
  ATTR *s;
  char *r;
  char buf[SBUF_LEN];
  int j;

  /* Do stuff that needs to be done for players only: add stuff to the
   * alias table, and refund money from queued commands at shutdown.
   */
  for (thing = 0; thing < db_top; thing++) {
    if (Typeof(thing) == TYPE_PLAYER) {
      if ((s = atr_get_noparent(thing, "ALIAS")) != NULL) {
	strcpy(buf, uncompress(s->value));
	add_player(thing, buf);
      }
      if ((s = atr_get_noparent(thing, "QUEUE")) != NULL) {
	giveto(thing, QUEUE_COST * atoi(uncompress(s->value)));
	atr_clr(thing, "QUEUE", GOD);
      }
      if ((s = atr_get_noparent(thing, "SEMAPHORE")) != NULL) {
	atr_clr(thing, "SEMAPHORE", GOD);
      }
    }
  }

  /* Once we load all that, then we can trigger the startups and 
   * begin queueing commands. Also, let's make sure that we get
   * rid of null names.
   */
  for (j = 0; j < 10; j++) {
    wnxt[j] = NULL;
    rnxt[j] = NULL;
  }
  for (thing = 0; thing < db_top; thing++) {
    if (Name(thing) == NULL) {
      if (Destroyed(thing))
	SET(Name(thing), "Garbage");
      else {
	do_log(LT_ERR, NOTHING, NOTHING, "Null name on object #%d",
	       thing);
	SET(Name(thing), "XXXX");
      }
    }
    if (!Destroyed(thing) &&
	(Flags(thing) & STARTUP) && !(Flags(thing) & HALT)) {
      s = atr_get_noparent(thing, "STARTUP");
      if (!s)
	continue;		/* just in case */
      r = safe_uncompress(s->value);
      parse_que(thing, r, thing);
      free((Malloc_t) r);
    }
  }
}

int
init_game(conf)
    const char *conf;
{
  FILE *f;
  int a;

  const char *infile, *outfile;
#ifdef USE_MAILER
  const char *mailfile;
#endif

  depth = 0;

  for (a = 0; a < 10; a++) {
    wenv[a] = NULL;
    renv[a][0] = '\0';
    wnxt[a] = NULL;
    rnxt[a] = NULL;
  }

  /* set MUSH start time */
  start_time = time((time_t *) 0);
  fprintf(stderr, "%s\n", VERSION);
  fprintf(stderr, "MUSH restarted, PID %d, at %s\n",
	  (int) getpid(), ctime(&start_time));

  /* initialize all the hash tables: flags, functions, and attributes. */
  init_flag_hashtab();
  init_func_hashtab();
  init_aname_hashtab();

  command_init_preconfig();
  config_file_startup(conf);
  command_init_postconfig();

#ifdef WIN32
  Win32MUSH_setup();		/* create index files, copy databases etc. */
#endif

  infile = restarting ? options.output_db : options.input_db;
  outfile = options.output_db;
#ifdef USE_MAILER
  mailfile = options.mail_db;
#endif

  /* read small text files into cache */
  fcache_init();

  f = db_open(infile);

  /* ok, read it in */
  fprintf(stderr, "ANALYZING: %s\n", infile);
  if (init_compress(f) < 0) {
    fprintf(stderr, "ERROR LOADING\n");
    return -1;
  }
  fprintf(stderr, "ANALYZING: %s (done)\n", infile);

  /* everything ok */
  db_close(f);

  f = db_open(infile);
  if (!f)
    return -1;

  /* ok, read it in */
  fprintf(stderr, "LOADING: %s\n", infile);
  if (db_read(f) < 0) {
    fprintf(stderr, "ERROR LOADING\n");
    return -1;
  }
  fprintf(stderr, "LOADING: %s (done)\n", infile);

  /* everything ok */
  db_close(f);

  /* complain about bad config options */
  if (!GoodObject(PLAYER_START) || (Typeof(PLAYER_START) != TYPE_ROOM))
    fprintf(stderr, "WARNING: Player_start (#%d) is NOT a room.\n",
	    PLAYER_START);
  if (DO_GLOBALS &&
      (!GoodObject(MASTER_ROOM) || (Typeof(MASTER_ROOM) != TYPE_ROOM)))
    fprintf(stderr, "WARNING: Master room (#%d) is NOT a room.\n",
	    MASTER_ROOM);

#ifdef USE_MAILER
  /* read mail database */
  f = db_open(mailfile);

  /* okay, read it in */
  if (f == NULL) {
    mail_init();
  } else {
    fprintf(stderr, "LOADING: %s\n", mailfile);
    load_mail(f);
    fprintf(stderr, "LOADING: %s (done)\n", mailfile);
    db_close(f);
  }

#endif				/* USE_MAILER */

#ifdef CHAT_SYSTEM
  init_chatdb();
  f = db_open(options.chatdb);
  if (f) {
    fprintf(stderr, "LOADING: %s\n", options.chatdb);
    if (load_chatdb(f)) {
      fprintf(stderr, "LOADING: %s (done)\n", options.chatdb);
      db_close(f);
    } else {
      fprintf(stderr, "ERROR LOADING %s\n", options.chatdb);
      db_close(f);
      return -1;
    }
  }
#endif

  /* now do access file stuff */
  read_access_file();

  /* now do the rpage stuff */
#ifdef ALLOW_RPAGE
  rpage_init();
#endif				/* ALLOW_RPAGE */

  /* initialize random number generator */
  srandom(getpid());

  /* set up dumper */
  strcpy(dumpfile, outfile);
  init_timer();
#ifndef WIN32
  signal(SIGCLD, (void *) reaper);
#endif

  /* Call Local Startup */
  local_startup();

  /* everything else ok. Restart all objects. */
  do_restart();
  return 0;
}

void
do_readcache(player)
    dbref player;
{
  if (!Wizard(player)) {
    notify(player, "Permission denied.");
    return;
  }
  fcache_load(player);
}

#ifdef CHAT_SYSTEM
int
parse_chat(player, command)
    dbref player;
    char *command;
{
  /* function hacks up something of the form "+<channel> <message>",
   * finding the two args, and passes it to do_chat
   */

  char *arg1;
  char *arg2;
  char tbuf1[MAX_COMMAND_LEN];
  char *s;

  strncpy(tbuf1, command, MAX_COMMAND_LEN - 1);		/* don't hack it up */
  tbuf1[MAX_COMMAND_LEN - 1] = '\0';
  s = tbuf1;

  arg1 = s;
  while (*s && !isspace(*s))
    s++;

  if (*s) {
    *s++ = '\0';
    while (*s && isspace(*s))
      s++;
  }
  arg2 = s;

  return do_chat_by_name(player, arg1, arg2);
}
#endif				/* CHAT_SYSTEM */

#define list_match(x)        list_check(x, player, '$', ':', cptr, 0)
#define cmd_match(x)         atr_comm_match(x, player, '$', ':', cptr, 0);

void
process_command(player, command, cause, from_port)
    dbref player;
    char *command;
    dbref cause;
    int from_port;		/* 1 if this is direct input from a port
				 * (i.e. typed directly by a player).
				 * attrib sets don't get parsed then.
				 */
{
  int a;
  char *p;			/* utility */

  char unp[BUFFER_LEN];		/* unparsed command */
  /* general form command arg0=arg1,arg2...arg10 */
  int gagged = 0;
  char temp[BUFFER_LEN];	/* utility */
  int i;			/* utility */
  char *cptr;

  depth = 0;
  if (!command) {
    do_log(LT_ERR, NOTHING, NOTHING, "ERROR: No command!!!");
    return;
  }
  /* robustify player */
  if (!GoodObject(player)) {
    do_log(LT_ERR, NOTHING, NOTHING, "process_command bad player #%d",
	   player);
    return;
  }
  gagged = IS(db[player].owner, TYPE_PLAYER, PLAYER_GAGGED);
  /* Access the player */
  Access(player);

  /* Destroyed objects shouldn't execute commands */
  if (Destroyed(player)) {
    /* No message - nobody to tell, and it's too easy to do to log. */
    return;
  }
  /* Halted objects can't execute commands */
  if ((Typeof(player) != TYPE_PLAYER) && (Flags(player) & HALT)) {
    notify(Owner(player),
	   tprintf("Attempt to execute command by halted object #%d",
		   player));
    return;
  }
  /* Players and things should not have invalid locations. This check
   * must be done _after_ the destroyed-object check.
   */
  if ((!GoodObject(Location(player)) ||
       (Destroyed(Location(player)))) &&
      Mobile(player)) {
    notify(Owner(player),
	   tprintf("Invalid location on command execution: %s(#%d)",
		   Name(player), player));
    do_log(LT_ERR, NOTHING, NOTHING,
	   "Command attempted by %s(#%d) in invalid location #%d.",
	   Name(player), player, Location(player));
    moveto(player, PLAYER_START);	/* move it someplace valid */
  }
  /* The following check is removed due to a security hole it causes!
   * 'If player is an exit or room execute command as owner'
   */
  /* if ((Typeof(player) == TYPE_ROOM) || (Typeof(player) == TYPE_EXIT))
   * player = db[player].owner;  */
  orator = player;

  if (options.log_commands || Suspect(player))
    do_log(LT_CMD, player, 0, "%s", command);

  if (Flags(player) & VERBOSE)
    raw_notify(Owner(player), tprintf("#%d] %s", player, command));

  /* eat leading whitespace */
  while (*command && isspace(*command))
    command++;

  /* eat trailing whitespace */
  p = command + strlen(command) - 1;
  while (isspace(*p) && (p >= command))
    p--;
  *++p = '\0';

  /* ignore null commands that aren't from players */
  if ((!command || !*command) && !from_port)
    return;

  /* important home checking comes first! */
  if (strcmp(command, "home") == 0) {
    if (Typeof(player) == TYPE_EXIT || Typeof(player) == TYPE_ROOM)
      return;
#ifdef FIXED_FLAG
    if (Fixed(Owner(player)))
      notify(player, "You can't do that IC!");
    else
#endif
      do_move(player, command, 0);
    return;
  }
  strcpy(unp, command);

  cptr = command_parse(player, cause, command, from_port);
  if (cptr) {
    a = 0;
    if (!gagged && Mobile(player)) {

      /* if the "player" is an exit or room, no need to do these checks */

      /* try matching enter aliases */
      if (Location(player) != NOTHING &&
	  (i = alias_list_check(db[Location(player)].contents,
				cptr, "EALIAS")) != -1) {

	sprintf(temp, "#%d", i);
	do_enter(player, temp, 1);
	goto done;
      }
      /* if that didn't work, try matching leave aliases */
      if ((Typeof(Location(player)) != TYPE_ROOM) &&
	  (loc_alias_check(Location(player), cptr, "LALIAS"))) {
	do_leave(player);
	goto done;
      }
      /* try matching user defined functions before chopping */

      /* try objects in the player's location, the location itself,
       * and objects in the player's inventory.
       */
      if (Location(player) != NOTHING) {
	a += list_match(Contents(Location(player)));
	if (Location(player) != player)
	  a += cmd_match(Location(player));
      }
      if (Location(player) != player)
	a += list_match(Contents(player));

      /* now do check on zones */
      if ((!a) && (Zone(Location(player)) != NOTHING)) {
	if (DO_GLOBALS && (Typeof(Zone(Location(player))) == TYPE_ROOM)) {

	  /* zone of player's location is a zone master room */

	  if (Location(player) != Zone(player)) {

	    /* check zone master room exits */
	    if (remote_exit(player, cptr)) {
	      if (!Mobile(player))
		goto done;
	      else {
		do_move(player, cptr, 2);
		goto done;
	      }
	    } else
	      /* check commands in the zone master room if no exits
	       * can match more than one $command in zone master room
	       */
	      a += list_match(Contents(Zone(Location(player))));
	  }			/* end of zone master room check */
	} else
	  /* try matching commands on area zone object if GLOBALS
	   * aren't in use or zone object isn't a room
	   */
	if ((!a) && (Zone(Location(player)) != NOTHING))
	  a += cmd_match(Zone(Location(player)));
      }				/* end of matching on zone of player's location */
      /* if nothing matched with zone master room/zone object, try
         * matching zone commands on the player's personal zone
       */
      if ((!a) && (Zone(player) != NOTHING) &&
	  (Zone(Location(player)) != Zone(player))) {
	a += cmd_match(Zone(player));
      }
      /* end of zone stuff */
      if (DO_GLOBALS) {
	/* check global exits only if no other commands are matched */
	if ((!a) && (Location(player) != MASTER_ROOM)) {
	  if (global_exit(player, cptr)) {
	    if (!Mobile(player))
	      goto done;
	    else {
	      do_move(player, cptr, 1);
	      goto done;
	    }
	  } else
	    /* global user-defined commands checked if all else fails.
	     * May match more than one command in the master room.
	     */
	    a += list_match(Contents(MASTER_ROOM));
	}
	/* end of master room check */
      }
    }				/* end of special checks */
    if (!a) {
      notify(player, "Huh?  (Type \"help\" for help.)");
      if (options.log_huhs)
	do_log(LT_HUH, player, 0, "%s", unp);
    }
  }
  /* command has been executed. Free up memory. */

done:
  ;
}


/* now undef everything that needs to be */
#undef list_match
#undef cmd_match

int
check_alias(command, list)
    const char *command;
    const char *list;
{
  /* check if a string matches part of a semi-colon separated list */
  const char *p;
  while (*list) {
    for (p = command; (*p && DOWNCASE(*p) == DOWNCASE(*list)
		       && *list != EXIT_DELIMITER);
	 p++, list++) ;
    if (*p == '\0') {
      while (isspace(*list))
	list++;
      if (*list == '\0' || *list == EXIT_DELIMITER)
	return 1;		/* word matched */
    }
    /* didn't match. check next word in list */
    while (*list && *list++ != EXIT_DELIMITER) ;
    while (isspace(*list))
      list++;
  }
  /* reached the end of the list without matching anything */
  return 0;
}

/* match a list of things */
#ifdef CAN_NEWSTYLE
int
list_check(dbref thing, dbref player, char type, char end, char *str,
	   int just_match)
#else
int
list_check(thing, player, type, end, str, just_match)
    dbref thing, player;
    char type, end;
    char *str;
    int just_match;
#endif
{
  int match = 0;

  while (thing != NOTHING) {
    if (atr_comm_match(thing, player, type, end, str, just_match))
      match = 1;
    thing = db[thing].next;
  }
  return (match);
}

int
alias_list_check(thing, command, type)
    dbref thing;
    const char *command;
    const char *type;
{
  ATTR *a;
  char alias[BUFFER_LEN];

  while (thing != NOTHING) {
    a = atr_get_noparent(thing, type);
    if (a) {
      strcpy(alias, uncompress(a->value));
      if (check_alias(command, alias) != 0)
	return thing;		/* matched an alias */
    }
    thing = db[thing].next;
  }
  return -1;
}

int
loc_alias_check(loc, command, type)
    dbref loc;
    const char *command;
    const char *type;
{
  ATTR *a;
  char alias[BUFFER_LEN];
  a = atr_get_noparent(loc, type);
  if (a) {
    strcpy(alias, uncompress(a->value));
    return (check_alias(command, alias));
  } else
    return 0;
}

int
Hearer(thing)
    dbref thing;
{
  ALIST *ptr;

  if (IS(thing, TYPE_PLAYER, PLAYER_CONNECT) || Puppet(thing))
    return (1);
  for (ptr = db[thing].list; ptr; ptr = AL_NEXT(ptr)) {
#ifndef VISIBLE_EMPTY_ATTRS
    if (!*AL_STR(ptr))
      continue;
#endif
    if (!strcmp(AL_NAME(ptr), "LISTEN"))
      return 1;
  }
  return (0);
}

int
Commer(thing)
    dbref thing;
{
  ALIST *ptr;

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT(ptr)) {
#ifndef VISIBLE_EMPTY_ATTRS
    if (!*AL_STR(ptr))
      continue;
#endif
    if (AL_FLAGS(ptr) & AF_COMMAND && !(AL_FLAGS(ptr) & AF_NOPROG))
      return (1);
  }
  return (0);
}

int
Listener(thing)
    dbref thing;
{
  ALIST *ptr;
  if (IS(thing, TYPE_THING, THING_LISTEN) || IS(thing, TYPE_ROOM, ROOM_LISTEN))
    return (1);
  for (ptr = db[thing].list; ptr; ptr = AL_NEXT(ptr)) {
#ifndef VISIBLE_EMPTY_ATTRS
    if (!*AL_STR(ptr))
      continue;
#endif
    if (AL_FLAGS(ptr) & AF_LISTEN && !(AL_FLAGS(ptr) & AF_NOPROG))
      return (1);
  }
  return (0);
}

void
do_poor(player, arg1)
    dbref player;
    char *arg1;
{
  int amt = atoi(arg1);
  dbref a;
  if (!God(player)) {
    notify(player, "Only God can cause financial ruin.");
    return;
  }
  for (a = 0; a < db_top; a++)
    if (Typeof(a) == TYPE_PLAYER)
      s_Pennies(a, amt);
  notify(player,
      tprintf("The money supply of all players has been reset to %d %s.",
	      amt, MONIES));
  do_log(LT_WIZ, player, NOTHING,
	 "** POOR done ** Money supply reset to %d %s.",
	 amt, MONIES);
  fflush(wizlog_fp);
}


void
do_writelog(player, str, ltype)
    dbref player;
    char *str;
    int ltype;
{
  if (!Wizard(player)) {
    notify(player, "Permission denied.");
    return;
  }
  do_rawlog(ltype, "LOG: %s(#%d%s): %s", Name(player), player,
	    unparse_flags(player, GOD), str);

  notify(player, "Logged.");
}

/* Bind occurences of '##' in "action" to "arg", then run "action" */

void
bind_and_queue(player, cause, action, arg)
    dbref player;
    dbref cause;
    char *action;
    char *arg;
{
  char *repl, *command;

  repl = replace_string("##", arg, action);
  command = strip_braces(repl);

  if (repl)
    free(repl);
#ifdef MEM_CHECK
  del_check("replace_string.buff");
#endif

  parse_que(player, command, cause);

  if (command)
    free(command);
#ifdef MEM_CHECK
  del_check("strip_braces.buff");
#endif
}

void
do_scan(player, command, flag)
    dbref player;
    char *command;
    int flag;
{
  /* scan for possible matches of user-def'ed commands */

#define ScanFind(p,x)  \
  (Can_Examine(p,x) && \
      ((num = atr_comm_match(x, p, '$', ':', command, 1)) != 0))


  dbref thing;
  int num;

  if (!GoodObject(Location(player))) {
    notify(player, "Sorry, you are in an invalid location.");
    return;
  }
  if (!command || !*command) {
    notify(player, "What command do you want to scan for?");
    return;
  }
  if (flag & CHECK_NEIGHBORS) {
    notify(player, "Matches on contents of this room:");
    DOLIST(thing, db[Location(player)].contents) {
      if (ScanFind(player, thing))
	notify(player,
	       tprintf("%s  [%d]", unparse_object(player, thing), num));
    }
  }
  if (flag & CHECK_HERE) {
    if (ScanFind(player, Location(player)))
      notify(player, tprintf("Matched here: %s  [%d]",
			 unparse_object(player, Location(player)), num));
  }
  if (flag & CHECK_INVENTORY) {
    notify(player, "Matches on carried objects:");
    DOLIST(thing, db[player].contents) {
      if (ScanFind(player, thing))
	notify(player, tprintf("%s  [%d]",
			       unparse_object(player, thing), num));
    }
  }
  if (flag & CHECK_SELF) {
    if (ScanFind(player, player))
      notify(player, tprintf("Matched self: %s  [%d]",
			     unparse_object(player, player), num));
  }
  if (flag & CHECK_ZONE) {
    /* zone checks */
    if (Zone(Location(player)) != NOTHING) {
      if (Typeof(Zone(Location(player))) == TYPE_ROOM) {
	/* zone of player's location is a zone master room */
	if (Location(player) != Zone(player)) {
	  notify(player, "Matches on zone master room of location:");
	  DOLIST(thing, db[Zone(Location(player))].contents) {
	    if (ScanFind(player, thing))
	      notify(player, tprintf("%s  [%d]",
				     unparse_object(player, thing), num));
	  }
	}
      } else {
	/* regular zone object */
	if (ScanFind(player, Zone(Location(player))))
	  notify(player,
		 tprintf("Matched zone of location: %s  [%d]",
		   unparse_object(player, Zone(Location(player))), num));
      }
    }
    if ((Zone(player) != NOTHING) &&
	(Zone(player) != Zone(Location(player)))) {
      /* check the player's personal zone */
      if (ScanFind(player, Zone(player)))
	notify(player, tprintf("Matched personal zone: %s  [%d]",
			     unparse_object(player, Zone(player)), num));
    }
  }
  if (DO_GLOBALS && (flag & CHECK_GLOBAL) &&
      (Location(player) != MASTER_ROOM) &&
      (Zone(Location(player)) != MASTER_ROOM) &&
      (Zone(player) != MASTER_ROOM)) {
    /* try Master Room stuff */
    notify(player, "Matches on objects in the Master Room:");
    DOLIST(thing, db[MASTER_ROOM].contents) {
      if (ScanFind(player, thing))
	notify(player, tprintf("%s  [%d]",
			       unparse_object(player, thing), num));
    }
  }
}

void
do_dolist(player, list, command, cause, flag, notify_flag)
    dbref player;
    char *list, *command;
    dbref cause;
    int flag;			/* 0 for @dolist, 1 for @map */
    int notify_flag;		/* execute '@notify me' at end? */
{
  char *curr, *objstring;
  char outbuf[BUFFER_LEN];
  char *ebuf1, *ebuf2, *bp;
  int place;
  char placestr[10];
  int j;

  if (!command || !*command) {
    notify(player, "What do you want to do with the list?");
    if (notify_flag)
      parse_que(player, "@notify me", cause);
    return;
  }
  /* set up environment for any spawned commands */
  for (j = 0; j < 10; j++) {
    wnxt[j] = wenv[j];
    rnxt[j] = renv[j];
  }

  bp = outbuf;
  curr = list;
  place = 0;
  while (curr && *curr) {
    while (*curr == ' ')
      curr++;
    if (*curr) {
      place++;
      sprintf(placestr, "%d", place);
      objstring = ebuf1 = curr;
      process_expression(objstring, &ebuf1, (char const **) &curr,
			 player, cause, cause,
			 PE_NOTHING, PT_SPACE, NULL);
      if (*curr == ' ')
	*curr++ = '\0';
      if (!flag) {
	/* @dolist, queue command */
	ebuf1 = replace_string("#@", placestr, command);
	bind_and_queue(player, cause, ebuf1, objstring);
#ifdef MEM_CHECK
	del_check("replace_string.buff");
#endif
	free(ebuf1);
      } else {
	/* it's @map, add to the output list */
	if (bp != outbuf)
	  safe_chr(' ', outbuf, &bp);
	ebuf1 = replace_string("##", objstring, command);
	ebuf2 = replace_string("#@", placestr, ebuf1);
	free(ebuf1);
#ifdef MEM_CHECK
	del_check("replace_string.buff");
#endif
	ebuf1 = ebuf2;
	process_expression(outbuf, &bp, (char const **) &ebuf1,
			   player, cause, cause,
			   PE_DEFAULT, PT_DEFAULT, NULL);
	free(ebuf2);
#ifdef MEM_CHECK
	del_check("replace_string.buff");
#endif
      }
    }
  }

  *bp = '\0';

  if (flag) {
    /* if we're doing a @map, copy the list to an attribute */
    atr_add(player, "MAPLIST", outbuf, GOD, NOTHING);
    notify(player, "Function mapped onto list.");
  }
  if (notify_flag) {
    /*  Execute a '@notify me' so the object knows we're done
     *  with the list execution. We don't execute nfy_que()
     *  directly, since we want the command to be queued
     *  _after_ the list has executed.
     */
    parse_que(player, "@notify me", cause);
  }
}

void
do_uptime(player)
    dbref player;
{
  char tbuf1[BUFFER_LEN];
  char *p;

#ifdef HAS_UPTIME
  FILE *fp;
  char c;
  int i;
#endif
  int pid;
#ifndef hpux
  int psize;
#endif
  int sec, min;
#ifdef HAS_GETRUSAGE
  struct rusage usage;
#endif				/* HAS_GETRUSAGE */

  /* calculate time until next dump */
  min = (options.dump_counter - mudtime) / 60;
  sec = (options.dump_counter - mudtime) % 60;

  sprintf(tbuf1, "Up since: %s", ctime(&start_time));
  if ((p = strchr(tbuf1, '\n')))
    *p = '\0';
  notify(player, tbuf1);

  sprintf(tbuf1, "Time now: %s", ctime(&mudtime));
  if ((p = strchr(tbuf1, '\n')))
    *p = '\0';
  notify(player, tbuf1);

  if (!Wizard(player)) {
    notify(player,
	 tprintf("Time until next database save: %d minutes %d seconds.",
		 min, sec));
    return;
  }
#ifdef HAS_UPTIME
  fp = popen(UPTIME_PATH, "r");

  /* just in case the system is screwy */
  if (fp == NULL) {
    notify(player, "Error -- cannot execute uptime.");
    fprintf(stderr, "** ERROR ** popen for @uptime returned NULL.");
    return;
  }
  /* print system uptime */
  for (i = 0; (c = getc(fp)) != '\n'; i++)
    tbuf1[i] = c;
  tbuf1[i] = '\0';
  pclose(fp);

  notify(player, tbuf1);
#endif

  /* do process stats */

  pid = getpid();
#ifndef hpux
#ifdef WIN32
  psize = 1024;			/* NJG: a guess! */
#else
  psize = getpagesize();
#endif
  notify(player, tprintf("\nProcess ID:  %10d        %10d bytes per page",
			 pid, psize));
#else
  notify(player, tprintf("\nProcess ID:  %10d", pid));
#endif				/* hpux  */

#ifdef HAS_GETRUSAGE
  getrusage(RUSAGE_SELF, &usage);
  notify(player, tprintf("Time used:   %10d user   %10d sys",
			 usage.ru_utime.tv_sec, usage.ru_stime.tv_sec));
#ifndef hpux
  notify(player, tprintf("Max res mem: %10d pages  %10d bytes",
			 usage.ru_maxrss, (usage.ru_maxrss * psize)));
#else
  notify(player, tprintf("Max res mem: %10 dpages", usage.ru_maxrss));
#endif				/* hpux */
  notify(player, tprintf("Integral mem:%10d shared %10d private %10d stack",
			 usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss));
  notify(player, tprintf("Page faults: %10d hard   %10d soft    %10d swapouts",
		      usage.ru_majflt, usage.ru_minflt, usage.ru_nswap));
  notify(player, tprintf("Disk I/O:    %10d reads  %10d writes",
			 usage.ru_inblock, usage.ru_oublock));
  notify(player, tprintf("Network I/O: %10d in     %10d out",
			 usage.ru_msgrcv, usage.ru_msgsnd));
  notify(player, tprintf("Context swi: %10d vol    %10d forced",
			 usage.ru_nvcsw, usage.ru_nivcsw));
  notify(player, tprintf("Signals:     %10d", usage.ru_nsignals));
#endif				/* HAS_GETRUSAGE */

  notify(player, tprintf("The head of the object free list is #%d.",
			 first_free));
  notify(player,
	 tprintf("Time until next database save: %d minutes %d seconds.",
		 min, sec));
}


/* Open a db file, which may be compressed, and return a file pointer */
static FILE *
db_open(filename)
    const char *filename;
{
  FILE *f;
#ifndef WIN32
  if (options.uncompressprog && *options.uncompressprog) {
    /* We do this because on some machines (SGI Irix, for example),
     * the popen will not return NULL if the mailfile isn't there.
     */
    f = fopen(tprintf("%s%s", filename, options.compresssuff), "r");
    if (f) {
      fclose(f);
      f = popen(tprintf("%s < %s%s", options.uncompressprog, filename, options.compresssuff), "r");
    }
  } else
#endif
  {
    f = fopen(filename, "r");
  }
  return f;
}

/* Open a file or pipe (if compressing) for writing */
static FILE *
db_open_write(filename)
    const char *filename;
{
  FILE *f;
#ifndef WIN32
  if (options.compressprog && *options.compressprog) {
    f = popen(tprintf("%s >%s%s", options.compressprog, filename, options.compresssuff), "w");
  } else
#endif
  {
    f = fopen(filename, "w");
  }
  return f;
}


/* Close a db file, which may really be a pipe */
static void
db_close(f)
    FILE *f;
{
#ifndef WIN32
  if (options.compressprog && *options.compressprog) {
    pclose(f);
  } else
#endif
  {
    fclose(f);
  }
}

void
do_list(player, arg)
    dbref player;
    char *arg;
{
  if (!arg || !*arg)
    notify(player, "I don't understand what you want to @list.");
  else if (string_prefix("commands", arg))
    do_list_commands(player);
  else if (string_prefix("functions", arg))
    do_list_functions(player);
  else if (string_prefix("motd", arg))
    do_motd(player, 3, "");
  else if (string_prefix("attribs", arg))
    do_list_attribs(player);
  else
    notify(player, "I don't understand what you want to @list.");
}

char *
make_new_epoch_file(basename, the_epoch)
    const char *basename;
    int the_epoch;
{
  static char result[BUFFER_LEN];	/* STATIC! */
  /* Unlink the last the_epoch and create a new one */

  sprintf(result, "%s.#%d#", basename, the_epoch - 1);
  unlink(result);
  sprintf(result, "%s.#%d#", basename, the_epoch);
  return result;
}