This source file includes following definitions.
- add_xml_changes_to_patchset
- is_config_change
- xml_repair_v1_diff
- xml_create_patchset_v1
- xml_create_patchset_v2
- xml_create_patchset
- patchset_process_digest
- not_id
- process_v1_removals
- process_v1_additions
- find_patch_xml_node
- xml_patch_versions
- xml_patch_version_check
- apply_v1_patchset
- first_matching_xml_child
- search_v2_xpath
- sort_change_obj_by_position
- apply_v2_patchset
- xml_apply_patchset
- purge_diff_markers
- diff_xml_object
- subtract_xml_comment
- subtract_xml_object
- apply_xml_diff
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <time.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <stdarg.h>
19 #include <bzlib.h>
20
21 #include <libxml/tree.h>
22
23 #include <crm/crm.h>
24 #include <crm/msg_xml.h>
25 #include <crm/common/xml.h>
26 #include <crm/common/xml_internal.h>
27 #include "crmcommon_private.h"
28
29 static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
30 xmlNode *right, gboolean *changed);
31
32
33
34
35 static void
36 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
37 {
38 xmlNode *cIter = NULL;
39 xmlAttr *pIter = NULL;
40 xmlNode *change = NULL;
41 xml_node_private_t *nodepriv = xml->_private;
42 const char *value = NULL;
43
44
45 if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
46 GString *xpath = pcmk__element_xpath(xml->parent);
47
48 if (xpath != NULL) {
49 int position = pcmk__xml_position(xml, pcmk__xf_deleted);
50
51 change = create_xml_node(patchset, XML_DIFF_CHANGE);
52
53 crm_xml_add(change, XML_DIFF_OP, "create");
54 crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
55 crm_xml_add_int(change, XML_DIFF_POSITION, position);
56 add_node_copy(change, xml);
57 g_string_free(xpath, TRUE);
58 }
59
60 return;
61 }
62
63
64 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
65 pIter = pIter->next) {
66 xmlNode *attr = NULL;
67
68 nodepriv = pIter->_private;
69 if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
70 continue;
71 }
72
73 if (change == NULL) {
74 GString *xpath = pcmk__element_xpath(xml);
75
76 if (xpath != NULL) {
77 change = create_xml_node(patchset, XML_DIFF_CHANGE);
78
79 crm_xml_add(change, XML_DIFF_OP, "modify");
80 crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
81
82 change = create_xml_node(change, XML_DIFF_LIST);
83 g_string_free(xpath, TRUE);
84 }
85 }
86
87 attr = create_xml_node(change, XML_DIFF_ATTR);
88
89 crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
90 if (nodepriv->flags & pcmk__xf_deleted) {
91 crm_xml_add(attr, XML_DIFF_OP, "unset");
92
93 } else {
94 crm_xml_add(attr, XML_DIFF_OP, "set");
95
96 value = crm_element_value(xml, (const char *) pIter->name);
97 crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
98 }
99 }
100
101 if (change) {
102 xmlNode *result = NULL;
103
104 change = create_xml_node(change->parent, XML_DIFF_RESULT);
105 result = create_xml_node(change, (const char *)xml->name);
106
107 for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
108 pIter = pIter->next) {
109 nodepriv = pIter->_private;
110 if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
111 value = crm_element_value(xml, (const char *) pIter->name);
112 crm_xml_add(result, (const char *)pIter->name, value);
113 }
114 }
115 }
116
117
118 for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
119 cIter = pcmk__xml_next(cIter)) {
120 add_xml_changes_to_patchset(cIter, patchset);
121 }
122
123 nodepriv = xml->_private;
124 if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
125 GString *xpath = pcmk__element_xpath(xml);
126
127 crm_trace("%s.%s moved to position %d",
128 xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
129
130 if (xpath != NULL) {
131 change = create_xml_node(patchset, XML_DIFF_CHANGE);
132
133 crm_xml_add(change, XML_DIFF_OP, "move");
134 crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
135 crm_xml_add_int(change, XML_DIFF_POSITION,
136 pcmk__xml_position(xml, pcmk__xf_deleted));
137 g_string_free(xpath, TRUE);
138 }
139 }
140 }
141
142 static bool
143 is_config_change(xmlNode *xml)
144 {
145 GList *gIter = NULL;
146 xml_node_private_t *nodepriv = NULL;
147 xml_doc_private_t *docpriv;
148 xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
149
150 if (config) {
151 nodepriv = config->_private;
152 }
153 if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
154 return TRUE;
155 }
156
157 if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
158 docpriv = xml->doc->_private;
159 for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
160 pcmk__deleted_xml_t *deleted_obj = gIter->data;
161
162 if (strstr(deleted_obj->path,
163 "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION) != NULL) {
164 return TRUE;
165 }
166 }
167 }
168 return FALSE;
169 }
170
171 static void
172 xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
173 gboolean changed)
174 {
175 int lpc = 0;
176 xmlNode *cib = NULL;
177 xmlNode *diff_child = NULL;
178
179 const char *tag = NULL;
180
181 const char *vfields[] = {
182 XML_ATTR_GENERATION_ADMIN,
183 XML_ATTR_GENERATION,
184 XML_ATTR_NUMUPDATES,
185 };
186
187 if (local_diff == NULL) {
188 crm_trace("Nothing to do");
189 return;
190 }
191
192 tag = "diff-removed";
193 diff_child = find_xml_node(local_diff, tag, FALSE);
194 if (diff_child == NULL) {
195 diff_child = create_xml_node(local_diff, tag);
196 }
197
198 tag = XML_TAG_CIB;
199 cib = find_xml_node(diff_child, tag, FALSE);
200 if (cib == NULL) {
201 cib = create_xml_node(diff_child, tag);
202 }
203
204 for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
205 const char *value = crm_element_value(last, vfields[lpc]);
206
207 crm_xml_add(diff_child, vfields[lpc], value);
208 if (changed || lpc == 2) {
209 crm_xml_add(cib, vfields[lpc], value);
210 }
211 }
212
213 tag = "diff-added";
214 diff_child = find_xml_node(local_diff, tag, FALSE);
215 if (diff_child == NULL) {
216 diff_child = create_xml_node(local_diff, tag);
217 }
218
219 tag = XML_TAG_CIB;
220 cib = find_xml_node(diff_child, tag, FALSE);
221 if (cib == NULL) {
222 cib = create_xml_node(diff_child, tag);
223 }
224
225 for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
226 const char *value = crm_element_value(next, vfields[lpc]);
227
228 crm_xml_add(diff_child, vfields[lpc], value);
229 }
230
231 for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
232 const char *p_value = crm_element_value(next, (const char *) a->name);
233
234 xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
235 }
236
237 crm_log_xml_explicit(local_diff, "Repaired-diff");
238 }
239
240 static xmlNode *
241 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
242 bool suppress)
243 {
244 xmlNode *patchset = diff_xml_object(source, target, suppress);
245
246 if (patchset) {
247 CRM_LOG_ASSERT(xml_document_dirty(target));
248 xml_repair_v1_diff(source, target, patchset, config);
249 crm_xml_add(patchset, "format", "1");
250 }
251 return patchset;
252 }
253
254 static xmlNode *
255 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
256 {
257 int lpc = 0;
258 GList *gIter = NULL;
259 xml_doc_private_t *docpriv;
260
261 xmlNode *v = NULL;
262 xmlNode *version = NULL;
263 xmlNode *patchset = NULL;
264 const char *vfields[] = {
265 XML_ATTR_GENERATION_ADMIN,
266 XML_ATTR_GENERATION,
267 XML_ATTR_NUMUPDATES,
268 };
269
270 CRM_ASSERT(target);
271 if (!xml_document_dirty(target)) {
272 return NULL;
273 }
274
275 CRM_ASSERT(target->doc);
276 docpriv = target->doc->_private;
277
278 patchset = create_xml_node(NULL, XML_TAG_DIFF);
279 crm_xml_add_int(patchset, "format", 2);
280
281 version = create_xml_node(patchset, XML_DIFF_VERSION);
282
283 v = create_xml_node(version, XML_DIFF_VSOURCE);
284 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
285 const char *value = crm_element_value(source, vfields[lpc]);
286
287 if (value == NULL) {
288 value = "1";
289 }
290 crm_xml_add(v, vfields[lpc], value);
291 }
292
293 v = create_xml_node(version, XML_DIFF_VTARGET);
294 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
295 const char *value = crm_element_value(target, vfields[lpc]);
296
297 if (value == NULL) {
298 value = "1";
299 }
300 crm_xml_add(v, vfields[lpc], value);
301 }
302
303 for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
304 pcmk__deleted_xml_t *deleted_obj = gIter->data;
305 xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
306
307 crm_xml_add(change, XML_DIFF_OP, "delete");
308 crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
309 if (deleted_obj->position >= 0) {
310 crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
311 }
312 }
313
314 add_xml_changes_to_patchset(target, patchset);
315 return patchset;
316 }
317
318 xmlNode *
319 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
320 bool *config_changed, bool manage_version)
321 {
322 int counter = 0;
323 bool config = FALSE;
324 xmlNode *patch = NULL;
325 const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
326
327 xml_acl_disable(target);
328 if (!xml_document_dirty(target)) {
329 crm_trace("No change %d", format);
330 return NULL;
331 }
332
333 config = is_config_change(target);
334 if (config_changed) {
335 *config_changed = config;
336 }
337
338 if (manage_version && config) {
339 crm_trace("Config changed %d", format);
340 crm_xml_add(target, XML_ATTR_NUMUPDATES, "0");
341
342 crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
343 crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
344
345 } else if (manage_version) {
346 crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
347 crm_trace("Status changed %d - %d %s", format, counter,
348 crm_element_value(source, XML_ATTR_NUMUPDATES));
349 crm_xml_add_int(target, XML_ATTR_NUMUPDATES, (counter + 1));
350 }
351
352 if (format == 0) {
353 if (compare_version("3.0.8", version) < 0) {
354 format = 2;
355 } else {
356 format = 1;
357 }
358 crm_trace("Using patch format %d for version: %s", format, version);
359 }
360
361 switch (format) {
362 case 1:
363 patch = xml_create_patchset_v1(source, target, config, FALSE);
364 break;
365 case 2:
366 patch = xml_create_patchset_v2(source, target);
367 break;
368 default:
369 crm_err("Unknown patch format: %d", format);
370 return NULL;
371 }
372 return patch;
373 }
374
375 void
376 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
377 bool with_digest)
378 {
379 int format = 1;
380 const char *version = NULL;
381 char *digest = NULL;
382
383 if ((patch == NULL) || (source == NULL) || (target == NULL)) {
384 return;
385 }
386
387
388
389
390 CRM_LOG_ASSERT(!xml_document_dirty(target));
391
392 crm_element_value_int(patch, "format", &format);
393 if ((format > 1) && !with_digest) {
394 return;
395 }
396
397 version = crm_element_value(source, XML_ATTR_CRM_VERSION);
398 digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
399
400 crm_xml_add(patch, XML_ATTR_DIGEST, digest);
401 free(digest);
402
403 return;
404 }
405
406
407 static bool
408 not_id(xmlAttrPtr attr, void *user_data)
409 {
410 return strcmp((const char *) attr->name, XML_ATTR_ID) != 0;
411 }
412
413
414 static void
415 process_v1_removals(xmlNode *target, xmlNode *patch)
416 {
417 xmlNode *patch_child = NULL;
418 xmlNode *cIter = NULL;
419
420 char *id = NULL;
421 const char *name = NULL;
422 const char *value = NULL;
423
424 if ((target == NULL) || (patch == NULL)) {
425 return;
426 }
427
428 if (target->type == XML_COMMENT_NODE) {
429 gboolean dummy;
430
431 subtract_xml_comment(target->parent, target, patch, &dummy);
432 }
433
434 name = crm_element_name(target);
435 CRM_CHECK(name != NULL, return);
436 CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
437 pcmk__str_casei),
438 return);
439 CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
440
441
442 id = crm_element_value_copy(target, XML_ATTR_ID);
443 value = crm_element_value(patch, XML_DIFF_MARKER);
444 if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
445 crm_trace("We are the root of the deletion: %s.id=%s", name, id);
446 free_xml(target);
447 free(id);
448 return;
449 }
450
451
452 pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
453
454
455 cIter = pcmk__xml_first_child(target);
456 while (cIter) {
457 xmlNode *target_child = cIter;
458
459 cIter = pcmk__xml_next(cIter);
460 patch_child = pcmk__xml_match(patch, target_child, false);
461 process_v1_removals(target_child, patch_child);
462 }
463 free(id);
464 }
465
466
467 static void
468 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
469 {
470 xmlNode *patch_child = NULL;
471 xmlNode *target_child = NULL;
472 xmlAttrPtr xIter = NULL;
473
474 const char *id = NULL;
475 const char *name = NULL;
476 const char *value = NULL;
477
478 if (patch == NULL) {
479 return;
480 } else if ((parent == NULL) && (target == NULL)) {
481 return;
482 }
483
484
485 value = crm_element_value(patch, XML_DIFF_MARKER);
486 if ((target == NULL) && (value != NULL)
487 && (strcmp(value, "added:top") == 0)) {
488 id = ID(patch);
489 name = crm_element_name(patch);
490 crm_trace("We are the root of the addition: %s.id=%s", name, id);
491 add_node_copy(parent, patch);
492 return;
493
494 } else if (target == NULL) {
495 id = ID(patch);
496 name = crm_element_name(patch);
497 crm_err("Could not locate: %s.id=%s", name, id);
498 return;
499 }
500
501 if (target->type == XML_COMMENT_NODE) {
502 pcmk__xc_update(parent, target, patch);
503 }
504
505 name = crm_element_name(target);
506 CRM_CHECK(name != NULL, return);
507 CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
508 pcmk__str_casei),
509 return);
510 CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
511
512 for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
513 xIter = xIter->next) {
514 const char *p_name = (const char *) xIter->name;
515 const char *p_value = crm_element_value(patch, p_name);
516
517 xml_remove_prop(target, p_name);
518 crm_xml_add(target, p_name, p_value);
519 }
520
521
522 for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
523 patch_child = pcmk__xml_next(patch_child)) {
524
525 target_child = pcmk__xml_match(target, patch_child, false);
526 process_v1_additions(target, target_child, patch_child);
527 }
528 }
529
530
531
532
533
534
535
536
537
538
539
540
541 static bool
542 find_patch_xml_node(const xmlNode *patchset, int format, bool added,
543 xmlNode **patch_node)
544 {
545 xmlNode *cib_node;
546 const char *label;
547
548 switch (format) {
549 case 1:
550 label = added? "diff-added" : "diff-removed";
551 *patch_node = find_xml_node(patchset, label, FALSE);
552 cib_node = find_xml_node(*patch_node, "cib", FALSE);
553 if (cib_node != NULL) {
554 *patch_node = cib_node;
555 }
556 break;
557 case 2:
558 label = added? "target" : "source";
559 *patch_node = find_xml_node(patchset, "version", FALSE);
560 *patch_node = find_xml_node(*patch_node, label, FALSE);
561 break;
562 default:
563 crm_warn("Unknown patch format: %d", format);
564 *patch_node = NULL;
565 return FALSE;
566 }
567 return TRUE;
568 }
569
570
571 bool
572 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
573 {
574 int lpc = 0;
575 int format = 1;
576 xmlNode *tmp = NULL;
577
578 const char *vfields[] = {
579 XML_ATTR_GENERATION_ADMIN,
580 XML_ATTR_GENERATION,
581 XML_ATTR_NUMUPDATES,
582 };
583
584
585 crm_element_value_int(patchset, "format", &format);
586
587
588 if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
589 return -EINVAL;
590 }
591 if (tmp != NULL) {
592 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
593 crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
594 crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
595 }
596 }
597
598
599 if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
600 return -EINVAL;
601 }
602 if (tmp != NULL) {
603 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
604 crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
605 crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
606 }
607 }
608 return pcmk_ok;
609 }
610
611
612
613
614
615
616
617
618
619
620
621 static int
622 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset, int format)
623 {
624 int lpc = 0;
625 bool changed = FALSE;
626
627 int this[] = { 0, 0, 0 };
628 int add[] = { 0, 0, 0 };
629 int del[] = { 0, 0, 0 };
630
631 const char *vfields[] = {
632 XML_ATTR_GENERATION_ADMIN,
633 XML_ATTR_GENERATION,
634 XML_ATTR_NUMUPDATES,
635 };
636
637 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
638 crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
639 crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
640 if (this[lpc] < 0) {
641 this[lpc] = 0;
642 }
643 }
644
645
646 add[0] = this[0];
647 add[1] = this[1];
648 add[2] = this[2] + 1;
649 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
650 del[lpc] = this[lpc];
651 }
652
653 xml_patch_versions(patchset, add, del);
654
655 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
656 if (this[lpc] < del[lpc]) {
657 crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
658 vfields[lpc], this[0], this[1], this[2],
659 del[0], del[1], del[2], add[0], add[1], add[2]);
660 return pcmk_rc_diff_resync;
661
662 } else if (this[lpc] > del[lpc]) {
663 crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
664 vfields[lpc], this[0], this[1], this[2],
665 del[0], del[1], del[2], add[0], add[1], add[2], patchset);
666 crm_log_xml_info(patchset, "OldPatch");
667 return pcmk_rc_old_data;
668 }
669 }
670
671 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
672 if (add[lpc] > del[lpc]) {
673 changed = TRUE;
674 }
675 }
676
677 if (!changed) {
678 crm_notice("Versions did not change in patch %d.%d.%d",
679 add[0], add[1], add[2]);
680 return pcmk_rc_old_data;
681 }
682
683 crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
684 add[0], add[1], add[2], this[0], this[1], this[2]);
685 return pcmk_rc_ok;
686 }
687
688
689
690
691
692
693
694
695
696
697 static int
698 apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
699 {
700 int rc = pcmk_rc_ok;
701 int root_nodes_seen = 0;
702
703 xmlNode *child_diff = NULL;
704 xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
705 xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
706 xmlNode *old = copy_xml(xml);
707
708 crm_trace("Subtraction Phase");
709 for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
710 child_diff = pcmk__xml_next(child_diff)) {
711 CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
712 if (root_nodes_seen == 0) {
713 process_v1_removals(xml, child_diff);
714 }
715 root_nodes_seen++;
716 }
717
718 if (root_nodes_seen > 1) {
719 crm_err("(-) Diffs cannot contain more than one change set... saw %d",
720 root_nodes_seen);
721 rc = ENOTUNIQ;
722 }
723
724 root_nodes_seen = 0;
725 crm_trace("Addition Phase");
726 if (rc == pcmk_rc_ok) {
727 xmlNode *child_diff = NULL;
728
729 for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
730 child_diff = pcmk__xml_next(child_diff)) {
731 CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
732 if (root_nodes_seen == 0) {
733 process_v1_additions(NULL, xml, child_diff);
734 }
735 root_nodes_seen++;
736 }
737 }
738
739 if (root_nodes_seen > 1) {
740 crm_err("(+) Diffs cannot contain more than one change set... saw %d",
741 root_nodes_seen);
742 rc = ENOTUNIQ;
743 }
744
745 purge_diff_markers(xml);
746
747 free_xml(old);
748 return rc;
749 }
750
751
752 static xmlNode *
753 first_matching_xml_child(const xmlNode *parent, const char *name,
754 const char *id, int position)
755 {
756 xmlNode *cIter = NULL;
757
758 for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
759 cIter = pcmk__xml_next(cIter)) {
760 if (strcmp((const char *) cIter->name, name) != 0) {
761 continue;
762 } else if (id) {
763 const char *cid = ID(cIter);
764
765 if ((cid == NULL) || (strcmp(cid, id) != 0)) {
766 continue;
767 }
768 }
769
770
771 if ((cIter->type == XML_COMMENT_NODE)
772 && (position >= 0)
773 && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
774 continue;
775 }
776
777 return cIter;
778 }
779 return NULL;
780 }
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795 static xmlNode *
796 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
797 {
798 xmlNode *target = (xmlNode *) top->doc;
799 const char *current = key;
800 char *section;
801 char *remainder;
802 char *id;
803 char *tag;
804 char *path = NULL;
805 int rc;
806 size_t key_len;
807
808 CRM_CHECK(key != NULL, return NULL);
809 key_len = strlen(key);
810
811
812
813
814
815 remainder = calloc(key_len, sizeof(char));
816 CRM_ASSERT(remainder != NULL);
817
818 section = calloc(key_len, sizeof(char));
819 CRM_ASSERT(section != NULL);
820
821 id = calloc(key_len, sizeof(char));
822 CRM_ASSERT(id != NULL);
823
824 tag = calloc(key_len, sizeof(char));
825 CRM_ASSERT(tag != NULL);
826
827 do {
828
829 rc = sscanf(current, "/%[^/]%s", section, remainder);
830 if (rc > 0) {
831
832 int f = sscanf(section, "%[^[][@" XML_ATTR_ID "='%[^']", tag, id);
833 int current_position = -1;
834
835
836
837
838 if ((rc == 1) && (target_position >= 0)) {
839 current_position = target_position;
840 }
841
842 switch (f) {
843 case 1:
844 target = first_matching_xml_child(target, tag, NULL,
845 current_position);
846 break;
847 case 2:
848 target = first_matching_xml_child(target, tag, id,
849 current_position);
850 break;
851 default:
852
853 target = NULL;
854 break;
855 }
856 current = remainder;
857 }
858
859
860 } while ((rc == 2) && target);
861
862 if (target) {
863 crm_trace("Found %s for %s",
864 (path = (char *) xmlGetNodePath(target)), key);
865 free(path);
866 } else {
867 crm_debug("No match for %s", key);
868 }
869
870 free(remainder);
871 free(section);
872 free(tag);
873 free(id);
874 return target;
875 }
876
877 typedef struct xml_change_obj_s {
878 const xmlNode *change;
879 xmlNode *match;
880 } xml_change_obj_t;
881
882 static gint
883 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
884 {
885 const xml_change_obj_t *change_obj_a = a;
886 const xml_change_obj_t *change_obj_b = b;
887 int position_a = -1;
888 int position_b = -1;
889
890 crm_element_value_int(change_obj_a->change, XML_DIFF_POSITION, &position_a);
891 crm_element_value_int(change_obj_b->change, XML_DIFF_POSITION, &position_b);
892
893 if (position_a < position_b) {
894 return -1;
895
896 } else if (position_a > position_b) {
897 return 1;
898 }
899
900 return 0;
901 }
902
903
904
905
906
907
908
909
910
911
912 static int
913 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
914 {
915 int rc = pcmk_rc_ok;
916 const xmlNode *change = NULL;
917 GList *change_objs = NULL;
918 GList *gIter = NULL;
919
920 for (change = pcmk__xml_first_child(patchset); change != NULL;
921 change = pcmk__xml_next(change)) {
922 xmlNode *match = NULL;
923 const char *op = crm_element_value(change, XML_DIFF_OP);
924 const char *xpath = crm_element_value(change, XML_DIFF_PATH);
925 int position = -1;
926
927 if (op == NULL) {
928 continue;
929 }
930
931 crm_trace("Processing %s %s", change->name, op);
932
933
934 if (strcmp(op, "delete") == 0) {
935 crm_element_value_int(change, XML_DIFF_POSITION, &position);
936 }
937 match = search_v2_xpath(xml, xpath, position);
938 crm_trace("Performing %s on %s with %p", op, xpath, match);
939
940 if ((match == NULL) && (strcmp(op, "delete") == 0)) {
941 crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
942 continue;
943
944 } else if (match == NULL) {
945 crm_err("No %s match for %s in %p", op, xpath, xml->doc);
946 rc = pcmk_rc_diff_failed;
947 continue;
948
949 } else if ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) {
950
951 xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
952
953 CRM_ASSERT(change_obj != NULL);
954
955 change_obj->change = change;
956 change_obj->match = match;
957
958 change_objs = g_list_append(change_objs, change_obj);
959
960 if (strcmp(op, "move") == 0) {
961
962 if ((match->parent != NULL) && (match->parent->last != NULL)) {
963 xmlAddNextSibling(match->parent->last, match);
964 }
965 }
966
967 } else if (strcmp(op, "delete") == 0) {
968 free_xml(match);
969
970 } else if (strcmp(op, "modify") == 0) {
971 xmlNode *attrs = NULL;
972
973 attrs = pcmk__xml_first_child(first_named_child(change,
974 XML_DIFF_RESULT));
975 if (attrs == NULL) {
976 rc = ENOMSG;
977 continue;
978 }
979 pcmk__xe_remove_matching_attrs(match, NULL, NULL);
980
981 for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
982 pIter = pIter->next) {
983 const char *name = (const char *) pIter->name;
984 const char *value = crm_element_value(attrs, name);
985
986 crm_xml_add(match, name, value);
987 }
988
989 } else {
990 crm_err("Unknown operation: %s", op);
991 rc = pcmk_rc_diff_failed;
992 }
993 }
994
995
996 change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
997
998 for (gIter = change_objs; gIter; gIter = gIter->next) {
999 xml_change_obj_t *change_obj = gIter->data;
1000 xmlNode *match = change_obj->match;
1001 const char *op = NULL;
1002 const char *xpath = NULL;
1003
1004 change = change_obj->change;
1005
1006 op = crm_element_value(change, XML_DIFF_OP);
1007 xpath = crm_element_value(change, XML_DIFF_PATH);
1008
1009 crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1010
1011 if (strcmp(op, "create") == 0) {
1012 int position = 0;
1013 xmlNode *child = NULL;
1014 xmlNode *match_child = NULL;
1015
1016 match_child = match->children;
1017 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1018
1019 while ((match_child != NULL)
1020 && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1021 match_child = match_child->next;
1022 }
1023
1024 child = xmlDocCopyNode(change->children, match->doc, 1);
1025 if (match_child) {
1026 crm_trace("Adding %s at position %d", child->name, position);
1027 xmlAddPrevSibling(match_child, child);
1028
1029 } else if (match->last) {
1030 crm_trace("Adding %s at position %d (end)",
1031 child->name, position);
1032 xmlAddNextSibling(match->last, child);
1033
1034 } else {
1035 crm_trace("Adding %s at position %d (first)",
1036 child->name, position);
1037 CRM_LOG_ASSERT(position == 0);
1038 xmlAddChild(match, child);
1039 }
1040 pcmk__mark_xml_created(child);
1041
1042 } else if (strcmp(op, "move") == 0) {
1043 int position = 0;
1044
1045 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1046 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1047 xmlNode *match_child = NULL;
1048 int p = position;
1049
1050 if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1051 p++;
1052 }
1053
1054 CRM_ASSERT(match->parent != NULL);
1055 match_child = match->parent->children;
1056
1057 while ((match_child != NULL)
1058 && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1059 match_child = match_child->next;
1060 }
1061
1062 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1063 match->name, position,
1064 pcmk__xml_position(match, pcmk__xf_skip),
1065 match->prev, (match_child? "next":"last"),
1066 (match_child? match_child : match->parent->last));
1067
1068 if (match_child) {
1069 xmlAddPrevSibling(match_child, match);
1070
1071 } else {
1072 CRM_ASSERT(match->parent->last != NULL);
1073 xmlAddNextSibling(match->parent->last, match);
1074 }
1075
1076 } else {
1077 crm_trace("%s is already in position %d",
1078 match->name, position);
1079 }
1080
1081 if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1082 crm_err("Moved %s.%s to position %d instead of %d (%p)",
1083 match->name, ID(match),
1084 pcmk__xml_position(match, pcmk__xf_skip),
1085 position, match->prev);
1086 rc = pcmk_rc_diff_failed;
1087 }
1088 }
1089 }
1090
1091 g_list_free_full(change_objs, free);
1092 return rc;
1093 }
1094
1095 int
1096 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1097 {
1098 int format = 1;
1099 int rc = pcmk_ok;
1100 xmlNode *old = NULL;
1101 const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1102
1103 if (patchset == NULL) {
1104 return rc;
1105 }
1106
1107 pcmk__if_tracing(
1108 {
1109 pcmk__output_t *logger_out = NULL;
1110
1111 rc = pcmk_rc2legacy(pcmk__log_output_new(&logger_out));
1112 CRM_CHECK(rc == pcmk_ok, return rc);
1113
1114 pcmk__output_set_log_level(logger_out, LOG_TRACE);
1115 rc = logger_out->message(logger_out, "xml-patchset", patchset);
1116 logger_out->finish(logger_out, pcmk_rc2exitc(rc), true,
1117 NULL);
1118 pcmk__output_free(logger_out);
1119 rc = pcmk_ok;
1120 },
1121 {}
1122 );
1123
1124 crm_element_value_int(patchset, "format", &format);
1125 if (check_version) {
1126 rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format));
1127 if (rc != pcmk_ok) {
1128 return rc;
1129 }
1130 }
1131
1132 if (digest) {
1133
1134 old = copy_xml(xml);
1135 }
1136
1137 if (rc == pcmk_ok) {
1138 switch (format) {
1139 case 1:
1140 rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1141 break;
1142 case 2:
1143 rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1144 break;
1145 default:
1146 crm_err("Unknown patch format: %d", format);
1147 rc = -EINVAL;
1148 }
1149 }
1150
1151 if ((rc == pcmk_ok) && (digest != NULL)) {
1152 char *new_digest = NULL;
1153 char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
1154
1155 new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1156 if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1157 crm_info("v%d digest mis-match: expected %s, calculated %s",
1158 format, digest, new_digest);
1159 rc = -pcmk_err_diff_failed;
1160 pcmk__if_tracing(
1161 {
1162 save_xml_to_file(old, "PatchDigest:input", NULL);
1163 save_xml_to_file(xml, "PatchDigest:result", NULL);
1164 save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1165 },
1166 {}
1167 );
1168
1169 } else {
1170 crm_trace("v%d digest matched: expected %s, calculated %s",
1171 format, digest, new_digest);
1172 }
1173 free(new_digest);
1174 free(version);
1175 }
1176 free_xml(old);
1177 return rc;
1178 }
1179
1180 void
1181 purge_diff_markers(xmlNode *a_node)
1182 {
1183 xmlNode *child = NULL;
1184
1185 CRM_CHECK(a_node != NULL, return);
1186
1187 xml_remove_prop(a_node, XML_DIFF_MARKER);
1188 for (child = pcmk__xml_first_child(a_node); child != NULL;
1189 child = pcmk__xml_next(child)) {
1190 purge_diff_markers(child);
1191 }
1192 }
1193
1194 xmlNode *
1195 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1196 {
1197 xmlNode *tmp1 = NULL;
1198 xmlNode *diff = create_xml_node(NULL, "diff");
1199 xmlNode *removed = create_xml_node(diff, "diff-removed");
1200 xmlNode *added = create_xml_node(diff, "diff-added");
1201
1202 crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
1203
1204 tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
1205 if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1206 free_xml(tmp1);
1207 }
1208
1209 tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
1210 if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1211 free_xml(tmp1);
1212 }
1213
1214 if ((added->children == NULL) && (removed->children == NULL)) {
1215 free_xml(diff);
1216 diff = NULL;
1217 }
1218
1219 return diff;
1220 }
1221
1222 static xmlNode *
1223 subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
1224 gboolean *changed)
1225 {
1226 CRM_CHECK(left != NULL, return NULL);
1227 CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
1228
1229 if ((right == NULL) || !pcmk__str_eq((const char *)left->content,
1230 (const char *)right->content,
1231 pcmk__str_casei)) {
1232 xmlNode *deleted = NULL;
1233
1234 deleted = add_node_copy(parent, left);
1235 *changed = TRUE;
1236
1237 return deleted;
1238 }
1239
1240 return NULL;
1241 }
1242
1243 xmlNode *
1244 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1245 gboolean full, gboolean *changed, const char *marker)
1246 {
1247 gboolean dummy = FALSE;
1248 xmlNode *diff = NULL;
1249 xmlNode *right_child = NULL;
1250 xmlNode *left_child = NULL;
1251 xmlAttrPtr xIter = NULL;
1252
1253 const char *id = NULL;
1254 const char *name = NULL;
1255 const char *value = NULL;
1256 const char *right_val = NULL;
1257
1258 if (changed == NULL) {
1259 changed = &dummy;
1260 }
1261
1262 if (left == NULL) {
1263 return NULL;
1264 }
1265
1266 if (left->type == XML_COMMENT_NODE) {
1267 return subtract_xml_comment(parent, left, right, changed);
1268 }
1269
1270 id = ID(left);
1271 if (right == NULL) {
1272 xmlNode *deleted = NULL;
1273
1274 crm_trace("Processing <%s " XML_ATTR_ID "=%s> (complete copy)",
1275 crm_element_name(left), id);
1276 deleted = add_node_copy(parent, left);
1277 crm_xml_add(deleted, XML_DIFF_MARKER, marker);
1278
1279 *changed = TRUE;
1280 return deleted;
1281 }
1282
1283 name = crm_element_name(left);
1284 CRM_CHECK(name != NULL, return NULL);
1285 CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right),
1286 pcmk__str_casei),
1287 return NULL);
1288
1289
1290 value = crm_element_value(right, XML_DIFF_MARKER);
1291 if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
1292 crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1293 *changed = TRUE;
1294 return NULL;
1295 }
1296
1297
1298 diff = create_xml_node(parent, name);
1299
1300
1301 for (left_child = pcmk__xml_first_child(left); left_child != NULL;
1302 left_child = pcmk__xml_next(left_child)) {
1303 gboolean child_changed = FALSE;
1304
1305 right_child = pcmk__xml_match(right, left_child, false);
1306 subtract_xml_object(diff, left_child, right_child, full, &child_changed,
1307 marker);
1308 if (child_changed) {
1309 *changed = TRUE;
1310 }
1311 }
1312
1313 if (!*changed) {
1314
1315
1316 } else if (full) {
1317 xmlAttrPtr pIter = NULL;
1318
1319 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1320 pIter = pIter->next) {
1321 const char *p_name = (const char *)pIter->name;
1322 const char *p_value = pcmk__xml_attr_value(pIter);
1323
1324 xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1325 }
1326
1327
1328 goto done;
1329 }
1330
1331
1332 for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
1333 xIter = xIter->next) {
1334 const char *prop_name = (const char *) xIter->name;
1335 xmlAttrPtr right_attr = NULL;
1336 xml_node_private_t *nodepriv = NULL;
1337
1338 if (strcmp(prop_name, XML_ATTR_ID) == 0) {
1339
1340 xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_ID, (pcmkXmlStr) id);
1341 continue;
1342 }
1343
1344 if (pcmk__xa_filterable(prop_name)) {
1345 continue;
1346 }
1347
1348 right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
1349 if (right_attr) {
1350 nodepriv = right_attr->_private;
1351 }
1352
1353 right_val = crm_element_value(right, prop_name);
1354 if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) {
1355
1356 *changed = TRUE;
1357 if (full) {
1358 xmlAttrPtr pIter = NULL;
1359
1360 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1361 pIter = pIter->next) {
1362 const char *p_name = (const char *) pIter->name;
1363 const char *p_value = pcmk__xml_attr_value(pIter);
1364
1365 xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1366 }
1367 break;
1368
1369 } else {
1370 const char *left_value = crm_element_value(left, prop_name);
1371
1372 xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
1373 crm_xml_add(diff, prop_name, left_value);
1374 }
1375
1376 } else {
1377
1378 const char *left_value = crm_element_value(left, prop_name);
1379
1380 if (strcmp(left_value, right_val) == 0) {
1381
1382
1383 } else {
1384 *changed = TRUE;
1385 if (full) {
1386 xmlAttrPtr pIter = NULL;
1387
1388 crm_trace("Changes detected to %s in "
1389 "<%s " XML_ATTR_ID "=%s>",
1390 prop_name, crm_element_name(left), id);
1391 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1392 pIter = pIter->next) {
1393 const char *p_name = (const char *) pIter->name;
1394 const char *p_value = pcmk__xml_attr_value(pIter);
1395
1396 xmlSetProp(diff, (pcmkXmlStr) p_name,
1397 (pcmkXmlStr) p_value);
1398 }
1399 break;
1400
1401 } else {
1402 crm_trace("Changes detected to %s (%s -> %s) in "
1403 "<%s " XML_ATTR_ID "=%s>",
1404 prop_name, left_value, right_val,
1405 crm_element_name(left), id);
1406 crm_xml_add(diff, prop_name, left_value);
1407 }
1408 }
1409 }
1410 }
1411
1412 if (!*changed) {
1413 free_xml(diff);
1414 return NULL;
1415
1416 } else if (!full && (id != NULL)) {
1417 crm_xml_add(diff, XML_ATTR_ID, id);
1418 }
1419 done:
1420 return diff;
1421 }
1422
1423
1424
1425
1426 #include <crm/common/xml_compat.h>
1427
1428 gboolean
1429 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1430 {
1431 gboolean result = TRUE;
1432 int root_nodes_seen = 0;
1433 const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
1434 const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
1435
1436 xmlNode *child_diff = NULL;
1437 xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
1438 xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
1439
1440 CRM_CHECK(new_xml != NULL, return FALSE);
1441
1442 crm_trace("Subtraction Phase");
1443 for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1444 child_diff = pcmk__xml_next(child_diff)) {
1445 CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1446 if (root_nodes_seen == 0) {
1447 *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
1448 NULL, NULL);
1449 }
1450 root_nodes_seen++;
1451 }
1452
1453 if (root_nodes_seen == 0) {
1454 *new_xml = copy_xml(old_xml);
1455
1456 } else if (root_nodes_seen > 1) {
1457 crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1458 root_nodes_seen);
1459 result = FALSE;
1460 }
1461
1462 root_nodes_seen = 0;
1463 crm_trace("Addition Phase");
1464 if (result) {
1465 xmlNode *child_diff = NULL;
1466
1467 for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1468 child_diff = pcmk__xml_next(child_diff)) {
1469 CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1470 if (root_nodes_seen == 0) {
1471 pcmk__xml_update(NULL, *new_xml, child_diff, true);
1472 }
1473 root_nodes_seen++;
1474 }
1475 }
1476
1477 if (root_nodes_seen > 1) {
1478 crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1479 root_nodes_seen);
1480 result = FALSE;
1481
1482 } else if (result && (digest != NULL)) {
1483 char *new_digest = NULL;
1484
1485 purge_diff_markers(*new_xml);
1486 new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1487 version);
1488 if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1489 crm_info("Digest mis-match: expected %s, calculated %s",
1490 digest, new_digest);
1491 result = FALSE;
1492
1493 pcmk__if_tracing(
1494 {
1495 save_xml_to_file(old_xml, "diff:original", NULL);
1496 save_xml_to_file(diff, "diff:input", NULL);
1497 save_xml_to_file(*new_xml, "diff:new", NULL);
1498 },
1499 {}
1500 );
1501
1502 } else {
1503 crm_trace("Digest matched: expected %s, calculated %s",
1504 digest, new_digest);
1505 }
1506 free(new_digest);
1507
1508 } else if (result) {
1509 purge_diff_markers(*new_xml);
1510 }
1511
1512 return result;
1513 }
1514
1515
1516