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