pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file attrib.c
 *
 * \brief Manipulate attributes on objects.
 *
 *
 */
#include "copyrite.h"

#include "config.h"
#include <string.h>
#include <ctype.h>
#include "conf.h"
#include "externs.h"
#include "chunk.h"
#include "attrib.h"
#include "dbdefs.h"
#include "match.h"
#include "parse.h"
#include "htab.h"
#include "privtab.h"
#include "mymalloc.h"
#include "strtree.h"
#include "flags.h"
#include "mushdb.h"
#include "lock.h"
#include "log.h"
#include "confmagic.h"

#ifdef WIN32
#pragma warning( disable : 4761)	/* disable warning re conversion */
#endif

/** A string tree of attribute names in use, to save us memory since
 * many are duplicated.
 */
StrTree atr_names;
/** Table of attribute flags. */
extern PRIV attr_privs[];

/** A flag to show if we're in the middle of a @wipe (this changes
 * behaviour for atr_clr()).  Yes, this is gross and ugly, but it
 * seemed like a better idea than propogating signature changes
 * for atr_clr() and do_set_atr() through the entire codebase.  If
 * you come up with a better way, PLEASE fix this...
 */
int we_are_wiping;

/** A string to hold the name of a missing prefix branch, set by
 * can_write_attr_internal.  Again, gross and ugly.  Please fix.
 */
static char missing_name[ATTRIBUTE_NAME_LIMIT + 1];

/*======================================================================*/

/** How many attributes go in a "page" of attribute memory? */
#define ATTRS_PER_PAGE (200)

/** A page of memory for attributes.
 * This structure is a collection of attribute memory. Rather than
 * allocate new attributes one at a time, we allocate them in pages,
 * and build a linked free list from the allocated page.
 */
typedef struct atrpage {
  ATTR atrs[ATTRS_PER_PAGE];	/**< Array of attribute structures */
} ATTRPAGE;

static ATTR *atr_free_list;
static ATTR *get_atr_free_list(void);
static ATTR *find_atr_pos_in_list(ATTR ***pos, char const *name);
static int can_create_attr(dbref player, dbref obj, char const *atr_name,
			   int flags);
static ATTR *find_atr_in_list(ATTR *atr, char const *name);
static ATTR *atr_get_with_parent(dbref obj, char const *atrname, dbref *parent);

/*======================================================================*/

/** Initialize the attribute string tree.
 */
void
init_atr_name_tree(void)
{
  st_init(&atr_names);
}

/** Lookup table for good_atr_name */
extern char atr_name_table[UCHAR_MAX + 1];

/** Decide if a name is valid for an attribute.
 * A good attribute name is at least one character long, no more than
 * ATTRIBUTE_NAME_LIMIT characters long, and every character is a 
 * valid character. An attribute name may not start or end with a backtick.
 * An attribute name may not contain multiple consecutive backticks.
 * \param s a string to test for validity as an attribute name.
 */
int
good_atr_name(char const *s)
{
  const unsigned char *a;
  int len = 0;
  if (!s || !*s)
    return 0;
  if (*s == '`')
    return 0;
  if (strstr(s, "``"))
    return 0;
  for (a = (const unsigned char *) s; *a; a++, len++)
    if (!atr_name_table[*a])
      return 0;
  if (*(s + len - 1) == '`')
    return 0;
  return len <= ATTRIBUTE_NAME_LIMIT;
}

/** Find an attribute table entry, given a name.
 * A trivial wrapper around aname_hash_lookup.
 * \param string an attribute name.
 */
ATTR *
atr_match(const char *string)
{
  return aname_hash_lookup(string);
}

/** Find the first attribute branching off the specified attribute.
 * \param branch the attribute to look under
 */
ATTR *
atr_sub_branch(ATTR *branch)
{
  char const *name, *n2;
  size_t len;

  name = AL_NAME(branch);
  len = strlen(name);
  for (branch = AL_NEXT(branch); branch; branch = AL_NEXT(branch)) {
    n2 = AL_NAME(branch);
    if (strlen(n2) <= len)
      return NULL;
    if (n2[len] == '`') {
      if (!strncmp(n2, name, len))
	return branch;
      else
	return NULL;
    }
  }
  return NULL;
}

/** Scan an attribute list for an attribute with the specified name.
 * This continues from whatever start point is passed in.
 * \param atr the start of the list to search from
 * \param name the attribute name to look for
 * \return the matching attribute, or NULL
 */
static ATTR *
find_atr_in_list(ATTR *atr, char const *name)
{
  int comp;

  while (atr) {
    comp = strcoll(name, AL_NAME(atr));
    if (comp < 0)
      return NULL;
    if (comp == 0)
      return atr;
    atr = AL_NEXT(atr);
  }

  return NULL;
}

/** Find the place to insert/delete an attribute with the specified name.
 * \param pos a pointer to the ATTR ** holding the list position
 * \param name the attribute name to look for
 * \return the matching attribute, or NULL if no matching attribute
 */
static ATTR *
find_atr_pos_in_list(ATTR ***pos, char const *name)
{
  int comp;

  while (**pos) {
    comp = strcoll(name, AL_NAME(**pos));
    if (comp < 0)
      return NULL;
    if (comp == 0)
      return **pos;
    *pos = &AL_NEXT(**pos);
  }

  return NULL;
}

/** Convert a string of attribute flags to a bitmask.
 * Given a space-separated string of attribute flags, look them up
 * and return a bitmask of them if player is permitted to use
 * all of those flags.
 * \param player the dbref to use for privilege checks.
 * \param p a space-separated string of attribute flags.
 * \return an attribute flag bitmask.
 */
int
string_to_atrflag(dbref player, char const *p)
{
  int f;
  f = string_to_privs(attr_privs, p, 0);
  if (!f)
    return -1;
  if (!Hasprivs(player) && (f & AF_MDARK))
    return -1;
  if (!See_All(player) && (f & AF_WIZARD))
    return -1;
  return f;
}

/** Convert an attribute flag bitmask into a list of the full
 * names of the flags.
 * \param mask the bitmask of attribute flags to display.
 * \return a pointer to a static buffer with the full names of the flags.
 */
const char *
atrflag_to_string(int mask)
{
  return privs_to_string(attr_privs, mask);
}

/*======================================================================*/

/** Traversal routine for Can_Read_Attr.
 * This function determines if an attribute can be read by examining
 * the tree path to the attribute.  This is not the full Can_Read_Attr
 * check; only the stuff after See_All (just to avoid function calls
 * when the answer is trivialized by special powers).  If the specified
 * player is NOTHING, then we're doing a generic mortal visibility check.
 * \param player the player trying to do the read.
 * \param obj the object targetted for the read (may be a child of a parent!).
 * \param atr the attribute being interrogated.
 * \retval 0 if the player cannot read the attribute.
 * \retval 1 if the player can read the attribute.
 */
int
can_read_attr_internal(dbref player, dbref obj, ATTR *atr)
{
  static char name[ATTRIBUTE_NAME_LIMIT + 1];
  char *p;
  int cansee;
  int canlook;
  dbref target;
  dbref ancestor;
  int visible;
  int parent_depth;
  visible = (player == NOTHING);
  if (visible) {
    cansee = (Visual(obj) &&
	      eval_lock(PLAYER_START, obj, Examine_Lock) &&
	      eval_lock(MASTER_ROOM, obj, Examine_Lock));
    canlook = 0;
  } else {
    cansee = controls(player, obj) ||
      (Visual(obj) && eval_lock(player, obj, Examine_Lock));
    canlook = can_look_at(player, obj);
  }

  /* Take an easy out if there is one... */
  /* If we can't see the attribute itself, then that's easy. */
  if (AF_Internal(atr) || AF_Mdark(atr) ||
      !(cansee ||
	(AF_Visual(atr) && (!AF_Nearby(atr) || canlook)) ||
	(!visible && !Mistrust(player) &&
	 (Owner(AL_CREATOR(atr)) == Owner(player)))))
    return 0;
  /* If the attribute isn't on a branch, then that's also easy. */
  if (!strchr(AL_NAME(atr), '`'))
    return 1;
  /* Nope, we actually have to go looking for the attribute in a tree. */
  strcpy(name, AL_NAME(atr));
  ancestor = Ancestor_Parent(obj);
  target = obj;
  parent_depth = 0;
  while (parent_depth < MAX_PARENTS && GoodObject(target)) {
    /* If the ancestor of the object is in its explict parent chain,
     * we use it there, and don't check the ancestor later.
     */
    if (target == ancestor)
      ancestor = NOTHING;
    atr = List(target);
    /* Check along the branch for permissions... */
    for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
      *p = '\0';
      atr = find_atr_in_list(atr, name);
      if (!atr || (target != obj && AF_Private(atr))) {
	*p = '`';
	goto continue_target;
      }
      if (AF_Internal(atr) || AF_Mdark(atr) ||
	  !(cansee ||
	    (AF_Visual(atr) && (!AF_Nearby(atr) || canlook)) ||
	    (!visible && !Mistrust(player) &&
	     (Owner(AL_CREATOR(atr)) == Owner(player)))))
	return 0;
      *p = '`';
    }

    /* Now actually find the attribute. */
    atr = find_atr_in_list(atr, name);
    if (atr)
      return 1;

  continue_target:

    /* Attribute wasn't on this object.  Check a parent or ancestor. */
    parent_depth++;
    target = Parent(target);
    if (!GoodObject(target)) {
      parent_depth = 0;
      target = ancestor;
    }
  }

  return 0;
}

/** Utility define for can_write_attr_internal and can_create_attr.
 * \param p the player trying to write
 * \param a the attribute to be written
 * \param s obey the safe flag?
 */
#define Cannot_Write_This_Attr(p,a,s) \
  (!God((p)) && \
   (AF_Internal((a)) || \
    ((s) && AF_Safe((a))) || \
    !(Wizard((p)) || \
      (!AF_Wizard((a)) && \
       (!AF_Locked((a)) || (AL_CREATOR((a)) == Owner((p))))))))

/** Traversal routine for Can_Write_Attr.
 * This function determines if an attribute can be written by examining
 * the tree path to the attribute.  As a side effect, missing_name is
 * set to the name of a missing prefix branch, if any.  Yes, side effects
 * are evil.  Please fix if you can.
 * \param player the player trying to do the write.
 * \param obj the object targetted for the write.
 * \param atr the attribute being interrogated.
 * \param safe whether to check the safe attribute flag.
 * \retval 0 if the player cannot write the attribute.
 * \retval 1 if the player can write the attribute.
 */
int
can_write_attr_internal(dbref player, dbref obj, ATTR *atr, int safe)
{
  char *p;
  missing_name[0] = '\0';
  if (Cannot_Write_This_Attr(player, atr, safe))
    return 0;
  strcpy(missing_name, AL_NAME(atr));
  atr = List(obj);
  for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
    *p = '\0';
    atr = find_atr_in_list(atr, missing_name);
    if (!atr)
      return 0;
    if (Cannot_Write_This_Attr(player, atr, safe)) {
      missing_name[0] = '\0';
      return 0;
    }
    *p = '`';
  }

  return 1;
}

/** Utility define for atr_add and can_create_attr */
#define set_default_flags(atr,flags) \
  do { \
    ATTR *std = atr_match(AL_NAME((atr))); \
    if (std && !strcmp(AL_NAME(std), AL_NAME((atr)))) { \
      AL_FLAGS(atr) = AL_FLAGS(std); \
    } else { \
      AL_FLAGS(atr) = 0; \
      if (flags != NOTHING) \
        AL_FLAGS(atr) |= flags; \
    } \
  } while (0)

/** Can an attribute of specified name be created?
 * This function determines if an attribute can be created by examining
 * the tree path to the attribute, and the standard attribute flags for
 * those parts of the path that don't exist yet.
 * \param player the player trying to do the write.
 * \param obj the object targetted for the write.
 * \param atr the attribute being interrogated.
 * \param flags the default flags to add to the attribute.
 * \retval 0 if the player cannot write the attribute.
 * \retval 1 if the player can write the attribute.
 */
static int
can_create_attr(dbref player, dbref obj, char const *atr_name, int flags)
{
  char *p;
  ATTR tmpatr, *atr;
  int num_new = 1;
  missing_name[0] = '\0';

  atr = &tmpatr;
  AL_CREATOR(atr) = player;
  AL_NAME(atr) = atr_name;
  set_default_flags(atr, flags);
  if (Cannot_Write_This_Attr(player, atr, 1))
    return 0;

  strcpy(missing_name, atr_name);
  atr = List(obj);
  for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
    *p = '\0';
    if (atr != &tmpatr)
      atr = find_atr_in_list(atr, missing_name);
    if (!atr) {
      atr = &tmpatr;
      AL_CREATOR(atr) = Owner(player);
    }
    if (atr == &tmpatr) {
      AL_NAME(atr) = missing_name;
      set_default_flags(atr, flags);
      num_new++;
    }
    if (Cannot_Write_This_Attr(player, atr, 1)) {
      missing_name[0] = '\0';
      return 0;
    }
    *p = '`';
  }

  if (AttrCount(obj) + num_new > MAX_ATTRCOUNT) {
    do_log(LT_ERR, player, obj,
	   T("Attempt by %s(%d) to create too many attributes on %s(%d)"),
	   Name(player), player, Name(obj), obj);
    return 0;
  }

  return 1;
}

/*======================================================================*/

/** Do the work of creating the attribute entry on an object.
 * This doesn't do any permissions checking.  You should do that yourself.
 * \param thing the object to hold the attribute
 * \param atr_name the name for the attribute
 */
static ATTR *
create_atr(dbref thing, char const *atr_name)
{
  ATTR *ptr, **ins;
  char const *name;

  /* put the name in the string table */
  name = st_insert(atr_name, &atr_names);
  if (!name)
    return NULL;

  /* allocate a new page, if needed */
  ptr = get_atr_free_list();
  atr_free_list = AL_NEXT(ptr);

  /* initialize atr */
  AL_NAME(ptr) = name;
  ptr->data = NULL_CHUNK_REFERENCE;

  /* link it in */
  ins = &List(thing);
  (void) find_atr_pos_in_list(&ins, AL_NAME(ptr));
  AL_NEXT(ptr) = *ins;
  *ins = ptr;
  AttrCount(thing)++;

  return ptr;
}

/** Add an attribute to an object, dangerously.
 * This is a stripped down version of atr_add, without duplicate checking,
 * permissions checking, attribute count checking, or auto-ODARKing.  
 * If anyone uses this outside of database load or atr_cpy (below), 
 * I will personally string them up by their toes.  - Alex 
 * \param thing object to set the attribute on.
 * \param atr name of the attribute to set.
 * \param s value of the attribute to set.
 * \param player the attribute creator.
 * \param flags bitmask of attribute flags for this attribute.
 * \param derefs the initial deref count to use for the attribute value.
 */
void
atr_new_add(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
	    dbref player, int flags, unsigned char derefs)
{
  ATTR *ptr;

  if (!EMPTY_ATTRS && !*s)
    return;

  /* Don't fail on a bad name, but do log it */
  if (!good_atr_name(atr))
    do_rawlog(LT_ERR, T("Bad attribute name %s on object %s"), atr,
	      unparse_dbref(thing));

  ptr = create_atr(thing, atr);
  if (!ptr)
    return;

  AL_FLAGS(ptr) = (flags != NOTHING) ? flags : 0;
  AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;
  AL_CREATOR(ptr) = player;

  /* replace string with new string */
  if (!s || !*s) {
    /* nothing */
  } else {
    unsigned char *t = compress(s);
    if (!t)
      return;
    ptr->data = chunk_create(t, u_strlen(t), derefs);
    free(t);

    if (*s == '$')
      AL_FLAGS(ptr) |= AF_COMMAND;
    if (*s == '^')
      AL_FLAGS(ptr) |= AF_LISTEN;
  }
}

/** Attribute error - invalid name */
#define AE_BADNAME -3
/** Attribute error - attempt to overwrite a safe attribute */
#define AE_SAFE -2
/** Attribute error - general failure */
#define AE_ERROR -1

/** Add an attribute to an object, safely.
 * \verbatim
 * This is the function that should be called in hardcode to add
 * an attribute to an object (but not to process things like @set that
 * may add or clear an attribute - see do_set_atr() for that).
 * \endverbatim
 * \param thing object to set the attribute on.
 * \param atr name of the attribute to set.
 * \param s value of the attribute to set.
 * \param player the attribute creator.
 * \param flags bitmask of attribute flags for this attribute.
 * \retval AE_BADNAME invalid attribute name.
 * \retval AE_SAFE attempt to overwrite a SAFE attribute.
 * \retval AE_ERROR general failure.
 * \retval 1 success.
 */
int
atr_add(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
	dbref player, int flags)
{
  ATTR *ptr;
  char *p;

  if (!s || (!EMPTY_ATTRS && !*s))
    return atr_clr(thing, atr, player);

  if (!good_atr_name(atr))
    return AE_BADNAME;

  /* walk the list, looking for a preexisting value */
  ptr = find_atr_in_list(List(thing), atr);

  /* check for permission to modify existing atr */
  if (ptr && AF_Safe(ptr))
    return AE_SAFE;
  if (ptr && !Can_Write_Attr(player, thing, ptr))
    return AE_ERROR;

  /* make a new atr, if needed */
  if (!ptr) {
    if (!can_create_attr(player, thing, atr, flags))
      return AE_ERROR;

    strcpy(missing_name, atr);
    ptr = List(thing);
    for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
      *p = '\0';

      ptr = find_atr_in_list(ptr, missing_name);

      if (!ptr) {
	ptr = create_atr(thing, missing_name);
	if (!ptr)
	  return AE_ERROR;

	/* update modification time here, because from now on,
	 * we modify even if we fail */
	if (!IsPlayer(thing) && !AF_Nodump(ptr))
	  ModTime(thing) = mudtime;

	set_default_flags(ptr, flags);
	AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;
	AL_CREATOR(ptr) = Owner(player);
	if (!EMPTY_ATTRS) {
	  unsigned char *t = compress(" ");
	  if (!t)
	    return AE_ERROR;
	  ptr->data = chunk_create(t, u_strlen(t), 0);
	  free(t);
	}
      }

      *p = '`';
    }

    ptr = create_atr(thing, atr);
    if (!ptr)
      return AE_ERROR;

    set_default_flags(ptr, flags);
  }
  /* update modification time here, because from now on,
   * we modify even if we fail */
  if (!IsPlayer(thing) && !AF_Nodump(ptr))
    ModTime(thing) = mudtime;

  /* change owner */
  AL_CREATOR(ptr) = Owner(player);

  AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;

  /* replace string with new string */
  if (ptr->data)
    chunk_delete(ptr->data);
  if (!s || !*s) {
    ptr->data = NULL_CHUNK_REFERENCE;
  } else {
    unsigned char *t = compress(s);
    if (!t) {
      ptr->data = NULL_CHUNK_REFERENCE;
      return AE_ERROR;
    }
    ptr->data = chunk_create(t, u_strlen(t), 0);
    free(t);

    if (*s == '$')
      AL_FLAGS(ptr) |= AF_COMMAND;
    if (*s == '^')
      AL_FLAGS(ptr) |= AF_LISTEN;
  }

  return 1;
}

/** Remove an attribute from an object.
 * This function clears an attribute from an object. 
 * Permission is denied if the attribute is a branch, not a leaf.
 * \param thing object to clear attribute from.
 * \param atr name of attribute to remove.
 * \param player enactor attempting to remove attribute.
 * \retval 0 no attribute found to reset
 * \retval AE_SAFE attribute is safe
 * \retval AE_ERROR other failure
 */
int
atr_clr(dbref thing, char const *atr, dbref player)
{
  ATTR *ptr, **prev, *sub;
  size_t len;

  prev = &List(thing);
  ptr = find_atr_pos_in_list(&prev, atr);

  if (!ptr)
    return 0;

  if (ptr && AF_Safe(ptr))
    return AE_SAFE;
  if (!Can_Write_Attr(player, thing, ptr))
    return AE_ERROR;

  sub = atr_sub_branch(ptr);
  if (!we_are_wiping && sub)
    return AE_ERROR;

  if (!IsPlayer(thing) && !AF_Nodump(ptr))
    ModTime(thing) = mudtime;

  *prev = AL_NEXT(ptr);

  if (ptr->data)
    chunk_delete(ptr->data);

  len = strlen(AL_NAME(ptr));
  st_delete(AL_NAME(ptr), &atr_names);

  AL_NEXT(ptr) = atr_free_list;
  AL_FLAGS(ptr) = 0;
  atr_free_list = ptr;
  AttrCount(thing)--;

  if (we_are_wiping && sub) {
    while (*prev != sub)
      prev = &AL_NEXT(*prev);
    ptr = *prev;
    while (ptr && strlen(AL_NAME(ptr)) > len && AL_NAME(ptr)[len] == '`') {
      *prev = AL_NEXT(ptr);

      if (ptr->data)
	chunk_delete(ptr->data);
      st_delete(AL_NAME(ptr), &atr_names);

      AL_NEXT(ptr) = atr_free_list;
      AL_FLAGS(ptr) = 0;
      atr_free_list = ptr;
      AttrCount(thing)--;

      ptr = *prev;
    }
  }
  return 1;
}

/** Retrieve an attribute from an object or its ancestors.
 * This function retrieves an attribute from an object, or from its
 * parent chain, returning a pointer to the first attribute that
 * matches or NULL. This is a pointer to an attribute structure, not
 * to the value of the attribute, so the value is usually accessed
 * through atr_value() or safe_atr_value().
 * \param obj the object containing the attribute.
 * \param atrname the name of the attribute.
 * \return pointer to the attribute structure retrieved, or NULL.
 */
ATTR *
atr_get(dbref obj, char const *atrname)
{
  return atr_get_with_parent(obj, atrname, NULL);
}

static ATTR *
atr_get_with_parent(dbref obj, char const *atrname, dbref *parent)
{
  static char name[ATTRIBUTE_NAME_LIMIT + 1];
  char *p;
  ATTR *atr;
  int parent_depth;
  dbref target;
  dbref ancestor;

  if (obj == NOTHING || !good_atr_name(atrname))
    return NULL;

  /* First try given name, then try alias match. */
  strcpy(name, atrname);
  for (;;) {
    /* Hunt through the parents/ancestor chain... */
    ancestor = Ancestor_Parent(obj);
    target = obj;
    parent_depth = 0;
    while (parent_depth < MAX_PARENTS && GoodObject(target)) {
      /* If the ancestor of the object is in its explict parent chain,
       * we use it there, and don't check the ancestor later.
       */
      if (target == ancestor)
	ancestor = NOTHING;
      atr = List(target);

      /* If we're looking at a parent/ancestor, then we
       * need to check the branch path for privacy... */
      if (target != obj) {
	for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
	  *p = '\0';
	  atr = find_atr_in_list(atr, name);
	  if (!atr || AF_Private(atr)) {
	    *p = '`';
	    goto continue_target;
	  }
	  *p = '`';
	}
      }

      /* Now actually find the attribute. */
      atr = find_atr_in_list(atr, name);
      if (atr && (target == obj || !AF_Private(atr))) {
	if (parent)
	  *parent = target;
	return atr;
      }

    continue_target:
      /* Attribute wasn't on this object.  Check a parent or ancestor. */
      parent_depth++;
      target = Parent(target);
      if (!GoodObject(target)) {
	parent_depth = 0;
	target = ancestor;
      }
    }

    /* Try the alias, too... */
    atr = atr_match(atrname);
    if (!atr || !strcmp(name, AL_NAME(atr)))
      break;
    strcpy(name, AL_NAME(atr));
  }

  return NULL;
}

/** Retrieve an attribute from an object.
 * This function retrieves an attribute from an object, and does not
 * check the parent chain. It returns a pointer to the attribute
 * or NULL.  This is a pointer to an attribute structure, not
 * to the value of the attribute, so the (compressed) value is usually 
 * to the value of the attribute, so the value is usually accessed
 * through atr_value() or safe_atr_value().
 * \param thing the object containing the attribute.
 * \param atr the name of the attribute.
 * \return pointer to the attribute structure retrieved, or NULL.
 */
ATTR *
atr_get_noparent(dbref thing, char const *atr)
{
  ATTR *ptr;

  if (thing == NOTHING || !good_atr_name(atr))
    return NULL;

  /* try real name */
  ptr = find_atr_in_list(List(thing), atr);
  if (ptr)
    return ptr;

  ptr = atr_match(atr);
  if (!ptr || !strcmp(atr, AL_NAME(ptr)))
    return NULL;
  atr = AL_NAME(ptr);

  /* try alias */
  ptr = find_atr_in_list(List(thing), atr);
  if (ptr)
    return ptr;

  return NULL;
}


/** Apply a function to a set of attributes.
 * This function applies another function to a set of attributes on an
 * object specified by a (wildcarded) pattern to match against the
 * attribute name.
 * \param player the enactor.
 * \param thing the object containing the attribute.
 * \param name the pattern to match against the attribute name.
 * \param mortal only fetch mortal-visible attributes?
 * \param func the function to call for each matching attribute.
 * \param args additional arguments to pass to the function.
 * \return the sum of the return values of the functions called.
 */
int
atr_iter_get(dbref player, dbref thing, const char *name, int mortal,
	     aig_func func, void *args)
{
  ATTR *ptr, **indirect;
  int result;
  int len;

  result = 0;
  if (!name || !*name)
    name = "*";
  len = strlen(name);

  if (!wildcard(name) && name[len - 1] != '`') {
    ptr = atr_get_noparent(thing, strupper(name));
    if (ptr && (mortal ? Is_Visible_Attr(thing, ptr)
		: Can_Read_Attr(player, thing, ptr)))
      result = func(player, thing, NOTHING, name, ptr, args);
  } else {
    indirect = &List(thing);
    while (*indirect) {
      ptr = *indirect;
      if ((mortal ? Is_Visible_Attr(thing, ptr)
	   : Can_Read_Attr(player, thing, ptr)) && atr_wild(name, AL_NAME(ptr)))
	result += func(player, thing, NOTHING, name, ptr, args);
      if (ptr == *indirect)
	indirect = &AL_NEXT(ptr);
    }
  }

  return result;
}

/** Count the number of attributes an object has that match a pattern,
 * If <doparent> is true, then count parent attributes as well,
 * but excluding duplicates.
 * \param player the enactor.
 * \param thing the object containing the attribute.
 * \param name the pattern to match against the attribute name.
 * \param mortal only fetch mortal-visible attributes?
 * \param doparent count parent attrbutes as well?
 * \return the count of matching attributes
 */
int
atr_pattern_count(dbref player, dbref thing, const char *name,
		  int doparent, int mortal)
{
  ATTR *ptr, **indirect;
  int result;
  int len;
  dbref parent = NOTHING;

  result = 0;
  if (!name || !*name)
    name = "*";
  len = strlen(name);

  if (!wildcard(name) && name[len - 1] != '`') {
    parent = thing;
    if (doparent)
      ptr = atr_get_with_parent(thing, strupper(name), &parent);
    else
      ptr = atr_get_noparent(thing, strupper(name));
    if (ptr && (mortal ? Is_Visible_Attr(parent, ptr)
		: Can_Read_Attr(player, parent, ptr)))
      result += 1;
  } else {
    StrTree seen;
    int parent_depth;
    st_init(&seen);
    for (parent_depth = MAX_PARENTS + 1, parent = thing;
	 (parent_depth-- && parent != NOTHING) &&
	 (doparent || (parent == thing)); parent = Parent(parent)) {
      indirect = &List(parent);
      while (*indirect) {
	ptr = *indirect;
	if (!st_find(AL_NAME(ptr), &seen)) {
	  st_insert(AL_NAME(ptr), &seen);
	  if ((parent == thing) || !AF_Private(ptr)) {
	    if ((mortal ? Is_Visible_Attr(parent, ptr)
		 : Can_Read_Attr(player, parent, ptr))
		&& atr_wild(name, AL_NAME(ptr)))
	      result += 1;
	  }
	}
	if (ptr == *indirect)
	  indirect = &AL_NEXT(ptr);
      }
    }
    st_flush(&seen);
  }

  return result;
}

/** Apply a function to a set of attributes, including inherited ones.
 * This function applies another function to a set of attributes on an
 * object specified by a (wildcarded) pattern to match against the
 * attribute name on an object or its parents.
 * \param player the enactor.
 * \param thing the object containing the attribute.
 * \param name the pattern to match against the attribute name.
 * \param mortal only fetch mortal-visible attributes?
 * \param func the function to call for each matching attribute, with
 *  a pointer to the dbref of the object the attribute is really on passed
 *  as the function's args argument.
 * \return the sum of the return values of the functions called.
 */
int
atr_iter_get_parent(dbref player, dbref thing, const char *name, int mortal,
		    aig_func func, void *args)
{
  ATTR *ptr, **indirect;
  int result;
  int len;
  dbref parent = NOTHING;

  result = 0;
  if (!name || !*name)
    name = "*";
  len = strlen(name);

  if (!wildcard(name) && name[len - 1] != '`') {
    ptr = atr_get_with_parent(thing, strupper(name), &parent);
    if (ptr && (mortal ? Is_Visible_Attr(parent, ptr)
		: Can_Read_Attr(player, parent, ptr)))
      result = func(player, thing, parent, name, ptr, args);
  } else {
    StrTree seen;
    int parent_depth;
    st_init(&seen);
    for (parent_depth = MAX_PARENTS + 1, parent = thing;
	 parent_depth-- && parent != NOTHING; parent = Parent(parent)) {
      indirect = &List(parent);
      while (*indirect) {
	ptr = *indirect;
	if (!st_find(AL_NAME(ptr), &seen)) {
	  st_insert(AL_NAME(ptr), &seen);
	  if ((parent == thing) || !AF_Private(ptr)) {
	    if ((mortal ? Is_Visible_Attr(parent, ptr)
		 : Can_Read_Attr(player, parent, ptr))
		&& atr_wild(name, AL_NAME(ptr)))
	      result += func(player, thing, parent, name, ptr, args);
	  }
	}
	if (ptr == *indirect)
	  indirect = &AL_NEXT(ptr);
      }
    }
    st_flush(&seen);
  }

  return result;
}

/** Free the memory associated with all attributes of an object.
 * This function frees all of an object's attribute memory.
 * This includes the memory allocated to hold the attribute's value,
 * and the attribute's entry in the object's string tree.
 * Freed attribute structures are added to the free list.
 * \param thing dbref of object
 */
void
atr_free(dbref thing)
{
  ATTR *ptr;

  if (!List(thing))
    return;

  if (!IsPlayer(thing))
    ModTime(thing) = mudtime;

  while ((ptr = List(thing))) {
    List(thing) = AL_NEXT(ptr);

    if (ptr->data)
      chunk_delete(ptr->data);
    st_delete(AL_NAME(ptr), &atr_names);

    AL_NEXT(ptr) = atr_free_list;
    atr_free_list = ptr;
  }
}

/** Copy all of the attributes from one object to another.
 * \verbatim
 * This function is used by @clone to copy all of the attributes
 * from one object to another.
 * \endverbatim
 * \param dest destination object to receive attributes.
 * \param source source object containing attributes.
 */
void
atr_cpy(dbref dest, dbref source)
{
  ATTR *ptr;

  List(dest) = NULL;
  for (ptr = List(source); ptr; ptr = AL_NEXT(ptr))
    if (!AF_Nocopy(ptr)
	&& (AttrCount(dest) < MAX_ATTRCOUNT)) {
      atr_new_add(dest, AL_NAME(ptr), atr_value(ptr),
		  AL_CREATOR(ptr), AL_FLAGS(ptr), AL_DEREFS(ptr));
      AttrCount(dest)++;
    }
}

/** Structure for keeping track of which attributes have appeared
 * on children when doing command matching. */
typedef struct used_attr {
  struct used_attr *next;	/**< Next attribute in list */
  char const *name;		/**< The name of the attribute */
  int no_prog;			/**< Was it AF_NOPROG */
} UsedAttr;

/** Find an attribute in the list of seen attributes.
 * Since attributes are checked in collation order, the pointer to the
 * list is updated to reflect the current search position.
 * For efficiency of insertions, the pointer used is a trailing pointer,
 * pointing at the pointer to the next used struct.
 * To allow a useful return code, the pointer used is actually a pointer
 * to the pointer mentioned above.  Yes, I know three-star coding is bad,
 * but I have good reason, here.
 * \param prev the pointer to the pointer to the pointer to the next
 *             used attribute.
 * \param name the name of the attribute to look for.
 * \retval 0 the attribute was not in the list,
 *           **prev now points to the next atfer.
 * \retval 1 the attribute was in the list,
 *           **prev now points to the entry for it.
 */
static int
find_attr(UsedAttr *** prev, char const *name)
{
  int comp;

  comp = 1;
  while (**prev) {
    comp = strcoll(name, prev[0][0]->name);
    if (comp <= 0)
      break;
    *prev = &prev[0][0]->next;
  }
  return comp == 0;
}

/** Insert an attribute in the list of seen attributes.
 * Since attributes are inserted in collation order, an updated insertion
 * point is returned (so subsequent calls don't have to go hunting as far).
 * \param prev the pointer to the pointer to the attribute list.
 * \param name the name of the attribute to insert.
 * \param no_prog the AF_NOPROG value from the attribute.
 * \return the pointer to the pointer to the next attribute after
 *         the one inserted.
 */
static UsedAttr **
use_attr(UsedAttr ** prev, char const *name, int no_prog)
{
  int found;
  UsedAttr *used;

  found = find_attr(&prev, name);
  if (!found) {
    used = mush_malloc(sizeof *used, "used_attr");
    used->next = *prev;
    used->name = name;
    used->no_prog = 0;
    *prev = used;
  }
  prev[0]->no_prog |= no_prog;
  /* do_rawlog(LT_TRACE, "Recorded %s: %d -> %d", name,
     no_prog, prev[0]->no_prog); */
  return &prev[0]->next;
}

/** Match input against a $command or ^listen attribute.
 * This function attempts to match a string against either the $commands
 * or ^listens on an object. Matches may be glob or regex matches, 
 * depending on the attribute's flags. With the reasonably safe assumption
 * that most of the matches are going to fail, the faster non-capturing
 * glob match is done first, and the capturing version only called when
 * we already know it'll match. Due to the way PCRE works, there's no
 * advantage to doing something similar for regular expression matches.
 * \param thing object containing attributes to check.
 * \param player the enactor, for privilege checks.
 * \param type either '$' or '^', indicating the type of attribute to check.
 * \param end character that denotes the end of a command (usually ':').
 * \param str string to match against attributes.
 * \param just_match if true, return match without executing code.
 * \param atrname used to return the list of matching object/attributes.
 * \param abp pointer to end of atrname.
 * \param errobj if an attribute matches, but the lock fails, this pointer
 *        is used to return the failing dbref. If NULL, we don't bother.
 * \return number of attributes that matched, or 0
 */
int
atr_comm_match(dbref thing, dbref player, int type, int end, char const *str,
	       int just_match, char *atrname, char **abp, dbref *errobj)
{
  int flag_mask;
  ATTR *ptr;
  int parent_depth;
  char tbuf1[BUFFER_LEN];
  char tbuf2[BUFFER_LEN];
  char *s;
  int match, match_found;
  dbref parent;
  UsedAttr *used_list, **prev;
  ATTR *skip[ATTRIBUTE_NAME_LIMIT / 2];
  int skipcount;
  int lock_checked = 0;

  /* check for lots of easy ways out */
  if ((type != '$' && type != '^') || !GoodObject(thing) || Halted(thing)
      || (type == '$' && NoCommand(thing)))
    return 0;

  if (type == '$') {
    flag_mask = AF_COMMAND;
    parent_depth = GoodObject(Parent(thing));
  } else {
    flag_mask = AF_LISTEN;
    if (ThingInhearit(thing) || RoomInhearit(thing)) {
      parent_depth = GoodObject(Parent(thing));
    } else {
      parent_depth = 0;
    }
  }

  match = 0;
  used_list = NULL;
  prev = &used_list;

  skipcount = 0;
  /* do_rawlog(LT_TRACE, "Searching %s:", Name(thing)); */
  for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
    if (skipcount && ptr == skip[skipcount - 1]) {
      size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
      while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
	     AL_NAME(AL_NEXT(ptr))[len] == '`') {
	ptr = AL_NEXT(ptr);
	/* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
      }
      skipcount--;
      continue;
    }
    if (parent_depth)
      prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
    if (AF_Noprog(ptr)) {
      skip[skipcount] = atr_sub_branch(ptr);
      if (skip[skipcount])
	skipcount++;
      continue;
    }
    if (!(AL_FLAGS(ptr) & flag_mask))
      continue;
    strcpy(tbuf1, atr_value(ptr));
    s = tbuf1;
    do {
      s = strchr(s + 1, end);
    } while (s && s[-1] == '\\');
    if (!s)
      continue;
    *s++ = '\0';

    if (AF_Regexp(ptr)) {
      /* Turn \: into : */
      char *from, *to;
      for (from = tbuf1, to = tbuf2; *from; from++, to++) {
	if (*from == '\\' && *(from + 1) == ':')
	  from++;
	*to = *from;
      }
      *to = '\0';
    } else
      strcpy(tbuf2, tbuf1);

    match_found = 0;
    if (AF_Regexp(ptr)) {
      if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
	match_found = 1;
	match++;
      }
    } else {
      if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
	match_found = 1;
	match++;
	wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
      }
    }
    if (match_found) {
      /* We only want to do the lock check once, so that any side
       * effects in the lock are only performed once per utterance.
       * Thus, '$foo *r:' and '$foo b*:' on the same object will only
       * run the lock once for 'foo bar'.
       */
      if (!lock_checked) {
	lock_checked = 1;
	if ((type == '$' && !eval_lock(player, thing, Command_Lock))
	    || (type == '^' && !eval_lock(player, thing, Listen_Lock))
	    || !eval_lock(player, thing, Use_Lock)) {
	  match--;
	  if (errobj)
	    *errobj = thing;
	  /* If we failed the lock, there's no point in continuing at all. */
	  goto exit_sequence;
	}
      }
      if (atrname && abp) {
	safe_chr(' ', atrname, abp);
	safe_dbref(thing, atrname, abp);
	safe_chr('/', atrname, abp);
	safe_str(AL_NAME(ptr), atrname, abp);
      }
      if (!just_match)
	parse_que(thing, s, player);
    }
  }

  /* Don't need to free used_list here, because if !parent_depth,
   * we would never have allocated it. */
  if (!parent_depth)
    return match;

  for (parent_depth = MAX_PARENTS, parent = Parent(thing);
       parent_depth-- && parent != NOTHING; parent = Parent(parent)) {
    /* do_rawlog(LT_TRACE, "Searching %s:", Name(parent)); */
    skipcount = 0;
    prev = &used_list;
    for (ptr = List(parent); ptr; ptr = AL_NEXT(ptr)) {
      if (skipcount && ptr == skip[skipcount - 1]) {
	size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
	while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
	       AL_NAME(AL_NEXT(ptr))[len] == '`') {
	  ptr = AL_NEXT(ptr);
	  /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
	}
	skipcount--;
	continue;
      }
      if (AF_Private(ptr)) {
	/* do_rawlog(LT_TRACE, "Private %s:", AL_NAME(ptr)); */
	skip[skipcount] = atr_sub_branch(ptr);
	if (skip[skipcount])
	  skipcount++;
	continue;
      }
      if (find_attr(&prev, AL_NAME(ptr))) {
	/* do_rawlog(LT_TRACE, "Found %s:", AL_NAME(ptr)); */
	if (prev[0]->no_prog || AF_Noprog(ptr)) {
	  skip[skipcount] = atr_sub_branch(ptr);
	  if (skip[skipcount])
	    skipcount++;
	  prev[0]->no_prog = AF_NOPROG;
	}
	continue;
      }
      if (GoodObject(Parent(parent)))
	prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
      if (AF_Noprog(ptr)) {
	/* do_rawlog(LT_TRACE, "NoProg %s:", AL_NAME(ptr)); */
	skip[skipcount] = atr_sub_branch(ptr);
	if (skip[skipcount])
	  skipcount++;
	continue;
      }
      if (!(AL_FLAGS(ptr) & flag_mask))
	continue;
      strcpy(tbuf1, atr_value(ptr));
      s = tbuf1;
      do {
	s = strchr(s + 1, end);
      } while (s && s[-1] == '\\');
      if (!s)
	continue;
      *s++ = '\0';

      if (AF_Regexp(ptr)) {
	/* Turn \: into : */
	char *from, *to;
	for (from = tbuf1, to = tbuf2; *from; from++, to++) {
	  if (*from == '\\' && *(from + 1) == ':')
	    from++;
	  *to = *from;
	}
	*to = '\0';
      } else
	strcpy(tbuf2, tbuf1);

      match_found = 0;
      if (AF_Regexp(ptr)) {
	if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
	  match_found = 1;
	  match++;
	}
      } else {
	if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
	  match_found = 1;
	  match++;
	  wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
	}
      }
      if (match_found) {
	/* Since we're still checking the lock on the child, not the
	 * parent, we don't actually want to reset lock_checked with
	 * each parent checked.  Sorry for the misdirection, Alan.
	 *  - Alex */
	if (!lock_checked) {
	  lock_checked = 1;
	  if ((type == '$' && !eval_lock(player, thing, Command_Lock))
	      || (type == '^' && !eval_lock(player, thing, Listen_Lock))
	      || !eval_lock(player, thing, Use_Lock)) {
	    match--;
	    if (errobj)
	      *errobj = thing;
	    /* If we failed the lock, there's no point in continuing at all. */
	    goto exit_sequence;
	  }
	}
	if (atrname && abp) {
	  safe_chr(' ', atrname, abp);
	  if (Can_Examine(player, parent))
	    safe_dbref(parent, atrname, abp);
	  else
	    safe_dbref(thing, atrname, abp);

	  safe_chr('/', atrname, abp);
	  safe_str(AL_NAME(ptr), atrname, abp);
	}
	if (!just_match) {
	  /* do_rawlog(LT_TRACE, "MATCHED %s:", AL_NAME(ptr)); */
	  parse_que(thing, s, player);
	}
      }
    }
  }

  /* This is where I wish for 'try {} finally {}'... */
exit_sequence:
  while (used_list) {
    UsedAttr *temp = used_list->next;
    mush_free(used_list, "used_attr");
    used_list = temp;
  }
  return match;
}

/** Match input against a specified object's specified $command 
 * attribute. Matches may be glob or regex matches. Used in command hooks.
 * depending on the attribute's flags. 
 * \param thing object containing attributes to check.
 * \param player the enactor, for privilege checks.
 * \param atr the name of the attribute
 * \param str the string to match
 * \retval 1 attribute matched.
 * \retval 0 attribute failed to match.
 */
int
one_comm_match(dbref thing, dbref player, const char *atr, const char *str)
{
  ATTR *ptr;
  char tbuf1[BUFFER_LEN];
  char tbuf2[BUFFER_LEN];
  char *s;

  /* check for lots of easy ways out */
  if (!GoodObject(thing) || Halted(thing) || NoCommand(thing))
    return 0;

  if (!(ptr = atr_get(thing, atr)))
    return 0;

  if (AF_Noprog(ptr) || !AF_Command(ptr))
    return 0;

  strcpy(tbuf1, atr_value(ptr));
  s = tbuf1;
  do {
    s = strchr(s + 1, ':');
  } while (s && s[-1] == '\\');
  if (!s)
    return 0;
  *s++ = '\0';

  if (AF_Regexp(ptr)) {
    /* Turn \: into : */
    char *from, *to;
    for (from = tbuf1, to = tbuf2; *from; from++, to++) {
      if (*from == '\\' && *(from + 1) == ':')
	from++;
      *to = *from;
    }
    *to = '\0';
  } else
    strcpy(tbuf2, tbuf1);

  if (AF_Regexp(ptr) ?
      regexp_match_case(tbuf2 + 1, str, AF_Case(ptr)) :
      wild_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
    if (!eval_lock(player, thing, Command_Lock)
	|| !eval_lock(player, thing, Use_Lock))
      return 0;
    parse_que(thing, s, player);
    return 1;
  }
  return 0;
}

/*======================================================================*/

/** Set or clear an attribute on an object.
 * \verbatim
 * This is the primary function for implementing @set.
 * A new interface (as of 1.6.9p0) for setting attributes,
 * which takes care of case-fixing, object-level flag munging,
 * alias recognition, add/clr distinction, etc.  Enjoy.
 * \endverbatim
 * \param thing object to set the attribute on or remove it from.
 * \param atr name of the attribute to set or clear.
 * \param s value to set the attribute to (or NULL to clear).
 * \param player enactor, for permission checks.
 * \param flags attribute flags.
 * \retval -1 failure of one sort.
 * \retval 0 failure of another sort.
 * \retval 1 success.
 */
int
do_set_atr(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
	   dbref player, int flags)
{
  ATTR *old;
  char name[BUFFER_LEN];
  char tbuf1[BUFFER_LEN];
  int res;
  int was_hearer;
  int was_listener;
  dbref contents;
  if (!EMPTY_ATTRS && s && !*s)
    s = NULL;
  if (IsGarbage(thing)) {
    notify(player, T("Garbage is garbage."));
    return 0;
  }
  if (!controls(player, thing))
    return 0;
  strcpy(name, atr);
  upcasestr(name);
  if (!strcmp(name, "ALIAS") && IsPlayer(thing)) {
    old = atr_get_noparent(thing, "ALIAS");
    if (old) {
      /* Old alias - we're allowed to change to a different case */
      strcpy(tbuf1, atr_value(old));
      if (s && (!*s || (strcasecmp(s, tbuf1) && !ok_player_name(s, player)))) {
	notify(player, T("That is not a valid alias."));
	return -1;
      }
      if (Can_Write_Attr(player, thing, old))
	delete_player(thing, tbuf1);
    } else {
      /* No old alias */
      if (s && *s && !ok_player_name(s, player)) {
	notify(player, T("That is not a valid alias."));
	return -1;
      }
    }
  } else if (s && *s && (!strcmp(name, "FORWARDLIST")
			 || !strcmp(name, "DEBUGFORWARDLIST"))) {
    /* You can only set this to dbrefs of things you're allowed to
     * forward to. If you get one wrong, we puke.
     */
    char *fwdstr, *curr;
    dbref fwd;
    strcpy(tbuf1, s);
    fwdstr = trim_space_sep(tbuf1, ' ');
    while ((curr = split_token(&fwdstr, ' ')) != NULL) {
      if (!is_objid(curr)) {
	notify_format(player, T("%s should contain only dbrefs."), name);
	return -1;
      }
      fwd = parse_objid(curr);
      if (!GoodObject(fwd) || IsGarbage(fwd)) {
	notify_format(player, T("Invalid dbref #%d in %s."), fwd, name);
	return -1;
      }
      if (!Can_Forward(thing, fwd)) {
	notify_format(player, T("I don't think #%d wants to hear from you."),
		      fwd);
	return -1;
      }
    }
    /* If you made it here, all your dbrefs were ok */
  }

  was_hearer = Hearer(thing);
  was_listener = Listener(thing);
  res =
    s ? atr_add(thing, name, s, player, (flags & 0x02) ? AF_NOPROG : NOTHING)
    : atr_clr(thing, name, player);
  if (res == AE_SAFE) {
    notify_format(player, T("Attribute %s is SAFE. Set it !SAFE to modify it."),
		  name);
    return 0;
  } else if (res == AE_BADNAME) {
    notify(player, T("That's not a very good name for an attribute."));
    return 0;
  } else if (res == AE_ERROR) {
    if (*missing_name) {
      if (s && (EMPTY_ATTRS || *s))
	notify_format(player, T("You must set %s first."), missing_name);
      else
	notify_format(player,
		      T("%s is a branch attribute; remove its children first."),
		      missing_name);
    } else
      notify(player, T("That attribute cannot be changed by you."));
    return 0;
  } else if (!res) {
    notify(player, T("No such attribute to reset."));
    return 0;
  }
  if (!strcmp(name, "ALIAS") && IsPlayer(thing)) {
    if (s && *s) {
      add_player(thing, s);
      notify(player, T("Alias set."));
    } else {
      notify(player, T("Alias removed."));
    }
    return 1;
  } else if (!strcmp(name, "LISTEN")) {
    if (IsRoom(thing))
      contents = Contents(thing);
    else {
      contents = Location(thing);
      if (GoodObject(contents))
	contents = Contents(contents);
    }
    if (GoodObject(contents)) {
      if (!s && !was_listener && !Hearer(thing)) {
	notify_except(contents, thing,
		      tprintf(T("%s loses its ears and becomes deaf."),
			      Name(thing)), NA_INTER_PRESENCE);
      } else if (s && !was_hearer && !was_listener) {
	notify_except(contents, thing,
		      tprintf(T("%s grows ears and can now hear."),
			      Name(thing)), NA_INTER_PRESENCE);
      }
    }
  }
  if ((flags & 0x01) && !AreQuiet(player, thing))
    notify_format(player,
		  "%s/%s - %s.", Name(thing), name,
		  s ? T("Set") : T("Cleared"));
  return 1;
}

/** Lock or unlock an attribute.
 * Attribute locks are largely obsolete and should be deprecated,
 * but this is the code that does them.
 * \param player the enactor.
 * \param arg1 the object/attribute, as a string.
 * \param arg2 the desired lock status ('on' or 'off').
 */
void
do_atrlock(dbref player, const char *arg1, const char *arg2)
{
  dbref thing;
  char *p;
  ATTR *ptr;
  int status;
  if (!arg2 || !*arg2)
    status = 0;
  else {
    if (!strcasecmp(arg2, "on")) {
      status = 1;
    } else if (!strcasecmp(arg2, "off")) {
      status = 2;
    } else
      status = 0;
  }

  if (!arg1 || !*arg1) {
    notify(player, T("You need to give an object/attribute pair."));
    return;
  }
  if (!(p = strchr(arg1, '/')) || !(*(p + 1))) {
    notify(player, T("You need to give an object/attribute pair."));
    return;
  }
  *p++ = '\0';
  if ((thing = noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) ==
      NOTHING)
    return;
  if (!controls(player, thing)) {
    notify(player, T("Permission denied."));
    return;
  }

  ptr = atr_get_noparent(thing, strupper(p));
  if (ptr && Can_Read_Attr(player, thing, ptr)) {
    if (!status) {
      notify_format(player, T("That attribute is %slocked."),
		    AF_Locked(ptr) ? "" : "un");
      return;
    } else if (!Can_Write_Attr(player, thing, ptr)) {
      notify(player,
	     T("You need to be able to set the attribute to change its lock."));
      return;
    } else {
      if (status == 1) {
	AL_FLAGS(ptr) |= AF_LOCKED;
	AL_CREATOR(ptr) = Owner(player);
	notify(player, T("Attribute locked."));
	return;
      } else if (status == 2) {
	AL_FLAGS(ptr) &= ~AF_LOCKED;
	notify(player, T("Attribute unlocked."));
	return;
      } else {
	notify(player, T("Invalid status on atrlock.. Notify god."));
	return;
      }
    }
  } else
    notify(player, T("No such attribute."));
  return;
}

/** Change ownership of an attribute.
 * \verbatim
 * This function is used to implement @atrchown.
 * \endverbatim
 * \param player the enactor, for permission checking.
 * \param arg1 the object/attribute to change, as a string.
 * \param arg2 the name of the new owner (or "me").
 */
void
do_atrchown(dbref player, const char *arg1, const char *arg2)
{
  dbref thing, new_owner;
  char *p;
  ATTR *ptr;
  if (!arg1 || !*arg1) {
    notify(player, T("You need to give an object/attribute pair."));
    return;
  }
  if (!(p = strchr(arg1, '/')) || !(*(p + 1))) {
    notify(player, T("You need to give an object/attribute pair."));
    return;
  }
  *p++ = '\0';
  if ((thing = noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) ==
      NOTHING)
    return;
  if (!controls(player, thing)) {
    notify(player, T("Permission denied."));
    return;
  }

  if ((!arg2 && !*arg2) || !strcasecmp(arg2, "me"))
    new_owner = player;
  else
    new_owner = lookup_player(arg2);
  if (new_owner == NOTHING) {
    notify(player, T("I can't find that player"));
    return;
  }

  ptr = atr_get_noparent(thing, strupper(p));
  if (ptr && Can_Read_Attr(player, thing, ptr)) {
    if (Can_Write_Attr(player, thing, ptr)) {
      if (new_owner != Owner(player) && !Wizard(player)) {
	notify(player, T("You can only chown an attribute to yourself."));
	return;
      }
      AL_CREATOR(ptr) = Owner(new_owner);
      notify(player, T("Attribute owner changed."));
      return;
    } else {
      notify(player, T("You don't have the permission to chown that."));
      return;
    }
  } else
    notify(player, T("No such attribute."));
}


/** Return the head of the attribute free list.
 * This function returns the head of the attribute free list. If there
 * are no more ATTR's on the free list, allocate a new page.
 * \return the pointer to the head of the attribute free list.
 */
static ATTR *
get_atr_free_list(void)
{
  if (!atr_free_list) {
    ATTRPAGE *page;
    int j;
    page = (ATTRPAGE *) mush_malloc(sizeof(ATTRPAGE), "ATTRPAGE");
    if (!page)
      mush_panic("Couldn't allocate memory in get_atr_free_list");
    for (j = 0; j < ATTRS_PER_PAGE - 1; j++)
      AL_NEXT(page->atrs + j) = page->atrs + j + 1;
    AL_NEXT(page->atrs + ATTRS_PER_PAGE - 1) = NULL;
    atr_free_list = page->atrs;
  }
  return atr_free_list;
}

/** Return the compressed data for an attribute.
 * This is a chokepoint function for accessing the chunk data.
 * \param atr the attribute struct from which to get the data reference.
 * \return a pointer to the compressed data, in a static buffer.
 */
unsigned char const *
atr_get_compressed_data(ATTR *atr)
{
  static unsigned char buffer[BUFFER_LEN * 2];
  unsigned int len;
  if (!atr->data)
    return (unsigned char *) "";
  len = chunk_fetch(atr->data, buffer, sizeof(buffer));
  if (len > sizeof(buffer))
    return (unsigned char *) "";
  buffer[len] = '\0';
  return buffer;
}

/** Return the uncompressed data for an attribute in a static buffer.
 * This is a wrapper function, to centralize the use of compression/
 * decompression on attributes.
 * \param atr the attribute struct from which to get the data reference.
 * \return a pointer to the uncompressed data, in a static buffer.
 */
char *
atr_value(ATTR *atr)
{
  return uncompress(atr_get_compressed_data(atr));
}

/** Return the uncompressed data for an attribute in a dynamic buffer.
 * This is a wrapper function, to centralize the use of compression/
 * decompression on attributes.
 * \param atr the attribute struct from which to get the data reference.
 * \return a pointer to the uncompressed data, in a dynamic buffer.
 */
char *
safe_atr_value(ATTR *atr)
{
  return safe_uncompress(atr_get_compressed_data(atr));
}