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]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
564 {
565 return g_slist_sort(list, pcmk__compare_nvpair);
566 }
567
568 GSList *
569 pcmk_xml_attrs2nvpairs(const xmlNode *xml)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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