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. process_v1_removals
  10. process_v1_additions
  11. find_patch_xml_node
  12. xml_patch_versions
  13. xml_patch_version_check
  14. apply_v1_patchset
  15. first_matching_xml_child
  16. search_v2_xpath
  17. sort_change_obj_by_position
  18. apply_v2_patchset
  19. xml_apply_patchset
  20. purge_diff_markers
  21. diff_xml_object
  22. subtract_xml_comment
  23. subtract_xml_object
  24. apply_xml_diff

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

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