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