/* walkdb.c */
#include "copyright.h"

/* Support for commands which trudge up 'n down the database like
   @find, @fnd and @stat. */

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stddef.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */

#include <ctype.h>

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "command.h"
#include "flags.h"
#include "misc.h"

/* Bind occurances of the universal var in ACTION to ARG, then run ACTION.
   Cmds run in low-prio Q after a 1 sec delay for the first one. */

static void bind_and_queue (dbref player, dbref cause, char *action,
	char *argstr, char *cargs[], int ncargs)
{
char	*command;		/* allocated by replace_string  */

	command = replace_string (BOUND_VAR, argstr, action),
	wait_que (player, cause, RU_ARG1_TAKE, 0, NOTHING, command,
		cargs, ncargs);
}

#ifdef notdef

/*************************************************************************/
/*                           Option-ish Schtuff                          */
/*************************************************************************/

#define FND_HELPFILE	"@fnd_helpfile"

/* These two statements define the operator tokens. They MUST be sorted
   longest first, so the longer ops will still match if they contain a
   shorter op in their beginning. */

typedef enum
{
  WILDNEQ, WILDEQ, STRNEQ, STREQ, ERR
} op_t;

static const char *Ops[] =
{
  "!==", "==", "!=", "=", NULL
};

/* union of all op chars, above */

#define OP_CHARS "=!"

/*************************************************************************/
/*                       End of Option-ish Schtuff                       */
/*************************************************************************/

/* struct used to remember each word in query after parsing */

typedef struct
{
  char *left, *right;
  op_t op;
} findit;

extern void abort ();
extern int atoi ();
extern dbref match_thing (dbref player, const char *name);
extern char **string2list (char *str, const char sep);

static void fnd_parse_word (char *word, findit * out)
{
  int where, len, wherelen, i;

  if (!word || *word == '\0' || ((len = strlen (word))
				 == (where = strcspn (word, OP_CHARS))))
    out->op = ERR;
  else
    {
      wherelen = strspn (word + where, OP_CHARS);
      for (i = 0; Ops[i]; i++)
	if (!strncmp (Ops[i], word + where, strlen (Ops[i])))
	  break;
      out->op = i;
      if (i != ERR)
	{
	  out->left = word;
	  *(word + where) = '\0';
	  out->right = word + where + wherelen;
	}
    }
}

/*
 * Returns number of non-null ptrs in array thereof
 */
static int count_ptrs (const void **p)
{
  int out = 0;

  if (p)
    while (*p++)
      out++;
  return out;
}

#define FNDUSAGE(x) 	if(1) {     notify(player,x);\
				    spit_file(player, FND_HELPFILE);\
				    goto byebye; }
#define MALLOCTST(x)	if(!x) {    perror("Bad malloc in do_fnd. No mem?");\
					return; }

#define FNDMALLOC(x,y,z) if(!((x) = XMALLOC (y, z)))\
                        { perror("Bad malloc in do_fnd. No mem?");\
		          return; }

#define HELP		" "
#define ATTRIB 		"Bad @fnd attribute/value syntax."
#define OPTION 		"Bad @fnd `-' option syntax."
#define CONFUSED 	"I don't think this does what you think it does."

/*#define FND_DEBUG*/

/* Command: @fnd - More like the unix find.
 * Created: Tue Dec 18 20:33:20 1990 mitch@alpha.ces.cwru.edu
 * Usage:   @fnd
 */

/**** Trial new version (old one below) ****/
void do_fnd (dbref player, const char *in_cmd)
{
  extern char *optarg;
  extern int optind, opterr, getopt (int, char **, const char *);

  int c, argc, nquery;		/* argc:how many words */
  char *command;		/* my copy of in_cmd */
  char **argv;			/* pntrs to words in cmd */
  char *buff;			/* temporary buffer */
  dbref i;			/* for walking dbase */
  findit *queries = NULL;	/* array of attribute/operation structs */

  /* Option vars and their defaults:: Set according to user input. */
  int debug = 0;		/* no debug mode by default */
  int quiet = 0;		/* print output by default */
  char *execstr = NULL;		/* execute this string on each hit */

  opterr = 0;			/* tell getopt: no error message */
  optind = 1;			/* tell getopt: start on second arg */

  if (!in_cmd || *in_cmd == '\0')
    {
      notify (player, "Sorry, internal error.");
      return;
    }

  if (!payfor (player, mudconf.searchcost))
    {
      notify (player, tprintf("You don't have enough %s.", mudconf.many_coins));
      return;
    }

  /*
   * Copy because s2l will modify it. This may not really be needed.
   */
  FNDMALLOC (command, sizeof (char *) * strlen (in_cmd) +1, "do_fnd");

  argv = string2list (strcpy (command, in_cmd), ' ');
  argc = count_ptrs ((const void **) argv);
  if (argc <= 1)
    FNDUSAGE (CONFUSED);

#ifdef FND_DEBUG
  for (i = 0; i < argc; i++)
    {
      buff=alloc_lbuf("do_fnd.debug.1");
      sprintf (buff, "FND-DEBUG:arg %d=`%s'", i, argv[i]);
      fputs (buff, stderr);
      notify (player, buff);
      free_lbuf(buff);
    }
#endif

  while ((c = getopt (argc, argv, "qhdx:")) != EOF)
    switch (c)
      {
      case 'x':
	execstr = optarg;
	break;
      case 'h':		/* help */
	FNDUSAGE (HELP);
      case 'q':		/* quiet */
	quiet = 1;
	break;
      case 'd':		/* debug */
	debug = 1;
	break;
      case '?':		/* huh? */
	FNDUSAGE (OPTION);
      }

  /* Check the non-option parameters. If any have no '=' in them,
     complain. Otherwise, treat as an ATTR=VALUE pair to look for */

  FNDMALLOC (buff, sizeof (findit) * (argc - optind + 1), "do_fnd.2");
  queries = (findit *)buff;
  for (nquery = 0; optind < argc; optind++, nquery++)
    {
      fnd_parse_word (argv[optind], &queries[nquery]);
      if (queries[nquery].op == ERR)
	FNDUSAGE (ATTRIB);
    }
  if (debug)
    {
      buff=alloc_lbuf("do_fnd.debug.2");
      sprintf (buff,
       "fnd debug:quiet=%d execstr=`%s' nquery=%d", quiet, execstr, nquery);
      notify (player, buff);
      for (i = 0; i < nquery; i++)
	{
	  sprintf (buff, "op=%d left=`%s' right=`%s'", queries[i].op,
		   queries[i].left, queries[i].right);
	  notify (player, buff);
	}
      free_lbuf(buff);
    }
  else
    {
#if 0
      /* We know the options, let's do the db walk */
      DO_WHOLE_DB(i)
	{
	  if (!controls (player, i))
	    continue;
	  if (!quiet) {
	    buff = unparse_object (player, i);
	    notify (player, buff);
	    free_lbuf(buff);
	  }
	  if (execstr && !(Flags (i) & GOING))
/* 	    bind_and_queue (player, player, execstr, i, NULL, 0); */
	  /*2nd player==cause */
	}
#endif
      if (!quiet)
	notify (player, "*** @fnd: End of List ***");
    }
byebye:
  XFREE (command, "do_fnd");
  XFREE (queries, "do_fnd.2");	/* TO DO: walk this, to free each one. */
}				/* end of do_fnd */

#endif	/* notdef */

/* New @dolist.  i.e.:
 * @dolist #12 #34 #45 #123 #34644=@emit [name(##)]
 */
void do_dolist (dbref player, dbref cause, int key, char *list, char *command,
	char *cargs[], int ncargs)
{
char	*curr, *objstring;

	if (!list || *list == '\0') {
		notify (player, "That's terrific, but what should I do with the list?");
		return;
	}
	curr = list;
	while (curr && *curr) {
		while (*curr==' ') curr++;
		if (*curr) {
			objstring = parse_to(&curr, ' ', EV_STRIP);
			bind_and_queue (player, cause, command, objstring,
				cargs, ncargs);
		}
	}
}

/* Regular @find command */

void do_find (dbref player, dbref cause, int key, char *name)
{
  dbref i;
  char *buff;

  if (!payfor (player, mudconf.searchcost))
    {
      notify (player, tprintf("You don't have enough %s.",mudconf.many_coins));
    }
  else
    {
      DO_WHOLE_DB(i)
	{
	  if ((i % 100) == 0)
	    {
	      tmp_sync();
	      cache_reset();
	    }
	  if (Typeof (i) != TYPE_EXIT
	      && controls (player, i)
	      && (!*name || string_match (Name (i), name)))
	    {
	      buff = unparse_object (player, i);
	      notify (player, buff);
	      free_lbuf(buff);
	    }
	}
      notify (player, "***End of List***");
    }
}

/* ---------------------------------------------------------------------------
 * get_stats, do_stats: Get counts of items in the db.
 */

int get_stats (dbref player, dbref who, STATS *info)
{
dbref	i;

	info->s_total = 0;
	info->s_rooms = 0;
	info->s_exits = 0;
	info->s_things = 0;
	info->s_players = 0;
	info->s_garbage = 0;

	/* Do we have permission? */

	if (Good_obj(who) && !Controls(player, who)) {
		notify(player, "Permission denied.");
		return 0;
	}

	/* Can we afford it? */

	if (!payfor (player, mudconf.searchcost)) {
		notify (player, tprintf("You don't have enough %s.",
			mudconf.many_coins));
		return 0;
	}

	DO_WHOLE_DB(i) {
		if ((who == NOTHING) || (who == Owner (i))) {
			info->s_total++;
			if ((Flags (i) & GOING) && (Typeof(i) != TYPE_ROOM)) {
				info->s_garbage++;
				continue;
			}
			switch (Typeof (i)) {
			case TYPE_ROOM:		info->s_rooms++;	break;
			case TYPE_EXIT:		info->s_exits++;	break;
			case TYPE_THING:	info->s_things++;	break;
			case TYPE_PLAYER:	info->s_players++;	break;
			default:		info->s_garbage++;
			}
		}
	}
	return 1;
}

void do_stats (dbref player, dbref cause, int key, char *name)
/* reworked by R'nice */

{
dbref	owner;
STATS	statinfo;

	switch (key) {
	case STAT_ALL:
		owner = NOTHING;
		break;
	case STAT_ME:
		owner = Owner(player);
		break;
	case STAT_PLAYER:
		if (!(name && *name)) {
			notify(player,
				tprintf("The universe contains %d objects.",
					mudstate.db_top));
			return;
		}
		owner = lookup_player (player, name, 1);
		if (owner == NOTHING) {
			notify(player, "Not found.");
			return;
		}
		break;
	default:
		notify(player, "Illegal combination of switches.");
		return;
	}

	if (!get_stats(player, owner, &statinfo))
		return;
	notify (player,
		tprintf ("%d objects = %d rooms, %d exits, %d things, %d players. (%d garbage)",
			statinfo.s_total, statinfo.s_rooms, statinfo.s_exits,
			statinfo.s_things, statinfo.s_players,
			statinfo.s_garbage));

#ifdef TEST_MALLOC
  if (Wizard (player))
    notify (player, tprintf ("Malloc count = %d.", malloc_count));
#endif /* TEST_MALLOC */
#ifdef MSTATS
  if(Wizard(player)) {
    struct mstats_value mval;
    int i;
    extern unsigned int malloc_sbrk_used, malloc_sbrk_unused;
    extern int nmal, nfre;
    extern struct mstats_value malloc_stats();
    extern int malloc_mem_used(), malloc_mem_free();

    notify(player, tprintf("Sbrk Unused: %d -- Sbrk Used: %d",
			   malloc_sbrk_unused, malloc_sbrk_used));
    notify(player, tprintf("Raw malloc cnt: %d -- Raw free cnt: %d",
			   nmal, nfre));
    for(i = 0; i < 15; i++) {
      mval = malloc_stats(i);
      notify(player, tprintf("Blocksz: %d -- Free: %d -- Used: %d",
		 	     mval.blocksize, mval.nfree, mval.nused));
    }
    notify(player, tprintf("Mem used: %d -- Mem free: %d",
			   malloc_mem_used(), malloc_mem_free()));
  }
#endif	/* MSTATS */
}

int chown_all (dbref from_player, dbref to_player)
{
int	i, count;

	if (Typeof(from_player) != TYPE_PLAYER)
		from_player = Owner(from_player);
	if (Typeof(to_player) != TYPE_PLAYER)
		to_player = Owner(to_player);
	count = 0;
	DO_WHOLE_DB(i) {
		if ((Owner(i) == from_player) && (Owner(i) != i)) {
			if (Typeof(i) == TYPE_PLAYER)
				s_Owner(i, i);
			else
				s_Owner (i, to_player);
			s_Flags(i, (Flags(i) & ~(CHOWN_OK|INHERIT)) | HALT);
			count++;
		}
	}
	return count;
}

void do_chownall (dbref player, dbref cause, int key, char *from, char *to)
{
int	count;
dbref	victim, recipient;

	init_match (player, from, TYPE_PLAYER);
	match_neighbor ();
	match_absolute ();
	match_player ();
	if ((victim = noisy_match_result ()) == NOTHING)
		return;

	if ((to != NULL) && *to) {
		init_match (player, to, TYPE_PLAYER);
		match_neighbor ();
		match_absolute ();
		match_player ();
		if ((recipient = noisy_match_result ()) == NOTHING)
			return;
	} else {
		recipient = player;
	}

	count = chown_all(victim, recipient);
	if (!Quiet(player)) {
		notify(player, tprintf("%d objects @chowned.", count));
	}
}

#define ANY_OWNER -2

/* ---------------------------------------------------------------------------
 * convert_flags: convert a list of flag letters into its bit pattern.
 * Also set the type qualifier if specified and not already set.
 */

FLAG convert_flags (dbref player, char *flaglist, FLAGSEL *selection,
	FLAG *p_type)
{
int	i, handled, handled_any, handled_real_flag;
char	*s;
FLAG	masks[8], type;
FLAGENT	*fp;

	for(i=0; i<8; i++)
		masks[i] = 0;
	handled_real_flag = 0;
	type = *p_type;
	for (s=flaglist; *s; s++) {
		handled = 0;

		/* Check for object type */

		for (i=0; (i<=7) && !handled; i++) {
			if ((object_types[i].lett == *s) &&
			    !(((object_types[i].perm & CA_WIZARD) &&
			       !Wizard(player)) ||
			      ((object_types[i].perm & CA_GOD) &&
			       !God(player)))) {
				if ((type != NOTYPE) && (type != i)) {
					notify(player,
						tprintf("%c: Conflicting type specifications.",
							*s));
					return 0;
				}
				type = i;
				handled = 1;
			}
		}

		/* Check generic flags */

		if (handled)
			continue;
		for (fp=gen_flags; (fp->flagname) && !handled; fp++) {
			if ((fp->flaglett == *s) &&
			    !(((fp->listperm & CA_WIZARD) &&
			       !Wizard(player)) ||
			      ((fp->listperm & CA_GOD) &&
			       !God(player)))) {
					for (i=0; i<8; i++)
						masks[i] |= fp->flagvalue;
				handled = 1;
				handled_real_flag = 1;
			}
		}

		/* Check type-specific flags */

		if (handled)
			continue;

		handled_any = 0;
		for (i=0; i<=7; i++) {
			handled = 0;
			if ((type != NOTYPE) && (type != i))
				continue;
			if (!(fp = object_types[i].flaglist))
				continue;
			for (; (fp->flagname) && !handled; fp++) {
				if ((fp->flaglett == *s) &&
				    !(((fp->listperm & CA_WIZARD) &&
				       !Wizard(player)) ||
				      ((fp->listperm & CA_GOD) &&
				       !God(player)))) {
					masks[i] |= fp->flagvalue;
					handled = 1;
					handled_any = 1;
					handled_real_flag = 1;
				}
			}
			if (!handled)
				masks[i] = -1;
		}
		if (handled_any)
			handled = 1;

		if (!handled) {
			notify(player,
				tprintf("%c: Flag unknown or not valid for specified object type",
					*s));
			return 0;
		}
	}

	/* Exclude classes that match nothing by setting their mask to -1 */

	if (handled_real_flag) {
		for (i=0; i<8; i++) {
			if (!masks[i]) masks[i] = -1;
		}
	}

	/* return new mask and type */

	selection->player_flags = masks[TYPE_PLAYER];
	selection->room_flags = masks[TYPE_ROOM];
	selection->thing_flags = masks[TYPE_THING];
	selection->exit_flags = masks[TYPE_EXIT];
	selection->player_eflags = 0;
	*p_type = type;
	return 1;
}

void er_mark_disabled (dbref player)
{
	notify(player,
		"The mark commands are not allowed while DB cleaning is enabled.");
	notify(player,
		"Use the '@disable cleaning' command to disable automatic cleaning.");
	notify(player,
		"Remember to '@unmark_all' before re-enabling automatic cleaning.");
}


/* ---------------------------------------------------------------------------
 * do_search: Walk the db reporting various things (or setting/clearing
 * mark bits)
 */

int search_setup (dbref player, char *searchfor, SEARCH *parm)
{
char	*pname, *searchtype;
int	err;

	/* Crack arg into <pname> <type>=<targ> */

	pname = parse_to(&searchfor, '=', EV_STRIP_TS);
	if (!pname || !*pname)
		pname = (char *) "me";
	searchtype = strchr(pname, ' ');
	if (searchtype) {
		*searchtype++ = '\0';
	} else {
		if (!searchfor || !*searchfor) {
			searchtype = (char *) "";
		} else {
			searchtype = pname;
			pname = (char *) "";
		}
	}

	parm->s_owner = Owner(player);
	parm->s_wizard = Wizard(player);

	/* set limits on who we search */

	parm->s_rst_owner = NOTHING;
	if (!*pname) {
		parm->s_rst_owner = parm->s_wizard ? ANY_OWNER : player;
	} else if (pname[0] == '#') {
		parm->s_rst_owner = atoi (&pname[1]);
		if (!Good_obj(parm->s_rst_owner))
			parm->s_rst_owner = NOTHING;
		else if (Typeof(parm->s_rst_owner) != TYPE_PLAYER)
			parm->s_rst_owner = NOTHING;

	} else if (strcmp (pname, "me") == 0) {
		parm->s_rst_owner = player;
	} else {
		parm->s_rst_owner = lookup_player (player, pname, 1);
	}

	if (parm->s_rst_owner == NOTHING) {
		notify (player, tprintf ("%s: No such player", pname));
		return 0;
	}

	/* set limits on what we search for */

	err = 0;
	parm->s_rst_name = NULL;
	parm->s_rst_eval = NULL;
	parm->s_rst_type = NOTYPE;
	parm->s_flagmasks.player_flags = 0;
	parm->s_flagmasks.room_flags = 0;
	parm->s_flagmasks.thing_flags = 0;
	parm->s_flagmasks.exit_flags = 0;
	parm->s_flagmasks.player_eflags = 0;

	switch (searchtype[0]) {
	case '\0':		/* the no class requested class  :)  */
		break;
	case 'e':
		if (string_prefix ("exits", searchtype)) {
			parm->s_rst_name = searchfor;
			parm->s_rst_type = TYPE_EXIT;
		} else if (string_prefix ("evaluate", searchtype)) {
			parm->s_rst_eval = searchfor;
		} else if (string_prefix ("eplayer", searchtype)) {
			parm->s_rst_type = TYPE_PLAYER;
			parm->s_rst_eval = searchfor;
		} else if (string_prefix ("eroom", searchtype)) {
			parm->s_rst_type = TYPE_ROOM;
			parm->s_rst_eval = searchfor;
		} else if (string_prefix ("eobject", searchtype)) {
			parm->s_rst_type = TYPE_THING;
			parm->s_rst_eval = searchfor;
		} else if (string_prefix ("eexit", searchtype)) {
			parm->s_rst_type = TYPE_EXIT;
			parm->s_rst_eval = searchfor;
		} else {
			err = 1;
		}
		break;
	case 'f':
		if (string_prefix ("flags", searchtype)) {

			/* convert_flags ignores previous values of flag_mask
			 * and s_rst_type while setting them */

			if (!convert_flags (player, searchfor,
			    &parm->s_flagmasks, &parm->s_rst_type))
				return 0;
		} else {
			err = 1;
		}
		break;
	case 'n':
		if (string_prefix ("name", searchtype)) {
			parm->s_rst_name = searchfor;
		} else {
			err = 1;
		}
		break;
	case 'o':
		if (string_prefix ("objects", searchtype)) {
			parm->s_rst_name = searchfor;
			parm->s_rst_type = TYPE_THING;
		} else {
			err = 1;
		}
		break;
	case 'p':
		if (string_prefix ("players", searchtype)) {
			parm->s_rst_name = searchfor;
			parm->s_rst_type = TYPE_PLAYER;
			if (!*pname)
				parm->s_rst_owner = ANY_OWNER;
		} else {
			err = 1;
		}
		break;
	case 'r':
		if (string_prefix ("rooms", searchtype)) {
			parm->s_rst_name = searchfor;
			parm->s_rst_type = TYPE_ROOM;
		} else {
			err = 1;
		}
		break;
	case 't':
		if (string_prefix ("type", searchtype)) {
			if (searchfor[0] == '\0')
				break;
			if (string_prefix ("room", searchfor))
				parm->s_rst_type = TYPE_ROOM;
			else if (string_prefix ("exit", searchfor))
				parm->s_rst_type = TYPE_EXIT;
			else if (string_prefix ("object", searchfor))
				parm->s_rst_type = TYPE_THING;
			else if (string_prefix ("player", searchfor)) {
				parm->s_rst_type = TYPE_PLAYER;
				if (!*pname)
					parm->s_rst_owner = ANY_OWNER;
			} else {
				notify(player,
					tprintf("%s: unknown type",
						searchfor));
				return 0;
			}
		} else {
			err = 1;
		}
		break;
	default:
		err = 1;
	}

	if (err) {
		notify (player, tprintf ("%s: unknown class", searchtype));
		return 0;
	}

	/* Make sure player is authorized to do the search */

	if (!parm->s_wizard &&
	    (parm->s_rst_type != TYPE_PLAYER) &&
	    (parm->s_rst_owner != player)) {
		notify (player, "You need a search warrant to do that!");
		return 0;
	}

	/* make sure player has money to do the search */

	if (!payfor(player, mudconf.searchcost)) {
		notify(player,
			tprintf("You don't have enough %s to search. (You need %d)",
				mudconf.many_coins, mudconf.searchcost));
		return 0;
	}

	return 1;
}

void search_perform (dbref player, dbref cause, SEARCH *parm)
{
FLAG	thingflags, mask;
dbref	thing;
char	*buff, *buff2, *result;
int	save_invk_ctr;

	buff = alloc_sbuf("search_perform.num");
	olist_init();
	save_invk_ctr = mudstate.func_invk_ctr;
	DO_WHOLE_DB(thing) {

		if ((thing % 100) == 0) {
			tmp_sync();
			cache_reset();
		}
		mudstate.func_invk_ctr = save_invk_ctr;

		/* Check for matching type */

		if ((parm->s_rst_type != NOTYPE) &&
		    (parm->s_rst_type != Typeof(thing)))
			continue;

		/* Check for matching owner */

		if ((parm->s_rst_owner != ANY_OWNER) &&
		    (parm->s_rst_owner != Owner(thing)))
			continue;

		/* Toss out destroyed things */

		thingflags = Flags(thing);
		if ((Typeof(thing) == TYPE_THING) && (thingflags & GOING))
			continue;

		/* Check for matching flags */

		switch (Typeof(thing)) {
		case TYPE_PLAYER:
			mask = parm->s_flagmasks.player_flags;
			break;
		case TYPE_ROOM:
			mask = parm->s_flagmasks.room_flags;
			break;
		case TYPE_THING:
			mask = parm->s_flagmasks.thing_flags;
			break;
		case TYPE_EXIT:
			mask = parm->s_flagmasks.exit_flags;
			break;
		default:
			mask = 0;
		}
		if ((thingflags & mask) != mask)
			continue;

		/* Check for matching name */

		if (parm->s_rst_name != NULL) {
			if (!string_prefix(Name(thing), parm->s_rst_name))
				continue;
		}

		/* Check for successful evaluation */

		if (parm->s_rst_eval != NULL) {
			sprintf(buff, "#%d", thing);
			buff2 = replace_string(BOUND_VAR, buff,
				parm->s_rst_eval);
			result = exec(player, cause, EV_FCHECK, buff2,
				(char **)NULL, 0);
			free_lbuf(buff2);
			if (!*result || !xlate(result)) {
				free_lbuf(result);
				continue;
			}
			free_lbuf(result);
		}

		/* It passed everything.  Amazing. */

		olist_add(thing);
	}
	free_sbuf(buff);
	mudstate.func_invk_ctr = save_invk_ctr;
}

static void search_mark (dbref player, int key)
{
dbref	thing;
int	nchanged, is_marked;

	nchanged = 0;
	for (thing=olist_first(); thing!=NOTHING; thing=olist_next()) {
		is_marked = Marked(thing);

		/* Don't bother checking if marking and already marked
		 * (or if unmarking and not marked) */

		if (((key == SRCH_MARK) && is_marked) ||
		    ((key == SRCH_UNMARK) && !is_marked))
			continue;
			/* Toggle the mark bit and update the counters */
			if (key == SRCH_MARK) {
			Mark(thing);
			nchanged++;
		} else {
			Unmark(thing);
			nchanged++;
		}
	}
	notify(player,
		tprintf("%d objects %smarked",
			nchanged, ((key==SRCH_MARK) ? "" : "un")));
	return;
}
					
void do_search (dbref player, dbref cause, int key, char *arg)
{
int	flag, destitute;
char	*buff, *outbuf, *bp;
dbref	thing, from, to;
SEARCH	searchparm;

	if ((key != SRCH_SEARCH) && (mudconf.control_flags & CF_DBCHECK)) {
		er_mark_disabled(player);
		return;
	}

	if (!search_setup (player, arg, &searchparm))
		return;
	search_perform (player, cause, &searchparm);
	destitute = 1;

	/* If we are doing a @mark command, handle that here. */

	if (key != SRCH_SEARCH) {
		search_mark(player, key);
		return;
	}

	outbuf = alloc_lbuf("do_search.outbuf");

	/* room search */
	if (searchparm.s_rst_type == TYPE_ROOM ||
	    searchparm.s_rst_type == NOTYPE) {
		flag = 1;
		for (thing=olist_first(); thing!=NOTHING; thing=olist_next()) {
			if (Typeof(thing) != TYPE_ROOM) continue;
			if (flag) {
				flag = 0;
				destitute = 0;
				notify (player, "\nROOMS:");
			}
			buff = unparse_object (player, thing);
			notify (player, buff);
			free_lbuf(buff);
		}
	}

	/* exit search */
	if (searchparm.s_rst_type == TYPE_EXIT ||
	    searchparm.s_rst_type == NOTYPE) {
		flag = 1;
		for (thing=olist_first(); thing!=NOTHING; thing=olist_next()) {
			if (Typeof(thing) != TYPE_EXIT) continue;
			if (flag) {
				flag = 0;
				destitute = 0;
				notify (player, "\nEXITS:");
			}
			from = Exits (thing);
			to = Location (thing);

			bp = outbuf;
			buff = unparse_object (player, thing);
			safe_str(buff, outbuf, &bp);
			free_lbuf(buff);

			safe_str((char *)" [from ", outbuf, &bp);
			buff = unparse_object (player, from);
			safe_str(((from==NOTHING) ? "NOWHERE" : buff),
				outbuf, &bp);
			free_lbuf(buff);

			safe_str((char *)" to ", outbuf, &bp);
			buff = unparse_object (player, to);
			safe_str(((to==NOTHING) ? "NOWHERE" : buff),
				outbuf, &bp);
			free_lbuf(buff);

			safe_chr(']', outbuf, &bp);
			*bp = '\0';
			notify (player, outbuf);
		}
	}

	/* object search */
	if (searchparm.s_rst_type == TYPE_THING ||
	    searchparm.s_rst_type == NOTYPE) {
		flag = 1;
		for (thing=olist_first(); thing!=NOTHING; thing=olist_next()) {
			if (Typeof(thing) != TYPE_THING) continue;
			if (flag) {
				flag = 0;
				destitute = 0;
				notify (player, "\nOBJECTS:");
			}

			bp = outbuf;
			buff = unparse_object (player, thing);
			safe_str(buff, outbuf, &bp);
			free_lbuf(buff);

			safe_str((char *)" [owner: ", outbuf, &bp);
			buff = unparse_object (player, Owner (thing));
			safe_str(buff, outbuf, &bp);
			free_lbuf(buff);

			safe_chr(']', outbuf, &bp);
			*bp = '\0';
			notify (player, outbuf);
		}
	}

	/* player search */
	if (searchparm.s_rst_type == TYPE_PLAYER ||
	    searchparm.s_rst_type == NOTYPE) {
		flag = 1;
		for (thing=olist_first(); thing!=NOTHING; thing=olist_next()) {
			if (Typeof(thing) != TYPE_PLAYER) continue;
			if (flag) {
				flag = 0;
				destitute = 0;
				notify (player, "\nPLAYERS:");
			}
			bp = outbuf;
			buff = unparse_object (player, thing);
			safe_str(buff, outbuf, &bp);
			free_lbuf(buff);
			if (searchparm.s_wizard) {
				safe_str((char *)" [location: ",
					outbuf, &bp);
				buff = unparse_object (player,
					Location (thing));
				safe_str(buff, outbuf, &bp);
				free_lbuf(buff);
				safe_chr(']', outbuf, &bp);
			}
			*bp = '\0';
			notify (player, outbuf);
		}
	}

	/* if nothing found matching search criteria */

	if (destitute) {
		notify (player, "Nothing found.");
	}
	free_lbuf(outbuf);
	olist_init();
}

/* ---------------------------------------------------------------------------
 * do_markall: set or clear the mark bits of all objects in the db.
 */

void do_markall (dbref player, dbref cause, int key)
{
int	i;

	if (mudconf.control_flags & CF_DBCHECK) {
		er_mark_disabled(player);
		return;
	}
	if (key == MARK_SET)
		Mark_all(i);
	else if (key == MARK_CLEAR)
		Unmark_all(i);
	if (!Quiet(player)) notify(player, "Done.");
}

/* ---------------------------------------------------------------------------
 * do_apply_marked: Perform a command for each marked obj in the db.
 */

void do_apply_marked (dbref player, dbref cause, int key, char *command,
	char *cargs[], int ncargs)
{
char	*buff;
int	i;

	if (mudconf.control_flags & CF_DBCHECK) {
		er_mark_disabled(player);
		return;
	}
	buff = alloc_sbuf("do_apply_marked");
	DO_WHOLE_DB(i) {
		if (Marked(i)) {
			sprintf(buff, "#%d", i);
			bind_and_queue (player, cause, command, buff,
				cargs, ncargs);
		}
	}
	free_sbuf(buff);
	if (!Quiet(player)) notify(player, "Done.");
}

/* ---------------------------------------------------------------------------
 * olist_init, olist_add, olist_first, olist_next: Object list management
 * routines.
 */

/* olist_init: Clear and initialize the object list */

void olist_init (void)
{
OBLOCK	*op, *onext;

	for (op=mudstate.olist_head; op!=NULL; op=onext) {
		onext = op->next;
		free_lbuf(op);
	}
	mudstate.olist_head = NULL;
	mudstate.olist_tail = NULL;
	mudstate.olist_cblock = NULL;
	mudstate.olist_count = 0;
	mudstate.olist_citm = 0;
}

/* olist_add: Add an entry to the object list */

void olist_add (dbref item)
{
OBLOCK	*op;

	if (!mudstate.olist_head) {
		op = (OBLOCK *)alloc_lbuf("olist_add.first");
		mudstate.olist_head = mudstate.olist_tail = op;
		mudstate.olist_count = 0;
		op->next = NULL;
	} else if (mudstate.olist_count >= OBLOCK_SIZE) {
		op = (OBLOCK *)alloc_lbuf("olist_add.next");
		mudstate.olist_tail->next = op;
		mudstate.olist_tail = op;
		mudstate.olist_count = 0;
		op->next = NULL;
	} else {
		op = mudstate.olist_tail;
	}
	op->data[mudstate.olist_count++] = item;
}

/* olist_first: Return the first entry in the object list */

dbref olist_first (void)
{
	if (!mudstate.olist_head)
		return NOTHING;
	if ((mudstate.olist_head == mudstate.olist_tail) &&
	    (mudstate.olist_count == 0))
		return NOTHING;
	mudstate.olist_cblock = mudstate.olist_head;
	mudstate.olist_citm = 0;
	return mudstate.olist_cblock -> data[mudstate.olist_citm++];
}

dbref olist_next (void)
{
dbref	thing;
	if (!mudstate.olist_cblock)
		return NOTHING;
	if ((mudstate.olist_cblock == mudstate.olist_tail) &&
	    (mudstate.olist_citm >= mudstate.olist_count))
		return NOTHING;
	thing = mudstate.olist_cblock->data[mudstate.olist_citm++];
	if (mudstate.olist_citm >= OBLOCK_SIZE) {
		mudstate.olist_cblock = mudstate.olist_cblock->next;
		mudstate.olist_citm = 0;
	}
	return thing;
}