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