root/lib/common/patchset.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. add_xml_changes_to_patchset
  2. is_config_change
  3. xml_repair_v1_diff
  4. xml_create_patchset_v1
  5. xml_create_patchset_v2
  6. xml_create_patchset
  7. patchset_process_digest
  8. xml_log_patchset
  9. not_id
  10. process_v1_removals
  11. process_v1_additions
  12. find_patch_xml_node
  13. xml_patch_versions
  14. xml_patch_version_check
  15. apply_v1_patchset
  16. first_matching_xml_child
  17. search_v2_xpath
  18. sort_change_obj_by_position
  19. apply_v2_patchset
  20. xml_apply_patchset
  21. purge_diff_markers
  22. diff_xml_object
  23. subtract_xml_comment
  24. subtract_xml_object
  25. apply_xml_diff

   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
  86 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
  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
 195 is_config_change(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 223 xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 *
 292 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 *
 306 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 *
 370 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 427 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 458 xml_log_patchset(uint8_t log_level, const char *function, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 638 not_id(xmlAttrPtr attr, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 645 process_v1_removals(xmlNode *target, xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 698 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 772 find_patch_xml_node(xmlNode *patchset, int format, bool added,
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 802 xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 852 xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 928 apply_v1_patchset(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 *
 983 first_matching_xml_child(xmlNode *parent, const char *name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 *
1026 search_v2_xpath(xmlNode *top, const char *key, int target_position)
     /* [previous][next][first][last][top][bottom][index][help] */
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
1113 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
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
1143 apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
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
1326 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
     /* [previous][next][first][last][top][bottom][index][help] */
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
1406 purge_diff_markers(xmlNode *a_node)
     /* [previous][next][first][last][top][bottom][index][help] */
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 *
1420 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
     /* [previous][next][first][last][top][bottom][index][help] */
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 *
1448 subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
     /* [previous][next][first][last][top][bottom][index][help] */
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 *
1469 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
     /* [previous][next][first][last][top][bottom][index][help] */
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
1651 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
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

/* [previous][next][first][last][top][bottom][index][help] */