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