1    	/*
2    	 * Copyright 2004-2023 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 <sys/types.h>
14   	#include <string.h>
15   	#include <ctype.h>
16   	#include <glib.h>
17   	#include <libxml/tree.h>
18   	
19   	#include <crm/crm.h>
20   	#include <crm/msg_xml.h>
21   	#include <crm/common/xml.h>
22   	#include <crm/common/xml_internal.h>
23   	#include "crmcommon_private.h"
24   	
25   	/*
26   	 * This file isolates handling of three types of name/value pairs:
27   	 *
28   	 * - pcmk_nvpair_t data type
29   	 * - XML attributes (<TAG ... NAME=VALUE ...>)
30   	 * - XML nvpair elements (<nvpair id=ID name=NAME value=VALUE>)
31   	 */
32   	
33   	// pcmk_nvpair_t handling
34   	
35   	/*!
36   	 * \internal
37   	 * \brief Allocate a new name/value pair
38   	 *
39   	 * \param[in] name   New name (required)
40   	 * \param[in] value  New value
41   	 *
42   	 * \return Newly allocated name/value pair
43   	 * \note The caller is responsible for freeing the result with
44   	 *       \c pcmk__free_nvpair().
45   	 */
46   	static pcmk_nvpair_t *
47   	pcmk__new_nvpair(const char *name, const char *value)
48   	{
49   	    pcmk_nvpair_t *nvpair = NULL;
50   	
51   	    CRM_ASSERT(name);
52   	
53   	    nvpair = calloc(1, sizeof(pcmk_nvpair_t));
54   	    CRM_ASSERT(nvpair);
55   	
56   	    pcmk__str_update(&nvpair->name, name);
57   	    pcmk__str_update(&nvpair->value, value);
58   	    return nvpair;
59   	}
60   	
61   	/*!
62   	 * \internal
63   	 * \brief Free a name/value pair
64   	 *
65   	 * \param[in,out] nvpair  Name/value pair to free
66   	 */
67   	static void
68   	pcmk__free_nvpair(gpointer data)
69   	{
70   	    if (data) {
71   	        pcmk_nvpair_t *nvpair = data;
72   	
73   	        free(nvpair->name);
74   	        free(nvpair->value);
75   	        free(nvpair);
76   	    }
77   	}
78   	
79   	/*!
80   	 * \brief Prepend a name/value pair to a list
81   	 *
82   	 * \param[in,out] nvpairs  List to modify
83   	 * \param[in]     name     New entry's name
84   	 * \param[in]     value    New entry's value
85   	 *
86   	 * \return New head of list
87   	 * \note The caller is responsible for freeing the list with
88   	 *       \c pcmk_free_nvpairs().
89   	 */
90   	GSList *
91   	pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
92   	{
93   	    return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value));
94   	}
95   	
96   	/*!
97   	 * \brief Free a list of name/value pairs
98   	 *
99   	 * \param[in,out] list  List to free
100  	 */
101  	void
102  	pcmk_free_nvpairs(GSList *nvpairs)
103  	{
104  	    g_slist_free_full(nvpairs, pcmk__free_nvpair);
105  	}
106  	
107  	/*!
108  	 * \internal
109  	 * \brief Compare two name/value pairs
110  	 *
111  	 * \param[in] a  First name/value pair to compare
112  	 * \param[in] b  Second name/value pair to compare
113  	 *
114  	 * \return 0 if a == b, 1 if a > b, -1 if a < b
115  	 */
116  	static gint
117  	pcmk__compare_nvpair(gconstpointer a, gconstpointer b)
118  	{
119  	    int rc = 0;
120  	    const pcmk_nvpair_t *pair_a = a;
121  	    const pcmk_nvpair_t *pair_b = b;
122  	
123  	    CRM_ASSERT(a != NULL);
124  	    CRM_ASSERT(pair_a->name != NULL);
125  	
126  	    CRM_ASSERT(b != NULL);
127  	    CRM_ASSERT(pair_b->name != NULL);
128  	
129  	    rc = strcmp(pair_a->name, pair_b->name);
130  	    if (rc < 0) {
131  	        return -1;
132  	    } else if (rc > 0) {
133  	        return 1;
134  	    }
135  	    return 0;
136  	}
137  	
138  	/*!
139  	 * \brief Sort a list of name/value pairs
140  	 *
141  	 * \param[in,out] list  List to sort
142  	 *
143  	 * \return New head of list
144  	 */
145  	GSList *
146  	pcmk_sort_nvpairs(GSList *list)
147  	{
148  	    return g_slist_sort(list, pcmk__compare_nvpair);
149  	}
150  	
151  	/*!
152  	 * \brief Create a list of name/value pairs from an XML node's attributes
153  	 *
154  	 * \param[in]  XML to parse
155  	 *
156  	 * \return New list of name/value pairs
157  	 * \note It is the caller's responsibility to free the list with
158  	 *       \c pcmk_free_nvpairs().
159  	 */
160  	GSList *
161  	pcmk_xml_attrs2nvpairs(const xmlNode *xml)
162  	{
163  	    GSList *result = NULL;
164  	
165  	    for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL;
166  	         iter = iter->next) {
167  	
168  	        result = pcmk_prepend_nvpair(result,
169  	                                     (const char *) iter->name,
170  	                                     (const char *) pcmk__xml_attr_value(iter));
171  	    }
172  	    return result;
173  	}
174  	
175  	/*!
176  	 * \internal
177  	 * \brief Add an XML attribute corresponding to a name/value pair
178  	 *
179  	 * Suitable for glib list iterators, this function adds a NAME=VALUE
180  	 * XML attribute based on a given name/value pair.
181  	 *
182  	 * \param[in]  data       Name/value pair
183  	 * \param[out] user_data  XML node to add attributes to
184  	 */
185  	static void
186  	pcmk__nvpair_add_xml_attr(gpointer data, gpointer user_data)
187  	{
188  	    pcmk_nvpair_t *pair = data;
189  	    xmlNode *parent = user_data;
190  	
191  	    crm_xml_add(parent, pair->name, pair->value);
192  	}
193  	
194  	/*!
195  	 * \brief Add XML attributes based on a list of name/value pairs
196  	 *
197  	 * \param[in,out] list  List of name/value pairs
198  	 * \param[in,out] xml   XML node to add attributes to
199  	 */
200  	void
201  	pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
202  	{
203  	    g_slist_foreach(list, pcmk__nvpair_add_xml_attr, xml);
204  	}
205  	
206  	// convenience function for name=value strings
207  	
208  	/*!
209  	 * \internal
210  	 * \brief Extract the name and value from an input string formatted as "name=value".
211  	 * If unable to extract them, they are returned as NULL.
212  	 *
213  	 * \param[in]  input The input string, likely from the command line
214  	 * \param[out] name  Everything before the first '=' in the input string
215  	 * \param[out] value Everything after the first '=' in the input string
216  	 *
217  	 * \return 2 if both name and value could be extracted, 1 if only one could, and
218  	 *         and error code otherwise
219  	 */
220  	int
221  	pcmk__scan_nvpair(const char *input, char **name, char **value)
222  	{
223  	#ifdef HAVE_SSCANF_M
224  	    *name = NULL;
225  	    *value = NULL;
(1) Event allocate_storage: Allocating storage in "*name" to hold parsed value.
(2) Event path: Condition "sscanf(input, "%m[^=]=%m[^\n]", name, value) <= 0", taking true branch.
226  	    if (sscanf(input, "%m[^=]=%m[^\n]", name, value) <= 0) {
227  	        return -pcmk_err_bad_nvpair;
228  	    }
229  	#else
230  	    char *sep = NULL;
231  	    *name = NULL;
232  	    *value = NULL;
233  	
234  	    sep = strstr(optarg, "=");
235  	    if (sep == NULL) {
236  	        return -pcmk_err_bad_nvpair;
237  	    }
238  	
239  	    *name = strndup(input, sep-input);
240  	
241  	    if (*name == NULL) {
242  	        return -ENOMEM;
243  	    }
244  	
245  	    /* If the last char in optarg is =, the user gave no
246  	     * value for the option.  Leave it as NULL.
247  	     */
248  	    if (*(sep+1) != '\0') {
249  	        *value = strdup(sep+1);
250  	
251  	        if (*value == NULL) {
252  	            return -ENOMEM;
253  	        }
254  	    }
255  	#endif
256  	
257  	    if (*name != NULL && *value != NULL) {
258  	        return 2;
259  	    } else if (*name != NULL || *value != NULL) {
260  	        return 1;
261  	    } else {
262  	        return -pcmk_err_bad_nvpair;
263  	    }
264  	}
265  	
266  	/*!
267  	 * \internal
268  	 * \brief Format a name/value pair.
269  	 *
270  	 * Units can optionally be provided for the value.  Note that unlike most
271  	 * formatting functions, this one returns the formatted string.  It is
272  	 * assumed that the most common use of this function will be to build up
273  	 * a string to be output as part of other functions.
274  	 *
275  	 * \note The caller is responsible for freeing the return value after use.
276  	 *
277  	 * \param[in]     name  The name of the nvpair.
278  	 * \param[in]     value The value of the nvpair.
279  	 * \param[in]     units Optional units for the value, or NULL.
280  	 *
281  	 * \return Newly allocated string with name/value pair
282  	 */
283  	char *
284  	pcmk__format_nvpair(const char *name, const char *value, const char *units)
285  	{
286  	    return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : "");
287  	}
288  	
289  	// XML attribute handling
290  	
291  	/*!
292  	 * \brief Create an XML attribute with specified name and value
293  	 *
294  	 * \param[in,out] node   XML node to modify
295  	 * \param[in]     name   Attribute name to set
296  	 * \param[in]     value  Attribute value to set
297  	 *
298  	 * \return New value on success, \c NULL otherwise
299  	 * \note This does nothing if node, name, or value are \c NULL or empty.
300  	 */
301  	const char *
302  	crm_xml_add(xmlNode *node, const char *name, const char *value)
303  	{
304  	    bool dirty = FALSE;
305  	    xmlAttr *attr = NULL;
306  	
307  	    CRM_CHECK(node != NULL, return NULL);
308  	    CRM_CHECK(name != NULL, return NULL);
309  	
310  	    if (value == NULL) {
311  	        return NULL;
312  	    }
313  	
314  	    if (pcmk__tracking_xml_changes(node, FALSE)) {
315  	        const char *old = crm_element_value(node, name);
316  	
317  	        if (old == NULL || value == NULL || strcmp(old, value) != 0) {
318  	            dirty = TRUE;
319  	        }
320  	    }
321  	
322  	    if (dirty && (pcmk__check_acl(node, name, pcmk__xf_acl_create) == FALSE)) {
323  	        crm_trace("Cannot add %s=%s to %s", name, value, node->name);
324  	        return NULL;
325  	    }
326  	
327  	    attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value);
328  	    if (dirty) {
329  	        pcmk__mark_xml_attr_dirty(attr);
330  	    }
331  	
332  	    CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
333  	    return (char *)attr->children->content;
334  	}
335  	
336  	/*!
337  	 * \brief Create an XML attribute with specified name and integer value
338  	 *
339  	 * This is like \c crm_xml_add() but taking an integer value.
340  	 *
341  	 * \param[in,out] node   XML node to modify
342  	 * \param[in]     name   Attribute name to set
343  	 * \param[in]     value  Attribute value to set
344  	 *
345  	 * \return New value as string on success, \c NULL otherwise
346  	 * \note This does nothing if node or name are \c NULL or empty.
347  	 */
348  	const char *
349  	crm_xml_add_int(xmlNode *node, const char *name, int value)
350  	{
351  	    char *number = pcmk__itoa(value);
352  	    const char *added = crm_xml_add(node, name, number);
353  	
354  	    free(number);
355  	    return added;
356  	}
357  	
358  	/*!
359  	 * \brief Create an XML attribute with specified name and unsigned value
360  	 *
361  	 * This is like \c crm_xml_add() but taking a guint value.
362  	 *
363  	 * \param[in,out] node   XML node to modify
364  	 * \param[in]     name   Attribute name to set
365  	 * \param[in]     ms     Attribute value to set
366  	 *
367  	 * \return New value as string on success, \c NULL otherwise
368  	 * \note This does nothing if node or name are \c NULL or empty.
369  	 */
370  	const char *
371  	crm_xml_add_ms(xmlNode *node, const char *name, guint ms)
372  	{
373  	    char *number = crm_strdup_printf("%u", ms);
374  	    const char *added = crm_xml_add(node, name, number);
375  	
376  	    free(number);
377  	    return added;
378  	}
379  	
380  	// Maximum size of null-terminated string representation of 64-bit integer
381  	// -9223372036854775808
382  	#define LLSTRSIZE 21
383  	
384  	/*!
385  	 * \brief Create an XML attribute with specified name and long long int value
386  	 *
387  	 * This is like \c crm_xml_add() but taking a long long int value. It is a
388  	 * useful equivalent for defined types like time_t, etc.
389  	 *
390  	 * \param[in,out] xml    XML node to modify
391  	 * \param[in]     name   Attribute name to set
392  	 * \param[in]     value  Attribute value to set
393  	 *
394  	 * \return New value as string on success, \c NULL otherwise
395  	 * \note This does nothing if xml or name are \c NULL or empty.
396  	 *       This does not support greater than 64-bit values.
397  	 */
398  	const char *
399  	crm_xml_add_ll(xmlNode *xml, const char *name, long long value)
400  	{
401  	    char s[LLSTRSIZE] = { '\0', };
402  	
403  	    if (snprintf(s, LLSTRSIZE, "%lld", (long long) value) == LLSTRSIZE) {
404  	        return NULL;
405  	    }
406  	    return crm_xml_add(xml, name, s);
407  	}
408  	
409  	/*!
410  	 * \brief Create XML attributes for seconds and microseconds
411  	 *
412  	 * This is like \c crm_xml_add() but taking a struct timeval.
413  	 *
414  	 * \param[in,out] xml        XML node to modify
415  	 * \param[in]     name_sec   Name of XML attribute for seconds
416  	 * \param[in]     name_usec  Name of XML attribute for microseconds (or NULL)
417  	 * \param[in]     value      Time value to set
418  	 *
419  	 * \return New seconds value as string on success, \c NULL otherwise
420  	 * \note This does nothing if xml, name_sec, or value is \c NULL.
421  	 */
422  	const char *
423  	crm_xml_add_timeval(xmlNode *xml, const char *name_sec, const char *name_usec,
424  	                    const struct timeval *value)
425  	{
426  	    const char *added = NULL;
427  	
428  	    if (xml && name_sec && value) {
429  	        added = crm_xml_add_ll(xml, name_sec, (long long) value->tv_sec);
430  	        if (added && name_usec) {
431  	            // Any error is ignored (we successfully added seconds)
432  	            crm_xml_add_ll(xml, name_usec, (long long) value->tv_usec);
433  	        }
434  	    }
435  	    return added;
436  	}
437  	
438  	/*!
439  	 * \brief Retrieve the value of an XML attribute
440  	 *
441  	 * \param[in] data   XML node to check
442  	 * \param[in] name   Attribute name to check
443  	 *
444  	 * \return Value of specified attribute (may be \c NULL)
445  	 */
446  	const char *
447  	crm_element_value(const xmlNode *data, const char *name)
448  	{
449  	    xmlAttr *attr = NULL;
450  	
451  	    if (data == NULL) {
452  	        crm_err("Couldn't find %s in NULL", name ? name : "<null>");
453  	        CRM_LOG_ASSERT(data != NULL);
454  	        return NULL;
455  	
456  	    } else if (name == NULL) {
457  	        crm_err("Couldn't find NULL in %s", data->name);
458  	        return NULL;
459  	    }
460  	
461  	    /* The first argument to xmlHasProp() has always been const,
462  	     * but libxml2 <2.9.2 didn't declare that, so cast it
463  	     */
464  	    attr = xmlHasProp((xmlNode *) data, (pcmkXmlStr) name);
465  	    if (!attr || !attr->children) {
466  	        return NULL;
467  	    }
468  	    return (const char *) attr->children->content;
469  	}
470  	
471  	/*!
472  	 * \brief Retrieve the integer value of an XML attribute
473  	 *
474  	 * This is like \c crm_element_value() but getting the value as an integer.
475  	 *
476  	 * \param[in]  data  XML node to check
477  	 * \param[in]  name  Attribute name to check
478  	 * \param[out] dest  Where to store element value
479  	 *
480  	 * \return 0 on success, -1 otherwise
481  	 */
482  	int
483  	crm_element_value_int(const xmlNode *data, const char *name, int *dest)
484  	{
485  	    const char *value = NULL;
486  	
487  	    CRM_CHECK(dest != NULL, return -1);
488  	    value = crm_element_value(data, name);
489  	    if (value) {
490  	        long long value_ll;
491  	
492  	        if ((pcmk__scan_ll(value, &value_ll, 0LL) != pcmk_rc_ok)
493  	            || (value_ll < INT_MIN) || (value_ll > INT_MAX)) {
494  	            *dest = PCMK__PARSE_INT_DEFAULT;
495  	        } else {
496  	            *dest = (int) value_ll;
497  	            return 0;
498  	        }
499  	    }
500  	    return -1;
501  	}
502  	
503  	/*!
504  	 * \brief Retrieve the long long integer value of an XML attribute
505  	 *
506  	 * This is like \c crm_element_value() but getting the value as a long long int.
507  	 *
508  	 * \param[in]  data  XML node to check
509  	 * \param[in]  name  Attribute name to check
510  	 * \param[out] dest  Where to store element value
511  	 *
512  	 * \return 0 on success, -1 otherwise
513  	 */
514  	int
515  	crm_element_value_ll(const xmlNode *data, const char *name, long long *dest)
516  	{
517  	    const char *value = NULL;
518  	
519  	    CRM_CHECK(dest != NULL, return -1);
520  	    value = crm_element_value(data, name);
521  	    if ((value != NULL)
522  	        && (pcmk__scan_ll(value, dest, PCMK__PARSE_INT_DEFAULT) == pcmk_rc_ok)) {
523  	        return 0;
524  	    }
525  	    return -1;
526  	}
527  	
528  	/*!
529  	 * \brief Retrieve the millisecond value of an XML attribute
530  	 *
531  	 * This is like \c crm_element_value() but returning the value as a guint.
532  	 *
533  	 * \param[in]  data   XML node to check
534  	 * \param[in]  name   Attribute name to check
535  	 * \param[out] dest   Where to store attribute value
536  	 *
537  	 * \return \c pcmk_ok on success, -1 otherwise
538  	 */
539  	int
540  	crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
541  	{
542  	    const char *value = NULL;
543  	    long long value_ll;
544  	
545  	    CRM_CHECK(dest != NULL, return -1);
546  	    *dest = 0;
547  	    value = crm_element_value(data, name);
548  	    if ((pcmk__scan_ll(value, &value_ll, 0LL) != pcmk_rc_ok)
549  	        || (value_ll < 0) || (value_ll > G_MAXUINT)) {
550  	        return -1;
551  	    }
552  	    *dest = (guint) value_ll;
553  	    return pcmk_ok;
554  	}
555  	
556  	/*!
557  	 * \brief Retrieve the seconds-since-epoch value of an XML attribute
558  	 *
559  	 * This is like \c crm_element_value() but returning the value as a time_t.
560  	 *
561  	 * \param[in]  xml    XML node to check
562  	 * \param[in]  name   Attribute name to check
563  	 * \param[out] dest   Where to store attribute value
564  	 *
565  	 * \return \c pcmk_ok on success, -1 otherwise
566  	 */
567  	int
568  	crm_element_value_epoch(const xmlNode *xml, const char *name, time_t *dest)
569  	{
570  	    long long value_ll = 0;
571  	
572  	    if (crm_element_value_ll(xml, name, &value_ll) < 0) {
573  	        return -1;
574  	    }
575  	
576  	    /* Unfortunately, we can't do any bounds checking, since time_t has neither
577  	     * standardized bounds nor constants defined for them.
578  	     */
579  	    *dest = (time_t) value_ll;
580  	    return pcmk_ok;
581  	}
582  	
583  	/*!
584  	 * \brief Retrieve the value of XML second/microsecond attributes as time
585  	 *
586  	 * This is like \c crm_element_value() but returning value as a struct timeval.
587  	 *
588  	 * \param[in]  xml        XML to parse
589  	 * \param[in]  name_sec   Name of XML attribute for seconds
590  	 * \param[in]  name_usec  Name of XML attribute for microseconds
591  	 * \param[out] dest       Where to store result
592  	 *
593  	 * \return \c pcmk_ok on success, -errno on error
594  	 * \note Values default to 0 if XML or XML attribute does not exist
595  	 */
596  	int
597  	crm_element_value_timeval(const xmlNode *xml, const char *name_sec,
598  	                          const char *name_usec, struct timeval *dest)
599  	{
600  	    long long value_i = 0;
601  	
602  	    CRM_CHECK(dest != NULL, return -EINVAL);
603  	    dest->tv_sec = 0;
604  	    dest->tv_usec = 0;
605  	
606  	    if (xml == NULL) {
607  	        return pcmk_ok;
608  	    }
609  	
610  	    /* Unfortunately, we can't do any bounds checking, since there are no
611  	     * constants provided for the bounds of time_t and suseconds_t, and
612  	     * calculating them isn't worth the effort. If there are XML values
613  	     * beyond the native sizes, there will probably be worse problems anyway.
614  	     */
615  	
616  	    // Parse seconds
617  	    errno = 0;
618  	    if (crm_element_value_ll(xml, name_sec, &value_i) < 0) {
619  	        return -errno;
620  	    }
621  	    dest->tv_sec = (time_t) value_i;
622  	
623  	    // Parse microseconds
624  	    if (crm_element_value_ll(xml, name_usec, &value_i) < 0) {
625  	        return -errno;
626  	    }
627  	    dest->tv_usec = (suseconds_t) value_i;
628  	
629  	    return pcmk_ok;
630  	}
631  	
632  	/*!
633  	 * \brief Retrieve a copy of the value of an XML attribute
634  	 *
635  	 * This is like \c crm_element_value() but allocating new memory for the result.
636  	 *
637  	 * \param[in] data   XML node to check
638  	 * \param[in] name   Attribute name to check
639  	 *
640  	 * \return Value of specified attribute (may be \c NULL)
641  	 * \note The caller is responsible for freeing the result.
642  	 */
643  	char *
644  	crm_element_value_copy(const xmlNode *data, const char *name)
645  	{
646  	    char *value_copy = NULL;
647  	
648  	    pcmk__str_update(&value_copy, crm_element_value(data, name));
649  	    return value_copy;
650  	}
651  	
652  	/*!
653  	 * \brief Add hash table entry to XML as (possibly legacy) name/value
654  	 *
655  	 * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
656  	 * and value, with an XML node passed as user data, and adds an XML attribute
657  	 * with the specified name and value if it does not already exist. If the key
658  	 * name starts with a digit, this will instead add a \<param name=NAME
659  	 * value=VALUE/> child to the XML (for legacy compatibility with heartbeat).
660  	 *
661  	 * \param[in]     key        Key of hash table entry
662  	 * \param[in]     value      Value of hash table entry
663  	 * \param[in,out] user_data  XML node
664  	 */
665  	void
666  	hash2smartfield(gpointer key, gpointer value, gpointer user_data)
667  	{
668  	    const char *name = key;
669  	    const char *s_value = value;
670  	
671  	    xmlNode *xml_node = user_data;
672  	
673  	    if (isdigit(name[0])) {
674  	        xmlNode *tmp = create_xml_node(xml_node, XML_TAG_PARAM);
675  	
676  	        crm_xml_add(tmp, XML_NVPAIR_ATTR_NAME, name);
677  	        crm_xml_add(tmp, XML_NVPAIR_ATTR_VALUE, s_value);
678  	
679  	    } else if (crm_element_value(xml_node, name) == NULL) {
680  	        crm_xml_add(xml_node, name, s_value);
681  	        crm_trace("dumped: %s=%s", name, s_value);
682  	
683  	    } else {
684  	        crm_trace("duplicate: %s=%s", name, s_value);
685  	    }
686  	}
687  	
688  	/*!
689  	 * \brief Set XML attribute based on hash table entry
690  	 *
691  	 * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
692  	 * and value, with an XML node passed as user data, and adds an XML attribute
693  	 * with the specified name and value if it does not already exist.
694  	 *
695  	 * \param[in]     key        Key of hash table entry
696  	 * \param[in]     value      Value of hash table entry
697  	 * \param[in,out] user_data  XML node
698  	 */
699  	void
700  	hash2field(gpointer key, gpointer value, gpointer user_data)
701  	{
702  	    const char *name = key;
703  	    const char *s_value = value;
704  	
705  	    xmlNode *xml_node = user_data;
706  	
707  	    if (crm_element_value(xml_node, name) == NULL) {
708  	        crm_xml_add(xml_node, name, s_value);
709  	
710  	    } else {
711  	        crm_trace("duplicate: %s=%s", name, s_value);
712  	    }
713  	}
714  	
715  	/*!
716  	 * \brief Set XML attribute based on hash table entry, as meta-attribute name
717  	 *
718  	 * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
719  	 * and value, with an XML node passed as user data, and adds an XML attribute
720  	 * with the meta-attribute version of the specified name and value if it does
721  	 * not already exist and if the name does not appear to be cluster-internal.
722  	 *
723  	 * \param[in]     key        Key of hash table entry
724  	 * \param[in]     value      Value of hash table entry
725  	 * \param[in,out] user_data  XML node
726  	 */
727  	void
728  	hash2metafield(gpointer key, gpointer value, gpointer user_data)
729  	{
730  	    char *crm_name = NULL;
731  	
732  	    if (key == NULL || value == NULL) {
733  	        return;
734  	    }
735  	
736  	    /* Filter out cluster-generated attributes that contain a '#' or ':'
737  	     * (like fail-count and last-failure).
738  	     */
739  	    for (crm_name = key; *crm_name; ++crm_name) {
740  	        if ((*crm_name == '#') || (*crm_name == ':')) {
741  	            return;
742  	        }
743  	    }
744  	
745  	    crm_name = crm_meta_name(key);
746  	    hash2field(crm_name, value, user_data);
747  	    free(crm_name);
748  	}
749  	
750  	// nvpair handling
751  	
752  	/*!
753  	 * \brief Create an XML name/value pair
754  	 *
755  	 * \param[in,out] parent  If not \c NULL, make new XML node a child of this one
756  	 * \param[in]     id      Set this as XML ID (or NULL to auto-generate)
757  	 * \param[in]     name    Name to use
758  	 * \param[in]     value   Value to use
759  	 *
760  	 * \return New XML object on success, \c NULL otherwise
761  	 */
762  	xmlNode *
763  	crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
764  	                      const char *value)
765  	{
766  	    xmlNode *nvp;
767  	
768  	    /* id can be NULL so we auto-generate one, and name can be NULL if this
769  	     * will be used to delete a name/value pair by ID, but both can't be NULL
770  	     */
771  	    CRM_CHECK(id || name, return NULL);
772  	
773  	    nvp = create_xml_node(parent, XML_CIB_TAG_NVPAIR);
774  	    CRM_CHECK(nvp, return NULL);
775  	
776  	    if (id) {
777  	        crm_xml_add(nvp, XML_ATTR_ID, id);
778  	    } else {
779  	        const char *parent_id = ID(parent);
780  	
781  	        crm_xml_set_id(nvp, "%s-%s",
782  	                       (parent_id? parent_id : XML_CIB_TAG_NVPAIR), name);
783  	    }
784  	    crm_xml_add(nvp, XML_NVPAIR_ATTR_NAME, name);
785  	    crm_xml_add(nvp, XML_NVPAIR_ATTR_VALUE, value);
786  	    return nvp;
787  	}
788  	
789  	/*!
790  	 * \brief Add XML nvpair element based on hash table entry
791  	 *
792  	 * Suitable for \c g_hash_table_foreach(), this function takes a hash table key
793  	 * and value, with an XML node passed as the user data, and adds an \c nvpair
794  	 * XML element with the specified name and value.
795  	 *
796  	 * \param[in]     key        Key of hash table entry
797  	 * \param[in]     value      Value of hash table entry
798  	 * \param[in,out] user_data  XML node
799  	 */
800  	void
801  	hash2nvpair(gpointer key, gpointer value, gpointer user_data)
802  	{
803  	    const char *name = key;
804  	    const char *s_value = value;
805  	    xmlNode *xml_node = user_data;
806  	
807  	    crm_create_nvpair_xml(xml_node, name, name, s_value);
808  	    crm_trace("dumped: name=%s value=%s", name, s_value);
809  	}
810  	
811  	/*!
812  	 * \brief Retrieve XML attributes as a hash table
813  	 *
814  	 * Given an XML element, this will look for any \<attributes> element child,
815  	 * creating a hash table of (newly allocated string) name/value pairs taken
816  	 * first from the attributes element's NAME=VALUE XML attributes, and then
817  	 * from any \<param name=NAME value=VALUE> children of attributes.
818  	 *
819  	 * \param[in]  XML node to parse
820  	 *
821  	 * \return Hash table with name/value pairs
822  	 * \note It is the caller's responsibility to free the result using
823  	 *       \c g_hash_table_destroy().
824  	 */
825  	GHashTable *
826  	xml2list(const xmlNode *parent)
827  	{
828  	    xmlNode *child = NULL;
829  	    xmlAttrPtr pIter = NULL;
830  	    xmlNode *nvpair_list = NULL;
831  	    GHashTable *nvpair_hash = pcmk__strkey_table(free, free);
832  	
833  	    CRM_CHECK(parent != NULL, return nvpair_hash);
834  	
835  	    nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE);
836  	    if (nvpair_list == NULL) {
837  	        crm_trace("No attributes in %s", parent->name);
838  	        crm_log_xml_trace(parent, "No attributes for resource op");
839  	    }
840  	
841  	    crm_log_xml_trace(nvpair_list, "Unpacking");
842  	
843  	    for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL;
844  	         pIter = pIter->next) {
845  	
846  	        const char *p_name = (const char *)pIter->name;
847  	        const char *p_value = pcmk__xml_attr_value(pIter);
848  	
849  	        crm_trace("Added %s=%s", p_name, p_value);
850  	
851  	        g_hash_table_insert(nvpair_hash, strdup(p_name), strdup(p_value));
852  	    }
853  	
854  	    for (child = pcmk__xml_first_child(nvpair_list); child != NULL;
855  	         child = pcmk__xml_next(child)) {
856  	
857  	        if (strcmp((const char *)child->name, XML_TAG_PARAM) == 0) {
858  	            const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
859  	            const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
860  	
861  	            crm_trace("Added %s=%s", key, value);
862  	            if (key != NULL && value != NULL) {
863  	                g_hash_table_insert(nvpair_hash, strdup(key), strdup(value));
864  	            }
865  	        }
866  	    }
867  	
868  	    return nvpair_hash;
869  	}
870  	
871  	void
872  	pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value)
873  	{
874  	    crm_xml_add(node, name, value ? XML_BOOLEAN_TRUE : XML_BOOLEAN_FALSE);
875  	}
876  	
877  	int
878  	pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *value)
879  	{
880  	    const char *xml_value = NULL;
881  	    int ret, rc;
882  	
883  	    if (node == NULL) {
884  	        return ENODATA;
885  	    } else if (name == NULL || value == NULL) {
886  	        return EINVAL;
887  	    }
888  	
889  	    xml_value = crm_element_value(node, name);
890  	
891  	    if (xml_value == NULL) {
892  	        return ENODATA;
893  	    }
894  	
895  	    rc = crm_str_to_boolean(xml_value, &ret);
896  	    if (rc == 1) {
897  	        *value = ret;
898  	        return pcmk_rc_ok;
899  	    } else {
900  	        return pcmk_rc_bad_input;
901  	    }
902  	}
903  	
904  	bool
905  	pcmk__xe_attr_is_true(const xmlNode *node, const char *name)
906  	{
907  	    bool value = false;
908  	    int rc;
909  	
910  	    rc = pcmk__xe_get_bool_attr(node, name, &value);
911  	    return rc == pcmk_rc_ok && value == true;
912  	}
913  	
914  	// Deprecated functions kept only for backward API compatibility
915  	// LCOV_EXCL_START
916  	
917  	#include <crm/common/util_compat.h>
918  	
919  	int
920  	pcmk_scan_nvpair(const char *input, char **name, char **value)
921  	{
922  	    return pcmk__scan_nvpair(input, name, value);
923  	}
924  	
925  	char *
926  	pcmk_format_nvpair(const char *name, const char *value,
927  	                   const char *units)
928  	{
929  	    return pcmk__format_nvpair(name, value, units);
930  	}
931  	
932  	char *
933  	pcmk_format_named_time(const char *name, time_t epoch_time)
934  	{
935  	    char *now_s = pcmk__epoch2str(&epoch_time, 0);
936  	    char *result = crm_strdup_printf("%s=\"%s\"", name, pcmk__s(now_s, ""));
937  	
938  	    free(now_s);
939  	    return result;
940  	}
941  	
942  	const char *
943  	crm_xml_replace(xmlNode *node, const char *name, const char *value)
944  	{
945  	    bool dirty = FALSE;
946  	    xmlAttr *attr = NULL;
947  	    const char *old_value = NULL;
948  	
949  	    CRM_CHECK(node != NULL, return NULL);
950  	    CRM_CHECK(name != NULL && name[0] != 0, return NULL);
951  	
952  	    old_value = crm_element_value(node, name);
953  	
954  	    /* Could be re-setting the same value */
955  	    CRM_CHECK(old_value != value, return value);
956  	
957  	    if (pcmk__check_acl(node, name, pcmk__xf_acl_write) == FALSE) {
958  	        /* Create a fake object linked to doc->_private instead? */
959  	        crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
960  	        return NULL;
961  	
962  	    } else if (old_value && !value) {
963  	        xml_remove_prop(node, name);
964  	        return NULL;
965  	    }
966  	
967  	    if (pcmk__tracking_xml_changes(node, FALSE)) {
968  	        if (!old_value || !value || !strcmp(old_value, value)) {
969  	            dirty = TRUE;
970  	        }
971  	    }
972  	
973  	    attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value);
974  	    if (dirty) {
975  	        pcmk__mark_xml_attr_dirty(attr);
976  	    }
977  	    CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
978  	    return (char *) attr->children->content;
979  	}
980  	
981  	// LCOV_EXCL_STOP
982  	// End deprecated API
983