pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
nvpair.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2025 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> // gchar, gint, etc.
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
48static pcmk_nvpair_t *
49pcmk__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
68static void
69pcmk__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
91GSList *
92pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
93{
94 return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value));
95}
96
102void
103pcmk_free_nvpairs(GSList *nvpairs)
104{
105 g_slist_free_full(nvpairs, pcmk__free_nvpair);
106}
107
108
109// name=value string handling
110
125int
126pcmk__scan_nvpair(const gchar *input, gchar **name, gchar **value)
127{
128 gchar **nvpair = NULL;
129 int rc = pcmk_rc_ok;
130
131 pcmk__assert(input != NULL);
132 pcmk__assert((name != NULL) && (*name == NULL));
133 pcmk__assert((value != NULL) && (*value == NULL));
134
135 nvpair = g_strsplit(input, "=", 2);
136
137 /* Check whether nvpair is well-formed (short-circuits if input was split
138 * into fewer than 2 tokens)
139 */
140 if (pcmk__str_empty(nvpair[0]) || pcmk__str_empty(nvpair[1])) {
142 goto done;
143 }
144
145 *name = nvpair[0];
146 *value = nvpair[1];
147 pcmk__trim((char *) *value);
148
149 // name and value took ownership
150 nvpair[0] = NULL;
151 nvpair[1] = NULL;
152
153done:
154 g_strfreev(nvpair);
155 return rc;
156}
157
175char *
176pcmk__format_nvpair(const char *name, const char *value, const char *units)
177{
178 return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : "");
179}
180
195void
196hash2smartfield(gpointer key, gpointer value, gpointer user_data)
197{
198 /* @TODO Generate PCMK__XE_PARAM nodes for all keys that aren't valid XML
199 * attribute names (not just those that start with digits), or possibly for
200 * all keys to simplify parsing.
201 *
202 * Consider either deprecating as public API or exposing PCMK__XE_PARAM.
203 * PCMK__XE_PARAM is currently private because it doesn't appear in any
204 * output that Pacemaker generates.
205 */
206 const char *name = key;
207 const char *s_value = value;
208
209 xmlNode *xml_node = user_data;
210
211 if (isdigit(name[0])) {
212 xmlNode *tmp = pcmk__xe_create(xml_node, PCMK__XE_PARAM);
213
215 crm_xml_add(tmp, PCMK_XA_VALUE, s_value);
216
217 } else if (crm_element_value(xml_node, name) == NULL) {
218 crm_xml_add(xml_node, name, s_value);
219 crm_trace("dumped: %s=%s", name, s_value);
220
221 } else {
222 crm_trace("duplicate: %s=%s", name, s_value);
223 }
224}
225
237void
238hash2field(gpointer key, gpointer value, gpointer user_data)
239{
240 const char *name = key;
241 const char *s_value = value;
242
243 xmlNode *xml_node = user_data;
244
245 if (crm_element_value(xml_node, name) == NULL) {
246 crm_xml_add(xml_node, name, s_value);
247
248 } else {
249 crm_trace("duplicate: %s=%s", name, s_value);
250 }
251}
252
265void
266hash2metafield(gpointer key, gpointer value, gpointer user_data)
267{
268 char *crm_name = NULL;
269
270 if (key == NULL || value == NULL) {
271 return;
272 }
273
274 /* Filter out cluster-generated attributes that contain a '#' or ':'
275 * (like fail-count and last-failure).
276 */
277 for (crm_name = key; *crm_name; ++crm_name) {
278 if ((*crm_name == '#') || (*crm_name == ':')) {
279 return;
280 }
281 }
282
283 crm_name = crm_meta_name(key);
284 hash2field(crm_name, value, user_data);
285 free(crm_name);
286}
287
288// nvpair handling
289
300xmlNode *
301crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
302 const char *value)
303{
304 xmlNode *nvp;
305
306 /* id can be NULL so we auto-generate one, and name can be NULL if this
307 * will be used to delete a name/value pair by ID, but both can't be NULL
308 */
309 CRM_CHECK(id || name, return NULL);
310
312
313 if (id) {
314 crm_xml_add(nvp, PCMK_XA_ID, id);
315 } else {
316 pcmk__xe_set_id(nvp, "%s-%s",
317 pcmk__s(pcmk__xe_id(parent), PCMK_XE_NVPAIR), name);
318 }
320 crm_xml_add(nvp, PCMK_XA_VALUE, value);
321 return nvp;
322}
323
338GHashTable *
339xml2list(const xmlNode *parent)
340{
341 xmlNode *child = NULL;
342 xmlAttrPtr pIter = NULL;
343 xmlNode *nvpair_list = NULL;
344 GHashTable *nvpair_hash = pcmk__strkey_table(free, free);
345
346 CRM_CHECK(parent != NULL, return nvpair_hash);
347
348 nvpair_list = pcmk__xe_first_child(parent, PCMK__XE_ATTRIBUTES, NULL, NULL);
349 if (nvpair_list == NULL) {
350 crm_trace("No attributes in %s", parent->name);
351 crm_log_xml_trace(parent, "No attributes for resource op");
352 }
353
354 crm_log_xml_trace(nvpair_list, "Unpacking");
355
356 for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL;
357 pIter = pIter->next) {
358
359 const char *p_name = (const char *)pIter->name;
360 const char *p_value = pcmk__xml_attr_value(pIter);
361
362 crm_trace("Added %s=%s", p_name, p_value);
363
364 pcmk__insert_dup(nvpair_hash, p_name, p_value);
365 }
366
367 for (child = pcmk__xe_first_child(nvpair_list, PCMK__XE_PARAM, NULL, NULL);
368 child != NULL; child = pcmk__xe_next(child, PCMK__XE_PARAM)) {
369
370 const char *key = crm_element_value(child, PCMK_XA_NAME);
371 const char *value = crm_element_value(child, PCMK_XA_VALUE);
372
373 crm_trace("Added %s=%s", key, value);
374 if (key != NULL && value != NULL) {
375 pcmk__insert_dup(nvpair_hash, key, value);
376 }
377 }
378
379 return nvpair_hash;
380}
381
391static int
392unpack_nvpair(xmlNode *nvpair, void *userdata)
393{
394 pcmk__nvpair_unpack_t *unpack_data = userdata;
395
396 const char *name = NULL;
397 const char *value = NULL;
398 const char *old_value = NULL;
399 const xmlNode *ref_nvpair = pcmk__xe_resolve_idref(nvpair, NULL);
400
401 if (ref_nvpair == NULL) {
402 /* Not possible with schema validation enabled (error already
403 * logged)
404 */
405 return pcmk_rc_ok;
406 }
407
408 name = crm_element_value(ref_nvpair, PCMK_XA_NAME);
409 value = crm_element_value(ref_nvpair, PCMK_XA_VALUE);
410 if ((name == NULL) || (value == NULL)) {
411 return pcmk_rc_ok; // Not possible with schema validation enabled
412 }
413
414 old_value = g_hash_table_lookup(unpack_data->values, name);
415
416 if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
417 // @COMPAT Deprecated since 2.1.8
418 pcmk__config_warn("Support for setting meta-attributes (such as "
419 "%s) to the explicit value '#default' is "
420 "deprecated and will be removed in a future "
421 "release", name);
422 if (old_value != NULL) {
423 g_hash_table_remove(unpack_data->values, name);
424 }
425
426 } else if ((old_value == NULL) || unpack_data->overwrite) {
427 crm_trace("Setting %s=\"%s\" (was %s)",
428 name, value, pcmk__s(old_value, "unset"));
429 pcmk__insert_dup(unpack_data->values, name, value);
430 }
431 return pcmk_rc_ok;
432}
433
444void
445pcmk__unpack_nvpair_block(gpointer data, gpointer user_data)
446{
447 xmlNode *pair = data;
448 pcmk__nvpair_unpack_t *unpack_data = user_data;
449
450 xmlNode *rule_xml = NULL;
451
452 pcmk__assert((pair != NULL) && (unpack_data != NULL)
453 && (unpack_data->values != NULL));
454
455 rule_xml = pcmk__xe_first_child(pair, PCMK_XE_RULE, NULL, NULL);
456 if ((rule_xml != NULL)
457 && (pcmk_evaluate_rule(rule_xml, &(unpack_data->rule_input),
458 unpack_data->next_change) != pcmk_rc_ok)) {
459 return;
460 }
461
462 crm_trace("Adding name/value pairs from %s %s overwrite",
463 pcmk__xe_id(pair), (unpack_data->overwrite? "with" : "without"));
464 if (pcmk__xe_is(pair->children, PCMK__XE_ATTRIBUTES)) {
465 pair = pair->children;
466 }
467 pcmk__xe_foreach_child(pair, PCMK_XE_NVPAIR, unpack_nvpair, unpack_data);
468}
469
482void
483pcmk_unpack_nvpair_blocks(const xmlNode *xml, const char *element_name,
484 const char *first_id,
485 const pcmk_rule_input_t *rule_input,
486 GHashTable *values, crm_time_t *next_change)
487{
488 GList *blocks = pcmk__xe_dereference_children(xml, element_name);
489
490 if (blocks != NULL) {
492 .values = values,
493 .first_id = first_id,
494 .rule_input = {
495 .now = NULL,
496 },
497 .overwrite = false,
498 .next_change = next_change,
499 };
500
501 if (rule_input != NULL) {
502 data.rule_input = *rule_input;
503 }
504 blocks = g_list_sort_with_data(blocks, pcmk__cmp_nvpair_blocks, &data);
505 g_list_foreach(blocks, pcmk__unpack_nvpair_block, &data);
506 g_list_free(blocks);
507 }
508}
509
510
511// Meta-attribute handling
512
524char *
525crm_meta_name(const char *attr_name)
526{
527 char *env_name = NULL;
528
529 pcmk__assert(!pcmk__str_empty(attr_name));
530
531 env_name = crm_strdup_printf(CRM_META "_%s", attr_name);
532 for (char *c = env_name; *c != '\0'; ++c) {
533 if (*c == '-') {
534 *c = '_';
535 }
536 }
537 return env_name;
538}
539
552const char *
553crm_meta_value(GHashTable *meta, const char *attr_name)
554{
555 if ((meta != NULL) && (attr_name != NULL)) {
556 char *key = crm_meta_name(attr_name);
557 const char *value = g_hash_table_lookup(meta, key);
558
559 free(key);
560 return value;
561 }
562 return NULL;
563}
564
581gint
582pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data)
583{
584 const xmlNode *pair_a = a;
585 const xmlNode *pair_b = b;
586 const pcmk__nvpair_unpack_t *unpack_data = user_data;
587
588 int score_a = 0;
589 int score_b = 0;
590 int rc = pcmk_rc_ok;
591
592 /* If we're overwriting values, we want to process blocks from
593 * lowest priority to highest, so higher-priority values overwrite
594 * lower-priority ones. If we're not overwriting values, we want to process
595 * from highest priority to lowest.
596 */
597 const gint a_is_higher = ((unpack_data != NULL)
598 && unpack_data->overwrite)? 1 : -1;
599 const gint b_is_higher = -a_is_higher;
600
601 /* NULL values have lowest priority, regardless of the other's score
602 * (it won't be possible in practice anyway, this is just a failsafe)
603 */
604 if (a == NULL) {
605 return (b == NULL)? 0 : b_is_higher;
606
607 } else if (b == NULL) {
608 return a_is_higher;
609 }
610
611 /* A particular XML ID can be specified as having highest priority
612 * regardless of score (schema validation, if enabled, prevents two blocks
613 * from having the same ID, so we can ignore handling that case
614 * specifically)
615 */
616 if ((unpack_data != NULL) && (unpack_data->first_id != NULL)) {
617 if (pcmk__str_eq(pcmk__xe_id(pair_a), unpack_data->first_id,
619 return a_is_higher;
620
621 } else if (pcmk__str_eq(pcmk__xe_id(pair_b), unpack_data->first_id,
623 return b_is_higher;
624 }
625 }
626
627 // Otherwise, check the scores
628
629 rc = pcmk__xe_get_score(pair_a, PCMK_XA_SCORE, &score_a, 0);
630 if (rc != pcmk_rc_ok) { // Not possible with schema validation enabled
631 pcmk__config_warn("Using 0 as %s score because '%s' "
632 "is not a valid score: %s",
633 pcmk__xe_id(pair_a),
635 pcmk_rc_str(rc));
636 }
637
638 rc = pcmk__xe_get_score(pair_b, PCMK_XA_SCORE, &score_b, 0);
639 if (rc != pcmk_rc_ok) { // Not possible with schema validation enabled
640 pcmk__config_warn("Using 0 as %s score because '%s' "
641 "is not a valid score: %s",
642 pcmk__xe_id(pair_b),
644 pcmk_rc_str(rc));
645 }
646
647 if (score_a < score_b) {
648 return b_is_higher;
649
650 } else if (score_a > score_b) {
651 return a_is_higher;
652 }
653 return 0;
654}
655
656// Deprecated functions kept only for backward API compatibility
657// LCOV_EXCL_START
658
660
661static gint
662pcmk__compare_nvpair(gconstpointer a, gconstpointer b)
663{
664 int rc = 0;
665 const pcmk_nvpair_t *pair_a = a;
666 const pcmk_nvpair_t *pair_b = b;
667
668 pcmk__assert((pair_a != NULL) && (pair_a->name != NULL)
669 && (pair_b != NULL) && (pair_b->name != NULL));
670
671 rc = strcmp(pair_a->name, pair_b->name);
672 if (rc < 0) {
673 return -1;
674 } else if (rc > 0) {
675 return 1;
676 }
677 return 0;
678}
679
680GSList *
681pcmk_sort_nvpairs(GSList *list)
682{
683 return g_slist_sort(list, pcmk__compare_nvpair);
684}
685
686GSList *
687pcmk_xml_attrs2nvpairs(const xmlNode *xml)
688{
689 GSList *result = NULL;
690
691 for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL;
692 iter = iter->next) {
693
695 (const char *) iter->name,
696 (const char *) pcmk__xml_attr_value(iter));
697 }
698 return result;
699}
700
701static void
702pcmk__nvpair_add_xml_attr(gpointer data, gpointer user_data)
703{
704 pcmk_nvpair_t *pair = data;
705 xmlNode *parent = user_data;
706
707 crm_xml_add(parent, pair->name, pair->value);
708}
709
710void
711pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
712{
713 g_slist_foreach(list, pcmk__nvpair_add_xml_attr, xml);
714}
715
716void
717hash2nvpair(gpointer key, gpointer value, gpointer user_data)
718{
719 const char *name = key;
720 const char *s_value = value;
721 xmlNode *xml_node = user_data;
722
723 crm_create_nvpair_xml(xml_node, name, name, s_value);
724 crm_trace("dumped: name=%s value=%s", name, s_value);
725}
726
727// LCOV_EXCL_STOP
728// End deprecated API
const char * parent
Definition cib.c:27
const char * name
Definition cib.c:26
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
int pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, crm_time_t *next_change)
Evaluate a single rule, including all its conditions.
Definition rules.c:1304
char data[0]
Definition cpg.c:10
A dumping ground.
#define CRM_META
Definition crm.h:75
struct crm_time_s crm_time_t
Definition iso8601.h:32
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_log_xml_trace(xml, text)
Definition logging.h:378
#define crm_trace(fmt, args...)
Definition logging.h:370
#define pcmk__config_warn(fmt...)
xmlNode * input
GHashTable * xml2list(const xmlNode *parent)
Retrieve XML attributes as a hash table.
Definition nvpair.c:339
void hash2nvpair(gpointer key, gpointer value, gpointer user_data)
Definition nvpair.c:717
GSList * pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
Prepend a name/value pair to a list.
Definition nvpair.c:92
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:301
void pcmk__unpack_nvpair_block(gpointer data, gpointer user_data)
Definition nvpair.c:445
void hash2metafield(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry, as meta-attribute name.
Definition nvpair.c:266
GSList * pcmk_sort_nvpairs(GSList *list)
Definition nvpair.c:681
void hash2field(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry.
Definition nvpair.c:238
gint pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data)
Definition nvpair.c:582
char * pcmk__format_nvpair(const char *name, const char *value, const char *units)
Definition nvpair.c:176
void pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
Definition nvpair.c:711
void pcmk_free_nvpairs(GSList *nvpairs)
Free a list of name/value pairs.
Definition nvpair.c:103
void pcmk_unpack_nvpair_blocks(const xmlNode *xml, const char *element_name, const char *first_id, const pcmk_rule_input_t *rule_input, GHashTable *values, crm_time_t *next_change)
Unpack nvpair blocks contained by an XML element into a hash table, evaluated for any rules.
Definition nvpair.c:483
int pcmk__scan_nvpair(const gchar *input, gchar **name, gchar **value)
Definition nvpair.c:126
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:196
char * crm_meta_name(const char *attr_name)
Get the environment variable equivalent of a meta-attribute name.
Definition nvpair.c:525
GSList * pcmk_xml_attrs2nvpairs(const xmlNode *xml)
Definition nvpair.c:687
const char * crm_meta_value(GHashTable *meta, const char *attr_name)
Get the value of a meta-attribute.
Definition nvpair.c:553
Deprecated Pacemaker name-value pair API.
pcmk__action_result_t result
Definition pcmk_fence.c:37
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition results.c:617
@ pcmk_rc_bad_nvpair
Definition results.h:151
@ pcmk_rc_ok
Definition results.h:159
#define pcmk__assert(expr)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
void pcmk__insert_dup(GHashTable *table, const char *name, const char *value)
Definition strings.c:703
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition strings.c:685
char * pcmk__trim(char *str)
Definition strings.c:530
@ pcmk__str_none
@ pcmk__str_casei
#define pcmk__str_copy(str)
char * value
Definition nvpair.h:34
char * name
Definition nvpair.h:33
Data used to evaluate a rule (any NULL items are ignored)
Definition rules.h:57
pcmk_rule_input_t rule_input
const char * first_id
crm_time_t * next_change
GHashTable * values
Wrappers for and extensions to libxml2.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
void int pcmk__xe_get_score(const xmlNode *xml, const char *name, int *score, int default_score)
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition xml_element.c:43
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int(*handler)(xmlNode *xml, void *userdata), void *userdata)
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
void pcmk__xe_set_id(xmlNode *xml, const char *format,...) G_GNUC_PRINTF(2
GList * pcmk__xe_dereference_children(const xmlNode *xml, const char *element_name)
Definition xml_idref.c:128
xmlNode * pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search)
Definition xml_idref.c:85
#define PCMK_XA_SCORE
Definition xml_names.h:396
#define PCMK_XE_RULE
Definition xml_names.h:191
#define PCMK_XA_ID
Definition xml_names.h:301
#define PCMK_XA_VALUE
Definition xml_names.h:442
#define PCMK_XE_NVPAIR
Definition xml_names.h:144
#define PCMK_XA_NAME
Definition xml_names.h:330
#define PCMK__XE_ATTRIBUTES
#define PCMK__XE_PARAM