1 /*
2 * Copyright 2004-2024 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <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
22 #include <crm/crm.h>
23 #include <crm/common/nvpair.h> // crm_xml_add(), etc.
24 #include <crm/common/results.h> // pcmk_rc_ok, etc.
25 #include <crm/common/xml.h>
26 #include "crmcommon_private.h"
27
28 /*!
29 * \internal
30 * \brief Find first XML child element matching given criteria
31 *
32 * \param[in] parent XML element to search (can be \c NULL)
33 * \param[in] node_name If not \c NULL, only match children of this type
34 * \param[in] attr_n If not \c NULL, only match children with an attribute
35 * of this name.
36 * \param[in] attr_v If \p attr_n and this are not NULL, only match children
37 * with an attribute named \p attr_n and this value
38 *
39 * \return Matching XML child element, or \c NULL if none found
40 */
41 xmlNode *
42 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)
*/
43 const char *attr_n, const char *attr_v)
44 {
45 xmlNode *child = NULL;
46 const char *parent_name = "<null>";
47
48 CRM_CHECK((attr_v == NULL) || (attr_n != NULL), return NULL);
49
50 if (parent != NULL) {
51 child = parent->children;
52 while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) {
53 child = child->next;
54 }
55
56 parent_name = (const char *) parent->name;
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 (node_name == NULL) {
84 node_name = "(any)"; // For logging
85 }
86 if (attr_n != NULL) {
87 crm_trace("XML child node <%s %s=%s> not found in %s",
88 node_name, attr_n, attr_v, parent_name);
89 } else {
90 crm_trace("XML child node <%s> not found in %s",
91 node_name, parent_name);
92 }
93 return NULL;
94 }
95
96 /*!
97 * \internal
98 * \brief Return next sibling element of an XML element
99 *
100 * \param[in] xml XML element to check
101 * \param[in] element_name If not NULL, get next sibling with this element name
102 *
103 * \return Next desired sibling of \p xml (or NULL if none)
104 */
105 xmlNode *
106 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)
*/
107 {
108 for (xmlNode *next = (xml == NULL)? NULL : xml->next;
109 next != NULL; next = next->next) {
110 if ((next->type == XML_ELEMENT_NODE)
111 && ((element_name == NULL) || pcmk__xe_is(next, element_name))) {
112 return next;
113 }
114 }
115 return NULL;
116 }
117
118 /*!
119 * \internal
120 * \brief Parse an integer score from an XML attribute
121 *
122 * \param[in] xml XML element with attribute to parse
123 * \param[in] name Name of attribute to parse
124 * \param[out] score Where to store parsed score (can be NULL to
125 * just validate)
126 * \param[in] default_score What to return if the attribute value is not
127 * present or invalid
128 *
129 * \return Standard Pacemaker return code
130 */
131 int
132 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)
*/
133 int default_score)
134 {
135 const char *value = NULL;
136
137 CRM_CHECK((xml != NULL) && (name != NULL), return EINVAL);
138 value = crm_element_value(xml, name);
139 return pcmk_parse_score(value, score, default_score);
140 }
141
142 /*!
143 * \internal
144 * \brief Set an XML attribute, expanding \c ++ and \c += where appropriate
145 *
146 * If \p target already has an attribute named \p name set to an integer value
147 * and \p value is an addition assignment expression on \p name, then expand
148 * \p value to an integer and set attribute \p name to the expanded value in
149 * \p target.
150 *
151 * Otherwise, set attribute \p name on \p target using the literal \p value.
152 *
153 * The original attribute value in \p target and the number in an assignment
154 * expression in \p value are parsed and added as scores (that is, their values
155 * are capped at \c INFINITY and \c -INFINITY). For more details, refer to
156 * \c pcmk_parse_score().
157 *
158 * For example, suppose \p target has an attribute named \c "X" with value
159 * \c "5", and that \p name is \c "X".
160 * * If \p value is \c "X++", the new value of \c "X" in \p target is \c "6".
161 * * If \p value is \c "X+=3", the new value of \c "X" in \p target is \c "8".
162 * * If \p value is \c "val", the new value of \c "X" in \p target is \c "val".
163 * * If \p value is \c "Y++", the new value of \c "X" in \p target is \c "Y++".
164 *
165 * \param[in,out] target XML node whose attribute to set
166 * \param[in] name Name of the attribute to set
167 * \param[in] value New value of attribute to set (if NULL, initial value
168 * will be left unchanged)
169 *
170 * \return Standard Pacemaker return code (specifically, \c EINVAL on invalid
171 * argument, or \c pcmk_rc_ok otherwise)
172 */
173 int
174 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)
*/
175 {
176 const char *old_value = NULL;
177
178 CRM_CHECK((target != NULL) && (name != NULL), return EINVAL);
179
180 if (value == NULL) {
181 // @TODO Maybe instead delete the attribute or set it to 0
182 return pcmk_rc_ok;
183 }
184
185 old_value = crm_element_value(target, name);
186
187 // If no previous value, skip to default case and set the value unexpanded.
188 if (old_value != NULL) {
189 const char *n = name;
190 const char *v = value;
191
192 // Stop at first character that differs between name and value
193 for (; (*n == *v) && (*n != '\0'); n++, v++);
194
195 // If value begins with name followed by a "++" or "+="
196 if ((*n == '\0')
197 && (*v++ == '+')
198 && ((*v == '+') || (*v == '='))) {
199
200 int add = 1;
201 int old_value_i = 0;
202 int rc = pcmk_rc_ok;
203
204 // If we're expanding ourselves, no previous value was set; use 0
205 if (old_value != value) {
206 rc = pcmk_parse_score(old_value, &old_value_i, 0);
207 if (rc != pcmk_rc_ok) {
208 // @TODO This is inconsistent with old_value==NULL
209 crm_trace("Using 0 before incrementing %s because '%s' "
210 "is not a score", name, old_value);
211 }
212 }
213
214 /* value="X++": new value of X is old_value + 1
215 * value="X+=Y": new value of X is old_value + Y (for some number Y)
216 */
217 if (*v != '+') {
218 rc = pcmk_parse_score(++v, &add, 0);
219 if (rc != pcmk_rc_ok) {
220 // @TODO We should probably skip expansion instead
221 crm_trace("Not incrementing %s because '%s' does not have "
222 "a valid increment", name, value);
223 }
224 }
225
226 crm_xml_add_int(target, name, pcmk__add_scores(old_value_i, add));
227 return pcmk_rc_ok;
228 }
229 }
230
231 // Default case: set the attribute unexpanded (with value treated literally)
232 if (old_value != value) {
233 crm_xml_add(target, name, value);
234 }
235 return pcmk_rc_ok;
236 }
237
238 /*!
239 * \internal
240 * \brief Copy XML attributes from a source element to a target element
241 *
242 * This is similar to \c xmlCopyPropList() except that attributes are marked
243 * as dirty for change tracking purposes.
244 *
245 * \param[in,out] target XML element to receive copied attributes from \p src
246 * \param[in] src XML element whose attributes to copy to \p target
247 * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
248 *
249 * \return Standard Pacemaker return code
250 */
251 int
252 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)
*/
253 {
254 CRM_CHECK((src != NULL) && (target != NULL), return EINVAL);
255
256 for (xmlAttr *attr = pcmk__xe_first_attr(src); attr != NULL;
257 attr = attr->next) {
258
259 const char *name = (const char *) attr->name;
260 const char *value = pcmk__xml_attr_value(attr);
261
262 if (pcmk_is_set(flags, pcmk__xaf_no_overwrite)
263 && (crm_element_value(target, name) != NULL)) {
264 continue;
265 }
266
267 if (pcmk_is_set(flags, pcmk__xaf_score_update)) {
268 pcmk__xe_set_score(target, name, value);
269 } else {
270 crm_xml_add(target, name, value);
271 }
272 }
273
274 return pcmk_rc_ok;
275 }
276
277 /*!
278 * \internal
279 * \brief Compare two XML attributes by name
280 *
281 * \param[in] a First XML attribute to compare
282 * \param[in] b Second XML attribute to compare
283 *
284 * \retval negative \c a->name is \c NULL or comes before \c b->name
285 * lexicographically
286 * \retval 0 \c a->name and \c b->name are equal
287 * \retval positive \c b->name is \c NULL or comes before \c a->name
288 * lexicographically
289 */
290 static gint
291 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)
*/
292 {
293 const xmlAttr *attr_a = a;
294 const xmlAttr *attr_b = b;
295
296 return pcmk__strcmp((const char *) attr_a->name,
297 (const char *) attr_b->name, pcmk__str_none);
298 }
299
300 /*!
301 * \internal
302 * \brief Sort an XML element's attributes by name
303 *
304 * This does not consider ACLs and does not mark the attributes as deleted or
305 * dirty. Upon return, all attributes still exist and are set to the same values
306 * as before the call. The only thing that may change is the order of the
307 * attribute list.
308 *
309 * \param[in,out] xml XML element whose attributes to sort
310 */
311 void
312 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)
*/
313 {
314 GSList *attr_list = NULL;
315
316 for (xmlAttr *iter = pcmk__xe_first_attr(xml); iter != NULL;
317 iter = iter->next) {
318 attr_list = g_slist_prepend(attr_list, iter);
319 }
320 attr_list = g_slist_sort(attr_list, compare_xml_attr);
321
322 for (GSList *iter = attr_list; iter != NULL; iter = iter->next) {
323 xmlNode *attr = iter->data;
324
325 xmlUnlinkNode(attr);
326 xmlAddChild(xml, attr);
327 }
328 g_slist_free(attr_list);
329 }
330
331 /*!
332 * \internal
333 * \brief Remove a named attribute from an XML element
334 *
335 * \param[in,out] element XML element to remove an attribute from
336 * \param[in] name Name of attribute to remove
337 */
338 void
339 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)
*/
340 {
341 if (name != NULL) {
342 pcmk__xa_remove(xmlHasProp(element, (pcmkXmlStr) name), false);
343 }
344 }
345
346 /*!
347 * \internal
348 * \brief Remove a named attribute from an XML element
349 *
350 * This is a wrapper for \c pcmk__xe_remove_attr() for use with
351 * \c pcmk__xml_tree_foreach().
352 *
353 * \param[in,out] xml XML element to remove an attribute from
354 * \param[in] user_data Name of attribute to remove
355 *
356 * \return \c true (to continue traversing the tree)
357 *
358 * \note This is compatible with \c pcmk__xml_tree_foreach().
359 */
360 bool
361 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)
*/
362 {
363 const char *name = user_data;
364
365 pcmk__xe_remove_attr(xml, name);
366 return true;
367 }
368
369 /*!
370 * \internal
371 * \brief Remove an XML element's attributes that match some criteria
372 *
373 * \param[in,out] element XML element to modify
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,
/* ![[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, false) != 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, (pcmkXmlStr) name, NULL);
417 pcmk__mem_assert(node);
418
419 xmlDocSetRootElement(doc, node);
420
421 } else {
422 node = xmlNewChild(parent, NULL, (pcmkXmlStr) 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, (pcmkXmlStr) 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((pcmkXmlStr) 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 (xml_tracking_changes(new)) {
760 // Replaced sections may have included relevant ACLs
761 pcmk__apply_acl(new);
762 }
763 xml_calculate_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__tracking_xml_changes(node, FALSE)) {
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, (pcmkXmlStr) name, (pcmkXmlStr) 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, (pcmkXmlStr) 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