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