root/lib/common/nvpair.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__new_nvpair
  2. pcmk__free_nvpair
  3. pcmk_prepend_nvpair
  4. pcmk_free_nvpairs
  5. pcmk__scan_nvpair
  6. pcmk__format_nvpair
  7. hash2smartfield
  8. hash2field
  9. hash2metafield
  10. crm_create_nvpair_xml
  11. xml2list
  12. crm_meta_name
  13. crm_meta_value
  14. pcmk__cmp_nvpair_blocks
  15. pcmk__compare_nvpair
  16. pcmk_sort_nvpairs
  17. pcmk_xml_attrs2nvpairs
  18. pcmk__nvpair_add_xml_attr
  19. pcmk_nvpairs2xml_attrs
  20. hash2nvpair

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <stdint.h>         // UINT32_MAX
  14 #include <inttypes.h>       // PRIu32
  15 #include <sys/types.h>
  16 #include <string.h>
  17 #include <ctype.h>
  18 #include <glib.h>
  19 #include <libxml/tree.h>
  20 
  21 #include <crm/crm.h>
  22 #include <crm/common/xml.h>
  23 #include <crm/common/xml_internal.h>
  24 #include "crmcommon_private.h"
  25 
  26 /*
  27  * This file isolates handling of various kinds of name/value pairs:
  28  *
  29  * - pcmk_nvpair_t data type
  30  * - name=value strings
  31  * - XML nvpair elements (<nvpair id=ID name=NAME value=VALUE>)
  32  * - Instance attributes and meta-attributes (for resources and actions)
  33  */
  34 
  35 // pcmk_nvpair_t handling
  36 
  37 /*!
  38  * \internal
  39  * \brief Allocate a new name/value pair
  40  *
  41  * \param[in] name   New name (required)
  42  * \param[in] value  New value
  43  *
  44  * \return Newly allocated name/value pair
  45  * \note The caller is responsible for freeing the result with
  46  *       \c pcmk__free_nvpair().
  47  */
  48 static pcmk_nvpair_t *
  49 pcmk__new_nvpair(const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
  50 {
  51     pcmk_nvpair_t *nvpair = NULL;
  52 
  53     pcmk__assert(name);
  54 
  55     nvpair = pcmk__assert_alloc(1, sizeof(pcmk_nvpair_t));
  56 
  57     nvpair->name = pcmk__str_copy(name);
  58     nvpair->value = pcmk__str_copy(value);
  59     return nvpair;
  60 }
  61 
  62 /*!
  63  * \internal
  64  * \brief Free a name/value pair
  65  *
  66  * \param[in,out] nvpair  Name/value pair to free
  67  */
  68 static void
  69 pcmk__free_nvpair(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
  70 {
  71     if (data) {
  72         pcmk_nvpair_t *nvpair = data;
  73 
  74         free(nvpair->name);
  75         free(nvpair->value);
  76         free(nvpair);
  77     }
  78 }
  79 
  80 /*!
  81  * \brief Prepend a name/value pair to a list
  82  *
  83  * \param[in,out] nvpairs  List to modify
  84  * \param[in]     name     New entry's name
  85  * \param[in]     value    New entry's value
  86  *
  87  * \return New head of list
  88  * \note The caller is responsible for freeing the list with
  89  *       \c pcmk_free_nvpairs().
  90  */
  91 GSList *
  92 pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value));
  95 }
  96 
  97 /*!
  98  * \brief Free a list of name/value pairs
  99  *
 100  * \param[in,out] list  List to free
 101  */
 102 void
 103 pcmk_free_nvpairs(GSList *nvpairs)
     /* [previous][next][first][last][top][bottom][index][help] */
 104 {
 105     g_slist_free_full(nvpairs, pcmk__free_nvpair);
 106 }
 107 
 108 
 109 // name=value string handling
 110 
 111 /*!
 112  * \internal
 113  * \brief Extract the name and value from an input string formatted as "name=value".
 114  * If unable to extract them, they are returned as NULL.
 115  *
 116  * \param[in]  input The input string, likely from the command line
 117  * \param[out] name  Everything before the first '=' in the input string
 118  * \param[out] value Everything after the first '=' in the input string
 119  *
 120  * \return 2 if both name and value could be extracted, 1 if only one could, and
 121  *         and error code otherwise
 122  */
 123 int
 124 pcmk__scan_nvpair(const char *input, char **name, char **value)
     /* [previous][next][first][last][top][bottom][index][help] */
 125 {
 126 #ifdef HAVE_SSCANF_M
 127     *name = NULL;
 128     *value = NULL;
 129     if (sscanf(input, "%m[^=]=%m[^\n]", name, value) <= 0) {
 130         return -pcmk_err_bad_nvpair;
 131     }
 132 #else
 133     char *sep = NULL;
 134     *name = NULL;
 135     *value = NULL;
 136 
 137     sep = strstr(optarg, "=");
 138     if (sep == NULL) {
 139         return -pcmk_err_bad_nvpair;
 140     }
 141 
 142     *name = strndup(input, sep-input);
 143 
 144     if (*name == NULL) {
 145         return -ENOMEM;
 146     }
 147 
 148     /* If the last char in optarg is =, the user gave no
 149      * value for the option.  Leave it as NULL.
 150      */
 151     if (*(sep+1) != '\0') {
 152         *value = strdup(sep+1);
 153 
 154         if (*value == NULL) {
 155             return -ENOMEM;
 156         }
 157     }
 158 #endif
 159 
 160     if (*name != NULL && *value != NULL) {
 161         return 2;
 162     } else if (*name != NULL || *value != NULL) {
 163         return 1;
 164     } else {
 165         return -pcmk_err_bad_nvpair;
 166     }
 167 }
 168 
 169 /*!
 170  * \internal
 171  * \brief Format a name/value pair.
 172  *
 173  * Units can optionally be provided for the value.  Note that unlike most
 174  * formatting functions, this one returns the formatted string.  It is
 175  * assumed that the most common use of this function will be to build up
 176  * a string to be output as part of other functions.
 177  *
 178  * \note The caller is responsible for freeing the return value after use.
 179  *
 180  * \param[in]     name  The name of the nvpair.
 181  * \param[in]     value The value of the nvpair.
 182  * \param[in]     units Optional units for the value, or NULL.
 183  *
 184  * \return Newly allocated string with name/value pair
 185  */
 186 char *
 187 pcmk__format_nvpair(const char *name, const char *value, const char *units)
     /* [previous][next][first][last][top][bottom][index][help] */
 188 {
 189     return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : "");
 190 }
 191 
 192 /*!
 193  * \brief Safely add hash table entry to XML as attribute or name-value pair
 194  *
 195  * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
 196  * and value, with an XML node passed as user data, and adds an XML attribute
 197  * with the specified name and value if it does not already exist. If the key
 198  * name starts with a digit, then it's not a valid XML attribute name. In that
 199  * case, this will instead add a <tt><param name=NAME value=VALUE/></tt> child
 200  * to the XML.
 201  *
 202  * \param[in]     key        Key of hash table entry
 203  * \param[in]     value      Value of hash table entry
 204  * \param[in,out] user_data  XML node
 205  */
 206 void
 207 hash2smartfield(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 208 {
 209     /* @TODO Generate PCMK__XE_PARAM nodes for all keys that aren't valid XML
 210      * attribute names (not just those that start with digits), or possibly for
 211      * all keys to simplify parsing.
 212      *
 213      * Consider either deprecating as public API or exposing PCMK__XE_PARAM.
 214      * PCMK__XE_PARAM is currently private because it doesn't appear in any
 215      * output that Pacemaker generates.
 216      */
 217     const char *name = key;
 218     const char *s_value = value;
 219 
 220     xmlNode *xml_node = user_data;
 221 
 222     if (isdigit(name[0])) {
 223         xmlNode *tmp = pcmk__xe_create(xml_node, PCMK__XE_PARAM);
 224 
 225         crm_xml_add(tmp, PCMK_XA_NAME, name);
 226         crm_xml_add(tmp, PCMK_XA_VALUE, s_value);
 227 
 228     } else if (crm_element_value(xml_node, name) == NULL) {
 229         crm_xml_add(xml_node, name, s_value);
 230         crm_trace("dumped: %s=%s", name, s_value);
 231 
 232     } else {
 233         crm_trace("duplicate: %s=%s", name, s_value);
 234     }
 235 }
 236 
 237 /*!
 238  * \brief Set XML attribute based on hash table entry
 239  *
 240  * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
 241  * and value, with an XML node passed as user data, and adds an XML attribute
 242  * with the specified name and value if it does not already exist.
 243  *
 244  * \param[in]     key        Key of hash table entry
 245  * \param[in]     value      Value of hash table entry
 246  * \param[in,out] user_data  XML node
 247  */
 248 void
 249 hash2field(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 250 {
 251     const char *name = key;
 252     const char *s_value = value;
 253 
 254     xmlNode *xml_node = user_data;
 255 
 256     if (crm_element_value(xml_node, name) == NULL) {
 257         crm_xml_add(xml_node, name, s_value);
 258 
 259     } else {
 260         crm_trace("duplicate: %s=%s", name, s_value);
 261     }
 262 }
 263 
 264 /*!
 265  * \brief Set XML attribute based on hash table entry, as meta-attribute name
 266  *
 267  * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
 268  * and value, with an XML node passed as user data, and adds an XML attribute
 269  * with the meta-attribute version of the specified name and value if it does
 270  * not already exist and if the name does not appear to be cluster-internal.
 271  *
 272  * \param[in]     key        Key of hash table entry
 273  * \param[in]     value      Value of hash table entry
 274  * \param[in,out] user_data  XML node
 275  */
 276 void
 277 hash2metafield(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 278 {
 279     char *crm_name = NULL;
 280 
 281     if (key == NULL || value == NULL) {
 282         return;
 283     }
 284 
 285     /* Filter out cluster-generated attributes that contain a '#' or ':'
 286      * (like fail-count and last-failure).
 287      */
 288     for (crm_name = key; *crm_name; ++crm_name) {
 289         if ((*crm_name == '#') || (*crm_name == ':')) {
 290             return;
 291         }
 292     }
 293 
 294     crm_name = crm_meta_name(key);
 295     hash2field(crm_name, value, user_data);
 296     free(crm_name);
 297 }
 298 
 299 // nvpair handling
 300 
 301 /*!
 302  * \brief Create an XML name/value pair
 303  *
 304  * \param[in,out] parent  If not \c NULL, make new XML node a child of this one
 305  * \param[in]     id      Set this as XML ID (or NULL to auto-generate)
 306  * \param[in]     name    Name to use
 307  * \param[in]     value   Value to use
 308  *
 309  * \return New XML object on success, \c NULL otherwise
 310  */
 311 xmlNode *
 312 crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 313                       const char *value)
 314 {
 315     xmlNode *nvp;
 316 
 317     /* id can be NULL so we auto-generate one, and name can be NULL if this
 318      * will be used to delete a name/value pair by ID, but both can't be NULL
 319      */
 320     CRM_CHECK(id || name, return NULL);
 321 
 322     nvp = pcmk__xe_create(parent, PCMK_XE_NVPAIR);
 323 
 324     if (id) {
 325         crm_xml_add(nvp, PCMK_XA_ID, id);
 326     } else {
 327         pcmk__xe_set_id(nvp, "%s-%s",
 328                         pcmk__s(pcmk__xe_id(parent), PCMK_XE_NVPAIR), name);
 329     }
 330     crm_xml_add(nvp, PCMK_XA_NAME, name);
 331     crm_xml_add(nvp, PCMK_XA_VALUE, value);
 332     return nvp;
 333 }
 334 
 335 /*!
 336  * \brief Retrieve XML attributes as a hash table
 337  *
 338  * Given an XML element, this will look for any \<attributes> element child,
 339  * creating a hash table of (newly allocated string) name/value pairs taken
 340  * first from the attributes element's NAME=VALUE XML attributes, and then
 341  * from any \<param name=NAME value=VALUE> children of attributes.
 342  *
 343  * \param[in]  XML node to parse
 344  *
 345  * \return Hash table with name/value pairs
 346  * \note It is the caller's responsibility to free the result using
 347  *       \c g_hash_table_destroy().
 348  */
 349 GHashTable *
 350 xml2list(const xmlNode *parent)
     /* [previous][next][first][last][top][bottom][index][help] */
 351 {
 352     xmlNode *child = NULL;
 353     xmlAttrPtr pIter = NULL;
 354     xmlNode *nvpair_list = NULL;
 355     GHashTable *nvpair_hash = pcmk__strkey_table(free, free);
 356 
 357     CRM_CHECK(parent != NULL, return nvpair_hash);
 358 
 359     nvpair_list = pcmk__xe_first_child(parent, PCMK__XE_ATTRIBUTES, NULL, NULL);
 360     if (nvpair_list == NULL) {
 361         crm_trace("No attributes in %s", parent->name);
 362         crm_log_xml_trace(parent, "No attributes for resource op");
 363     }
 364 
 365     crm_log_xml_trace(nvpair_list, "Unpacking");
 366 
 367     for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL;
 368          pIter = pIter->next) {
 369 
 370         const char *p_name = (const char *)pIter->name;
 371         const char *p_value = pcmk__xml_attr_value(pIter);
 372 
 373         crm_trace("Added %s=%s", p_name, p_value);
 374 
 375         pcmk__insert_dup(nvpair_hash, p_name, p_value);
 376     }
 377 
 378     for (child = pcmk__xe_first_child(nvpair_list, PCMK__XE_PARAM, NULL, NULL);
 379          child != NULL; child = pcmk__xe_next(child, PCMK__XE_PARAM)) {
 380 
 381         const char *key = crm_element_value(child, PCMK_XA_NAME);
 382         const char *value = crm_element_value(child, PCMK_XA_VALUE);
 383 
 384         crm_trace("Added %s=%s", key, value);
 385         if (key != NULL && value != NULL) {
 386             pcmk__insert_dup(nvpair_hash, key, value);
 387         }
 388     }
 389 
 390     return nvpair_hash;
 391 }
 392 
 393 // Meta-attribute handling
 394 
 395 /*!
 396  * \brief Get the environment variable equivalent of a meta-attribute name
 397  *
 398  * \param[in] attr_name  Name of meta-attribute
 399  *
 400  * \return Newly allocated string for \p attr_name with "CRM_meta_" prefix and
 401  *         underbars instead of dashes
 402  * \note This asserts on an invalid argument or memory allocation error, so
 403  *       callers can assume the result is non-NULL. The caller is responsible
 404  *       for freeing the result using free().
 405  */
 406 char *
 407 crm_meta_name(const char *attr_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 408 {
 409     char *env_name = NULL;
 410 
 411     pcmk__assert(!pcmk__str_empty(attr_name));
 412 
 413     env_name = crm_strdup_printf(CRM_META "_%s", attr_name);
 414     for (char *c = env_name; *c != '\0'; ++c) {
 415         if (*c == '-') {
 416             *c = '_';
 417         }
 418     }
 419     return env_name;
 420 }
 421 
 422 /*!
 423  * \brief Get the value of a meta-attribute
 424  *
 425  * Get the value of a meta-attribute from a hash table whose keys are
 426  * meta-attribute environment variable names (as crm_meta_name() would
 427  * create, like pcmk__graph_action_t:params, not pcmk_resource_t:meta).
 428  *
 429  * \param[in] meta       Hash table of meta-attributes
 430  * \param[in] attr_name  Name of meta-attribute to get
 431  *
 432  * \return Value of given meta-attribute
 433  */
 434 const char *
 435 crm_meta_value(GHashTable *meta, const char *attr_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 436 {
 437     if ((meta != NULL) && (attr_name != NULL)) {
 438         char *key = crm_meta_name(attr_name);
 439         const char *value = g_hash_table_lookup(meta, key);
 440 
 441         free(key);
 442         return value;
 443     }
 444     return NULL;
 445 }
 446 
 447 /*!
 448  * \internal
 449  * \brief Compare processing order of two XML blocks of name/value pairs
 450  *
 451  * \param[in] a          First XML block to compare
 452  * \param[in] b          Second XML block to compare
 453  * \param[in] user_data  pcmk__nvpair_unpack_t with first_id (whether a
 454  *                       particular XML ID should have priority) and overwrite
 455  *                       (whether later-processed blocks will overwrite values
 456  *                       from earlier ones) set as desired
 457  *
 458  * \return Standard comparison return code (a negative value if \p a should sort
 459  *         first, a positive value if \p b should sort first, and 0 if they
 460  *         should sort equally)
 461  * \note This is suitable for use as a GList sorting function.
 462  */
 463 gint
 464 pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 465 {
 466     const xmlNode *pair_a = a;
 467     const xmlNode *pair_b = b;
 468     const pcmk__nvpair_unpack_t *unpack_data = user_data;
 469 
 470     int score_a = 0;
 471     int score_b = 0;
 472     int rc = pcmk_rc_ok;
 473 
 474     /* If we're overwriting values, we want to process blocks from
 475      * lowest priority to highest, so higher-priority values overwrite
 476      * lower-priority ones. If we're not overwriting values, we want to process
 477      * from highest priority to lowest.
 478      */
 479     const gint a_is_higher = ((unpack_data != NULL)
 480                               && unpack_data->overwrite)? 1 : -1;
 481     const gint b_is_higher = -a_is_higher;
 482 
 483     /* NULL values have lowest priority, regardless of the other's score
 484      * (it won't be possible in practice anyway, this is just a failsafe)
 485      */
 486     if (a == NULL) {
 487         return (b == NULL)? 0 : b_is_higher;
 488 
 489     } else if (b == NULL) {
 490         return a_is_higher;
 491     }
 492 
 493     /* A particular XML ID can be specified as having highest priority
 494      * regardless of score (schema validation, if enabled, prevents two blocks
 495      * from having the same ID, so we can ignore handling that case
 496      * specifically)
 497      */
 498     if ((unpack_data != NULL) && (unpack_data->first_id != NULL)) {
 499         if (pcmk__str_eq(pcmk__xe_id(pair_a), unpack_data->first_id,
 500                          pcmk__str_none)) {
 501             return a_is_higher;
 502 
 503         } else if (pcmk__str_eq(pcmk__xe_id(pair_b), unpack_data->first_id,
 504                                 pcmk__str_none)) {
 505             return b_is_higher;
 506         }
 507     }
 508 
 509     // Otherwise, check the scores
 510 
 511     rc = pcmk__xe_get_score(pair_a, PCMK_XA_SCORE, &score_a, 0);
 512     if (rc != pcmk_rc_ok) { // Not possible with schema validation enabled
 513         pcmk__config_warn("Using 0 as %s score because '%s' "
 514                           "is not a valid score: %s",
 515                           pcmk__xe_id(pair_a),
 516                           crm_element_value(pair_a, PCMK_XA_SCORE),
 517                           pcmk_rc_str(rc));
 518     }
 519 
 520     rc = pcmk__xe_get_score(pair_b, PCMK_XA_SCORE, &score_b, 0);
 521     if (rc != pcmk_rc_ok) { // Not possible with schema validation enabled
 522         pcmk__config_warn("Using 0 as %s score because '%s' "
 523                           "is not a valid score: %s",
 524                           pcmk__xe_id(pair_b),
 525                           crm_element_value(pair_b, PCMK_XA_SCORE),
 526                           pcmk_rc_str(rc));
 527     }
 528 
 529     if (score_a < score_b) {
 530         return b_is_higher;
 531 
 532     } else if (score_a > score_b) {
 533         return a_is_higher;
 534     }
 535     return 0;
 536 }
 537 
 538 // Deprecated functions kept only for backward API compatibility
 539 // LCOV_EXCL_START
 540 
 541 #include <crm/common/nvpair_compat.h>
 542 
 543 static gint
 544 pcmk__compare_nvpair(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 545 {
 546     int rc = 0;
 547     const pcmk_nvpair_t *pair_a = a;
 548     const pcmk_nvpair_t *pair_b = b;
 549 
 550     pcmk__assert((pair_a != NULL) && (pair_a->name != NULL)
 551                  && (pair_b != NULL) && (pair_b->name != NULL));
 552 
 553     rc = strcmp(pair_a->name, pair_b->name);
 554     if (rc < 0) {
 555         return -1;
 556     } else if (rc > 0) {
 557         return 1;
 558     }
 559     return 0;
 560 }
 561 
 562 GSList *
 563 pcmk_sort_nvpairs(GSList *list)
     /* [previous][next][first][last][top][bottom][index][help] */
 564 {
 565     return g_slist_sort(list, pcmk__compare_nvpair);
 566 }
 567 
 568 GSList *
 569 pcmk_xml_attrs2nvpairs(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 570 {
 571     GSList *result = NULL;
 572 
 573     for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL;
 574          iter = iter->next) {
 575 
 576         result = pcmk_prepend_nvpair(result,
 577                                      (const char *) iter->name,
 578                                      (const char *) pcmk__xml_attr_value(iter));
 579     }
 580     return result;
 581 }
 582 
 583 static void
 584 pcmk__nvpair_add_xml_attr(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 585 {
 586     pcmk_nvpair_t *pair = data;
 587     xmlNode *parent = user_data;
 588 
 589     crm_xml_add(parent, pair->name, pair->value);
 590 }
 591 
 592 void
 593 pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 594 {
 595     g_slist_foreach(list, pcmk__nvpair_add_xml_attr, xml);
 596 }
 597 
 598 void
 599 hash2nvpair(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 600 {
 601     const char *name = key;
 602     const char *s_value = value;
 603     xmlNode *xml_node = user_data;
 604 
 605     crm_create_nvpair_xml(xml_node, name, name, s_value);
 606     crm_trace("dumped: name=%s value=%s", name, s_value);
 607 }
 608 
 609 // LCOV_EXCL_STOP
 610 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */