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

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