pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
patchset.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10#include <crm_internal.h>
11
12#include <stdio.h>
13#include <sys/types.h>
14#include <unistd.h>
15#include <time.h>
16#include <string.h>
17#include <stdlib.h>
18#include <stdarg.h>
19#include <bzlib.h>
20
21#include <libxml/tree.h> // xmlNode
22
23#include <crm/crm.h>
25#include <crm/common/xml.h>
26#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
27#include "crmcommon_private.h"
28
29/* Add changes for specified XML to patchset.
30 * For patchset format, refer to diff schema.
31 */
32static void
33add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
34{
35 xmlNode *cIter = NULL;
36 xmlAttr *pIter = NULL;
37 xmlNode *change = NULL;
38 xml_node_private_t *nodepriv = xml->_private;
39 const char *value = NULL;
40
41 if (nodepriv == NULL) {
42 /* Elements that shouldn't occur in a CIB don't have _private set. They
43 * should be stripped out, ignored, or have an error thrown by any code
44 * that processes their parent, so we ignore any changes to them.
45 */
46 return;
47 }
48
49 // If this XML node is new, just report that
50 if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
51 GString *xpath = pcmk__element_xpath(xml->parent);
52
53 if (xpath != NULL) {
54 int position = pcmk__xml_position(xml, pcmk__xf_deleted);
55
56 change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
57
59 crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
60 crm_xml_add_int(change, PCMK_XE_POSITION, position);
61 pcmk__xml_copy(change, xml);
62 g_string_free(xpath, TRUE);
63 }
64
65 return;
66 }
67
68 // Check each of the XML node's attributes for changes
69 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
70 pIter = pIter->next) {
71 xmlNode *attr = NULL;
72
73 nodepriv = pIter->_private;
74 if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
75 continue;
76 }
77
78 if (change == NULL) {
79 GString *xpath = pcmk__element_xpath(xml);
80
81 if (xpath != NULL) {
82 change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
83
85 crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
86
87 change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
88 g_string_free(xpath, TRUE);
89 }
90 }
91
93
94 crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
95 if (nodepriv->flags & pcmk__xf_deleted) {
96 crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
97
98 } else {
99 crm_xml_add(attr, PCMK_XA_OPERATION, "set");
100
101 value = pcmk__xml_attr_value(pIter);
102 crm_xml_add(attr, PCMK_XA_VALUE, value);
103 }
104 }
105
106 if (change) {
107 xmlNode *result = NULL;
108
109 change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
110 result = pcmk__xe_create(change, (const char *)xml->name);
111
112 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
113 pIter = pIter->next) {
114 nodepriv = pIter->_private;
115 if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
116 value = crm_element_value(xml, (const char *) pIter->name);
117 crm_xml_add(result, (const char *)pIter->name, value);
118 }
119 }
120 }
121
122 // Now recursively do the same for each child node of this node
123 for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
124 cIter = pcmk__xml_next(cIter)) {
125 add_xml_changes_to_patchset(cIter, patchset);
126 }
127
128 nodepriv = xml->_private;
129 if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
130 GString *xpath = pcmk__element_xpath(xml);
131
132 crm_trace("%s.%s moved to position %d",
133 xml->name, pcmk__xe_id(xml),
135
136 if (xpath != NULL) {
137 change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
138
140 crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
143 g_string_free(xpath, TRUE);
144 }
145 }
146}
147
148static bool
149is_config_change(xmlNode *xml)
150{
151 GList *gIter = NULL;
152 xml_node_private_t *nodepriv = NULL;
153 xml_doc_private_t *docpriv;
154 xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
155 NULL);
156
157 if (config) {
158 nodepriv = config->_private;
159 }
160 if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
161 return TRUE;
162 }
163
164 if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
165 docpriv = xml->doc->_private;
166 for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
167 pcmk__deleted_xml_t *deleted_obj = gIter->data;
168
169 if (strstr(deleted_obj->path,
170 "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
171 return TRUE;
172 }
173 }
174 }
175 return FALSE;
176}
177
178static xmlNode *
179xml_create_patchset_v2(xmlNode *source, xmlNode *target)
180{
181 int lpc = 0;
182 GList *gIter = NULL;
183 xml_doc_private_t *docpriv;
184
185 xmlNode *v = NULL;
186 xmlNode *version = NULL;
187 xmlNode *patchset = NULL;
188 const char *vfields[] = {
192 };
193
194 pcmk__assert(target != NULL);
195
197 return NULL;
198 }
199
200 pcmk__assert(target->doc != NULL);
201 docpriv = target->doc->_private;
202
203 patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
204 crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
205
207
209 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
210 const char *value = crm_element_value(source, vfields[lpc]);
211
212 if (value == NULL) {
213 value = "1";
214 }
215 crm_xml_add(v, vfields[lpc], value);
216 }
217
219 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
220 const char *value = crm_element_value(target, vfields[lpc]);
221
222 if (value == NULL) {
223 value = "1";
224 }
225 crm_xml_add(v, vfields[lpc], value);
226 }
227
228 for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
229 pcmk__deleted_xml_t *deleted_obj = gIter->data;
230 xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
231
233 crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
234 if (deleted_obj->position >= 0) {
235 crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
236 }
237 }
238
239 add_xml_changes_to_patchset(target, patchset);
240 return patchset;
241}
242
243xmlNode *
244xml_create_patchset(int format, xmlNode *source, xmlNode *target,
245 bool *config_changed, bool manage_version)
246{
247 bool local_config_changed = false;
248
249 if (format == 0) {
250 format = 2;
251 }
252 if (format != 2) {
253 crm_err("Unknown patch format: %d", format);
254 return NULL;
255 }
256
258 if ((target == NULL)
260
261 crm_trace("No change %d", format);
262 return NULL;
263 }
264
265 if (config_changed == NULL) {
266 config_changed = &local_config_changed;
267 }
268 *config_changed = is_config_change(target);
269
270 if (manage_version) {
271 int counter = 0;
272
273 if (*config_changed) {
275
277 crm_xml_add_int(target, PCMK_XA_EPOCH, counter + 1);
278
279 } else {
282 }
283 }
284
285 return xml_create_patchset_v2(source, target);
286}
287
288void
289patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
290 bool with_digest)
291{
292 char *digest = NULL;
293
294 if ((patch == NULL) || (source == NULL) || (target == NULL)
295 || !with_digest) {
296 return;
297 }
298
299 /* We should always call pcmk__xml_commit_changes() before calculating a
300 * digest. Otherwise, with an on-tracking dirty target, we could get a wrong
301 * digest.
302 */
304
305 digest = pcmk__digest_xml(target, true);
306
307 crm_xml_add(patch, PCMK__XA_DIGEST, digest);
308 free(digest);
309
310 return;
311}
312
313// Get CIB versions used for additions and deletions in a patchset
314bool
315xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
316{
317 static const char *const vfields[] = {
321 };
322
323 const xmlNode *version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION,
324 NULL, NULL);
325 const xmlNode *source = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL,
326 NULL);
327 const xmlNode *target = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL,
328 NULL);
329 int format = 1;
330
331 crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
332 if (format != 2) {
333 crm_err("Unknown patch format: %d", format);
334 return -EINVAL;
335 }
336
337 if (source != NULL) {
338 for (int i = 0; i < PCMK__NELEM(vfields); i++) {
339 crm_element_value_int(source, vfields[i], &(del[i]));
340 crm_trace("Got %d for del[%s]", del[i], vfields[i]);
341 }
342 }
343
344 if (target != NULL) {
345 for (int i = 0; i < PCMK__NELEM(vfields); i++) {
346 crm_element_value_int(target, vfields[i], &(add[i]));
347 crm_trace("Got %d for add[%s]", add[i], vfields[i]);
348 }
349 }
350 return pcmk_ok;
351}
352
362static int
363xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
364{
365 int lpc = 0;
366 bool changed = FALSE;
367
368 int this[] = { 0, 0, 0 };
369 int add[] = { 0, 0, 0 };
370 int del[] = { 0, 0, 0 };
371
372 const char *vfields[] = {
376 };
377
378 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
379 crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
380 crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
381 if (this[lpc] < 0) {
382 this[lpc] = 0;
383 }
384 }
385
386 /* Set some defaults in case nothing is present */
387 add[0] = this[0];
388 add[1] = this[1];
389 add[2] = this[2] + 1;
390 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
391 del[lpc] = this[lpc];
392 }
393
394 xml_patch_versions(patchset, add, del);
395
396 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
397 if (this[lpc] < del[lpc]) {
398 crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
399 vfields[lpc], this[0], this[1], this[2],
400 del[0], del[1], del[2], add[0], add[1], add[2]);
401 return pcmk_rc_diff_resync;
402
403 } else if (this[lpc] > del[lpc]) {
404 crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
405 vfields[lpc], this[0], this[1], this[2],
406 del[0], del[1], del[2], add[0], add[1], add[2], patchset);
407 crm_log_xml_info(patchset, "OldPatch");
408 return pcmk_rc_old_data;
409 }
410 }
411
412 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
413 if (add[lpc] > del[lpc]) {
414 changed = TRUE;
415 }
416 }
417
418 if (!changed) {
419 crm_notice("Versions did not change in patch %d.%d.%d",
420 add[0], add[1], add[2]);
421 return pcmk_rc_old_data;
422 }
423
424 crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
425 add[0], add[1], add[2], this[0], this[1], this[2]);
426 return pcmk_rc_ok;
427}
428
429// Return first child matching element name and optionally id or position
430static xmlNode *
431first_matching_xml_child(const xmlNode *parent, const char *name,
432 const char *id, int position)
433{
434 xmlNode *cIter = NULL;
435
436 for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
437 cIter = pcmk__xml_next(cIter)) {
438 if (strcmp((const char *) cIter->name, name) != 0) {
439 continue;
440 } else if (id) {
441 const char *cid = pcmk__xe_id(cIter);
442
443 if ((cid == NULL) || (strcmp(cid, id) != 0)) {
444 continue;
445 }
446 }
447
448 // "position" makes sense only for XML comments for now
449 if ((cIter->type == XML_COMMENT_NODE)
450 && (position >= 0)
451 && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
452 continue;
453 }
454
455 return cIter;
456 }
457 return NULL;
458}
459
473static xmlNode *
474search_v2_xpath(const xmlNode *top, const char *key, int target_position)
475{
476 xmlNode *target = (xmlNode *) top->doc;
477 const char *current = key;
478 char *section;
479 char *remainder;
480 char *id;
481 char *tag;
482 char *path = NULL;
483 int rc;
484 size_t key_len;
485
486 CRM_CHECK(key != NULL, return NULL);
487 key_len = strlen(key);
488
489 /* These are scanned from key after a slash, so they can't be bigger
490 * than key_len - 1 characters plus a null terminator.
491 */
492
493 remainder = pcmk__assert_alloc(key_len, sizeof(char));
494 section = pcmk__assert_alloc(key_len, sizeof(char));
495 id = pcmk__assert_alloc(key_len, sizeof(char));
496 tag = pcmk__assert_alloc(key_len, sizeof(char));
497
498 do {
499 // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
500 rc = sscanf(current, "/%[^/]%s", section, remainder);
501 if (rc > 0) {
502 // Separate FIRST_COMPONENT into TAG[@id='ID']
503 int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
504 int current_position = -1;
505
506 /* The target position is for the final component tag, so only use
507 * it if there is nothing left to search after this component.
508 */
509 if ((rc == 1) && (target_position >= 0)) {
510 current_position = target_position;
511 }
512
513 switch (f) {
514 case 1:
515 target = first_matching_xml_child(target, tag, NULL,
516 current_position);
517 break;
518 case 2:
519 target = first_matching_xml_child(target, tag, id,
520 current_position);
521 break;
522 default:
523 // This should not be possible
524 target = NULL;
525 break;
526 }
527 current = remainder;
528 }
529
530 // Continue if something remains to search, and we've matched so far
531 } while ((rc == 2) && target);
532
533 if (target) {
534 crm_trace("Found %s for %s",
535 (path = (char *) xmlGetNodePath(target)), key);
536 free(path);
537 } else {
538 crm_debug("No match for %s", key);
539 }
540
541 free(remainder);
542 free(section);
543 free(tag);
544 free(id);
545 return target;
546}
547
548typedef struct xml_change_obj_s {
549 const xmlNode *change;
550 xmlNode *match;
552
553static gint
554sort_change_obj_by_position(gconstpointer a, gconstpointer b)
555{
556 const xml_change_obj_t *change_obj_a = a;
557 const xml_change_obj_t *change_obj_b = b;
558 int position_a = -1;
559 int position_b = -1;
560
561 crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
562 crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
563
564 if (position_a < position_b) {
565 return -1;
566
567 } else if (position_a > position_b) {
568 return 1;
569 }
570
571 return 0;
572}
573
583static int
584apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
585{
586 int rc = pcmk_rc_ok;
587 const xmlNode *change = NULL;
588 GList *change_objs = NULL;
589 GList *gIter = NULL;
590
591 for (change = pcmk__xml_first_child(patchset); change != NULL;
592 change = pcmk__xml_next(change)) {
593 xmlNode *match = NULL;
594 const char *op = crm_element_value(change, PCMK_XA_OPERATION);
595 const char *xpath = crm_element_value(change, PCMK_XA_PATH);
596 int position = -1;
597
598 if (op == NULL) {
599 continue;
600 }
601
602 crm_trace("Processing %s %s", change->name, op);
603
604 /* PCMK_VALUE_DELETE changes for XML comments are generated with
605 * PCMK_XE_POSITION
606 */
607 if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
608 crm_element_value_int(change, PCMK_XE_POSITION, &position);
609 }
610 match = search_v2_xpath(xml, xpath, position);
611 crm_trace("Performing %s on %s with %p", op, xpath, match);
612
613 if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
614 crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
615 continue;
616
617 } else if (match == NULL) {
618 crm_err("No %s match for %s in %p", op, xpath, xml->doc);
620 continue;
621
622 } else if (pcmk__str_any_of(op,
624 // Delay the adding of a PCMK_VALUE_CREATE object
625 xml_change_obj_t *change_obj =
627
628 change_obj->change = change;
629 change_obj->match = match;
630
631 change_objs = g_list_append(change_objs, change_obj);
632
633 if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
634 // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
635 if ((match->parent != NULL) && (match->parent->last != NULL)) {
636 xmlAddNextSibling(match->parent->last, match);
637 }
638 }
639
640 } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
641 pcmk__xml_free(match);
642
643 } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
644 const xmlNode *child = pcmk__xe_first_child(change,
646 NULL, NULL);
647 const xmlNode *attrs = pcmk__xml_first_child(child);
648
649 if (attrs == NULL) {
650 rc = ENOMSG;
651 continue;
652 }
653
654 // Remove all attributes
655 pcmk__xe_remove_matching_attrs(match, false, NULL, NULL);
656
657 for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
658 pIter = pIter->next) {
659 const char *name = (const char *) pIter->name;
660 const char *value = pcmk__xml_attr_value(pIter);
661
662 crm_xml_add(match, name, value);
663 }
664
665 } else {
666 crm_err("Unknown operation: %s", op);
668 }
669 }
670
671 // Changes should be generated in the right order. Double checking.
672 change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
673
674 for (gIter = change_objs; gIter; gIter = gIter->next) {
675 xml_change_obj_t *change_obj = gIter->data;
676 xmlNode *match = change_obj->match;
677 const char *op = NULL;
678 const char *xpath = NULL;
679
680 change = change_obj->change;
681
683 xpath = crm_element_value(change, PCMK_XA_PATH);
684
685 crm_trace("Continue performing %s on %s with %p", op, xpath, match);
686
687 if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
688 int position = 0;
689 xmlNode *child = NULL;
690 xmlNode *match_child = NULL;
691
692 match_child = match->children;
693 crm_element_value_int(change, PCMK_XE_POSITION, &position);
694
695 while ((match_child != NULL)
696 && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
697 match_child = match_child->next;
698 }
699
700 child = pcmk__xml_copy(match, change->children);
701
702 if (match_child != NULL) {
703 crm_trace("Adding %s at position %d", child->name, position);
704 xmlAddPrevSibling(match_child, child);
705
706 } else {
707 crm_trace("Adding %s at position %d (end)",
708 child->name, position);
709 }
710
711 } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
712 int position = 0;
713
714 crm_element_value_int(change, PCMK_XE_POSITION, &position);
715 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
716 xmlNode *match_child = NULL;
717 int p = position;
718
719 if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
720 p++; // Skip ourselves
721 }
722
723 pcmk__assert(match->parent != NULL);
724 match_child = match->parent->children;
725
726 while ((match_child != NULL)
727 && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
728 match_child = match_child->next;
729 }
730
731 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
732 match->name, position,
734 match->prev, (match_child? "next":"last"),
735 (match_child? match_child : match->parent->last));
736
737 if (match_child) {
738 xmlAddPrevSibling(match_child, match);
739
740 } else {
741 pcmk__assert(match->parent->last != NULL);
742 xmlAddNextSibling(match->parent->last, match);
743 }
744
745 } else {
746 crm_trace("%s is already in position %d",
747 match->name, position);
748 }
749
750 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
751 crm_err("Moved %s.%s to position %d instead of %d (%p)",
752 match->name, pcmk__xe_id(match),
754 position, match->prev);
756 }
757 }
758 }
759
760 g_list_free_full(change_objs, free);
761 return rc;
762}
763
764int
765xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
766{
767 int format = 1;
768 int rc = pcmk_ok;
769 xmlNode *old = NULL;
770 const char *digest = NULL;
771
772 if (patchset == NULL) {
773 return rc;
774 }
775
777
778 if (check_version) {
779 rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
780 if (rc != pcmk_ok) {
781 return rc;
782 }
783 }
784
785 digest = crm_element_value(patchset, PCMK__XA_DIGEST);
786 if (digest != NULL) {
787 /* Make original XML available for logging in case result doesn't have
788 * expected digest
789 */
790 pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
791 }
792
793 if (rc == pcmk_ok) {
794 crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
795
796 if (format != 2) {
797 crm_err("Unknown patch format: %d", format);
798 rc = -EINVAL;
799
800 } else {
801 rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
802 }
803 }
804
805 if ((rc == pcmk_ok) && (digest != NULL)) {
806 char *new_digest = NULL;
807
808 new_digest = pcmk__digest_xml(xml, true);
809 if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
810 crm_info("v%d digest mis-match: expected %s, calculated %s",
811 format, digest, new_digest);
814 {
815 save_xml_to_file(old, "PatchDigest:input", NULL);
816 save_xml_to_file(xml, "PatchDigest:result", NULL);
817 save_xml_to_file(patchset, "PatchDigest:diff", NULL);
818 },
819 {}
820 );
821
822 } else {
823 crm_trace("v%d digest matched: expected %s, calculated %s",
824 format, digest, new_digest);
825 }
826 free(new_digest);
827 }
828 pcmk__xml_free(old);
829 return rc;
830}
831
832bool
833pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
834{
835 const char *element_xpath = pcmk__cib_abs_xpath_for(element);
836 const char *parent_xpath = pcmk_cib_parent_name_for(element);
837 char *element_regex = NULL;
838 bool rc = false;
839 int format = 1;
840
841 pcmk__assert(patchset != NULL);
842
843 crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
844 if (format != 2) {
845 crm_warn("Unknown patch format: %d", format);
846 return false;
847 }
848
849 CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
850
851 /* Matches if and only if element_xpath is part of a changed path
852 * (supported values for element never contain XML IDs with schema
853 * validation enabled)
854 *
855 * @TODO Use POSIX word boundary instead of (/|$), if it works:
856 * https://www.regular-expressions.info/wordboundaries.html.
857 */
858 element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
859
860 for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
861 NULL, NULL);
862 change != NULL; change = pcmk__xe_next(change, PCMK_XE_CHANGE)) {
863
864 const char *op = crm_element_value(change, PCMK_XA_OPERATION);
865 const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
866
867 if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
868 // Change to an existing element
869 rc = true;
870 break;
871 }
872
873 if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
874 && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
875 && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
876 element)) {
877 // Newly added element
878 rc = true;
879 break;
880 }
881 }
882
883 free(element_regex);
884 return rc;
885}
void xml_acl_disable(xmlNode *xml)
Definition acl.c:682
const char * parent
Definition cib.c:27
const char * path
Definition cib.c:28
const char * name
Definition cib.c:26
const char * pcmk__cib_abs_xpath_for(const char *element)
Definition cib.c:133
const char * pcmk_cib_parent_name_for(const char *element_name)
Get the parent element name of a given CIB element name.
Definition cib.c:150
#define PCMK__NELEM(a)
Definition internal.h:50
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
uint32_t version
Definition remote.c:1
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition util.h:80
uint32_t id
Definition cpg.c:0
A dumping ground.
G_GNUC_INTERNAL int pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set)
Definition xml.c:414
char * pcmk__digest_xml(xmlNode *input, bool filter)
Definition digest.c:160
#define crm_log_xml_info(xml, text)
Definition logging.h:376
#define crm_info(fmt, args...)
Definition logging.h:365
#define crm_warn(fmt, args...)
Definition logging.h:360
#define CRM_LOG_ASSERT(expr)
Definition logging.h:196
#define crm_notice(fmt, args...)
Definition logging.h:363
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_debug(fmt, args...)
Definition logging.h:368
#define crm_err(fmt, args...)
Definition logging.h:357
#define crm_trace(fmt, args...)
Definition logging.h:370
#define LOG_TRACE
Definition logging.h:38
#define pcmk__log_xml_patchset(level, patchset)
#define pcmk__if_tracing(if_action, else_action)
#define PCMK_VALUE_MOVE
Definition options.h:177
#define PCMK_VALUE_DELETE
Definition options.h:146
#define PCMK_VALUE_MODIFY
Definition options.h:176
#define PCMK_VALUE_CREATE
Definition options.h:141
bool pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
Definition patchset.c:833
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition patchset.c:244
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition patchset.c:315
struct xml_change_obj_s xml_change_obj_t
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition patchset.c:289
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition patchset.c:765
pcmk__action_result_t result
Definition pcmk_fence.c:37
const char * target
Definition pcmk_fence.c:31
@ pcmk_rc_old_data
Definition results.h:141
@ pcmk_rc_ok
Definition results.h:159
@ pcmk_rc_diff_resync
Definition results.h:143
@ pcmk_rc_diff_failed
Definition results.h:142
#define pcmk_ok
Definition results.h:65
int pcmk_rc2legacy(int rc)
Definition results.c:662
#define pcmk_err_diff_failed
Definition results.h:76
#define pcmk__assert(expr)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
@ pcmk__str_regex
@ pcmk__str_none
@ pcmk__str_casei
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1053
int position
Position of the deleted node among its siblings.
gchar * path
XPath expression identifying the deleted node.
GList * deleted_objs
XML nodes marked as deleted (list of pcmk__deleted_xml_t)
uint32_t flags
Group of enum pcmk__xml_flags
Wrappers for and extensions to libxml2.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
const char * crm_xml_add_int(xmlNode *node, const char *name, int value)
Create an XML attribute with specified name and integer value.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool force, bool(*match)(xmlAttrPtr, void *), void *user_data)
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition xml_element.c:43
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition xml.c:832
bool pcmk__xml_doc_all_flags_set(const xmlDoc *xml, uint32_t flags)
Definition xml.c:147
@ pcmk__xf_deleted
Node was deleted (set for attribute only)
@ pcmk__xf_created
Node was created.
@ pcmk__xf_dirty
@ pcmk__xf_skip
Skip counting this node when getting a node's position among siblings.
@ pcmk__xf_moved
Node was moved.
void pcmk__xml_free(xmlNode *xml)
Definition xml.c:816
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition xml_io.c:604
#define PCMK_XE_CIB
Definition xml_names.h:79
#define PCMK_XE_CHANGE
Definition xml_names.h:74
#define PCMK_XA_FORMAT
Definition xml_names.h:291
#define PCMK_XA_EPOCH
Definition xml_names.h:268
#define PCMK_XA_OPERATION
Definition xml_names.h:349
#define PCMK_XA_PATH
Definition xml_names.h:355
#define PCMK_XA_ID
Definition xml_names.h:301
#define PCMK_XE_TARGET
Definition xml_names.h:210
#define PCMK_XE_VERSION
Definition xml_names.h:220
#define PCMK_XE_CHANGE_LIST
Definition xml_names.h:76
#define PCMK_XE_SOURCE
Definition xml_names.h:200
#define PCMK_XA_ADMIN_EPOCH
Definition xml_names.h:232
#define PCMK_XA_VALUE
Definition xml_names.h:442
#define PCMK_XE_DIFF
Definition xml_names.h:100
#define PCMK_XE_CONFIGURATION
Definition xml_names.h:87
#define PCMK_XA_NUM_UPDATES
Definition xml_names.h:341
#define PCMK_XA_NAME
Definition xml_names.h:330
#define PCMK_XE_CHANGE_ATTR
Definition xml_names.h:75
#define PCMK_XE_CHANGE_RESULT
Definition xml_names.h:77
#define PCMK_XE_POSITION
Definition xml_names.h:163
#define PCMK__XA_DIGEST
GString * pcmk__element_xpath(const xmlNode *xml)
Definition xpath.c:281