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 <stdarg.h> // va_start(), etc.
13 #include <stdint.h> // uint32_t
14 #include <stdio.h> // NULL, etc.
15 #include <stdlib.h> // free(), etc.
16 #include <string.h> // strchr(), etc.
17 #include <sys/types.h> // time_t, etc.
18
19 #include <libxml/tree.h> // xmlNode, etc.
20 #include <libxml/valid.h> // xmlValidateNameValue()
21 #include <libxml/xmlstring.h> // xmlChar
22
23 #include <crm/crm.h>
24 #include <crm/common/nvpair.h> // crm_xml_add(), etc.
25 #include <crm/common/results.h> // pcmk_rc_ok, etc.
26 #include <crm/common/xml.h>
27 #include "crmcommon_private.h"
28
29 /*!
30 * \internal
31 * \brief Find first XML child element matching given criteria
32 *
33 * \param[in] parent XML element to search (can be \c NULL)
34 * \param[in] node_name If not \c NULL, only match children of this type
35 * \param[in] attr_n If not \c NULL, only match children with an attribute
36 * of this name.
37 * \param[in] attr_v If \p attr_n and this are not NULL, only match children
38 * with an attribute named \p attr_n and this value
39 *
40 * \return Matching XML child element, or \c NULL if none found
41 */
42 xmlNode *
43 pcmk__xe_first_child(const xmlNode *parent, const char *node_name,
/* ![[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)
*/
44 const char *attr_n, const char *attr_v)
45 {
46 xmlNode *child = NULL;
47
48 CRM_CHECK((attr_v == NULL) || (attr_n != NULL), return NULL);
49
50 if (parent == NULL) {
51 return NULL;
52 }
53
54 child = parent->children;
55 while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) {
56 child = child->next;
57 }
58
59 for (; child != NULL; child = pcmk__xe_next(child, NULL)) {
60 const char *value = NULL;
61
62 if ((node_name != NULL) && !pcmk__xe_is(child, node_name)) {
63 // Node name mismatch
64 continue;
65 }
66 if (attr_n == NULL) {
67 // No attribute match needed
68 return child;
69 }
70
71 value = crm_element_value(child, attr_n);
72
73 if ((attr_v == NULL) && (value != NULL)) {
74 // attr_v == NULL: Attribute attr_n must be set (to any value)
75 return child;
76 }
77 if ((attr_v != NULL) && (pcmk__str_eq(value, attr_v, pcmk__str_none))) {
78 // attr_v != NULL: Attribute attr_n must be set to value attr_v
79 return child;
80 }
81 }
82
83 if (attr_n == NULL) {
84 crm_trace("%s XML has no child element of %s type",
85 (const char *) parent->name, pcmk__s(node_name, "any"));
86 } else {
87 crm_trace("%s XML has no child element of %s type with %s='%s'",
88 (const char *) parent->name, pcmk__s(node_name, "any"),
89 attr_n, attr_v);
90 }
91 return NULL;
92 }
93
94 /*!
95 * \internal
96 * \brief Return next sibling element of an XML element
97 *
98 * \param[in] xml XML element to check
99 * \param[in] element_name If not NULL, get next sibling with this element name
100 *
101 * \return Next desired sibling of \p xml (or NULL if none)
102 */
103 xmlNode *
104 pcmk__xe_next(const xmlNode *xml, const char *element_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)
*/
105 {
106 for (xmlNode *next = (xml == NULL)? NULL : xml->next;
107 next != NULL; next = next->next) {
108 if ((next->type == XML_ELEMENT_NODE)
109 && ((element_name == NULL) || pcmk__xe_is(next, element_name))) {
110 return next;
111 }
112 }
113 return NULL;
114 }
115
116 /*!
117 * \internal
118 * \brief Parse an integer score from an XML attribute
119 *
120 * \param[in] xml XML element with attribute to parse
121 * \param[in] name Name of attribute to parse
122 * \param[out] score Where to store parsed score (can be NULL to
123 * just validate)
124 * \param[in] default_score What to return if the attribute value is not
125 * present or invalid
126 *
127 * \return Standard Pacemaker return code
128 */
129 int
130 pcmk__xe_get_score(const xmlNode *xml, const char *name, int *score,
/* ![[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)
*/
131 int default_score)
132 {
133 const char *value = NULL;
134
135 CRM_CHECK((xml != NULL) && (name != NULL), return EINVAL);
136 value = crm_element_value(xml, name);
137 return pcmk_parse_score(value, score, default_score);
138 }
139
140 /*!
141 * \internal
142 * \brief Set an XML attribute, expanding \c ++ and \c += where appropriate
143 *
144 * If \p target already has an attribute named \p name set to an integer value
145 * and \p value is an addition assignment expression on \p name, then expand
146 * \p value to an integer and set attribute \p name to the expanded value in
147 * \p target.
148 *
149 * Otherwise, set attribute \p name on \p target using the literal \p value.
150 *
151 * The original attribute value in \p target and the number in an assignment
152 * expression in \p value are parsed and added as scores (that is, their values
153 * are capped at \c INFINITY and \c -INFINITY). For more details, refer to
154 * \c pcmk_parse_score().
155 *
156 * For example, suppose \p target has an attribute named \c "X" with value
157 * \c "5", and that \p name is \c "X".
158 * * If \p value is \c "X++", the new value of \c "X" in \p target is \c "6".
159 * * If \p value is \c "X+=3", the new value of \c "X" in \p target is \c "8".
160 * * If \p value is \c "val", the new value of \c "X" in \p target is \c "val".
161 * * If \p value is \c "Y++", the new value of \c "X" in \p target is \c "Y++".
162 *
163 * \param[in,out] target XML node whose attribute to set
164 * \param[in] name Name of the attribute to set
165 * \param[in] value New value of attribute to set (if NULL, initial value
166 * will be left unchanged)
167 *
168 * \return Standard Pacemaker return code (specifically, \c EINVAL on invalid
169 * argument, or \c pcmk_rc_ok otherwise)
170 */
171 int
172 pcmk__xe_set_score(xmlNode *target, 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)
*/
173 {
174 const char *old_value = NULL;
175
176 CRM_CHECK((target != NULL) && (name != NULL), return EINVAL);
177
178 if (value == NULL) {
179 // @TODO Maybe instead delete the attribute or set it to 0
180 return pcmk_rc_ok;
181 }
182
183 old_value = crm_element_value(target, name);
184
185 // If no previous value, skip to default case and set the value unexpanded.
186 if (old_value != NULL) {
187 const char *n = name;
188 const char *v = value;
189
190 // Stop at first character that differs between name and value
191 for (; (*n == *v) && (*n != '\0'); n++, v++);
192
193 // If value begins with name followed by a "++" or "+="
194 if ((*n == '\0')
195 && (*v++ == '+')
196 && ((*v == '+') || (*v == '='))) {
197
198 int add = 1;
199 int old_value_i = 0;
200 int rc = pcmk_rc_ok;
201
202 // If we're expanding ourselves, no previous value was set; use 0
203 if (old_value != value) {
204 rc = pcmk_parse_score(old_value, &old_value_i, 0);
205 if (rc != pcmk_rc_ok) {
206 // @TODO This is inconsistent with old_value==NULL
207 crm_trace("Using 0 before incrementing %s because '%s' "
208 "is not a score", name, old_value);
209 }
210 }
211
212 /* value="X++": new value of X is old_value + 1
213 * value="X+=Y": new value of X is old_value + Y (for some number Y)
214 */
215 if (*v != '+') {
216 rc = pcmk_parse_score(++v, &add, 0);
217 if (rc != pcmk_rc_ok) {
218 // @TODO We should probably skip expansion instead
219 crm_trace("Not incrementing %s because '%s' does not have "
220 "a valid increment", name, value);
221 }
222 }
223
224 crm_xml_add_int(target, name, pcmk__add_scores(old_value_i, add));
225 return pcmk_rc_ok;
226 }
227 }
228
229 // Default case: set the attribute unexpanded (with value treated literally)
230 if (old_value != value) {
231 crm_xml_add(target, name, value);
232 }
233 return pcmk_rc_ok;
234 }
235
236 /*!
237 * \internal
238 * \brief Copy XML attributes from a source element to a target element
239 *
240 * This is similar to \c xmlCopyPropList() except that attributes are marked
241 * as dirty for change tracking purposes.
242 *
243 * \param[in,out] target XML element to receive copied attributes from \p src
244 * \param[in] src XML element whose attributes to copy to \p target
245 * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
246 *
247 * \return Standard Pacemaker return code
248 */
249 int
250 pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags)
/* ![[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)
*/
251 {
252 CRM_CHECK((src != NULL) && (target != NULL), return EINVAL);
253
254 for (xmlAttr *attr = pcmk__xe_first_attr(src); attr != NULL;
255 attr = attr->next) {
256
257 const char *name = (const char *) attr->name;
258 const char *value = pcmk__xml_attr_value(attr);
259
260 if (pcmk_is_set(flags, pcmk__xaf_no_overwrite)
261 && (crm_element_value(target, name) != NULL)) {
262 continue;
263 }
264
265 if (pcmk_is_set(flags, pcmk__xaf_score_update)) {
266 pcmk__xe_set_score(target, name, value);
267 } else {
268 crm_xml_add(target, name, value);
269 }
270 }
271
272 return pcmk_rc_ok;
273 }
274
275 /*!
276 * \internal
277 * \brief Compare two XML attributes by name
278 *
279 * \param[in] a First XML attribute to compare
280 * \param[in] b Second XML attribute to compare
281 *
282 * \retval negative \c a->name is \c NULL or comes before \c b->name
283 * lexicographically
284 * \retval 0 \c a->name and \c b->name are equal
285 * \retval positive \c b->name is \c NULL or comes before \c a->name
286 * lexicographically
287 */
288 static gint
289 compare_xml_attr(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)
*/
290 {
291 const xmlAttr *attr_a = a;
292 const xmlAttr *attr_b = b;
293
294 return pcmk__strcmp((const char *) attr_a->name,
295 (const char *) attr_b->name, pcmk__str_none);
296 }
297
298 /*!
299 * \internal
300 * \brief Sort an XML element's attributes by name
301 *
302 * This does not consider ACLs and does not mark the attributes as deleted or
303 * dirty. Upon return, all attributes still exist and are set to the same values
304 * as before the call. The only thing that may change is the order of the
305 * attribute list.
306 *
307 * \param[in,out] xml XML element whose attributes to sort
308 */
309 void
310 pcmk__xe_sort_attrs(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)
*/
311 {
312 GSList *attr_list = NULL;
313
314 for (xmlAttr *iter = pcmk__xe_first_attr(xml); iter != NULL;
315 iter = iter->next) {
316 attr_list = g_slist_prepend(attr_list, iter);
317 }
318 attr_list = g_slist_sort(attr_list, compare_xml_attr);
319
320 for (GSList *iter = attr_list; iter != NULL; iter = iter->next) {
321 xmlNode *attr = iter->data;
322
323 xmlUnlinkNode(attr);
324 xmlAddChild(xml, attr);
325 }
326 g_slist_free(attr_list);
327 }
328
329 /*!
330 * \internal
331 * \brief Remove a named attribute from an XML element
332 *
333 * \param[in,out] element XML element to remove an attribute from
334 * \param[in] name Name of attribute to remove
335 */
336 void
337 pcmk__xe_remove_attr(xmlNode *element, 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)
*/
338 {
339 if (name != NULL) {
340 pcmk__xa_remove(xmlHasProp(element, (const xmlChar *) name), false);
341 }
342 }
343
344 /*!
345 * \internal
346 * \brief Remove a named attribute from an XML element
347 *
348 * This is a wrapper for \c pcmk__xe_remove_attr() for use with
349 * \c pcmk__xml_tree_foreach().
350 *
351 * \param[in,out] xml XML element to remove an attribute from
352 * \param[in] user_data Name of attribute to remove
353 *
354 * \return \c true (to continue traversing the tree)
355 *
356 * \note This is compatible with \c pcmk__xml_tree_foreach().
357 */
358 bool
359 pcmk__xe_remove_attr_cb(xmlNode *xml, void *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)
*/
360 {
361 const char *name = user_data;
362
363 pcmk__xe_remove_attr(xml, name);
364 return true;
365 }
366
367 /*!
368 * \internal
369 * \brief Remove an XML element's attributes that match some criteria
370 *
371 * \param[in,out] element XML element to modify
372 * \param[in] force If \c true, remove matching attributes immediately,
373 * ignoring ACLs and change tracking
374 * \param[in] match If not NULL, only remove attributes for which
375 * this function returns true
376 * \param[in,out] user_data Data to pass to \p match
377 */
378 void
379 pcmk__xe_remove_matching_attrs(xmlNode *element, bool force,
/* ![[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)
*/
380 bool (*match)(xmlAttrPtr, void *),
381 void *user_data)
382 {
383 xmlAttrPtr next = NULL;
384
385 for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
386 next = a->next; // Grab now because attribute might get removed
387 if ((match == NULL) || match(a, user_data)) {
388 if (pcmk__xa_remove(a, force) != pcmk_rc_ok) {
389 return;
390 }
391 }
392 }
393 }
394
395 /*!
396 * \internal
397 * \brief Create a new XML element under a given parent
398 *
399 * \param[in,out] parent XML element that will be the new element's parent
400 * (\c NULL to create a new XML document with the new
401 * node as root)
402 * \param[in] name Name of new element
403 *
404 * \return Newly created XML element (guaranteed not to be \c NULL)
405 */
406 xmlNode *
407 pcmk__xe_create(xmlNode *parent, 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)
*/
408 {
409 xmlNode *node = NULL;
410
411 pcmk__assert(!pcmk__str_empty(name));
412
413 if (parent == NULL) {
414 xmlDoc *doc = pcmk__xml_new_doc();
415
416 node = xmlNewDocRawNode(doc, NULL, (const xmlChar *) name, NULL);
417 pcmk__mem_assert(node);
418
419 xmlDocSetRootElement(doc, node);
420
421 } else {
422 node = xmlNewChild(parent, NULL, (const xmlChar *) name, NULL);
423 pcmk__mem_assert(node);
424 }
425
426 pcmk__xml_new_private_data(node);
427 return node;
428 }
429
430 /*!
431 * \internal
432 * \brief Set a formatted string as an XML node's content
433 *
434 * \param[in,out] node Node whose content to set
435 * \param[in] format <tt>printf(3)</tt>-style format string
436 * \param[in] ... Arguments for \p format
437 *
438 * \note This function escapes special characters. \c xmlNodeSetContent() does
439 * not.
440 */
441 G_GNUC_PRINTF(2, 3)
/* ![[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)
*/
442 void
443 pcmk__xe_set_content(xmlNode *node, const char *format, ...)
444 {
445 if (node != NULL) {
446 const char *content = NULL;
447 char *buf = NULL;
448
449 /* xmlNodeSetContent() frees node->children and replaces it with new
450 * text. If this function is called for a node that already has a non-
451 * text child, it's a bug.
452 */
453 CRM_CHECK((node->children == NULL)
454 || (node->children->type == XML_TEXT_NODE),
455 return);
456
457 if (strchr(format, '%') == NULL) {
458 // Nothing to format
459 content = format;
460
461 } else {
462 va_list ap;
463
464 va_start(ap, format);
465
466 if (pcmk__str_eq(format, "%s", pcmk__str_none)) {
467 // No need to make a copy
468 content = va_arg(ap, const char *);
469
470 } else {
471 pcmk__assert(vasprintf(&buf, format, ap) >= 0);
472 content = buf;
473 }
474 va_end(ap);
475 }
476
477 xmlNodeSetContent(node, (const xmlChar *) content);
478 free(buf);
479 }
480 }
481
482 /*!
483 * \internal
484 * \brief Set a formatted string as an XML element's ID
485 *
486 * If the formatted string would not be a valid ID, it's first sanitized by
487 * \c pcmk__xml_sanitize_id().
488 *
489 * \param[in,out] node Node whose ID to set
490 * \param[in] format <tt>printf(3)</tt>-style format string
491 * \param[in] ... Arguments for \p format
492 */
493 G_GNUC_PRINTF(2, 3)
/* ![[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)
*/
494 void
495 pcmk__xe_set_id(xmlNode *node, const char *format, ...)
496 {
497 char *id = NULL;
498 va_list ap;
499
500 pcmk__assert(!pcmk__str_empty(format));
501
502 if (node == NULL) {
503 return;
504 }
505
506 va_start(ap, format);
507 pcmk__assert(vasprintf(&id, format, ap) >= 0);
508 va_end(ap);
509
510 if (!xmlValidateNameValue((const xmlChar *) id)) {
511 pcmk__xml_sanitize_id(id);
512 }
513 crm_xml_add(node, PCMK_XA_ID, id);
514 free(id);
515 }
516
517 /*!
518 * \internal
519 * \brief Add a "last written" attribute to an XML element, set to current time
520 *
521 * \param[in,out] xe XML element to add attribute to
522 *
523 * \return Value that was set, or NULL on error
524 */
525 const char *
526 pcmk__xe_add_last_written(xmlNode *xe)
/* ![[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)
*/
527 {
528 char *now_s = pcmk__epoch2str(NULL, 0);
529 const char *result = NULL;
530
531 result = crm_xml_add(xe, PCMK_XA_CIB_LAST_WRITTEN,
532 pcmk__s(now_s, "Could not determine current time"));
533 free(now_s);
534 return result;
535 }
536
537 /*!
538 * \internal
539 * \brief Merge one XML tree into another
540 *
541 * Here, "merge" means:
542 * 1. Copy attribute values from \p update to the target, overwriting in case of
543 * conflict.
544 * 2. Descend through \p update and the target in parallel. At each level, for
545 * each child of \p update, look for a matching child of the target.
546 * a. For each child, if a match is found, go to step 1, recursively merging
547 * the child of \p update into the child of the target.
548 * b. Otherwise, copy the child of \p update as a child of the target.
549 *
550 * A match is defined as the first child of the same type within the target,
551 * with:
552 * * the \c PCMK_XA_ID attribute matching, if set in \p update; otherwise,
553 * * the \c PCMK_XA_ID_REF attribute matching, if set in \p update
554 *
555 * This function does not delete any elements or attributes from the target. It
556 * may add elements or overwrite attributes, as described above.
557 *
558 * \param[in,out] parent If \p target is NULL and this is not, add or update
559 * child of this XML node that matches \p update
560 * \param[in,out] target If not NULL, update this XML
561 * \param[in] update Make the desired XML match this (must not be \c NULL)
562 * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
563 *
564 * \note At least one of \p parent and \p target must be non-<tt>NULL</tt>.
565 * \note This function is recursive. For the top-level call, \p parent is
566 * \c NULL and \p target is not \c NULL. For recursive calls, \p target is
567 * \c NULL and \p parent is not \c NULL.
568 */
569 static void
570 update_xe(xmlNode *parent, xmlNode *target, xmlNode *update, uint32_t flags)
/* ![[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)
*/
571 {
572 // @TODO Try to refactor further, possibly using pcmk__xml_tree_foreach()
573 const char *update_name = NULL;
574 const char *update_id_attr = NULL;
575 const char *update_id_val = NULL;
576 char *trace_s = NULL;
577
578 crm_log_xml_trace(update, "update");
579 crm_log_xml_trace(target, "target");
580
581 CRM_CHECK(update != NULL, goto done);
582
583 if (update->type == XML_COMMENT_NODE) {
584 pcmk__xc_update(parent, target, update);
585 goto done;
586 }
587
588 update_name = (const char *) update->name;
589
590 CRM_CHECK(update_name != NULL, goto done);
591 CRM_CHECK((target != NULL) || (parent != NULL), goto done);
592
593 update_id_val = pcmk__xe_id(update);
594 if (update_id_val != NULL) {
595 update_id_attr = PCMK_XA_ID;
596
597 } else {
598 update_id_val = crm_element_value(update, PCMK_XA_ID_REF);
599 if (update_id_val != NULL) {
600 update_id_attr = PCMK_XA_ID_REF;
601 }
602 }
603
604 pcmk__if_tracing(
605 {
606 if (update_id_attr != NULL) {
607 trace_s = crm_strdup_printf("<%s %s=%s/>",
608 update_name, update_id_attr,
609 update_id_val);
610 } else {
611 trace_s = crm_strdup_printf("<%s/>", update_name);
612 }
613 },
614 {}
615 );
616
617 if (target == NULL) {
618 // Recursive call
619 target = pcmk__xe_first_child(parent, update_name, update_id_attr,
620 update_id_val);
621 }
622
623 if (target == NULL) {
624 // Recursive call with no existing matching child
625 target = pcmk__xe_create(parent, update_name);
626 crm_trace("Added %s", pcmk__s(trace_s, update_name));
627
628 } else {
629 // Either recursive call with match, or top-level call
630 crm_trace("Found node %s to update", pcmk__s(trace_s, update_name));
631 }
632
633 CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
634
635 pcmk__xe_copy_attrs(target, update, flags);
636
637 for (xmlNode *child = pcmk__xml_first_child(update); child != NULL;
638 child = pcmk__xml_next(child)) {
639
640 crm_trace("Updating child of %s", pcmk__s(trace_s, update_name));
641 update_xe(target, NULL, child, flags);
642 }
643
644 crm_trace("Finished with %s", pcmk__s(trace_s, update_name));
645
646 done:
647 free(trace_s);
648 }
649
650 /*!
651 * \internal
652 * \brief Delete an XML subtree if it matches a search element
653 *
654 * A match is defined as follows:
655 * * \p xml and \p user_data are both element nodes of the same type.
656 * * If \p user_data has attributes set, \p xml has those attributes set to the
657 * same values. (\p xml may have additional attributes set to arbitrary
658 * values.)
659 *
660 * \param[in,out] xml XML subtree to delete upon match
661 * \param[in] user_data Search element
662 *
663 * \return \c true to continue traversing the tree, or \c false to stop (because
664 * \p xml was deleted)
665 *
666 * \note This is compatible with \c pcmk__xml_tree_foreach().
667 */
668 static bool
669 delete_xe_if_matching(xmlNode *xml, void *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)
*/
670 {
671 xmlNode *search = user_data;
672
673 if (!pcmk__xe_is(search, (const char *) xml->name)) {
674 // No match: either not both elements, or different element types
675 return true;
676 }
677
678 for (const xmlAttr *attr = pcmk__xe_first_attr(search); attr != NULL;
679 attr = attr->next) {
680
681 const char *search_val = pcmk__xml_attr_value(attr);
682 const char *xml_val = crm_element_value(xml, (const char *) attr->name);
683
684 if (!pcmk__str_eq(search_val, xml_val, pcmk__str_casei)) {
685 // No match: an attr in xml doesn't match the attr in search
686 return true;
687 }
688 }
689
690 crm_log_xml_trace(xml, "delete-match");
691 crm_log_xml_trace(search, "delete-search");
692 pcmk__xml_free(xml);
693
694 // Found a match and deleted it; stop traversing tree
695 return false;
696 }
697
698 /*!
699 * \internal
700 * \brief Search an XML tree depth-first and delete the first matching element
701 *
702 * This function does not attempt to match the tree root (\p xml).
703 *
704 * A match with a node \c node is defined as follows:
705 * * \c node and \p search are both element nodes of the same type.
706 * * If \p search has attributes set, \c node has those attributes set to the
707 * same values. (\c node may have additional attributes set to arbitrary
708 * values.)
709 *
710 * \param[in,out] xml XML subtree to search
711 * \param[in] search Element to match against
712 *
713 * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
714 * successful deletion and an error code otherwise)
715 */
716 int
717 pcmk__xe_delete_match(xmlNode *xml, xmlNode *search)
/* ![[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)
*/
718 {
719 // See @COMPAT comment in pcmk__xe_replace_match()
720 CRM_CHECK((xml != NULL) && (search != NULL), return EINVAL);
721
722 for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
723 xml = pcmk__xe_next(xml, NULL)) {
724
725 if (!pcmk__xml_tree_foreach(xml, delete_xe_if_matching, search)) {
726 // Found and deleted an element
727 return pcmk_rc_ok;
728 }
729 }
730
731 // No match found in this subtree
732 return ENXIO;
733 }
734
735 /*!
736 * \internal
737 * \brief Replace one XML node with a copy of another XML node
738 *
739 * This function handles change tracking and applies ACLs.
740 *
741 * \param[in,out] old XML node to replace
742 * \param[in] new XML node to copy as replacement for \p old
743 *
744 * \note This frees \p old.
745 */
746 static void
747 replace_node(xmlNode *old, xmlNode *new)
/* ![[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)
*/
748 {
749 // Pass old for its doc; it won't remain the parent of new
750 new = pcmk__xml_copy(old, new);
751 old = xmlReplaceNode(old, new);
752
753 // old == NULL means memory allocation error
754 pcmk__assert(old != NULL);
755
756 // May be unnecessary but avoids slight changes to some test outputs
757 pcmk__xml_tree_foreach(new, pcmk__xml_reset_node_flags, NULL);
758
759 if (pcmk__xml_doc_all_flags_set(new->doc, pcmk__xf_tracking)) {
760 // Replaced sections may have included relevant ACLs
761 pcmk__apply_acl(new);
762 }
763 pcmk__xml_mark_changes(old, new);
764 pcmk__xml_free_node(old);
765 }
766
767 /*!
768 * \internal
769 * \brief Replace one XML subtree with a copy of another if the two match
770 *
771 * A match is defined as follows:
772 * * \p xml and \p user_data are both element nodes of the same type.
773 * * If \p user_data has the \c PCMK_XA_ID attribute set, then \p xml has
774 * \c PCMK_XA_ID set to the same value.
775 *
776 * \param[in,out] xml XML subtree to replace with \p user_data upon match
777 * \param[in] user_data XML to replace \p xml with a copy of upon match
778 *
779 * \return \c true to continue traversing the tree, or \c false to stop (because
780 * \p xml was replaced by \p user_data)
781 *
782 * \note This is compatible with \c pcmk__xml_tree_foreach().
783 */
784 static bool
785 replace_xe_if_matching(xmlNode *xml, void *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)
*/
786 {
787 xmlNode *replace = user_data;
788 const char *xml_id = NULL;
789 const char *replace_id = NULL;
790
791 xml_id = pcmk__xe_id(xml);
792 replace_id = pcmk__xe_id(replace);
793
794 if (!pcmk__xe_is(replace, (const char *) xml->name)) {
795 // No match: either not both elements, or different element types
796 return true;
797 }
798
799 if ((replace_id != NULL)
800 && !pcmk__str_eq(replace_id, xml_id, pcmk__str_none)) {
801
802 // No match: ID was provided in replace and doesn't match xml's ID
803 return true;
804 }
805
806 crm_log_xml_trace(xml, "replace-match");
807 crm_log_xml_trace(replace, "replace-with");
808 replace_node(xml, replace);
809
810 // Found a match and replaced it; stop traversing tree
811 return false;
812 }
813
814 /*!
815 * \internal
816 * \brief Search an XML tree depth-first and replace the first matching element
817 *
818 * This function does not attempt to match the tree root (\p xml).
819 *
820 * A match with a node \c node is defined as follows:
821 * * \c node and \p replace are both element nodes of the same type.
822 * * If \p replace has the \c PCMK_XA_ID attribute set, then \c node has
823 * \c PCMK_XA_ID set to the same value.
824 *
825 * \param[in,out] xml XML tree to search
826 * \param[in] replace XML to replace a matching element with a copy of
827 *
828 * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
829 * successful replacement and an error code otherwise)
830 */
831 int
832 pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace)
/* ![[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)
*/
833 {
834 /* @COMPAT Some of this behavior (like not matching the tree root, which is
835 * allowed by pcmk__xe_update_match()) is questionable for general use but
836 * required for backward compatibility by cib_process_replace() and
837 * cib_process_delete(). Behavior can change at a major version release if
838 * desired.
839 */
840 CRM_CHECK((xml != NULL) && (replace != NULL), return EINVAL);
841
842 for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
843 xml = pcmk__xe_next(xml, NULL)) {
844
845 if (!pcmk__xml_tree_foreach(xml, replace_xe_if_matching, replace)) {
846 // Found and replaced an element
847 return pcmk_rc_ok;
848 }
849 }
850
851 // No match found in this subtree
852 return ENXIO;
853 }
854
855 //! User data for \c update_xe_if_matching()
856 struct update_data {
857 xmlNode *update; //!< Update source
858 uint32_t flags; //!< Group of <tt>enum pcmk__xa_flags</tt>
859 };
860
861 /*!
862 * \internal
863 * \brief Update one XML subtree with another if the two match
864 *
865 * "Update" means to merge a source subtree into a target subtree (see
866 * \c update_xe()).
867 *
868 * A match is defined as follows:
869 * * \p xml and \p user_data->update are both element nodes of the same type.
870 * * \p xml and \p user_data->update have the same \c PCMK_XA_ID attribute
871 * value, or \c PCMK_XA_ID is unset in both
872 *
873 * \param[in,out] xml XML subtree to update with \p user_data->update
874 * upon match
875 * \param[in] user_data <tt>struct update_data</tt> object
876 *
877 * \return \c true to continue traversing the tree, or \c false to stop (because
878 * \p xml was updated by \p user_data->update)
879 *
880 * \note This is compatible with \c pcmk__xml_tree_foreach().
881 */
882 static bool
883 update_xe_if_matching(xmlNode *xml, void *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)
*/
884 {
885 struct update_data *data = user_data;
886 xmlNode *update = data->update;
887
888 if (!pcmk__xe_is(update, (const char *) xml->name)) {
889 // No match: either not both elements, or different element types
890 return true;
891 }
892
893 if (!pcmk__str_eq(pcmk__xe_id(xml), pcmk__xe_id(update), pcmk__str_none)) {
894 // No match: ID mismatch
895 return true;
896 }
897
898 crm_log_xml_trace(xml, "update-match");
899 crm_log_xml_trace(update, "update-with");
900 update_xe(NULL, xml, update, data->flags);
901
902 // Found a match and replaced it; stop traversing tree
903 return false;
904 }
905
906 /*!
907 * \internal
908 * \brief Search an XML tree depth-first and update the first matching element
909 *
910 * "Update" means to merge a source subtree into a target subtree (see
911 * \c update_xe()).
912 *
913 * A match with a node \c node is defined as follows:
914 * * \c node and \p update are both element nodes of the same type.
915 * * \c node and \p update have the same \c PCMK_XA_ID attribute value, or
916 * \c PCMK_XA_ID is unset in both
917 *
918 * \param[in,out] xml XML tree to search
919 * \param[in] update XML to update a matching element with
920 * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
921 *
922 * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
923 * successful update and an error code otherwise)
924 */
925 int
926 pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags)
/* ![[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)
*/
927 {
928 /* @COMPAT In pcmk__xe_delete_match() and pcmk__xe_replace_match(), we
929 * compare IDs only if the equivalent of the update argument has an ID.
930 * Here, we're stricter: we consider it a mismatch if only one element has
931 * an ID attribute, or if both elements have IDs but they don't match.
932 *
933 * Perhaps we should align the behavior at a major version release.
934 */
935 struct update_data data = {
936 .update = update,
937 .flags = flags,
938 };
939
940 CRM_CHECK((xml != NULL) && (update != NULL), return EINVAL);
941
942 if (!pcmk__xml_tree_foreach(xml, update_xe_if_matching, &data)) {
943 // Found and updated an element
944 return pcmk_rc_ok;
945 }
946
947 // No match found in this subtree
948 return ENXIO;
949 }
950
951 void
952 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
/* ![[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)
*/
953 {
954 while (true) {
955 const char *name, *value;
956
957 name = va_arg(pairs, const char *);
958 if (name == NULL) {
959 return;
960 }
961
962 value = va_arg(pairs, const char *);
963 if (value != NULL) {
964 crm_xml_add(node, name, value);
965 }
966 }
967 }
968
969 void
970 pcmk__xe_set_props(xmlNodePtr node, ...)
/* ![[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)
*/
971 {
972 va_list pairs;
973 va_start(pairs, node);
974 pcmk__xe_set_propv(node, pairs);
975 va_end(pairs);
976 }
977
978 int
979 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_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)
*/
980 int (*handler)(xmlNode *xml, void *userdata),
981 void *userdata)
982 {
983 xmlNode *children = (xml? xml->children : NULL);
984
985 pcmk__assert(handler != NULL);
986
987 for (xmlNode *node = children; node != NULL; node = node->next) {
988 if ((node->type == XML_ELEMENT_NODE)
989 && ((child_element_name == NULL)
990 || pcmk__xe_is(node, child_element_name))) {
991 int rc = handler(node, userdata);
992
993 if (rc != pcmk_rc_ok) {
994 return rc;
995 }
996 }
997 }
998
999 return pcmk_rc_ok;
1000 }
1001
1002 // XML attribute handling
1003
1004 /*!
1005 * \brief Create an XML attribute with specified name and value
1006 *
1007 * \param[in,out] node XML node to modify
1008 * \param[in] name Attribute name to set
1009 * \param[in] value Attribute value to set
1010 *
1011 * \return New value on success, \c NULL otherwise
1012 * \note This does nothing if node, name, or value are \c NULL or empty.
1013 */
1014 const char *
1015 crm_xml_add(xmlNode *node, 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)
*/
1016 {
1017 // @TODO Replace with internal function that returns the new attribute
1018 bool dirty = FALSE;
1019 xmlAttr *attr = NULL;
1020
1021 CRM_CHECK(node != NULL, return NULL);
1022 CRM_CHECK(name != NULL, return NULL);
1023
1024 if (value == NULL) {
1025 return NULL;
1026 }
1027
1028 if (pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking)) {
1029 const char *old = crm_element_value(node, name);
1030
1031 if (old == NULL || value == NULL || strcmp(old, value) != 0) {
1032 dirty = TRUE;
1033 }
1034 }
1035
1036 if (dirty && (pcmk__check_acl(node, name, pcmk__xf_acl_create) == FALSE)) {
1037 crm_trace("Cannot add %s=%s to %s", name, value, node->name);
1038 return NULL;
1039 }
1040
1041 attr = xmlSetProp(node, (const xmlChar *) name, (const xmlChar *) value);
1042
1043 /* If the attribute already exists, this does nothing. Attribute values
1044 * don't get private data.
1045 */
1046 pcmk__xml_new_private_data((xmlNode *) attr);
1047
1048 if (dirty) {
1049 pcmk__mark_xml_attr_dirty(attr);
1050 }
1051
1052 CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
1053 return (char *)attr->children->content;
1054 }
1055
1056
1057 /*!
1058 * \brief Create an XML attribute with specified name and integer value
1059 *
1060 * This is like \c crm_xml_add() but taking an integer value.
1061 *
1062 * \param[in,out] node XML node to modify
1063 * \param[in] name Attribute name to set
1064 * \param[in] value Attribute value to set
1065 *
1066 * \return New value as string on success, \c NULL otherwise
1067 * \note This does nothing if node or name are \c NULL or empty.
1068 */
1069 const char *
1070 crm_xml_add_int(xmlNode *node, const char *name, int 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)
*/
1071 {
1072 char *number = pcmk__itoa(value);
1073 const char *added = crm_xml_add(node, name, number);
1074
1075 free(number);
1076 return added;
1077 }
1078
1079 /*!
1080 * \brief Create an XML attribute with specified name and unsigned value
1081 *
1082 * This is like \c crm_xml_add() but taking a guint value.
1083 *
1084 * \param[in,out] node XML node to modify
1085 * \param[in] name Attribute name to set
1086 * \param[in] ms Attribute value to set
1087 *
1088 * \return New value as string on success, \c NULL otherwise
1089 * \note This does nothing if node or name are \c NULL or empty.
1090 */
1091 const char *
1092 crm_xml_add_ms(xmlNode *node, const char *name, guint ms)
/* ![[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)
*/
1093 {
1094 char *number = crm_strdup_printf("%u", ms);
1095 const char *added = crm_xml_add(node, name, number);
1096
1097 free(number);
1098 return added;
1099 }
1100
1101 // Maximum size of null-terminated string representation of 64-bit integer
1102 // -9223372036854775808
1103 #define LLSTRSIZE 21
1104
1105 /*!
1106 * \brief Create an XML attribute with specified name and long long int value
1107 *
1108 * This is like \c crm_xml_add() but taking a long long int value. It is a
1109 * useful equivalent for defined types like time_t, etc.
1110 *
1111 * \param[in,out] xml XML node to modify
1112 * \param[in] name Attribute name to set
1113 * \param[in] value Attribute value to set
1114 *
1115 * \return New value as string on success, \c NULL otherwise
1116 * \note This does nothing if xml or name are \c NULL or empty.
1117 * This does not support greater than 64-bit values.
1118 */
1119 const char *
1120 crm_xml_add_ll(xmlNode *xml, const char *name, long long 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)
*/
1121 {
1122 char s[LLSTRSIZE] = { '\0', };
1123
1124 if (snprintf(s, LLSTRSIZE, "%lld", (long long) value) == LLSTRSIZE) {
1125 return NULL;
1126 }
1127 return crm_xml_add(xml, name, s);
1128 }
1129
1130 /*!
1131 * \brief Create XML attributes for seconds and microseconds
1132 *
1133 * This is like \c crm_xml_add() but taking a struct timeval.
1134 *
1135 * \param[in,out] xml XML node to modify
1136 * \param[in] name_sec Name of XML attribute for seconds
1137 * \param[in] name_usec Name of XML attribute for microseconds (or NULL)
1138 * \param[in] value Time value to set
1139 *
1140 * \return New seconds value as string on success, \c NULL otherwise
1141 * \note This does nothing if xml, name_sec, or value is \c NULL.
1142 */
1143 const char *
1144 crm_xml_add_timeval(xmlNode *xml, const char *name_sec, const char *name_usec,
/* ![[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)
*/
1145 const struct timeval *value)
1146 {
1147 const char *added = NULL;
1148
1149 if (xml && name_sec && value) {
1150 added = crm_xml_add_ll(xml, name_sec, (long long) value->tv_sec);
1151 if (added && name_usec) {
1152 // Any error is ignored (we successfully added seconds)
1153 crm_xml_add_ll(xml, name_usec, (long long) value->tv_usec);
1154 }
1155 }
1156 return added;
1157 }
1158
1159 /*!
1160 * \brief Retrieve the value of an XML attribute
1161 *
1162 * \param[in] data XML node to check
1163 * \param[in] name Attribute name to check
1164 *
1165 * \return Value of specified attribute (may be \c NULL)
1166 */
1167 const char *
1168 crm_element_value(const xmlNode *data, 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)
*/
1169 {
1170 xmlAttr *attr = NULL;
1171
1172 if (data == NULL) {
1173 crm_err("Couldn't find %s in NULL", name ? name : "<null>");
1174 CRM_LOG_ASSERT(data != NULL);
1175 return NULL;
1176
1177 } else if (name == NULL) {
1178 crm_err("Couldn't find NULL in %s", data->name);
1179 return NULL;
1180 }
1181
1182 attr = xmlHasProp(data, (const xmlChar *) name);
1183 if (!attr || !attr->children) {
1184 return NULL;
1185 }
1186 return (const char *) attr->children->content;
1187 }
1188
1189 /*!
1190 * \brief Retrieve the integer value of an XML attribute
1191 *
1192 * This is like \c crm_element_value() but getting the value as an integer.
1193 *
1194 * \param[in] data XML node to check
1195 * \param[in] name Attribute name to check
1196 * \param[out] dest Where to store element value
1197 *
1198 * \return 0 on success, -1 otherwise
1199 */
1200 int
1201 crm_element_value_int(const xmlNode *data, const char *name, int *dest)
/* ![[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)
*/
1202 {
1203 const char *value = NULL;
1204
1205 CRM_CHECK(dest != NULL, return -1);
1206 value = crm_element_value(data, name);
1207 if (value) {
1208 long long value_ll;
1209 int rc = pcmk__scan_ll(value, &value_ll, 0LL);
1210
1211 *dest = PCMK__PARSE_INT_DEFAULT;
1212 if (rc != pcmk_rc_ok) {
1213 crm_warn("Using default for %s "
1214 "because '%s' is not a valid integer: %s",
1215 name, value, pcmk_rc_str(rc));
1216 } else if ((value_ll < INT_MIN) || (value_ll > INT_MAX)) {
1217 crm_warn("Using default for %s because '%s' is out of range",
1218 name, value);
1219 } else {
1220 *dest = (int) value_ll;
1221 return 0;
1222 }
1223 }
1224 return -1;
1225 }
1226
1227 /*!
1228 * \internal
1229 * \brief Retrieve a flag group from an XML attribute value
1230 *
1231 * This is like \c crm_element_value() except getting the value as a 32-bit
1232 * unsigned integer.
1233 *
1234 * \param[in] xml XML node to check
1235 * \param[in] name Attribute name to check (must not be NULL)
1236 * \param[out] dest Where to store flags (may be NULL to just
1237 * validate type)
1238 * \param[in] default_value What to use for missing or invalid value
1239 *
1240 * \return Standard Pacemaker return code
1241 */
1242 int
1243 pcmk__xe_get_flags(const xmlNode *xml, const char *name, uint32_t *dest,
/* ![[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)
*/
1244 uint32_t default_value)
1245 {
1246 const char *value = NULL;
1247 long long value_ll = 0LL;
1248 int rc = pcmk_rc_ok;
1249
1250 if (dest != NULL) {
1251 *dest = default_value;
1252 }
1253
1254 if (name == NULL) {
1255 return EINVAL;
1256 }
1257 if (xml == NULL) {
1258 return pcmk_rc_ok;
1259 }
1260 value = crm_element_value(xml, name);
1261 if (value == NULL) {
1262 return pcmk_rc_ok;
1263 }
1264
1265 rc = pcmk__scan_ll(value, &value_ll, default_value);
1266 if ((value_ll < 0) || (value_ll > UINT32_MAX)) {
1267 value_ll = default_value;
1268 if (rc == pcmk_rc_ok) {
1269 rc = pcmk_rc_bad_input;
1270 }
1271 }
1272
1273 if (dest != NULL) {
1274 *dest = (uint32_t) value_ll;
1275 }
1276 return rc;
1277 }
1278
1279 /*!
1280 * \brief Retrieve the long long integer value of an XML attribute
1281 *
1282 * This is like \c crm_element_value() but getting the value as a long long int.
1283 *
1284 * \param[in] data XML node to check
1285 * \param[in] name Attribute name to check
1286 * \param[out] dest Where to store element value
1287 *
1288 * \return 0 on success, -1 otherwise
1289 */
1290 int
1291 crm_element_value_ll(const xmlNode *data, const char *name, long long *dest)
/* ![[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)
*/
1292 {
1293 const char *value = NULL;
1294
1295 CRM_CHECK(dest != NULL, return -1);
1296 value = crm_element_value(data, name);
1297 if (value != NULL) {
1298 int rc = pcmk__scan_ll(value, dest, PCMK__PARSE_INT_DEFAULT);
1299
1300 if (rc == pcmk_rc_ok) {
1301 return 0;
1302 }
1303 crm_warn("Using default for %s "
1304 "because '%s' is not a valid integer: %s",
1305 name, value, pcmk_rc_str(rc));
1306 }
1307 return -1;
1308 }
1309
1310 /*!
1311 * \brief Retrieve the millisecond value of an XML attribute
1312 *
1313 * This is like \c crm_element_value() but returning the value as a guint.
1314 *
1315 * \param[in] data XML node to check
1316 * \param[in] name Attribute name to check
1317 * \param[out] dest Where to store attribute value
1318 *
1319 * \return \c pcmk_ok on success, -1 otherwise
1320 */
1321 int
1322 crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
/* ![[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)
*/
1323 {
1324 const char *value = NULL;
1325 long long value_ll;
1326 int rc = pcmk_rc_ok;
1327
1328 CRM_CHECK(dest != NULL, return -1);
1329 *dest = 0;
1330 value = crm_element_value(data, name);
1331 rc = pcmk__scan_ll(value, &value_ll, 0LL);
1332 if (rc != pcmk_rc_ok) {
1333 crm_warn("Using default for %s "
1334 "because '%s' is not valid milliseconds: %s",
1335 name, value, pcmk_rc_str(rc));
1336 return -1;
1337 }
1338 if ((value_ll < 0) || (value_ll > G_MAXUINT)) {
1339 crm_warn("Using default for %s because '%s' is out of range",
1340 name, value);
1341 return -1;
1342 }
1343 *dest = (guint) value_ll;
1344 return pcmk_ok;
1345 }
1346
1347 /*!
1348 * \brief Retrieve the seconds-since-epoch value of an XML attribute
1349 *
1350 * This is like \c crm_element_value() but returning the value as a time_t.
1351 *
1352 * \param[in] xml XML node to check
1353 * \param[in] name Attribute name to check
1354 * \param[out] dest Where to store attribute value
1355 *
1356 * \return \c pcmk_ok on success, -1 otherwise
1357 */
1358 int
1359 crm_element_value_epoch(const xmlNode *xml, const char *name, time_t *dest)
/* ![[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)
*/
1360 {
1361 long long value_ll = 0;
1362
1363 if (crm_element_value_ll(xml, name, &value_ll) < 0) {
1364 return -1;
1365 }
1366
1367 /* Unfortunately, we can't do any bounds checking, since time_t has neither
1368 * standardized bounds nor constants defined for them.
1369 */
1370 *dest = (time_t) value_ll;
1371 return pcmk_ok;
1372 }
1373
1374 /*!
1375 * \brief Retrieve the value of XML second/microsecond attributes as time
1376 *
1377 * This is like \c crm_element_value() but returning value as a struct timeval.
1378 *
1379 * \param[in] xml XML to parse
1380 * \param[in] name_sec Name of XML attribute for seconds
1381 * \param[in] name_usec Name of XML attribute for microseconds
1382 * \param[out] dest Where to store result
1383 *
1384 * \return \c pcmk_ok on success, -errno on error
1385 * \note Values default to 0 if XML or XML attribute does not exist
1386 */
1387 int
1388 crm_element_value_timeval(const xmlNode *xml, const char *name_sec,
/* ![[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)
*/
1389 const char *name_usec, struct timeval *dest)
1390 {
1391 long long value_i = 0;
1392
1393 CRM_CHECK(dest != NULL, return -EINVAL);
1394 dest->tv_sec = 0;
1395 dest->tv_usec = 0;
1396
1397 if (xml == NULL) {
1398 return pcmk_ok;
1399 }
1400
1401 /* Unfortunately, we can't do any bounds checking, since there are no
1402 * constants provided for the bounds of time_t and suseconds_t, and
1403 * calculating them isn't worth the effort. If there are XML values
1404 * beyond the native sizes, there will probably be worse problems anyway.
1405 */
1406
1407 // Parse seconds
1408 errno = 0;
1409 if (crm_element_value_ll(xml, name_sec, &value_i) < 0) {
1410 return -errno;
1411 }
1412 dest->tv_sec = (time_t) value_i;
1413
1414 // Parse microseconds
1415 if (crm_element_value_ll(xml, name_usec, &value_i) < 0) {
1416 return -errno;
1417 }
1418 dest->tv_usec = (suseconds_t) value_i;
1419
1420 return pcmk_ok;
1421 }
1422
1423 /*!
1424 * \internal
1425 * \brief Get a date/time object from an XML attribute value
1426 *
1427 * \param[in] xml XML with attribute to parse (from CIB)
1428 * \param[in] attr Name of attribute to parse
1429 * \param[out] t Where to create date/time object
1430 * (\p *t must be NULL initially)
1431 *
1432 * \return Standard Pacemaker return code
1433 * \note The caller is responsible for freeing \p *t using crm_time_free().
1434 */
1435 int
1436 pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t)
/* ![[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)
*/
1437 {
1438 const char *value = NULL;
1439
1440 if ((t == NULL) || (*t != NULL) || (xml == NULL) || (attr == NULL)) {
1441 return EINVAL;
1442 }
1443
1444 value = crm_element_value(xml, attr);
1445 if (value != NULL) {
1446 *t = crm_time_new(value);
1447 if (*t == NULL) {
1448 return pcmk_rc_unpack_error;
1449 }
1450 }
1451 return pcmk_rc_ok;
1452 }
1453
1454 /*!
1455 * \brief Retrieve a copy of the value of an XML attribute
1456 *
1457 * This is like \c crm_element_value() but allocating new memory for the result.
1458 *
1459 * \param[in] data XML node to check
1460 * \param[in] name Attribute name to check
1461 *
1462 * \return Value of specified attribute (may be \c NULL)
1463 * \note The caller is responsible for freeing the result.
1464 */
1465 char *
1466 crm_element_value_copy(const xmlNode *data, 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)
*/
1467 {
1468 return pcmk__str_copy(crm_element_value(data, name));
1469 }
1470
1471 /*!
1472 * \internal
1473 * \brief Add a boolean attribute to an XML node.
1474 *
1475 * \param[in,out] node XML node to add attributes to
1476 * \param[in] name XML attribute to create
1477 * \param[in] value Value to give to the attribute
1478 */
1479 void
1480 pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool 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)
*/
1481 {
1482 crm_xml_add(node, name, pcmk__btoa(value));
1483 }
1484
1485 /*!
1486 * \internal
1487 * \brief Extract a boolean attribute's value from an XML element, with
1488 * error checking
1489 *
1490 * \param[in] node XML node to get attribute from
1491 * \param[in] name XML attribute to get
1492 * \param[out] value Destination for the value of the attribute
1493 *
1494 * \return EINVAL if \p name or \p value are NULL, ENODATA if \p node is
1495 * NULL or the attribute does not exist, pcmk_rc_unknown_format
1496 * if the attribute is not a boolean, and pcmk_rc_ok otherwise.
1497 *
1498 * \note \p value only has any meaning if the return value is pcmk_rc_ok.
1499 */
1500 int
1501 pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *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)
*/
1502 {
1503 const char *xml_value = NULL;
1504 int ret, rc;
1505
1506 if (node == NULL) {
1507 return ENODATA;
1508 } else if (name == NULL || value == NULL) {
1509 return EINVAL;
1510 }
1511
1512 xml_value = crm_element_value(node, name);
1513
1514 if (xml_value == NULL) {
1515 return ENODATA;
1516 }
1517
1518 rc = crm_str_to_boolean(xml_value, &ret);
1519 if (rc == 1) {
1520 *value = ret;
1521 return pcmk_rc_ok;
1522 } else {
1523 return pcmk_rc_bad_input;
1524 }
1525 }
1526
1527 /*!
1528 * \internal
1529 * \brief Extract a boolean attribute's value from an XML element
1530 *
1531 * \param[in] node XML node to get attribute from
1532 * \param[in] name XML attribute to get
1533 *
1534 * \return True if the given \p name is an attribute on \p node and has
1535 * the value \c PCMK_VALUE_TRUE, False in all other cases
1536 */
1537 bool
1538 pcmk__xe_attr_is_true(const xmlNode *node, 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)
*/
1539 {
1540 bool value = false;
1541 int rc;
1542
1543 rc = pcmk__xe_get_bool_attr(node, name, &value);
1544 return rc == pcmk_rc_ok && value == true;
1545 }
1546
1547 // Deprecated functions kept only for backward API compatibility
1548 // LCOV_EXCL_START
1549
1550 #include <glib.h> // gboolean, GSList
1551
1552 #include <crm/common/nvpair_compat.h> // pcmk_xml_attrs2nvpairs(), etc.
1553 #include <crm/common/xml_compat.h> // crm_xml_sanitize_id()
1554 #include <crm/common/xml_element_compat.h>
1555
1556 xmlNode *
1557 expand_idref(xmlNode *input, xmlNode *top)
/* ![[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)
*/
1558 {
1559 return pcmk__xe_resolve_idref(input, top);
1560 }
1561
1562 void
1563 crm_xml_set_id(xmlNode *xml, const char *format, ...)
/* ![[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)
*/
1564 {
1565 va_list ap;
1566 int len = 0;
1567 char *id = NULL;
1568
1569 /* equivalent to crm_strdup_printf() */
1570 va_start(ap, format);
1571 len = vasprintf(&id, format, ap);
1572 va_end(ap);
1573 pcmk__assert(len > 0);
1574
1575 crm_xml_sanitize_id(id);
1576 crm_xml_add(xml, PCMK_XA_ID, id);
1577 free(id);
1578 }
1579
1580 xmlNode *
1581 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
/* ![[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)
*/
1582 {
1583 xmlNode *child = NULL;
1584 GSList *nvpairs = NULL;
1585 xmlNode *result = NULL;
1586
1587 CRM_CHECK(input != NULL, return NULL);
1588
1589 result = pcmk__xe_create(parent, (const char *) input->name);
1590 nvpairs = pcmk_xml_attrs2nvpairs(input);
1591 nvpairs = pcmk_sort_nvpairs(nvpairs);
1592 pcmk_nvpairs2xml_attrs(nvpairs, result);
1593 pcmk_free_nvpairs(nvpairs);
1594
1595 for (child = pcmk__xe_first_child(input, NULL, NULL, NULL); child != NULL;
1596 child = pcmk__xe_next(child, NULL)) {
1597
1598 if (recursive) {
1599 sorted_xml(child, result, recursive);
1600 } else {
1601 pcmk__xml_copy(result, child);
1602 }
1603 }
1604
1605 return result;
1606 }
1607
1608 // LCOV_EXCL_STOP
1609 // End deprecated API