pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
nvpair.c
Go to the documentation of this file.
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>
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 
48 static pcmk_nvpair_t *
49 pcmk__new_nvpair(const char *name, const char *value)
50 {
51  pcmk_nvpair_t *nvpair = NULL;
52 
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 
68 static void
69 pcmk__free_nvpair(gpointer data)
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 
91 GSList *
92 pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
93 {
94  return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value));
95 }
96 
102 void
103 pcmk_free_nvpairs(GSList *nvpairs)
104 {
105  g_slist_free_full(nvpairs, pcmk__free_nvpair);
106 }
107 
108 
109 // name=value string handling
110 
123 int
124 pcmk__scan_nvpair(const char *input, char **name, char **value)
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 
186 char *
187 pcmk__format_nvpair(const char *name, const char *value, const char *units)
188 {
189  return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : "");
190 }
191 
206 void
207 hash2smartfield(gpointer key, gpointer value, gpointer user_data)
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 
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 
248 void
249 hash2field(gpointer key, gpointer value, gpointer user_data)
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 
276 void
277 hash2metafield(gpointer key, gpointer value, gpointer user_data)
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 
311 xmlNode *
312 crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
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 
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  }
331  crm_xml_add(nvp, PCMK_XA_VALUE, value);
332  return nvp;
333 }
334 
349 GHashTable *
350 xml2list(const xmlNode *parent)
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 
406 char *
407 crm_meta_name(const char *attr_name)
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 
434 const char *
435 crm_meta_value(GHashTable *meta, const char *attr_name)
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 
463 gint
464 pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data)
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),
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),
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 
542 
543 static gint
544 pcmk__compare_nvpair(gconstpointer a, gconstpointer b)
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)
564 {
565  return g_slist_sort(list, pcmk__compare_nvpair);
566 }
567 
568 GSList *
569 pcmk_xml_attrs2nvpairs(const xmlNode *xml)
570 {
571  GSList *result = NULL;
572 
573  for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL;
574  iter = iter->next) {
575 
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)
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)
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)
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
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
GSList * pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
Prepend a name/value pair to a list.
Definition: nvpair.c:92
A dumping ground.
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml_element.c:42
#define PCMK_XE_NVPAIR
Definition: xml_names.h:144
#define PCMK_XA_NAME
Definition: xml_names.h:330
char * name
Definition: nvpair.h:30
char data[0]
Definition: cpg.c:58
const char * name
Definition: cib.c:26
char * value
Definition: nvpair.h:31
char * pcmk__format_nvpair(const char *name, const char *value, const char *units)
Definition: nvpair.c:187
#define pcmk__config_warn(fmt...)
char * crm_meta_name(const char *attr_name)
Get the environment variable equivalent of a meta-attribute name.
Definition: nvpair.c:407
GHashTable * xml2list(const xmlNode *parent)
Retrieve XML attributes as a hash table.
Definition: nvpair.c:350
GSList * pcmk_xml_attrs2nvpairs(const xmlNode *xml)
Definition: nvpair.c:569
#define PCMK__XE_ATTRIBUTES
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:609
Deprecated Pacemaker name-value pair API.
xmlNode * crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name, const char *value)
Create an XML name/value pair.
Definition: nvpair.c:312
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml_element.c:407
const char * first_id
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
void hash2metafield(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry, as meta-attribute name.
Definition: nvpair.c:277
void pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
Definition: nvpair.c:593
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define crm_trace(fmt, args...)
Definition: logging.h:372
void hash2field(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry.
Definition: nvpair.c:249
void pcmk_free_nvpairs(GSList *nvpairs)
Free a list of name/value pairs.
Definition: nvpair.c:103
void hash2smartfield(gpointer key, gpointer value, gpointer user_data)
Safely add hash table entry to XML as attribute or name-value pair.
Definition: nvpair.c:207
Wrappers for and extensions to libxml2.
void hash2nvpair(gpointer key, gpointer value, gpointer user_data)
Definition: nvpair.c:599
#define PCMK_XA_ID
Definition: xml_names.h:301
#define PCMK_XA_VALUE
Definition: xml_names.h:442
#define PCMK_XA_SCORE
Definition: xml_names.h:396
#define pcmk__str_copy(str)
GSList * pcmk_sort_nvpairs(GSList *list)
Definition: nvpair.c:563
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
Definition: xml_element.c:106
const char * crm_meta_value(GHashTable *meta, const char *attr_name)
Get the value of a meta-attribute.
Definition: nvpair.c:435
#define pcmk__assert(expr)
void int pcmk__xe_get_score(const xmlNode *xml, const char *name, int *score, int default_score)
Definition: xml_element.c:132
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:685
pcmk__action_result_t result
Definition: pcmk_fence.c:37
#define CRM_META
Definition: crm.h:75
xmlNode * input
void pcmk__xe_set_id(xmlNode *xml, const char *format,...) G_GNUC_PRINTF(2
#define PCMK__XE_PARAM
#define crm_log_xml_trace(xml, text)
Definition: logging.h:380
const char * parent
Definition: cib.c:27
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
#define pcmk_err_bad_nvpair
Definition: results.h:94
gint pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data)
Definition: nvpair.c:464
void pcmk__insert_dup(GHashTable *table, const char *name, const char *value)
Definition: strings.c:703
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
int pcmk__scan_nvpair(const char *input, char **name, char **value)
Definition: nvpair.c:124