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

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