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