diff --git a/include/crm/common/nvpair_internal.h b/include/crm/common/nvpair_internal.h index e2d126ec45..4cfc1bcbe2 100644 --- a/include/crm/common/nvpair_internal.h +++ b/include/crm/common/nvpair_internal.h @@ -1,69 +1,71 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_NVPAIR_INTERNAL__H #define PCMK__CRM_COMMON_NVPAIR_INTERNAL__H #include // gboolean #include // xmlNode #include // pcmk_rule_input_t #include // crm_time_t #include // pcmk__str_eq(), etc. #ifdef __cplusplus extern "C" { #endif // Data needed to sort XML blocks of name/value pairs typedef struct unpack_data_s { GHashTable *values; // Where to put name/value pairs const char *first_id; // Block with this XML ID should sort first pcmk_rule_input_t rule_input; // Data used to evaluate rules /* Whether each block's values should overwrite any existing ones * * @COMPAT Only external call paths set this to true. Drop it when we drop * pe_eval_nvpairs() and pe_unpack_nvpairs() after replacing with a new * public API that doesn't overwrite. */ bool overwrite; // If not NULL, this will be set to when rule evaluations will change next crm_time_t *next_change; } pcmk__nvpair_unpack_t; /*! * \internal * \brief Insert a meta-attribute into a hash table * * \param[in] obj Resource (pcmk__resource_private_t) * or action (pcmk_action_t) to add to * \param[in] name Meta-attribute name * \param[in] value Value to add */ #define pcmk__insert_meta(obj, name, value) do { \ if (pcmk__str_eq((value), "#default", pcmk__str_casei)) { \ /* @COMPAT Deprecated since 2.1.8 */ \ pcmk__config_warn("Support for setting meta-attributes " \ "(such as %s) to the explicit value " \ "'#default' is deprecated and will be " \ "removed in a future release", (name)); \ } else if ((value) != NULL) { \ pcmk__insert_dup((obj)->meta, (name), (value)); \ } \ } while (0) int pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t); +int pcmk__xe_get_flags(const xmlNode *xml, const char *name, uint32_t *dest, + uint32_t default_value); #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_NVPAIR_INTERNAL__H diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index 4c8c640226..1637c78e21 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -1,975 +1,1028 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include +#include // UINT32_MAX +#include // PRIu32 #include #include #include #include #include #include #include #include #include "crmcommon_private.h" /* * This file isolates handling of various kinds of name/value pairs: * * - pcmk_nvpair_t data type * - XML attributes () * - XML nvpair elements () * - Meta-attributes (for resources and actions) */ // pcmk_nvpair_t handling /*! * \internal * \brief Allocate a new name/value pair * * \param[in] name New name (required) * \param[in] value New value * * \return Newly allocated name/value pair * \note The caller is responsible for freeing the result with * \c pcmk__free_nvpair(). */ static pcmk_nvpair_t * pcmk__new_nvpair(const char *name, const char *value) { pcmk_nvpair_t *nvpair = NULL; pcmk__assert(name); nvpair = pcmk__assert_alloc(1, sizeof(pcmk_nvpair_t)); nvpair->name = pcmk__str_copy(name); nvpair->value = pcmk__str_copy(value); return nvpair; } /*! * \internal * \brief Free a name/value pair * * \param[in,out] nvpair Name/value pair to free */ static void pcmk__free_nvpair(gpointer data) { if (data) { pcmk_nvpair_t *nvpair = data; free(nvpair->name); free(nvpair->value); free(nvpair); } } /*! * \brief Prepend a name/value pair to a list * * \param[in,out] nvpairs List to modify * \param[in] name New entry's name * \param[in] value New entry's value * * \return New head of list * \note The caller is responsible for freeing the list with * \c pcmk_free_nvpairs(). */ GSList * pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value) { return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value)); } /*! * \brief Free a list of name/value pairs * * \param[in,out] list List to free */ void pcmk_free_nvpairs(GSList *nvpairs) { g_slist_free_full(nvpairs, pcmk__free_nvpair); } // convenience function for name=value strings /*! * \internal * \brief Extract the name and value from an input string formatted as "name=value". * If unable to extract them, they are returned as NULL. * * \param[in] input The input string, likely from the command line * \param[out] name Everything before the first '=' in the input string * \param[out] value Everything after the first '=' in the input string * * \return 2 if both name and value could be extracted, 1 if only one could, and * and error code otherwise */ int pcmk__scan_nvpair(const char *input, char **name, char **value) { #ifdef HAVE_SSCANF_M *name = NULL; *value = NULL; if (sscanf(input, "%m[^=]=%m[^\n]", name, value) <= 0) { return -pcmk_err_bad_nvpair; } #else char *sep = NULL; *name = NULL; *value = NULL; sep = strstr(optarg, "="); if (sep == NULL) { return -pcmk_err_bad_nvpair; } *name = strndup(input, sep-input); if (*name == NULL) { return -ENOMEM; } /* If the last char in optarg is =, the user gave no * value for the option. Leave it as NULL. */ if (*(sep+1) != '\0') { *value = strdup(sep+1); if (*value == NULL) { return -ENOMEM; } } #endif if (*name != NULL && *value != NULL) { return 2; } else if (*name != NULL || *value != NULL) { return 1; } else { return -pcmk_err_bad_nvpair; } } /*! * \internal * \brief Format a name/value pair. * * Units can optionally be provided for the value. Note that unlike most * formatting functions, this one returns the formatted string. It is * assumed that the most common use of this function will be to build up * a string to be output as part of other functions. * * \note The caller is responsible for freeing the return value after use. * * \param[in] name The name of the nvpair. * \param[in] value The value of the nvpair. * \param[in] units Optional units for the value, or NULL. * * \return Newly allocated string with name/value pair */ char * pcmk__format_nvpair(const char *name, const char *value, const char *units) { return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : ""); } // XML attribute handling /*! * \brief Create an XML attribute with specified name and value * * \param[in,out] node XML node to modify * \param[in] name Attribute name to set * \param[in] value Attribute value to set * * \return New value on success, \c NULL otherwise * \note This does nothing if node, name, or value are \c NULL or empty. */ const char * crm_xml_add(xmlNode *node, const char *name, const char *value) { // @TODO Replace with internal function that returns the new attribute bool dirty = FALSE; xmlAttr *attr = NULL; CRM_CHECK(node != NULL, return NULL); CRM_CHECK(name != NULL, return NULL); if (value == NULL) { return NULL; } if (pcmk__tracking_xml_changes(node, FALSE)) { const char *old = crm_element_value(node, name); if (old == NULL || value == NULL || strcmp(old, value) != 0) { dirty = TRUE; } } if (dirty && (pcmk__check_acl(node, name, pcmk__xf_acl_create) == FALSE)) { crm_trace("Cannot add %s=%s to %s", name, value, node->name); return NULL; } attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value); /* If the attribute already exists, this does nothing. Attribute values * don't get private data. */ pcmk__xml_new_private_data((xmlNode *) attr); if (dirty) { pcmk__mark_xml_attr_dirty(attr); } CRM_CHECK(attr && attr->children && attr->children->content, return NULL); return (char *)attr->children->content; } /*! * \brief Create an XML attribute with specified name and integer value * * This is like \c crm_xml_add() but taking an integer value. * * \param[in,out] node XML node to modify * \param[in] name Attribute name to set * \param[in] value Attribute value to set * * \return New value as string on success, \c NULL otherwise * \note This does nothing if node or name are \c NULL or empty. */ const char * crm_xml_add_int(xmlNode *node, const char *name, int value) { char *number = pcmk__itoa(value); const char *added = crm_xml_add(node, name, number); free(number); return added; } /*! * \brief Create an XML attribute with specified name and unsigned value * * This is like \c crm_xml_add() but taking a guint value. * * \param[in,out] node XML node to modify * \param[in] name Attribute name to set * \param[in] ms Attribute value to set * * \return New value as string on success, \c NULL otherwise * \note This does nothing if node or name are \c NULL or empty. */ const char * crm_xml_add_ms(xmlNode *node, const char *name, guint ms) { char *number = crm_strdup_printf("%u", ms); const char *added = crm_xml_add(node, name, number); free(number); return added; } // Maximum size of null-terminated string representation of 64-bit integer // -9223372036854775808 #define LLSTRSIZE 21 /*! * \brief Create an XML attribute with specified name and long long int value * * This is like \c crm_xml_add() but taking a long long int value. It is a * useful equivalent for defined types like time_t, etc. * * \param[in,out] xml XML node to modify * \param[in] name Attribute name to set * \param[in] value Attribute value to set * * \return New value as string on success, \c NULL otherwise * \note This does nothing if xml or name are \c NULL or empty. * This does not support greater than 64-bit values. */ const char * crm_xml_add_ll(xmlNode *xml, const char *name, long long value) { char s[LLSTRSIZE] = { '\0', }; if (snprintf(s, LLSTRSIZE, "%lld", (long long) value) == LLSTRSIZE) { return NULL; } return crm_xml_add(xml, name, s); } /*! * \brief Create XML attributes for seconds and microseconds * * This is like \c crm_xml_add() but taking a struct timeval. * * \param[in,out] xml XML node to modify * \param[in] name_sec Name of XML attribute for seconds * \param[in] name_usec Name of XML attribute for microseconds (or NULL) * \param[in] value Time value to set * * \return New seconds value as string on success, \c NULL otherwise * \note This does nothing if xml, name_sec, or value is \c NULL. */ const char * crm_xml_add_timeval(xmlNode *xml, const char *name_sec, const char *name_usec, const struct timeval *value) { const char *added = NULL; if (xml && name_sec && value) { added = crm_xml_add_ll(xml, name_sec, (long long) value->tv_sec); if (added && name_usec) { // Any error is ignored (we successfully added seconds) crm_xml_add_ll(xml, name_usec, (long long) value->tv_usec); } } return added; } /*! * \brief Retrieve the value of an XML attribute * * \param[in] data XML node to check * \param[in] name Attribute name to check * * \return Value of specified attribute (may be \c NULL) */ const char * crm_element_value(const xmlNode *data, const char *name) { xmlAttr *attr = NULL; if (data == NULL) { crm_err("Couldn't find %s in NULL", name ? name : ""); CRM_LOG_ASSERT(data != NULL); return NULL; } else if (name == NULL) { crm_err("Couldn't find NULL in %s", data->name); return NULL; } attr = xmlHasProp(data, (pcmkXmlStr) name); if (!attr || !attr->children) { return NULL; } return (const char *) attr->children->content; } /*! * \brief Retrieve the integer value of an XML attribute * * This is like \c crm_element_value() but getting the value as an integer. * * \param[in] data XML node to check * \param[in] name Attribute name to check * \param[out] dest Where to store element value * * \return 0 on success, -1 otherwise */ int crm_element_value_int(const xmlNode *data, const char *name, int *dest) { const char *value = NULL; CRM_CHECK(dest != NULL, return -1); value = crm_element_value(data, name); if (value) { long long value_ll; int rc = pcmk__scan_ll(value, &value_ll, 0LL); *dest = PCMK__PARSE_INT_DEFAULT; if (rc != pcmk_rc_ok) { crm_warn("Using default for %s " "because '%s' is not a valid integer: %s", name, value, pcmk_rc_str(rc)); } else if ((value_ll < INT_MIN) || (value_ll > INT_MAX)) { crm_warn("Using default for %s because '%s' is out of range", name, value); } else { *dest = (int) value_ll; return 0; } } return -1; } +/*! + * \brief Retrieve a flag group from an XML attribute value + * + * This is like \c crm_element_value() except getting the value as a 32-bit + * unsigned integer. + * + * \param[in] xml XML node to check + * \param[in] name Attribute name to check (must not be NULL) + * \param[out] dest Where to store flags (may be NULL to just + * validate type) + * \param[in] default_value What to use for missing or invalid value + * + * \return Standard Pacemaker return code + */ +int +pcmk__xe_get_flags(const xmlNode *xml, const char *name, uint32_t *dest, + uint32_t default_value) +{ + const char *value = NULL; + long long value_ll = 0LL; + int rc = pcmk_rc_ok; + + if (dest != NULL) { + *dest = default_value; + } + + if (name == NULL) { + return EINVAL; + } + if (xml == NULL) { + return pcmk_rc_ok; + } + value = crm_element_value(xml, name); + if (value == NULL) { + return pcmk_rc_ok; + } + + rc = pcmk__scan_ll(value, &value_ll, default_value); + if ((value_ll < 0) || (value_ll > UINT32_MAX)) { + value_ll = default_value; + if (rc == pcmk_rc_ok) { + rc = pcmk_rc_bad_input; + } + } + + if (dest != NULL) { + *dest = (uint32_t) value_ll; + } + return rc; +} + /*! * \brief Retrieve the long long integer value of an XML attribute * * This is like \c crm_element_value() but getting the value as a long long int. * * \param[in] data XML node to check * \param[in] name Attribute name to check * \param[out] dest Where to store element value * * \return 0 on success, -1 otherwise */ int crm_element_value_ll(const xmlNode *data, const char *name, long long *dest) { const char *value = NULL; CRM_CHECK(dest != NULL, return -1); value = crm_element_value(data, name); if (value != NULL) { int rc = pcmk__scan_ll(value, dest, PCMK__PARSE_INT_DEFAULT); if (rc == pcmk_rc_ok) { return 0; } crm_warn("Using default for %s " "because '%s' is not a valid integer: %s", name, value, pcmk_rc_str(rc)); } return -1; } /*! * \brief Retrieve the millisecond value of an XML attribute * * This is like \c crm_element_value() but returning the value as a guint. * * \param[in] data XML node to check * \param[in] name Attribute name to check * \param[out] dest Where to store attribute value * * \return \c pcmk_ok on success, -1 otherwise */ int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest) { const char *value = NULL; long long value_ll; int rc = pcmk_rc_ok; CRM_CHECK(dest != NULL, return -1); *dest = 0; value = crm_element_value(data, name); rc = pcmk__scan_ll(value, &value_ll, 0LL); if (rc != pcmk_rc_ok) { crm_warn("Using default for %s " "because '%s' is not valid milliseconds: %s", name, value, pcmk_rc_str(rc)); return -1; } if ((value_ll < 0) || (value_ll > G_MAXUINT)) { crm_warn("Using default for %s because '%s' is out of range", name, value); return -1; } *dest = (guint) value_ll; return pcmk_ok; } /*! * \brief Retrieve the seconds-since-epoch value of an XML attribute * * This is like \c crm_element_value() but returning the value as a time_t. * * \param[in] xml XML node to check * \param[in] name Attribute name to check * \param[out] dest Where to store attribute value * * \return \c pcmk_ok on success, -1 otherwise */ int crm_element_value_epoch(const xmlNode *xml, const char *name, time_t *dest) { long long value_ll = 0; if (crm_element_value_ll(xml, name, &value_ll) < 0) { return -1; } /* Unfortunately, we can't do any bounds checking, since time_t has neither * standardized bounds nor constants defined for them. */ *dest = (time_t) value_ll; return pcmk_ok; } /*! * \brief Retrieve the value of XML second/microsecond attributes as time * * This is like \c crm_element_value() but returning value as a struct timeval. * * \param[in] xml XML to parse * \param[in] name_sec Name of XML attribute for seconds * \param[in] name_usec Name of XML attribute for microseconds * \param[out] dest Where to store result * * \return \c pcmk_ok on success, -errno on error * \note Values default to 0 if XML or XML attribute does not exist */ int crm_element_value_timeval(const xmlNode *xml, const char *name_sec, const char *name_usec, struct timeval *dest) { long long value_i = 0; CRM_CHECK(dest != NULL, return -EINVAL); dest->tv_sec = 0; dest->tv_usec = 0; if (xml == NULL) { return pcmk_ok; } /* Unfortunately, we can't do any bounds checking, since there are no * constants provided for the bounds of time_t and suseconds_t, and * calculating them isn't worth the effort. If there are XML values * beyond the native sizes, there will probably be worse problems anyway. */ // Parse seconds errno = 0; if (crm_element_value_ll(xml, name_sec, &value_i) < 0) { return -errno; } dest->tv_sec = (time_t) value_i; // Parse microseconds if (crm_element_value_ll(xml, name_usec, &value_i) < 0) { return -errno; } dest->tv_usec = (suseconds_t) value_i; return pcmk_ok; } /*! * \internal * \brief Get a date/time object from an XML attribute value * * \param[in] xml XML with attribute to parse (from CIB) * \param[in] attr Name of attribute to parse * \param[out] t Where to create date/time object * (\p *t must be NULL initially) * * \return Standard Pacemaker return code * \note The caller is responsible for freeing \p *t using crm_time_free(). */ int pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t) { const char *value = NULL; if ((t == NULL) || (*t != NULL) || (xml == NULL) || (attr == NULL)) { return EINVAL; } value = crm_element_value(xml, attr); if (value != NULL) { *t = crm_time_new(value); if (*t == NULL) { return pcmk_rc_unpack_error; } } return pcmk_rc_ok; } /*! * \brief Retrieve a copy of the value of an XML attribute * * This is like \c crm_element_value() but allocating new memory for the result. * * \param[in] data XML node to check * \param[in] name Attribute name to check * * \return Value of specified attribute (may be \c NULL) * \note The caller is responsible for freeing the result. */ char * crm_element_value_copy(const xmlNode *data, const char *name) { return pcmk__str_copy(crm_element_value(data, name)); } /*! * \brief Safely add hash table entry to XML as attribute or name-value pair * * Suitable for \c g_hash_table_foreach(), this function takes a hash table key * and value, with an XML node passed as user data, and adds an XML attribute * with the specified name and value if it does not already exist. If the key * name starts with a digit, then it's not a valid XML attribute name. In that * case, this will instead add a child * to the XML. * * \param[in] key Key of hash table entry * \param[in] value Value of hash table entry * \param[in,out] user_data XML node */ void hash2smartfield(gpointer key, gpointer value, gpointer user_data) { /* @TODO Generate PCMK__XE_PARAM nodes for all keys that aren't valid XML * attribute names (not just those that start with digits), or possibly for * all keys to simplify parsing. * * Consider either deprecating as public API or exposing PCMK__XE_PARAM. * PCMK__XE_PARAM is currently private because it doesn't appear in any * output that Pacemaker generates. */ const char *name = key; const char *s_value = value; xmlNode *xml_node = user_data; if (isdigit(name[0])) { xmlNode *tmp = pcmk__xe_create(xml_node, PCMK__XE_PARAM); crm_xml_add(tmp, PCMK_XA_NAME, name); crm_xml_add(tmp, PCMK_XA_VALUE, s_value); } else if (crm_element_value(xml_node, name) == NULL) { crm_xml_add(xml_node, name, s_value); crm_trace("dumped: %s=%s", name, s_value); } else { crm_trace("duplicate: %s=%s", name, s_value); } } /*! * \brief Set XML attribute based on hash table entry * * Suitable for \c g_hash_table_foreach(), this function takes a hash table key * and value, with an XML node passed as user data, and adds an XML attribute * with the specified name and value if it does not already exist. * * \param[in] key Key of hash table entry * \param[in] value Value of hash table entry * \param[in,out] user_data XML node */ void hash2field(gpointer key, gpointer value, gpointer user_data) { const char *name = key; const char *s_value = value; xmlNode *xml_node = user_data; if (crm_element_value(xml_node, name) == NULL) { crm_xml_add(xml_node, name, s_value); } else { crm_trace("duplicate: %s=%s", name, s_value); } } /*! * \brief Set XML attribute based on hash table entry, as meta-attribute name * * Suitable for \c g_hash_table_foreach(), this function takes a hash table key * and value, with an XML node passed as user data, and adds an XML attribute * with the meta-attribute version of the specified name and value if it does * not already exist and if the name does not appear to be cluster-internal. * * \param[in] key Key of hash table entry * \param[in] value Value of hash table entry * \param[in,out] user_data XML node */ void hash2metafield(gpointer key, gpointer value, gpointer user_data) { char *crm_name = NULL; if (key == NULL || value == NULL) { return; } /* Filter out cluster-generated attributes that contain a '#' or ':' * (like fail-count and last-failure). */ for (crm_name = key; *crm_name; ++crm_name) { if ((*crm_name == '#') || (*crm_name == ':')) { return; } } crm_name = crm_meta_name(key); hash2field(crm_name, value, user_data); free(crm_name); } // nvpair handling /*! * \brief Create an XML name/value pair * * \param[in,out] parent If not \c NULL, make new XML node a child of this one * \param[in] id Set this as XML ID (or NULL to auto-generate) * \param[in] name Name to use * \param[in] value Value to use * * \return New XML object on success, \c NULL otherwise */ xmlNode * crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name, const char *value) { xmlNode *nvp; /* id can be NULL so we auto-generate one, and name can be NULL if this * will be used to delete a name/value pair by ID, but both can't be NULL */ CRM_CHECK(id || name, return NULL); nvp = pcmk__xe_create(parent, PCMK_XE_NVPAIR); if (id) { crm_xml_add(nvp, PCMK_XA_ID, id); } else { pcmk__xe_set_id(nvp, "%s-%s", pcmk__s(pcmk__xe_id(parent), PCMK_XE_NVPAIR), name); } crm_xml_add(nvp, PCMK_XA_NAME, name); crm_xml_add(nvp, PCMK_XA_VALUE, value); return nvp; } /*! * \brief Retrieve XML attributes as a hash table * * Given an XML element, this will look for any \ element child, * creating a hash table of (newly allocated string) name/value pairs taken * first from the attributes element's NAME=VALUE XML attributes, and then * from any \ children of attributes. * * \param[in] XML node to parse * * \return Hash table with name/value pairs * \note It is the caller's responsibility to free the result using * \c g_hash_table_destroy(). */ GHashTable * xml2list(const xmlNode *parent) { xmlNode *child = NULL; xmlAttrPtr pIter = NULL; xmlNode *nvpair_list = NULL; GHashTable *nvpair_hash = pcmk__strkey_table(free, free); CRM_CHECK(parent != NULL, return nvpair_hash); nvpair_list = pcmk__xe_first_child(parent, PCMK__XE_ATTRIBUTES, NULL, NULL); if (nvpair_list == NULL) { crm_trace("No attributes in %s", parent->name); crm_log_xml_trace(parent, "No attributes for resource op"); } crm_log_xml_trace(nvpair_list, "Unpacking"); for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *)pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); crm_trace("Added %s=%s", p_name, p_value); pcmk__insert_dup(nvpair_hash, p_name, p_value); } for (child = pcmk__xe_first_child(nvpair_list, PCMK__XE_PARAM, NULL, NULL); child != NULL; child = pcmk__xe_next_same(child)) { const char *key = crm_element_value(child, PCMK_XA_NAME); const char *value = crm_element_value(child, PCMK_XA_VALUE); crm_trace("Added %s=%s", key, value); if (key != NULL && value != NULL) { pcmk__insert_dup(nvpair_hash, key, value); } } return nvpair_hash; } void pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value) { crm_xml_add(node, name, pcmk__btoa(value)); } int pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *value) { const char *xml_value = NULL; int ret, rc; if (node == NULL) { return ENODATA; } else if (name == NULL || value == NULL) { return EINVAL; } xml_value = crm_element_value(node, name); if (xml_value == NULL) { return ENODATA; } rc = crm_str_to_boolean(xml_value, &ret); if (rc == 1) { *value = ret; return pcmk_rc_ok; } else { return pcmk_rc_bad_input; } } bool pcmk__xe_attr_is_true(const xmlNode *node, const char *name) { bool value = false; int rc; rc = pcmk__xe_get_bool_attr(node, name, &value); return rc == pcmk_rc_ok && value == true; } // Meta-attribute handling /*! * \brief Get the environment variable equivalent of a meta-attribute name * * \param[in] attr_name Name of meta-attribute * * \return Newly allocated string for \p attr_name with "CRM_meta_" prefix and * underbars instead of dashes * \note This asserts on an invalid argument or memory allocation error, so * callers can assume the result is non-NULL. The caller is responsible * for freeing the result using free(). */ char * crm_meta_name(const char *attr_name) { char *env_name = NULL; pcmk__assert(!pcmk__str_empty(attr_name)); env_name = crm_strdup_printf(CRM_META "_%s", attr_name); for (char *c = env_name; *c != '\0'; ++c) { if (*c == '-') { *c = '_'; } } return env_name; } /*! * \brief Get the value of a meta-attribute * * Get the value of a meta-attribute from a hash table whose keys are * meta-attribute environment variable names (as crm_meta_name() would * create, like pcmk__graph_action_t:params, not pcmk_resource_t:meta). * * \param[in] meta Hash table of meta-attributes * \param[in] attr_name Name of meta-attribute to get * * \return Value of given meta-attribute */ const char * crm_meta_value(GHashTable *meta, const char *attr_name) { if ((meta != NULL) && (attr_name != NULL)) { char *key = crm_meta_name(attr_name); const char *value = g_hash_table_lookup(meta, key); free(key); return value; } return NULL; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include static gint pcmk__compare_nvpair(gconstpointer a, gconstpointer b) { int rc = 0; const pcmk_nvpair_t *pair_a = a; const pcmk_nvpair_t *pair_b = b; pcmk__assert((pair_a != NULL) && (pair_a->name != NULL) && (pair_b != NULL) && (pair_b->name != NULL)); rc = strcmp(pair_a->name, pair_b->name); if (rc < 0) { return -1; } else if (rc > 0) { return 1; } return 0; } GSList * pcmk_sort_nvpairs(GSList *list) { return g_slist_sort(list, pcmk__compare_nvpair); } GSList * pcmk_xml_attrs2nvpairs(const xmlNode *xml) { GSList *result = NULL; for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL; iter = iter->next) { result = pcmk_prepend_nvpair(result, (const char *) iter->name, (const char *) pcmk__xml_attr_value(iter)); } return result; } static void pcmk__nvpair_add_xml_attr(gpointer data, gpointer user_data) { pcmk_nvpair_t *pair = data; xmlNode *parent = user_data; crm_xml_add(parent, pair->name, pair->value); } void pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml) { g_slist_foreach(list, pcmk__nvpair_add_xml_attr, xml); } void hash2nvpair(gpointer key, gpointer value, gpointer user_data) { const char *name = key; const char *s_value = value; xmlNode *xml_node = user_data; crm_create_nvpair_xml(xml_node, name, name, s_value); crm_trace("dumped: name=%s value=%s", name, s_value); } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/tests/nvpair/Makefile.am b/lib/common/tests/nvpair/Makefile.am index 9f762d4853..59d3788242 100644 --- a/lib/common/tests/nvpair/Makefile.am +++ b/lib/common/tests/nvpair/Makefile.am @@ -1,21 +1,22 @@ # # Copyright 2021-2024 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = crm_meta_name_test \ crm_meta_value_test \ pcmk__xe_attr_is_true_test \ - pcmk__xe_get_datetime_test \ pcmk__xe_get_bool_attr_test \ + pcmk__xe_get_datetime_test \ + pcmk__xe_get_flags_test \ pcmk__xe_set_bool_attr_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/nvpair/pcmk__xe_get_flags_test.c b/lib/common/tests/nvpair/pcmk__xe_get_flags_test.c new file mode 100644 index 0000000000..3462405ba5 --- /dev/null +++ b/lib/common/tests/nvpair/pcmk__xe_get_flags_test.c @@ -0,0 +1,117 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // UINT32_MAX +#include + +#include + +#include +#include +#include + +#define DEFAULT_VALUE 0xfff + +static void +assert_flags(const char *value, int expected_rc, unsigned int expected_flags) +{ + int rc = pcmk_rc_ok; + uint32_t flags = 0U; + xmlNode *xml = pcmk__xe_create(NULL, "element"); + + assert_non_null(xml); + crm_xml_add(xml, "attribute", value); + + // Without output argument + assert_int_equal(pcmk__xe_get_flags(xml, "attribute", NULL, DEFAULT_VALUE), + expected_rc); + + // With output argument + rc = pcmk__xe_get_flags(xml, "attribute", &flags, DEFAULT_VALUE); + assert_int_equal(rc, expected_rc); + assert_true(flags == expected_flags); + + pcmk__xml_free(xml); +} + +static void +null_name_invalid(void **state) +{ + int rc = pcmk_rc_ok; + uint32_t flags = 0U; + xmlNode *xml = pcmk__xe_create(NULL, "element"); + + assert_non_null(xml); + + assert_int_equal(pcmk__xe_get_flags(NULL, NULL, NULL, DEFAULT_VALUE), + EINVAL); + + assert_int_equal(pcmk__xe_get_flags(xml, NULL, NULL, DEFAULT_VALUE), + EINVAL); + + rc = pcmk__xe_get_flags(xml, NULL, &flags, DEFAULT_VALUE); + assert_int_equal(rc, EINVAL); + assert_true(flags == DEFAULT_VALUE); + + flags = 0U; + rc = pcmk__xe_get_flags(NULL, NULL, &flags, DEFAULT_VALUE); + assert_int_equal(rc, EINVAL); + assert_true(flags == DEFAULT_VALUE); + + pcmk__xml_free(xml); +} + +static void +null_xml_default(void **state) +{ + int rc = pcmk_rc_ok; + uint32_t flags = 0U; + + assert_int_equal(pcmk__xe_get_flags(NULL, "attribute", NULL, DEFAULT_VALUE), + pcmk_rc_ok); + + rc = pcmk__xe_get_flags(NULL, "attribute", &flags, DEFAULT_VALUE); + assert_int_equal(rc, pcmk_rc_ok); + assert_true(flags == DEFAULT_VALUE); +} + +static void +no_attr_default(void **state) +{ + assert_flags(NULL, pcmk_rc_ok, DEFAULT_VALUE); +} + +static void +invalid_attr_default(void **state) +{ + char *too_big = crm_strdup_printf("%lld", UINT32_MAX + 1LL); + + assert_flags("x", pcmk_rc_bad_input, DEFAULT_VALUE); + assert_flags("-1", pcmk_rc_bad_input, DEFAULT_VALUE); + assert_flags(too_big, pcmk_rc_bad_input, DEFAULT_VALUE); + free(too_big); +} + +static void +valid_attr(void **state) +{ + assert_flags("0", pcmk_rc_ok, 0x0); + assert_flags("15", pcmk_rc_ok, 0x0f); + assert_flags("61462", pcmk_rc_ok, 0xf016); + assert_flags("4294967295", pcmk_rc_ok, 0xffffffff); +} + +PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, + cmocka_unit_test(null_name_invalid), + cmocka_unit_test(null_xml_default), + cmocka_unit_test(no_attr_default), + cmocka_unit_test(invalid_attr_default), + cmocka_unit_test(valid_attr))