pacemaker 2.1.4-dc6eb4362e
Scalable High-Availability cluster resource manager
patchset.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2021 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/parser.h>
22#include <libxml/tree.h>
23#include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
24
25#include <crm/crm.h>
26#include <crm/msg_xml.h>
27#include <crm/common/xml.h>
28#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
29#include "crmcommon_private.h"
30
31static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
32 xmlNode *right, gboolean *changed);
33
34/*
35<diff format="2.0">
36 <version>
37 <source admin_epoch="1" epoch="2" num_updates="3"/>
38 <target admin_epoch="1" epoch="3" num_updates="0"/>
39 </version>
40 <change operation="add" xpath="/cib/configuration/nodes">
41 <node id="node2" uname="node2" description="foo"/>
42 </change>
43 <change operation="add" xpath="/cib/configuration/nodes/node[node2]">
44 <instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
45 <nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
46 </instance_attributes>
47 </change>
48 <change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
49 <change-list>
50 <change-attr operation="set" name="type" value="member"/>
51 <change-attr operation="unset" name="description"/>
52 </change-list>
53 <change-result>
54 <node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
55 </change-result>
56 </change>
57 <change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
58 <change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
59 <change-list>
60 <change-attr operation="set" name="description" value="some garbage here"/>
61 </change-list>
62 <change-result>
63 <group id="g1" description="some garbage here"/><!-- NOTE: not recursive -->
64 </change-result>
65 </change>
66 <change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
67 <change-list>
68 <change-attr operation="set" name="oper" value="member"/>
69 <change-attr operation="set" name="operation_key" value="Fence_start_0"/>
70 <change-attr operation="set" name="operation" value="start"/>
71 <change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
72 <change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
73 <change-attr operation="set" name="call-id" value="2"/>
74 <change-attr operation="set" name="rc-code" value="0"/>
75 </change-list>
76 <change-result>
77 <lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate" transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
78 </change-result>
79 </change>
80</diff>
81 */
82
83// Add changes for specified XML to patchset
84static void
85add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
86{
87 xmlNode *cIter = NULL;
88 xmlAttr *pIter = NULL;
89 xmlNode *change = NULL;
90 xml_private_t *p = xml->_private;
91 const char *value = NULL;
92
93 // If this XML node is new, just report that
94 if (patchset && pcmk_is_set(p->flags, pcmk__xf_created)) {
95 int offset = 0;
96 char buffer[PCMK__BUFFER_SIZE];
97
98 if (pcmk__element_xpath(NULL, xml->parent, buffer, offset,
99 sizeof(buffer)) > 0) {
100 int position = pcmk__xml_position(xml, pcmk__xf_deleted);
101
102 change = create_xml_node(patchset, XML_DIFF_CHANGE);
103
104 crm_xml_add(change, XML_DIFF_OP, "create");
105 crm_xml_add(change, XML_DIFF_PATH, buffer);
106 crm_xml_add_int(change, XML_DIFF_POSITION, position);
107 add_node_copy(change, xml);
108 }
109
110 return;
111 }
112
113 // Check each of the XML node's attributes for changes
114 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
115 pIter = pIter->next) {
116 xmlNode *attr = NULL;
117
118 p = pIter->_private;
119 if (!pcmk_any_flags_set(p->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
120 continue;
121 }
122
123 if (change == NULL) {
124 int offset = 0;
125 char buffer[PCMK__BUFFER_SIZE];
126
127 if (pcmk__element_xpath(NULL, xml, buffer, offset,
128 sizeof(buffer)) > 0) {
129 change = create_xml_node(patchset, XML_DIFF_CHANGE);
130
131 crm_xml_add(change, XML_DIFF_OP, "modify");
132 crm_xml_add(change, XML_DIFF_PATH, buffer);
133
134 change = create_xml_node(change, XML_DIFF_LIST);
135 }
136 }
137
138 attr = create_xml_node(change, XML_DIFF_ATTR);
139
140 crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
141 if (p->flags & pcmk__xf_deleted) {
142 crm_xml_add(attr, XML_DIFF_OP, "unset");
143
144 } else {
145 crm_xml_add(attr, XML_DIFF_OP, "set");
146
147 value = crm_element_value(xml, (const char *) pIter->name);
149 }
150 }
151
152 if (change) {
153 xmlNode *result = NULL;
154
155 change = create_xml_node(change->parent, XML_DIFF_RESULT);
156 result = create_xml_node(change, (const char *)xml->name);
157
158 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
159 pIter = pIter->next) {
160 p = pIter->_private;
162 value = crm_element_value(xml, (const char *) pIter->name);
163 crm_xml_add(result, (const char *)pIter->name, value);
164 }
165 }
166 }
167
168 // Now recursively do the same for each child node of this node
169 for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
170 cIter = pcmk__xml_next(cIter)) {
171 add_xml_changes_to_patchset(cIter, patchset);
172 }
173
174 p = xml->_private;
175 if (patchset && pcmk_is_set(p->flags, pcmk__xf_moved)) {
176 int offset = 0;
177 char buffer[PCMK__BUFFER_SIZE];
178
179 crm_trace("%s.%s moved to position %d",
180 xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
181 if (pcmk__element_xpath(NULL, xml, buffer, offset,
182 sizeof(buffer)) > 0) {
183 change = create_xml_node(patchset, XML_DIFF_CHANGE);
184
185 crm_xml_add(change, XML_DIFF_OP, "move");
186 crm_xml_add(change, XML_DIFF_PATH, buffer);
189 }
190 }
191}
192
193static bool
194is_config_change(xmlNode *xml)
195{
196 GList *gIter = NULL;
197 xml_private_t *p = NULL;
198 xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
199
200 if (config) {
201 p = config->_private;
202 }
203 if ((p != NULL) && pcmk_is_set(p->flags, pcmk__xf_dirty)) {
204 return TRUE;
205 }
206
207 if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
208 p = xml->doc->_private;
209 for (gIter = p->deleted_objs; gIter; gIter = gIter->next) {
210 pcmk__deleted_xml_t *deleted_obj = gIter->data;
211
212 if (strstr(deleted_obj->path,
213 "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION) != NULL) {
214 return TRUE;
215 }
216 }
217 }
218 return FALSE;
219}
220
221static void
222xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
223 gboolean changed)
224{
225 int lpc = 0;
226 xmlNode *cib = NULL;
227 xmlNode *diff_child = NULL;
228
229 const char *tag = NULL;
230
231 const char *vfields[] = {
235 };
236
237 if (local_diff == NULL) {
238 crm_trace("Nothing to do");
239 return;
240 }
241
242 tag = "diff-removed";
243 diff_child = find_xml_node(local_diff, tag, FALSE);
244 if (diff_child == NULL) {
245 diff_child = create_xml_node(local_diff, tag);
246 }
247
248 tag = XML_TAG_CIB;
249 cib = find_xml_node(diff_child, tag, FALSE);
250 if (cib == NULL) {
251 cib = create_xml_node(diff_child, tag);
252 }
253
254 for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
255 const char *value = crm_element_value(last, vfields[lpc]);
256
257 crm_xml_add(diff_child, vfields[lpc], value);
258 if (changed || lpc == 2) {
259 crm_xml_add(cib, vfields[lpc], value);
260 }
261 }
262
263 tag = "diff-added";
264 diff_child = find_xml_node(local_diff, tag, FALSE);
265 if (diff_child == NULL) {
266 diff_child = create_xml_node(local_diff, tag);
267 }
268
269 tag = XML_TAG_CIB;
270 cib = find_xml_node(diff_child, tag, FALSE);
271 if (cib == NULL) {
272 cib = create_xml_node(diff_child, tag);
273 }
274
275 for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
276 const char *value = crm_element_value(next, vfields[lpc]);
277
278 crm_xml_add(diff_child, vfields[lpc], value);
279 }
280
281 for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
282 const char *p_value = crm_element_value(next, (const char *) a->name);
283
284 xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
285 }
286
287 crm_log_xml_explicit(local_diff, "Repaired-diff");
288}
289
290static xmlNode *
291xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
292 bool suppress)
293{
294 xmlNode *patchset = diff_xml_object(source, target, suppress);
295
296 if (patchset) {
298 xml_repair_v1_diff(source, target, patchset, config);
299 crm_xml_add(patchset, "format", "1");
300 }
301 return patchset;
302}
303
304static xmlNode *
305xml_create_patchset_v2(xmlNode *source, xmlNode *target)
306{
307 int lpc = 0;
308 GList *gIter = NULL;
309 xml_private_t *doc = NULL;
310
311 xmlNode *v = NULL;
312 xmlNode *version = NULL;
313 xmlNode *patchset = NULL;
314 const char *vfields[] = {
318 };
319
322 return NULL;
323 }
324
325 CRM_ASSERT(target->doc);
326 doc = target->doc->_private;
327
328 patchset = create_xml_node(NULL, XML_TAG_DIFF);
329 crm_xml_add_int(patchset, "format", 2);
330
332
334 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
335 const char *value = crm_element_value(source, vfields[lpc]);
336
337 if (value == NULL) {
338 value = "1";
339 }
340 crm_xml_add(v, vfields[lpc], value);
341 }
342
344 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
345 const char *value = crm_element_value(target, vfields[lpc]);
346
347 if (value == NULL) {
348 value = "1";
349 }
350 crm_xml_add(v, vfields[lpc], value);
351 }
352
353 for (gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
354 pcmk__deleted_xml_t *deleted_obj = gIter->data;
355 xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
356
357 crm_xml_add(change, XML_DIFF_OP, "delete");
358 crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
359 if (deleted_obj->position >= 0) {
360 crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
361 }
362 }
363
364 add_xml_changes_to_patchset(target, patchset);
365 return patchset;
366}
367
368xmlNode *
369xml_create_patchset(int format, xmlNode *source, xmlNode *target,
370 bool *config_changed, bool manage_version)
371{
372 int counter = 0;
373 bool config = FALSE;
374 xmlNode *patch = NULL;
375 const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
376
379 crm_trace("No change %d", format);
380 return NULL; /* No change */
381 }
382
383 config = is_config_change(target);
384 if (config_changed) {
385 *config_changed = config;
386 }
387
388 if (manage_version && config) {
389 crm_trace("Config changed %d", format);
391
394
395 } else if (manage_version) {
397 crm_trace("Status changed %d - %d %s", format, counter,
400 }
401
402 if (format == 0) {
403 if (compare_version("3.0.8", version) < 0) {
404 format = 2;
405 } else {
406 format = 1;
407 }
408 crm_trace("Using patch format %d for version: %s", format, version);
409 }
410
411 switch (format) {
412 case 1:
413 patch = xml_create_patchset_v1(source, target, config, FALSE);
414 break;
415 case 2:
416 patch = xml_create_patchset_v2(source, target);
417 break;
418 default:
419 crm_err("Unknown patch format: %d", format);
420 return NULL;
421 }
422 return patch;
423}
424
425void
426patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
427 bool with_digest)
428{
429 int format = 1;
430 const char *version = NULL;
431 char *digest = NULL;
432
433 if ((patch == NULL) || (source == NULL) || (target == NULL)) {
434 return;
435 }
436
437 /* We should always call xml_accept_changes() before calculating a digest.
438 * Otherwise, with an on-tracking dirty target, we could get a wrong digest.
439 */
441
442 crm_element_value_int(patch, "format", &format);
443 if ((format > 1) && !with_digest) {
444 return;
445 }
446
448 digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
449
450 crm_xml_add(patch, XML_ATTR_DIGEST, digest);
451 free(digest);
452
453 return;
454}
455
456void
457xml_log_patchset(uint8_t log_level, const char *function, xmlNode *patchset)
458{
459 int format = 1;
460 xmlNode *child = NULL;
461 xmlNode *added = NULL;
462 xmlNode *removed = NULL;
463 gboolean is_first = TRUE;
464
465 int add[] = { 0, 0, 0 };
466 int del[] = { 0, 0, 0 };
467
468 const char *fmt = NULL;
469 const char *digest = NULL;
470 int options = xml_log_option_formatted;
471
472 static struct qb_log_callsite *patchset_cs = NULL;
473
474 if (log_level == LOG_NEVER) {
475 return;
476 }
477 if (patchset_cs == NULL) {
478 patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset",
479 log_level, __LINE__, 0);
480 }
481
482 if (patchset == NULL) {
483 crm_trace("Empty patch");
484 return;
485
486 } else if ((log_level != LOG_STDOUT)
487 && !crm_is_callsite_active(patchset_cs, log_level, 0)) {
488 return;
489 }
490
491 xml_patch_versions(patchset, add, del);
492 fmt = crm_element_value(patchset, "format");
493 digest = crm_element_value(patchset, XML_ATTR_DIGEST);
494
495 if ((add[2] != del[2]) || (add[1] != del[1]) || (add[0] != del[0])) {
496 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
497 "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
498 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
499 "Diff: +++ %d.%d.%d %s",
500 add[0], add[1], add[2], digest);
501
502 } else if ((patchset != NULL) && (add[0] || add[1] || add[2])) {
503 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
504 "%s: Local-only Change: %d.%d.%d",
505 (function? function : ""), add[0], add[1], add[2]);
506 }
507
508 crm_element_value_int(patchset, "format", &format);
509 if (format == 2) {
510 xmlNode *change = NULL;
511
512 for (change = pcmk__xml_first_child(patchset); change != NULL;
513 change = pcmk__xml_next(change)) {
514 const char *op = crm_element_value(change, XML_DIFF_OP);
515 const char *xpath = crm_element_value(change, XML_DIFF_PATH);
516
517 if (op == NULL) {
518 } else if (strcmp(op, "create") == 0) {
519 int lpc = 0, max = 0;
520 char *prefix = crm_strdup_printf("++ %s: ", xpath);
521
522 max = strlen(prefix);
523 pcmk__xe_log(log_level, __FILE__, function, __LINE__, prefix,
524 change->children, 0,
526
527 for (lpc = 2; lpc < max; lpc++) {
528 prefix[lpc] = ' ';
529 }
530
531 pcmk__xe_log(log_level, __FILE__, function, __LINE__, prefix,
532 change->children, 0,
535 free(prefix);
536
537 } else if (strcmp(op, "move") == 0) {
538 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
539 "+~ %s moved to offset %s", xpath,
541
542 } else if (strcmp(op, "modify") == 0) {
543 xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
544 char buffer_set[PCMK__BUFFER_SIZE];
545 char buffer_unset[PCMK__BUFFER_SIZE];
546 int o_set = 0;
547 int o_unset = 0;
548
549 buffer_set[0] = 0;
550 buffer_unset[0] = 0;
551 for (child = pcmk__xml_first_child(clist); child != NULL;
552 child = pcmk__xml_next(child)) {
553 const char *name = crm_element_value(child, "name");
554
555 op = crm_element_value(child, XML_DIFF_OP);
556 if (op == NULL) {
557 } else if (strcmp(op, "set") == 0) {
558 const char *value = crm_element_value(child, "value");
559
560 if (o_set > 0) {
561 o_set += snprintf(buffer_set + o_set,
562 PCMK__BUFFER_SIZE - o_set, ", ");
563 }
564 o_set += snprintf(buffer_set + o_set,
565 PCMK__BUFFER_SIZE - o_set, "@%s=%s",
566 name, value);
567
568 } else if (strcmp(op, "unset") == 0) {
569 if (o_unset > 0) {
570 o_unset += snprintf(buffer_unset + o_unset,
571 PCMK__BUFFER_SIZE - o_unset,
572 ", ");
573 }
574 o_unset += snprintf(buffer_unset + o_unset,
575 PCMK__BUFFER_SIZE - o_unset, "@%s",
576 name);
577 }
578 }
579 if (o_set) {
580 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
581 "+ %s: %s", xpath, buffer_set);
582 }
583 if (o_unset) {
584 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
585 "-- %s: %s", xpath, buffer_unset);
586 }
587
588 } else if (strcmp(op, "delete") == 0) {
589 int position = -1;
590
591 crm_element_value_int(change, XML_DIFF_POSITION, &position);
592 if (position >= 0) {
593 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
594 "-- %s (%d)", xpath, position);
595
596 } else {
597 do_crm_log_alias(log_level, __FILE__, function, __LINE__,
598 "-- %s", xpath);
599 }
600 }
601 }
602 return;
603 }
604
605 if ((log_level < LOG_DEBUG) || (function == NULL)) {
606 options |= xml_log_option_diff_short;
607 }
608
609 removed = find_xml_node(patchset, "diff-removed", FALSE);
610 for (child = pcmk__xml_first_child(removed); child != NULL;
611 child = pcmk__xml_next(child)) {
612 log_data_element(log_level, __FILE__, function, __LINE__, "- ", child,
613 0, options|xml_log_option_diff_minus);
614 if (is_first) {
615 is_first = FALSE;
616 } else {
617 do_crm_log_alias(log_level, __FILE__, function, __LINE__, " --- ");
618 }
619 }
620
621 is_first = TRUE;
622 added = find_xml_node(patchset, "diff-added", FALSE);
623 for (child = pcmk__xml_first_child(added); child != NULL;
624 child = pcmk__xml_next(child)) {
625 log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child,
626 0, options|xml_log_option_diff_plus);
627 if (is_first) {
628 is_first = FALSE;
629 } else {
630 do_crm_log_alias(log_level, __FILE__, function, __LINE__, " +++ ");
631 }
632 }
633}
634
635// Return true if attribute name is not "id"
636static bool
637not_id(xmlAttrPtr attr, void *user_data)
638{
639 return strcmp((const char *) attr->name, XML_ATTR_ID) != 0;
640}
641
642// Apply the removals section of an v1 patchset to an XML node
643static void
644process_v1_removals(xmlNode *target, xmlNode *patch)
645{
646 xmlNode *patch_child = NULL;
647 xmlNode *cIter = NULL;
648
649 char *id = NULL;
650 const char *name = NULL;
651 const char *value = NULL;
652
653 if ((target == NULL) || (patch == NULL)) {
654 return;
655 }
656
657 if (target->type == XML_COMMENT_NODE) {
658 gboolean dummy;
659
660 subtract_xml_comment(target->parent, target, patch, &dummy);
661 }
662
663 name = crm_element_name(target);
664 CRM_CHECK(name != NULL, return);
665 CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
667 return);
668 CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
669
670 // Check for XML_DIFF_MARKER in a child
672 value = crm_element_value(patch, XML_DIFF_MARKER);
673 if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
674 crm_trace("We are the root of the deletion: %s.id=%s", name, id);
676 free(id);
677 return;
678 }
679
680 // Removing then restoring id would change ordering of properties
681 pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
682
683 // Changes to child objects
684 cIter = pcmk__xml_first_child(target);
685 while (cIter) {
686 xmlNode *target_child = cIter;
687
688 cIter = pcmk__xml_next(cIter);
689 patch_child = pcmk__xml_match(patch, target_child, false);
690 process_v1_removals(target_child, patch_child);
691 }
692 free(id);
693}
694
695// Apply the additions section of an v1 patchset to an XML node
696static void
697process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
698{
699 xmlNode *patch_child = NULL;
700 xmlNode *target_child = NULL;
701 xmlAttrPtr xIter = NULL;
702
703 const char *id = NULL;
704 const char *name = NULL;
705 const char *value = NULL;
706
707 if (patch == NULL) {
708 return;
709 } else if ((parent == NULL) && (target == NULL)) {
710 return;
711 }
712
713 // Check for XML_DIFF_MARKER in a child
714 value = crm_element_value(patch, XML_DIFF_MARKER);
715 if ((target == NULL) && (value != NULL)
716 && (strcmp(value, "added:top") == 0)) {
717 id = ID(patch);
718 name = crm_element_name(patch);
719 crm_trace("We are the root of the addition: %s.id=%s", name, id);
720 add_node_copy(parent, patch);
721 return;
722
723 } else if (target == NULL) {
724 id = ID(patch);
725 name = crm_element_name(patch);
726 crm_err("Could not locate: %s.id=%s", name, id);
727 return;
728 }
729
730 if (target->type == XML_COMMENT_NODE) {
732 }
733
734 name = crm_element_name(target);
735 CRM_CHECK(name != NULL, return);
736 CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
738 return);
739 CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
740
741 for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
742 xIter = xIter->next) {
743 const char *p_name = (const char *) xIter->name;
744 const char *p_value = crm_element_value(patch, p_name);
745
746 xml_remove_prop(target, p_name); // Preserve patch order
747 crm_xml_add(target, p_name, p_value);
748 }
749
750 // Changes to child objects
751 for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
752 patch_child = pcmk__xml_next(patch_child)) {
753
754 target_child = pcmk__xml_match(target, patch_child, false);
755 process_v1_additions(target, target_child, patch_child);
756 }
757}
758
770static bool
771find_patch_xml_node(xmlNode *patchset, int format, bool added,
772 xmlNode **patch_node)
773{
774 xmlNode *cib_node;
775 const char *label;
776
777 switch (format) {
778 case 1:
779 label = added? "diff-added" : "diff-removed";
780 *patch_node = find_xml_node(patchset, label, FALSE);
781 cib_node = find_xml_node(*patch_node, "cib", FALSE);
782 if (cib_node != NULL) {
783 *patch_node = cib_node;
784 }
785 break;
786 case 2:
787 label = added? "target" : "source";
788 *patch_node = find_xml_node(patchset, "version", FALSE);
789 *patch_node = find_xml_node(*patch_node, label, FALSE);
790 break;
791 default:
792 crm_warn("Unknown patch format: %d", format);
793 *patch_node = NULL;
794 return FALSE;
795 }
796 return TRUE;
797}
798
799// Get CIB versions used for additions and deletions in a patchset
800bool
801xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
802{
803 int lpc = 0;
804 int format = 1;
805 xmlNode *tmp = NULL;
806
807 const char *vfields[] = {
811 };
812
813
814 crm_element_value_int(patchset, "format", &format);
815
816 /* Process removals */
817 if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
818 return -EINVAL;
819 }
820 if (tmp != NULL) {
821 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
822 crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
823 crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
824 }
825 }
826
827 /* Process additions */
828 if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
829 return -EINVAL;
830 }
831 if (tmp != NULL) {
832 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
833 crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
834 crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
835 }
836 }
837 return pcmk_ok;
838}
839
850static int
851xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
852{
853 int lpc = 0;
854 bool changed = FALSE;
855
856 int this[] = { 0, 0, 0 };
857 int add[] = { 0, 0, 0 };
858 int del[] = { 0, 0, 0 };
859
860 const char *vfields[] = {
864 };
865
866 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
867 crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
868 crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
869 if (this[lpc] < 0) {
870 this[lpc] = 0;
871 }
872 }
873
874 /* Set some defaults in case nothing is present */
875 add[0] = this[0];
876 add[1] = this[1];
877 add[2] = this[2] + 1;
878 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
879 del[lpc] = this[lpc];
880 }
881
882 xml_patch_versions(patchset, add, del);
883
884 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
885 if (this[lpc] < del[lpc]) {
886 crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
887 vfields[lpc], this[0], this[1], this[2],
888 del[0], del[1], del[2], add[0], add[1], add[2]);
889 return pcmk_rc_diff_resync;
890
891 } else if (this[lpc] > del[lpc]) {
892 crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
893 vfields[lpc], this[0], this[1], this[2],
894 del[0], del[1], del[2], add[0], add[1], add[2], patchset);
895 crm_log_xml_info(patchset, "OldPatch");
896 return pcmk_rc_old_data;
897 }
898 }
899
900 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
901 if (add[lpc] > del[lpc]) {
902 changed = TRUE;
903 }
904 }
905
906 if (!changed) {
907 crm_notice("Versions did not change in patch %d.%d.%d",
908 add[0], add[1], add[2]);
909 return pcmk_rc_old_data;
910 }
911
912 crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
913 add[0], add[1], add[2], this[0], this[1], this[2]);
914 return pcmk_rc_ok;
915}
916
926static int
927apply_v1_patchset(xmlNode *xml, xmlNode *patchset)
928{
929 int rc = pcmk_rc_ok;
930 int root_nodes_seen = 0;
931
932 xmlNode *child_diff = NULL;
933 xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
934 xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
935 xmlNode *old = copy_xml(xml);
936
937 crm_trace("Subtraction Phase");
938 for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
939 child_diff = pcmk__xml_next(child_diff)) {
940 CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
941 if (root_nodes_seen == 0) {
942 process_v1_removals(xml, child_diff);
943 }
944 root_nodes_seen++;
945 }
946
947 if (root_nodes_seen > 1) {
948 crm_err("(-) Diffs cannot contain more than one change set... saw %d",
949 root_nodes_seen);
950 rc = ENOTUNIQ;
951 }
952
953 root_nodes_seen = 0;
954 crm_trace("Addition Phase");
955 if (rc == pcmk_rc_ok) {
956 xmlNode *child_diff = NULL;
957
958 for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
959 child_diff = pcmk__xml_next(child_diff)) {
960 CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
961 if (root_nodes_seen == 0) {
962 process_v1_additions(NULL, xml, child_diff);
963 }
964 root_nodes_seen++;
965 }
966 }
967
968 if (root_nodes_seen > 1) {
969 crm_err("(+) Diffs cannot contain more than one change set... saw %d",
970 root_nodes_seen);
971 rc = ENOTUNIQ;
972 }
973
974 purge_diff_markers(xml); // Purge prior to checking digest
975
976 free_xml(old);
977 return rc;
978}
979
980// Return first child matching element name and optionally id or position
981static xmlNode *
982first_matching_xml_child(xmlNode *parent, const char *name, const char *id,
983 int position)
984{
985 xmlNode *cIter = NULL;
986
987 for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
988 cIter = pcmk__xml_next(cIter)) {
989 if (strcmp((const char *) cIter->name, name) != 0) {
990 continue;
991 } else if (id) {
992 const char *cid = ID(cIter);
993
994 if ((cid == NULL) || (strcmp(cid, id) != 0)) {
995 continue;
996 }
997 }
998
999 // "position" makes sense only for XML comments for now
1000 if ((cIter->type == XML_COMMENT_NODE)
1001 && (position >= 0)
1002 && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
1003 continue;
1004 }
1005
1006 return cIter;
1007 }
1008 return NULL;
1009}
1010
1024static xmlNode *
1025search_v2_xpath(xmlNode *top, const char *key, int target_position)
1026{
1027 xmlNode *target = (xmlNode *) top->doc;
1028 const char *current = key;
1029 char *section;
1030 char *remainder;
1031 char *id;
1032 char *tag;
1033 char *path = NULL;
1034 int rc;
1035 size_t key_len;
1036
1037 CRM_CHECK(key != NULL, return NULL);
1038 key_len = strlen(key);
1039
1040 /* These are scanned from key after a slash, so they can't be bigger
1041 * than key_len - 1 characters plus a null terminator.
1042 */
1043
1044 remainder = calloc(key_len, sizeof(char));
1045 CRM_ASSERT(remainder != NULL);
1046
1047 section = calloc(key_len, sizeof(char));
1048 CRM_ASSERT(section != NULL);
1049
1050 id = calloc(key_len, sizeof(char));
1051 CRM_ASSERT(id != NULL);
1052
1053 tag = calloc(key_len, sizeof(char));
1054 CRM_ASSERT(tag != NULL);
1055
1056 do {
1057 // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1058 rc = sscanf(current, "/%[^/]%s", section, remainder);
1059 if (rc > 0) {
1060 // Separate FIRST_COMPONENT into TAG[@id='ID']
1061 int f = sscanf(section, "%[^[][@id='%[^']", tag, id);
1062 int current_position = -1;
1063
1064 /* The target position is for the final component tag, so only use
1065 * it if there is nothing left to search after this component.
1066 */
1067 if ((rc == 1) && (target_position >= 0)) {
1068 current_position = target_position;
1069 }
1070
1071 switch (f) {
1072 case 1:
1073 target = first_matching_xml_child(target, tag, NULL,
1074 current_position);
1075 break;
1076 case 2:
1077 target = first_matching_xml_child(target, tag, id,
1078 current_position);
1079 break;
1080 default:
1081 // This should not be possible
1082 target = NULL;
1083 break;
1084 }
1085 current = remainder;
1086 }
1087
1088 // Continue if something remains to search, and we've matched so far
1089 } while ((rc == 2) && target);
1090
1091 if (target) {
1092 crm_trace("Found %s for %s",
1093 (path = (char *) xmlGetNodePath(target)), key);
1094 free(path);
1095 } else {
1096 crm_debug("No match for %s", key);
1097 }
1098
1099 free(remainder);
1100 free(section);
1101 free(tag);
1102 free(id);
1103 return target;
1104}
1105
1106typedef struct xml_change_obj_s {
1107 xmlNode *change;
1108 xmlNode *match;
1110
1111static gint
1112sort_change_obj_by_position(gconstpointer a, gconstpointer b)
1113{
1114 const xml_change_obj_t *change_obj_a = a;
1115 const xml_change_obj_t *change_obj_b = b;
1116 int position_a = -1;
1117 int position_b = -1;
1118
1119 crm_element_value_int(change_obj_a->change, XML_DIFF_POSITION, &position_a);
1120 crm_element_value_int(change_obj_b->change, XML_DIFF_POSITION, &position_b);
1121
1122 if (position_a < position_b) {
1123 return -1;
1124
1125 } else if (position_a > position_b) {
1126 return 1;
1127 }
1128
1129 return 0;
1130}
1131
1141static int
1142apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
1143{
1144 int rc = pcmk_rc_ok;
1145 xmlNode *change = NULL;
1146 GList *change_objs = NULL;
1147 GList *gIter = NULL;
1148
1149 for (change = pcmk__xml_first_child(patchset); change != NULL;
1150 change = pcmk__xml_next(change)) {
1151 xmlNode *match = NULL;
1152 const char *op = crm_element_value(change, XML_DIFF_OP);
1153 const char *xpath = crm_element_value(change, XML_DIFF_PATH);
1154 int position = -1;
1155
1156 if (op == NULL) {
1157 continue;
1158 }
1159
1160 crm_trace("Processing %s %s", change->name, op);
1161
1162 // "delete" changes for XML comments are generated with "position"
1163 if (strcmp(op, "delete") == 0) {
1164 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1165 }
1166 match = search_v2_xpath(xml, xpath, position);
1167 crm_trace("Performing %s on %s with %p", op, xpath, match);
1168
1169 if ((match == NULL) && (strcmp(op, "delete") == 0)) {
1170 crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1171 continue;
1172
1173 } else if (match == NULL) {
1174 crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1176 continue;
1177
1178 } else if ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) {
1179 // Delay the adding of a "create" object
1180 xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
1181
1182 CRM_ASSERT(change_obj != NULL);
1183
1184 change_obj->change = change;
1185 change_obj->match = match;
1186
1187 change_objs = g_list_append(change_objs, change_obj);
1188
1189 if (strcmp(op, "move") == 0) {
1190 // Temporarily put the "move" object after the last sibling
1191 if ((match->parent != NULL) && (match->parent->last != NULL)) {
1192 xmlAddNextSibling(match->parent->last, match);
1193 }
1194 }
1195
1196 } else if (strcmp(op, "delete") == 0) {
1197 free_xml(match);
1198
1199 } else if (strcmp(op, "modify") == 0) {
1200 xmlNode *attrs = NULL;
1201
1202 attrs = pcmk__xml_first_child(first_named_child(change,
1204 if (attrs == NULL) {
1205 rc = ENOMSG;
1206 continue;
1207 }
1208 pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
1209
1210 for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
1211 pIter = pIter->next) {
1212 const char *name = (const char *) pIter->name;
1213 const char *value = crm_element_value(attrs, name);
1214
1215 crm_xml_add(match, name, value);
1216 }
1217
1218 } else {
1219 crm_err("Unknown operation: %s", op);
1221 }
1222 }
1223
1224 // Changes should be generated in the right order. Double checking.
1225 change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
1226
1227 for (gIter = change_objs; gIter; gIter = gIter->next) {
1228 xml_change_obj_t *change_obj = gIter->data;
1229 xmlNode *match = change_obj->match;
1230 const char *op = NULL;
1231 const char *xpath = NULL;
1232
1233 change = change_obj->change;
1234
1235 op = crm_element_value(change, XML_DIFF_OP);
1236 xpath = crm_element_value(change, XML_DIFF_PATH);
1237
1238 crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1239
1240 if (strcmp(op, "create") == 0) {
1241 int position = 0;
1242 xmlNode *child = NULL;
1243 xmlNode *match_child = NULL;
1244
1245 match_child = match->children;
1246 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1247
1248 while ((match_child != NULL)
1249 && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1250 match_child = match_child->next;
1251 }
1252
1253 child = xmlDocCopyNode(change->children, match->doc, 1);
1254 if (match_child) {
1255 crm_trace("Adding %s at position %d", child->name, position);
1256 xmlAddPrevSibling(match_child, child);
1257
1258 } else if (match->last) {
1259 crm_trace("Adding %s at position %d (end)",
1260 child->name, position);
1261 xmlAddNextSibling(match->last, child);
1262
1263 } else {
1264 crm_trace("Adding %s at position %d (first)",
1265 child->name, position);
1266 CRM_LOG_ASSERT(position == 0);
1267 xmlAddChild(match, child);
1268 }
1270
1271 } else if (strcmp(op, "move") == 0) {
1272 int position = 0;
1273
1274 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1275 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1276 xmlNode *match_child = NULL;
1277 int p = position;
1278
1279 if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1280 p++; // Skip ourselves
1281 }
1282
1283 CRM_ASSERT(match->parent != NULL);
1284 match_child = match->parent->children;
1285
1286 while ((match_child != NULL)
1287 && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1288 match_child = match_child->next;
1289 }
1290
1291 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1292 match->name, position,
1294 match->prev, (match_child? "next":"last"),
1295 (match_child? match_child : match->parent->last));
1296
1297 if (match_child) {
1298 xmlAddPrevSibling(match_child, match);
1299
1300 } else {
1301 CRM_ASSERT(match->parent->last != NULL);
1302 xmlAddNextSibling(match->parent->last, match);
1303 }
1304
1305 } else {
1306 crm_trace("%s is already in position %d",
1307 match->name, position);
1308 }
1309
1310 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1311 crm_err("Moved %s.%s to position %d instead of %d (%p)",
1312 match->name, ID(match),
1314 position, match->prev);
1316 }
1317 }
1318 }
1319
1320 g_list_free_full(change_objs, free);
1321 return rc;
1322}
1323
1324int
1325xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1326{
1327 int format = 1;
1328 int rc = pcmk_ok;
1329 xmlNode *old = NULL;
1330 const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1331
1332 if (patchset == NULL) {
1333 return rc;
1334 }
1335
1336 xml_log_patchset(LOG_TRACE, __func__, patchset);
1337
1338 crm_element_value_int(patchset, "format", &format);
1339 if (check_version) {
1340 rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format));
1341 if (rc != pcmk_ok) {
1342 return rc;
1343 }
1344 }
1345
1346 if (digest) {
1347 // Make it available for logging if result doesn't have expected digest
1348 old = copy_xml(xml);
1349 }
1350
1351 if (rc == pcmk_ok) {
1352 switch (format) {
1353 case 1:
1354 rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1355 break;
1356 case 2:
1357 rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1358 break;
1359 default:
1360 crm_err("Unknown patch format: %d", format);
1361 rc = -EINVAL;
1362 }
1363 }
1364
1365 if ((rc == pcmk_ok) && (digest != NULL)) {
1366 static struct qb_log_callsite *digest_cs = NULL;
1367
1368 char *new_digest = NULL;
1370
1371 if (digest_cs == NULL) {
1372 digest_cs = qb_log_callsite_get(__func__, __FILE__, "diff-digest",
1373 LOG_TRACE, __LINE__,
1375 }
1376
1377 new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1378 if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1379 crm_info("v%d digest mis-match: expected %s, calculated %s",
1380 format, digest, new_digest);
1382
1383 if ((digest_cs != NULL) && digest_cs->targets) {
1384 save_xml_to_file(old, "PatchDigest:input", NULL);
1385 save_xml_to_file(xml, "PatchDigest:result", NULL);
1386 save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1387
1388 } else {
1389 crm_trace("%p %.6x", digest_cs,
1390 ((digest_cs != NULL)? digest_cs->targets : 0));
1391 }
1392
1393 } else {
1394 crm_trace("v%d digest matched: expected %s, calculated %s",
1395 format, digest, new_digest);
1396 }
1397 free(new_digest);
1398 free(version);
1399 }
1400 free_xml(old);
1401 return rc;
1402}
1403
1404void
1405purge_diff_markers(xmlNode *a_node)
1406{
1407 xmlNode *child = NULL;
1408
1409 CRM_CHECK(a_node != NULL, return);
1410
1412 for (child = pcmk__xml_first_child(a_node); child != NULL;
1413 child = pcmk__xml_next(child)) {
1414 purge_diff_markers(child);
1415 }
1416}
1417
1418xmlNode *
1419diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1420{
1421 xmlNode *tmp1 = NULL;
1422 xmlNode *diff = create_xml_node(NULL, "diff");
1423 xmlNode *removed = create_xml_node(diff, "diff-removed");
1424 xmlNode *added = create_xml_node(diff, "diff-added");
1425
1427
1428 tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
1429 if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1430 free_xml(tmp1);
1431 }
1432
1433 tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
1434 if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1435 free_xml(tmp1);
1436 }
1437
1438 if ((added->children == NULL) && (removed->children == NULL)) {
1439 free_xml(diff);
1440 diff = NULL;
1441 }
1442
1443 return diff;
1444}
1445
1446static xmlNode *
1447subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
1448 gboolean *changed)
1449{
1450 CRM_CHECK(left != NULL, return NULL);
1451 CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
1452
1453 if ((right == NULL) || !pcmk__str_eq((const char *)left->content,
1454 (const char *)right->content,
1455 pcmk__str_casei)) {
1456 xmlNode *deleted = NULL;
1457
1458 deleted = add_node_copy(parent, left);
1459 *changed = TRUE;
1460
1461 return deleted;
1462 }
1463
1464 return NULL;
1465}
1466
1467xmlNode *
1468subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1469 gboolean full, gboolean *changed, const char *marker)
1470{
1471 gboolean dummy = FALSE;
1472 xmlNode *diff = NULL;
1473 xmlNode *right_child = NULL;
1474 xmlNode *left_child = NULL;
1475 xmlAttrPtr xIter = NULL;
1476
1477 const char *id = NULL;
1478 const char *name = NULL;
1479 const char *value = NULL;
1480 const char *right_val = NULL;
1481
1482 if (changed == NULL) {
1483 changed = &dummy;
1484 }
1485
1486 if (left == NULL) {
1487 return NULL;
1488 }
1489
1490 if (left->type == XML_COMMENT_NODE) {
1491 return subtract_xml_comment(parent, left, right, changed);
1492 }
1493
1494 id = ID(left);
1495 if (right == NULL) {
1496 xmlNode *deleted = NULL;
1497
1498 crm_trace("Processing <%s id=%s> (complete copy)",
1499 crm_element_name(left), id);
1500 deleted = add_node_copy(parent, left);
1501 crm_xml_add(deleted, XML_DIFF_MARKER, marker);
1502
1503 *changed = TRUE;
1504 return deleted;
1505 }
1506
1507 name = crm_element_name(left);
1508 CRM_CHECK(name != NULL, return NULL);
1509 CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right),
1511 return NULL);
1512
1513 // Check for XML_DIFF_MARKER in a child
1514 value = crm_element_value(right, XML_DIFF_MARKER);
1515 if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
1516 crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1517 *changed = TRUE;
1518 return NULL;
1519 }
1520
1521 // @TODO Avoiding creating the full hierarchy would save work here
1522 diff = create_xml_node(parent, name);
1523
1524 // Changes to child objects
1525 for (left_child = pcmk__xml_first_child(left); left_child != NULL;
1526 left_child = pcmk__xml_next(left_child)) {
1527 gboolean child_changed = FALSE;
1528
1529 right_child = pcmk__xml_match(right, left_child, false);
1530 subtract_xml_object(diff, left_child, right_child, full, &child_changed,
1531 marker);
1532 if (child_changed) {
1533 *changed = TRUE;
1534 }
1535 }
1536
1537 if (!*changed) {
1538 /* Nothing to do */
1539
1540 } else if (full) {
1541 xmlAttrPtr pIter = NULL;
1542
1543 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1544 pIter = pIter->next) {
1545 const char *p_name = (const char *)pIter->name;
1546 const char *p_value = pcmk__xml_attr_value(pIter);
1547
1548 xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1549 }
1550
1551 // We have everything we need
1552 goto done;
1553 }
1554
1555 // Changes to name/value pairs
1556 for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
1557 xIter = xIter->next) {
1558 const char *prop_name = (const char *) xIter->name;
1559 xmlAttrPtr right_attr = NULL;
1560 xml_private_t *p = NULL;
1561
1562 if (strcmp(prop_name, XML_ATTR_ID) == 0) {
1563 // id already obtained when present ~ this case, so just reuse
1564 xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_ID, (pcmkXmlStr) id);
1565 continue;
1566 }
1567
1568 if (pcmk__xa_filterable(prop_name)) {
1569 continue;
1570 }
1571
1572 right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
1573 if (right_attr) {
1574 p = right_attr->_private;
1575 }
1576
1577 right_val = crm_element_value(right, prop_name);
1578 if ((right_val == NULL) || (p && pcmk_is_set(p->flags, pcmk__xf_deleted))) {
1579 /* new */
1580 *changed = TRUE;
1581 if (full) {
1582 xmlAttrPtr pIter = NULL;
1583
1584 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1585 pIter = pIter->next) {
1586 const char *p_name = (const char *) pIter->name;
1587 const char *p_value = pcmk__xml_attr_value(pIter);
1588
1589 xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1590 }
1591 break;
1592
1593 } else {
1594 const char *left_value = crm_element_value(left, prop_name);
1595
1596 xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
1597 crm_xml_add(diff, prop_name, left_value);
1598 }
1599
1600 } else {
1601 /* Only now do we need the left value */
1602 const char *left_value = crm_element_value(left, prop_name);
1603
1604 if (strcmp(left_value, right_val) == 0) {
1605 /* unchanged */
1606
1607 } else {
1608 *changed = TRUE;
1609 if (full) {
1610 xmlAttrPtr pIter = NULL;
1611
1612 crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
1613 crm_element_name(left), id);
1614 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1615 pIter = pIter->next) {
1616 const char *p_name = (const char *) pIter->name;
1617 const char *p_value = pcmk__xml_attr_value(pIter);
1618
1619 xmlSetProp(diff, (pcmkXmlStr) p_name,
1620 (pcmkXmlStr) p_value);
1621 }
1622 break;
1623
1624 } else {
1625 crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
1626 prop_name, left_value, right_val,
1627 crm_element_name(left), id);
1628 crm_xml_add(diff, prop_name, left_value);
1629 }
1630 }
1631 }
1632 }
1633
1634 if (!*changed) {
1635 free_xml(diff);
1636 return NULL;
1637
1638 } else if (!full && (id != NULL)) {
1639 crm_xml_add(diff, XML_ATTR_ID, id);
1640 }
1641 done:
1642 return diff;
1643}
1644
1645// Deprecated functions kept only for backward API compatibility
1646// LCOV_EXCL_START
1647
1648#include <crm/common/xml_compat.h>
1649
1650gboolean
1651apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1652{
1653 gboolean result = TRUE;
1654 int root_nodes_seen = 0;
1655 static struct qb_log_callsite *digest_cs = NULL;
1656 const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
1657 const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
1658
1659 xmlNode *child_diff = NULL;
1660 xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
1661 xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
1662
1663 CRM_CHECK(new_xml != NULL, return FALSE);
1664 if (digest_cs == NULL) {
1665 digest_cs = qb_log_callsite_get(__func__, __FILE__, "diff-digest",
1666 LOG_TRACE, __LINE__, crm_trace_nonlog);
1667 }
1668
1669 crm_trace("Subtraction Phase");
1670 for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1671 child_diff = pcmk__xml_next(child_diff)) {
1672 CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1673 if (root_nodes_seen == 0) {
1674 *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
1675 NULL, NULL);
1676 }
1677 root_nodes_seen++;
1678 }
1679
1680 if (root_nodes_seen == 0) {
1681 *new_xml = copy_xml(old_xml);
1682
1683 } else if (root_nodes_seen > 1) {
1684 crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1685 root_nodes_seen);
1686 result = FALSE;
1687 }
1688
1689 root_nodes_seen = 0;
1690 crm_trace("Addition Phase");
1691 if (result) {
1692 xmlNode *child_diff = NULL;
1693
1694 for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1695 child_diff = pcmk__xml_next(child_diff)) {
1696 CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1697 if (root_nodes_seen == 0) {
1698 pcmk__xml_update(NULL, *new_xml, child_diff, true);
1699 }
1700 root_nodes_seen++;
1701 }
1702 }
1703
1704 if (root_nodes_seen > 1) {
1705 crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1706 root_nodes_seen);
1707 result = FALSE;
1708
1709 } else if (result && (digest != NULL)) {
1710 char *new_digest = NULL;
1711
1712 purge_diff_markers(*new_xml); // Purge now so diff is ok
1713 new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1714 version);
1715 if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1716 crm_info("Digest mis-match: expected %s, calculated %s",
1717 digest, new_digest);
1718 result = FALSE;
1719
1720 crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
1721 if ((digest_cs != NULL) && digest_cs->targets) {
1722 save_xml_to_file(old_xml, "diff:original", NULL);
1723 save_xml_to_file(diff, "diff:input", NULL);
1724 save_xml_to_file(*new_xml, "diff:new", NULL);
1725 }
1726
1727 } else {
1728 crm_trace("Digest matched: expected %s, calculated %s",
1729 digest, new_digest);
1730 }
1731 free(new_digest);
1732
1733 } else if (result) {
1734 purge_diff_markers(*new_xml); // Purge now so diff is ok
1735 }
1736
1737 return result;
1738}
1739
1740// LCOV_EXCL_STOP
1741// End deprecated API
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:587
const char * parent
Definition: cib.c:25
const char * path
Definition: cib.c:26
const char * name
Definition: cib.c:24
#define PCMK__NELEM(a)
Definition: internal.h:42
uint32_t version
Definition: remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
int compare_version(const char *version1, const char *version2)
Definition: utils.c:160
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:122
A dumping ground.
#define CRM_FEATURE_SET
Definition: crm.h:69
G_GNUC_INTERNAL int pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer, int offset, size_t buffer_size)
Definition: xpath.c:269
G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
Definition: xml.c:2484
G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff)
Definition: xml.c:2515
#define PCMK__BUFFER_SIZE
G_GNUC_INTERNAL void pcmk__xe_log(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, int options)
Definition: xml.c:1444
G_GNUC_INTERNAL void pcmk__mark_xml_created(xmlNode *xml)
Definition: xml.c:155
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:252
G_GNUC_INTERNAL int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:314
G_GNUC_INTERNAL xmlNode * pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact)
Definition: xml.c:369
gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
Definition: logging.c:635
#define crm_log_xml_info(xml, text)
Definition: logging.h:370
#define crm_info(fmt, args...)
Definition: logging.h:361
void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, gboolean formatted)
#define crm_warn(fmt, args...)
Definition: logging.h:359
#define LOG_STDOUT
Definition: logging.h:42
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:210
unsigned int crm_trace_nonlog
Definition: logging.c:46
#define crm_log_xml_explicit(xml, text)
Definition: logging.h:374
#define crm_notice(fmt, args...)
Definition: logging.h:360
@ xml_log_option_diff_minus
Definition: logging.h:92
@ xml_log_option_diff_short
Definition: logging.h:93
@ xml_log_option_diff_plus
Definition: logging.h:91
@ xml_log_option_close
Definition: logging.h:98
@ xml_log_option_formatted
Definition: logging.h:88
@ xml_log_option_children
Definition: logging.h:97
@ xml_log_option_open
Definition: logging.h:96
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
#define crm_debug(fmt, args...)
Definition: logging.h:363
#define crm_err(fmt, args...)
Definition: logging.h:358
#define LOG_NEVER
Definition: logging.h:47
#define do_crm_log_alias(level, file, function, line, fmt, args...)
Log a message as if it came from a different code location.
Definition: logging.h:281
#define crm_trace(fmt, args...)
Definition: logging.h:364
#define LOG_TRACE
Definition: logging.h:37
#define XML_TAG_CIB
Definition: msg_xml.h:115
#define ID(x)
Definition: msg_xml.h:460
#define XML_ATTR_CRM_VERSION
Definition: msg_xml.h:118
#define XML_DIFF_PATH
Definition: msg_xml.h:457
#define XML_NVPAIR_ATTR_VALUE
Definition: msg_xml.h:384
#define XML_DIFF_CHANGE
Definition: msg_xml.h:452
#define XML_TAG_DIFF
Definition: msg_xml.h:448
#define XML_DIFF_VSOURCE
Definition: msg_xml.h:450
#define XML_ATTR_ID
Definition: msg_xml.h:135
#define XML_DIFF_POSITION
Definition: msg_xml.h:458
#define XML_DIFF_VERSION
Definition: msg_xml.h:449
#define XML_CIB_TAG_CONFIGURATION
Definition: msg_xml.h:184
#define XML_NVPAIR_ATTR_NAME
Definition: msg_xml.h:383
#define XML_ATTR_GENERATION_ADMIN
Definition: msg_xml.h:127
#define XML_ATTR_NUMUPDATES
Definition: msg_xml.h:128
#define XML_DIFF_RESULT
Definition: msg_xml.h:455
#define XML_DIFF_MARKER
Definition: msg_xml.h:114
#define XML_ATTR_GENERATION
Definition: msg_xml.h:126
#define XML_ATTR_DIGEST
Definition: msg_xml.h:119
#define XML_DIFF_ATTR
Definition: msg_xml.h:454
#define XML_DIFF_VTARGET
Definition: msg_xml.h:451
#define XML_DIFF_OP
Definition: msg_xml.h:456
#define XML_DIFF_LIST
Definition: msg_xml.h:453
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:529
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:565
const char * crm_xml_add_int(xmlNode *node, const char *name, int value)
Create an XML attribute with specified name and integer value.
Definition: nvpair.c:431
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:726
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: nvpair.c:323
xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
Definition: patchset.c:1419
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition: patchset.c:369
struct xml_change_obj_s xml_change_obj_t
void xml_log_patchset(uint8_t log_level, const char *function, xmlNode *patchset)
Definition: patchset.c:457
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition: patchset.c:426
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:801
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:1325
void purge_diff_markers(xmlNode *a_node)
Definition: patchset.c:1405
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: patchset.c:1651
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: patchset.c:1468
pcmk__action_result_t result
Definition: pcmk_fence.c:34
const char * target
Definition: pcmk_fence.c:28
pe_resource_t * dummy
#define ENOTUNIQ
Definition: portability.h:120
#define CRM_ASSERT(expr)
Definition: results.h:42
@ pcmk_rc_old_data
Definition: results.h:129
@ pcmk_rc_ok
Definition: results.h:146
@ pcmk_rc_diff_resync
Definition: results.h:131
@ pcmk_rc_diff_failed
Definition: results.h:130
#define pcmk_ok
Definition: results.h:68
int pcmk_rc2legacy(int rc)
Definition: results.c:415
#define pcmk_err_diff_failed
Definition: results.h:76
@ pcmk__str_casei
Wrappers for and extensions to libxml2.
xmlNode * find_xml_node(xmlNode *cib, const char *node_path, gboolean must_find)
Definition: xml.c:445
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:2794
const xmlChar * pcmkXmlStr
Definition: xml.h:52
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:297
char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version)
Calculate and return digest of XML tree.
Definition: digest.c:187
void free_xml(xmlNode *child)
Definition: xml.c:824
gboolean can_prune_leaf(xmlNode *xml_node)
Definition: xml.c:2392
xmlNode * add_node_copy(xmlNode *new_parent, xmlNode *xml_node)
Definition: xml.c:674
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:696
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:2037
void save_xml_to_file(xmlNode *xml, const char *desc, const char *filename)
Definition: xml.c:2056
Deprecated Pacemaker XML API.
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml.c:629
@ pcmk__xf_deleted
Definition: xml_internal.h:318
@ pcmk__xf_created
Definition: xml_internal.h:319
@ pcmk__xf_dirty
Definition: xml_internal.h:317
@ pcmk__xf_skip
Definition: xml_internal.h:324
@ pcmk__xf_moved
Definition: xml_internal.h:325