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_create_patchset_v2
  4. xml_create_patchset
  5. patchset_process_digest
  6. xml_patch_versions
  7. xml_patch_version_check
  8. first_matching_xml_child
  9. search_v2_xpath
  10. sort_change_obj_by_position
  11. apply_v2_patchset
  12. xml_apply_patchset
  13. pcmk__cib_element_in_patchset

   1 /*
   2  * Copyright 2004-2025 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/tree.h>                // xmlNode
  22 
  23 #include <crm/crm.h>
  24 #include <crm/common/cib_internal.h>
  25 #include <crm/common/xml.h>
  26 #include <crm/common/xml_internal.h>  // CRM_XML_LOG_BASE, etc.
  27 #include "crmcommon_private.h"
  28 
  29 /* Add changes for specified XML to patchset.
  30  * For patchset format, refer to diff schema.
  31  */
  32 static void
  33 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
  34 {
  35     xmlNode *cIter = NULL;
  36     xmlAttr *pIter = NULL;
  37     xmlNode *change = NULL;
  38     xml_node_private_t *nodepriv = xml->_private;
  39     const char *value = NULL;
  40 
  41     if (nodepriv == NULL) {
  42         /* Elements that shouldn't occur in a CIB don't have _private set. They
  43          * should be stripped out, ignored, or have an error thrown by any code
  44          * that processes their parent, so we ignore any changes to them.
  45          */
  46         return;
  47     }
  48 
  49     // If this XML node is new, just report that
  50     if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
  51         GString *xpath = pcmk__element_xpath(xml->parent);
  52 
  53         if (xpath != NULL) {
  54             int position = pcmk__xml_position(xml, pcmk__xf_deleted);
  55 
  56             change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
  57 
  58             crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE);
  59             crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
  60             crm_xml_add_int(change, PCMK_XE_POSITION, position);
  61             pcmk__xml_copy(change, xml);
  62             g_string_free(xpath, TRUE);
  63         }
  64 
  65         return;
  66     }
  67 
  68     // Check each of the XML node's attributes for changes
  69     for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
  70          pIter = pIter->next) {
  71         xmlNode *attr = NULL;
  72 
  73         nodepriv = pIter->_private;
  74         if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
  75             continue;
  76         }
  77 
  78         if (change == NULL) {
  79             GString *xpath = pcmk__element_xpath(xml);
  80 
  81             if (xpath != NULL) {
  82                 change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
  83 
  84                 crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY);
  85                 crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
  86 
  87                 change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
  88                 g_string_free(xpath, TRUE);
  89             }
  90         }
  91 
  92         attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR);
  93 
  94         crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
  95         if (nodepriv->flags & pcmk__xf_deleted) {
  96             crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
  97 
  98         } else {
  99             crm_xml_add(attr, PCMK_XA_OPERATION, "set");
 100 
 101             value = pcmk__xml_attr_value(pIter);
 102             crm_xml_add(attr, PCMK_XA_VALUE, value);
 103         }
 104     }
 105 
 106     if (change) {
 107         xmlNode *result = NULL;
 108 
 109         change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
 110         result = pcmk__xe_create(change, (const char *)xml->name);
 111 
 112         for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
 113              pIter = pIter->next) {
 114             nodepriv = pIter->_private;
 115             if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 116                 value = crm_element_value(xml, (const char *) pIter->name);
 117                 crm_xml_add(result, (const char *)pIter->name, value);
 118             }
 119         }
 120     }
 121 
 122     // Now recursively do the same for each child node of this node
 123     for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
 124          cIter = pcmk__xml_next(cIter)) {
 125         add_xml_changes_to_patchset(cIter, patchset);
 126     }
 127 
 128     nodepriv = xml->_private;
 129     if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 130         GString *xpath = pcmk__element_xpath(xml);
 131 
 132         crm_trace("%s.%s moved to position %d",
 133                   xml->name, pcmk__xe_id(xml),
 134                   pcmk__xml_position(xml, pcmk__xf_skip));
 135 
 136         if (xpath != NULL) {
 137             change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
 138 
 139             crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE);
 140             crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
 141             crm_xml_add_int(change, PCMK_XE_POSITION,
 142                             pcmk__xml_position(xml, pcmk__xf_deleted));
 143             g_string_free(xpath, TRUE);
 144         }
 145     }
 146 }
 147 
 148 static bool
 149 is_config_change(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 150 {
 151     GList *gIter = NULL;
 152     xml_node_private_t *nodepriv = NULL;
 153     xml_doc_private_t *docpriv;
 154     xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
 155                                            NULL);
 156 
 157     if (config) {
 158         nodepriv = config->_private;
 159     }
 160     if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 161         return TRUE;
 162     }
 163 
 164     if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
 165         docpriv = xml->doc->_private;
 166         for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
 167             pcmk__deleted_xml_t *deleted_obj = gIter->data;
 168 
 169             if (strstr(deleted_obj->path,
 170                        "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
 171                 return TRUE;
 172             }
 173         }
 174     }
 175     return FALSE;
 176 }
 177 
 178 static xmlNode *
 179 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 180 {
 181     int lpc = 0;
 182     GList *gIter = NULL;
 183     xml_doc_private_t *docpriv;
 184 
 185     xmlNode *v = NULL;
 186     xmlNode *version = NULL;
 187     xmlNode *patchset = NULL;
 188     const char *vfields[] = {
 189         PCMK_XA_ADMIN_EPOCH,
 190         PCMK_XA_EPOCH,
 191         PCMK_XA_NUM_UPDATES,
 192     };
 193 
 194     pcmk__assert(target != NULL);
 195 
 196     if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
 197         return NULL;
 198     }
 199 
 200     pcmk__assert(target->doc != NULL);
 201     docpriv = target->doc->_private;
 202 
 203     patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
 204     crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
 205 
 206     version = pcmk__xe_create(patchset, PCMK_XE_VERSION);
 207 
 208     v = pcmk__xe_create(version, PCMK_XE_SOURCE);
 209     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 210         const char *value = crm_element_value(source, vfields[lpc]);
 211 
 212         if (value == NULL) {
 213             value = "1";
 214         }
 215         crm_xml_add(v, vfields[lpc], value);
 216     }
 217 
 218     v = pcmk__xe_create(version, PCMK_XE_TARGET);
 219     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 220         const char *value = crm_element_value(target, vfields[lpc]);
 221 
 222         if (value == NULL) {
 223             value = "1";
 224         }
 225         crm_xml_add(v, vfields[lpc], value);
 226     }
 227 
 228     for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
 229         pcmk__deleted_xml_t *deleted_obj = gIter->data;
 230         xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
 231 
 232         crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE);
 233         crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
 234         if (deleted_obj->position >= 0) {
 235             crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
 236         }
 237     }
 238 
 239     add_xml_changes_to_patchset(target, patchset);
 240     return patchset;
 241 }
 242 
 243 xmlNode *
 244 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
     /* [previous][next][first][last][top][bottom][index][help] */
 245                     bool *config_changed, bool manage_version)
 246 {
 247     bool local_config_changed = false;
 248 
 249     if (format == 0) {
 250         format = 2;
 251     }
 252     if (format != 2) {
 253         crm_err("Unknown patch format: %d", format);
 254         return NULL;
 255     }
 256 
 257     xml_acl_disable(target);
 258     if ((target == NULL)
 259         || !pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
 260 
 261         crm_trace("No change %d", format);
 262         return NULL;
 263     }
 264 
 265     if (config_changed == NULL) {
 266         config_changed = &local_config_changed;
 267     }
 268     *config_changed = is_config_change(target);
 269 
 270     if (manage_version) {
 271         int counter = 0;
 272 
 273         if (*config_changed) {
 274             crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0");
 275 
 276             crm_element_value_int(target, PCMK_XA_EPOCH, &counter);
 277             crm_xml_add_int(target, PCMK_XA_EPOCH, counter + 1);
 278 
 279         } else {
 280             crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter);
 281             crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, counter + 1);
 282         }
 283     }
 284 
 285     return xml_create_patchset_v2(source, target);
 286 }
 287 
 288 void
 289 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
     /* [previous][next][first][last][top][bottom][index][help] */
 290                         bool with_digest)
 291 {
 292     char *digest = NULL;
 293 
 294     if ((patch == NULL) || (source == NULL) || (target == NULL)
 295         || !with_digest) {
 296         return;
 297     }
 298 
 299     /* We should always call pcmk__xml_commit_changes() before calculating a
 300      * digest. Otherwise, with an on-tracking dirty target, we could get a wrong
 301      * digest.
 302      */
 303     CRM_LOG_ASSERT(!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty));
 304 
 305     digest = pcmk__digest_xml(target, true);
 306 
 307     crm_xml_add(patch, PCMK__XA_DIGEST, digest);
 308     free(digest);
 309 
 310     return;
 311 }
 312 
 313 // Get CIB versions used for additions and deletions in a patchset
 314 bool
 315 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
     /* [previous][next][first][last][top][bottom][index][help] */
 316 {
 317     static const char *const vfields[] = {
 318         PCMK_XA_ADMIN_EPOCH,
 319         PCMK_XA_EPOCH,
 320         PCMK_XA_NUM_UPDATES,
 321     };
 322 
 323     const xmlNode *version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION,
 324                                                   NULL, NULL);
 325     const xmlNode *source = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL,
 326                                                  NULL);
 327     const xmlNode *target = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL,
 328                                                  NULL);
 329     int format = 1;
 330 
 331     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 332     if (format != 2) {
 333         crm_err("Unknown patch format: %d", format);
 334         return -EINVAL;
 335     }
 336 
 337     if (source != NULL) {
 338         for (int i = 0; i < PCMK__NELEM(vfields); i++) {
 339             crm_element_value_int(source, vfields[i], &(del[i]));
 340             crm_trace("Got %d for del[%s]", del[i], vfields[i]);
 341         }
 342     }
 343 
 344     if (target != NULL) {
 345         for (int i = 0; i < PCMK__NELEM(vfields); i++) {
 346             crm_element_value_int(target, vfields[i], &(add[i]));
 347             crm_trace("Got %d for add[%s]", add[i], vfields[i]);
 348         }
 349     }
 350     return pcmk_ok;
 351 }
 352 
 353 /*!
 354  * \internal
 355  * \brief Check whether patchset can be applied to current CIB
 356  *
 357  * \param[in] xml       Root of current CIB
 358  * \param[in] patchset  Patchset to check
 359  *
 360  * \return Standard Pacemaker return code
 361  */
 362 static int
 363 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 364 {
 365     int lpc = 0;
 366     bool changed = FALSE;
 367 
 368     int this[] = { 0, 0, 0 };
 369     int add[] = { 0, 0, 0 };
 370     int del[] = { 0, 0, 0 };
 371 
 372     const char *vfields[] = {
 373         PCMK_XA_ADMIN_EPOCH,
 374         PCMK_XA_EPOCH,
 375         PCMK_XA_NUM_UPDATES,
 376     };
 377 
 378     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 379         crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
 380         crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
 381         if (this[lpc] < 0) {
 382             this[lpc] = 0;
 383         }
 384     }
 385 
 386     /* Set some defaults in case nothing is present */
 387     add[0] = this[0];
 388     add[1] = this[1];
 389     add[2] = this[2] + 1;
 390     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 391         del[lpc] = this[lpc];
 392     }
 393 
 394     xml_patch_versions(patchset, add, del);
 395 
 396     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 397         if (this[lpc] < del[lpc]) {
 398             crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
 399                       vfields[lpc], this[0], this[1], this[2],
 400                       del[0], del[1], del[2], add[0], add[1], add[2]);
 401             return pcmk_rc_diff_resync;
 402 
 403         } else if (this[lpc] > del[lpc]) {
 404             crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
 405                      vfields[lpc], this[0], this[1], this[2],
 406                      del[0], del[1], del[2], add[0], add[1], add[2], patchset);
 407             crm_log_xml_info(patchset, "OldPatch");
 408             return pcmk_rc_old_data;
 409         }
 410     }
 411 
 412     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 413         if (add[lpc] > del[lpc]) {
 414             changed = TRUE;
 415         }
 416     }
 417 
 418     if (!changed) {
 419         crm_notice("Versions did not change in patch %d.%d.%d",
 420                    add[0], add[1], add[2]);
 421         return pcmk_rc_old_data;
 422     }
 423 
 424     crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
 425               add[0], add[1], add[2], this[0], this[1], this[2]);
 426     return pcmk_rc_ok;
 427 }
 428 
 429 // Return first child matching element name and optionally id or position
 430 static xmlNode *
 431 first_matching_xml_child(const xmlNode *parent, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 432                          const char *id, int position)
 433 {
 434     xmlNode *cIter = NULL;
 435 
 436     for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
 437          cIter = pcmk__xml_next(cIter)) {
 438         if (strcmp((const char *) cIter->name, name) != 0) {
 439             continue;
 440         } else if (id) {
 441             const char *cid = pcmk__xe_id(cIter);
 442 
 443             if ((cid == NULL) || (strcmp(cid, id) != 0)) {
 444                 continue;
 445             }
 446         }
 447 
 448         // "position" makes sense only for XML comments for now
 449         if ((cIter->type == XML_COMMENT_NODE)
 450             && (position >= 0)
 451             && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
 452             continue;
 453         }
 454 
 455         return cIter;
 456     }
 457     return NULL;
 458 }
 459 
 460 /*!
 461  * \internal
 462  * \brief Simplified, more efficient alternative to pcmk__xpath_find_one()
 463  *
 464  * \param[in] top              Root of XML to search
 465  * \param[in] key              Search xpath
 466  * \param[in] target_position  If deleting, where to delete
 467  *
 468  * \return XML child matching xpath if found, NULL otherwise
 469  *
 470  * \note This only works on simplified xpaths found in v2 patchset diffs,
 471  *       i.e. the only allowed search predicate is [@id='XXX'].
 472  */
 473 static xmlNode *
 474 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
     /* [previous][next][first][last][top][bottom][index][help] */
 475 {
 476     xmlNode *target = (xmlNode *) top->doc;
 477     const char *current = key;
 478     char *section;
 479     char *remainder;
 480     char *id;
 481     char *tag;
 482     char *path = NULL;
 483     int rc;
 484     size_t key_len;
 485 
 486     CRM_CHECK(key != NULL, return NULL);
 487     key_len = strlen(key);
 488 
 489     /* These are scanned from key after a slash, so they can't be bigger
 490      * than key_len - 1 characters plus a null terminator.
 491      */
 492 
 493     remainder = pcmk__assert_alloc(key_len, sizeof(char));
 494     section = pcmk__assert_alloc(key_len, sizeof(char));
 495     id = pcmk__assert_alloc(key_len, sizeof(char));
 496     tag = pcmk__assert_alloc(key_len, sizeof(char));
 497 
 498     do {
 499         // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
 500         rc = sscanf(current, "/%[^/]%s", section, remainder);
 501         if (rc > 0) {
 502             // Separate FIRST_COMPONENT into TAG[@id='ID']
 503             int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
 504             int current_position = -1;
 505 
 506             /* The target position is for the final component tag, so only use
 507              * it if there is nothing left to search after this component.
 508              */
 509             if ((rc == 1) && (target_position >= 0)) {
 510                 current_position = target_position;
 511             }
 512 
 513             switch (f) {
 514                 case 1:
 515                     target = first_matching_xml_child(target, tag, NULL,
 516                                                       current_position);
 517                     break;
 518                 case 2:
 519                     target = first_matching_xml_child(target, tag, id,
 520                                                       current_position);
 521                     break;
 522                 default:
 523                     // This should not be possible
 524                     target = NULL;
 525                     break;
 526             }
 527             current = remainder;
 528         }
 529 
 530     // Continue if something remains to search, and we've matched so far
 531     } while ((rc == 2) && target);
 532 
 533     if (target) {
 534         crm_trace("Found %s for %s",
 535                   (path = (char *) xmlGetNodePath(target)), key);
 536         free(path);
 537     } else {
 538         crm_debug("No match for %s", key);
 539     }
 540 
 541     free(remainder);
 542     free(section);
 543     free(tag);
 544     free(id);
 545     return target;
 546 }
 547 
 548 typedef struct xml_change_obj_s {
 549     const xmlNode *change;
 550     xmlNode *match;
 551 } xml_change_obj_t;
 552 
 553 static gint
 554 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 555 {
 556     const xml_change_obj_t *change_obj_a = a;
 557     const xml_change_obj_t *change_obj_b = b;
 558     int position_a = -1;
 559     int position_b = -1;
 560 
 561     crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
 562     crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
 563 
 564     if (position_a < position_b) {
 565         return -1;
 566 
 567     } else if (position_a > position_b) {
 568         return 1;
 569     }
 570 
 571     return 0;
 572 }
 573 
 574 /*!
 575  * \internal
 576  * \brief Apply a version 2 patchset to an XML node
 577  *
 578  * \param[in,out] xml       XML to apply patchset to
 579  * \param[in]     patchset  Patchset to apply
 580  *
 581  * \return Standard Pacemaker return code
 582  */
 583 static int
 584 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 585 {
 586     int rc = pcmk_rc_ok;
 587     const xmlNode *change = NULL;
 588     GList *change_objs = NULL;
 589     GList *gIter = NULL;
 590 
 591     for (change = pcmk__xml_first_child(patchset); change != NULL;
 592          change = pcmk__xml_next(change)) {
 593         xmlNode *match = NULL;
 594         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 595         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
 596         int position = -1;
 597 
 598         if (op == NULL) {
 599             continue;
 600         }
 601 
 602         crm_trace("Processing %s %s", change->name, op);
 603 
 604         /* PCMK_VALUE_DELETE changes for XML comments are generated with
 605          * PCMK_XE_POSITION
 606          */
 607         if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
 608             crm_element_value_int(change, PCMK_XE_POSITION, &position);
 609         }
 610         match = search_v2_xpath(xml, xpath, position);
 611         crm_trace("Performing %s on %s with %p", op, xpath, match);
 612 
 613         if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
 614             crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
 615             continue;
 616 
 617         } else if (match == NULL) {
 618             crm_err("No %s match for %s in %p", op, xpath, xml->doc);
 619             rc = pcmk_rc_diff_failed;
 620             continue;
 621 
 622         } else if (pcmk__str_any_of(op,
 623                                     PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) {
 624             // Delay the adding of a PCMK_VALUE_CREATE object
 625             xml_change_obj_t *change_obj =
 626                 pcmk__assert_alloc(1, sizeof(xml_change_obj_t));
 627 
 628             change_obj->change = change;
 629             change_obj->match = match;
 630 
 631             change_objs = g_list_append(change_objs, change_obj);
 632 
 633             if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
 634                 // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
 635                 if ((match->parent != NULL) && (match->parent->last != NULL)) {
 636                     xmlAddNextSibling(match->parent->last, match);
 637                 }
 638             }
 639 
 640         } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
 641             pcmk__xml_free(match);
 642 
 643         } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
 644             const xmlNode *child = pcmk__xe_first_child(change,
 645                                                         PCMK_XE_CHANGE_RESULT,
 646                                                         NULL, NULL);
 647             const xmlNode *attrs = pcmk__xml_first_child(child);
 648 
 649             if (attrs == NULL) {
 650                 rc = ENOMSG;
 651                 continue;
 652             }
 653 
 654             // Remove all attributes
 655             pcmk__xe_remove_matching_attrs(match, false, NULL, NULL);
 656 
 657             for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
 658                  pIter = pIter->next) {
 659                 const char *name = (const char *) pIter->name;
 660                 const char *value = pcmk__xml_attr_value(pIter);
 661 
 662                 crm_xml_add(match, name, value);
 663             }
 664 
 665         } else {
 666             crm_err("Unknown operation: %s", op);
 667             rc = pcmk_rc_diff_failed;
 668         }
 669     }
 670 
 671     // Changes should be generated in the right order. Double checking.
 672     change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
 673 
 674     for (gIter = change_objs; gIter; gIter = gIter->next) {
 675         xml_change_obj_t *change_obj = gIter->data;
 676         xmlNode *match = change_obj->match;
 677         const char *op = NULL;
 678         const char *xpath = NULL;
 679 
 680         change = change_obj->change;
 681 
 682         op = crm_element_value(change, PCMK_XA_OPERATION);
 683         xpath = crm_element_value(change, PCMK_XA_PATH);
 684 
 685         crm_trace("Continue performing %s on %s with %p", op, xpath, match);
 686 
 687         if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
 688             int position = 0;
 689             xmlNode *child = NULL;
 690             xmlNode *match_child = NULL;
 691 
 692             match_child = match->children;
 693             crm_element_value_int(change, PCMK_XE_POSITION, &position);
 694 
 695             while ((match_child != NULL)
 696                    && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
 697                 match_child = match_child->next;
 698             }
 699 
 700             child = pcmk__xml_copy(match, change->children);
 701 
 702             if (match_child != NULL) {
 703                 crm_trace("Adding %s at position %d", child->name, position);
 704                 xmlAddPrevSibling(match_child, child);
 705 
 706             } else {
 707                 crm_trace("Adding %s at position %d (end)",
 708                           child->name, position);
 709             }
 710 
 711         } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
 712             int position = 0;
 713 
 714             crm_element_value_int(change, PCMK_XE_POSITION, &position);
 715             if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
 716                 xmlNode *match_child = NULL;
 717                 int p = position;
 718 
 719                 if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
 720                     p++; // Skip ourselves
 721                 }
 722 
 723                 pcmk__assert(match->parent != NULL);
 724                 match_child = match->parent->children;
 725 
 726                 while ((match_child != NULL)
 727                        && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
 728                     match_child = match_child->next;
 729                 }
 730 
 731                 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
 732                           match->name, position,
 733                           pcmk__xml_position(match, pcmk__xf_skip),
 734                           match->prev, (match_child? "next":"last"),
 735                           (match_child? match_child : match->parent->last));
 736 
 737                 if (match_child) {
 738                     xmlAddPrevSibling(match_child, match);
 739 
 740                 } else {
 741                     pcmk__assert(match->parent->last != NULL);
 742                     xmlAddNextSibling(match->parent->last, match);
 743                 }
 744 
 745             } else {
 746                 crm_trace("%s is already in position %d",
 747                           match->name, position);
 748             }
 749 
 750             if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
 751                 crm_err("Moved %s.%s to position %d instead of %d (%p)",
 752                         match->name, pcmk__xe_id(match),
 753                         pcmk__xml_position(match, pcmk__xf_skip),
 754                         position, match->prev);
 755                 rc = pcmk_rc_diff_failed;
 756             }
 757         }
 758     }
 759 
 760     g_list_free_full(change_objs, free);
 761     return rc;
 762 }
 763 
 764 int
 765 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
     /* [previous][next][first][last][top][bottom][index][help] */
 766 {
 767     int format = 1;
 768     int rc = pcmk_ok;
 769     xmlNode *old = NULL;
 770     const char *digest = NULL;
 771 
 772     if (patchset == NULL) {
 773         return rc;
 774     }
 775 
 776     pcmk__log_xml_patchset(LOG_TRACE, patchset);
 777 
 778     if (check_version) {
 779         rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
 780         if (rc != pcmk_ok) {
 781             return rc;
 782         }
 783     }
 784 
 785     digest = crm_element_value(patchset, PCMK__XA_DIGEST);
 786     if (digest != NULL) {
 787         /* Make original XML available for logging in case result doesn't have
 788          * expected digest
 789          */
 790         pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
 791     }
 792 
 793     if (rc == pcmk_ok) {
 794         crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 795 
 796         if (format != 2) {
 797             crm_err("Unknown patch format: %d", format);
 798             rc = -EINVAL;
 799 
 800         } else {
 801             rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
 802         }
 803     }
 804 
 805     if ((rc == pcmk_ok) && (digest != NULL)) {
 806         char *new_digest = NULL;
 807 
 808         new_digest = pcmk__digest_xml(xml, true);
 809         if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
 810             crm_info("v%d digest mis-match: expected %s, calculated %s",
 811                      format, digest, new_digest);
 812             rc = -pcmk_err_diff_failed;
 813             pcmk__if_tracing(
 814                 {
 815                     save_xml_to_file(old, "PatchDigest:input", NULL);
 816                     save_xml_to_file(xml, "PatchDigest:result", NULL);
 817                     save_xml_to_file(patchset, "PatchDigest:diff", NULL);
 818                 },
 819                 {}
 820             );
 821 
 822         } else {
 823             crm_trace("v%d digest matched: expected %s, calculated %s",
 824                       format, digest, new_digest);
 825         }
 826         free(new_digest);
 827     }
 828     pcmk__xml_free(old);
 829     return rc;
 830 }
 831 
 832 bool
 833 pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
     /* [previous][next][first][last][top][bottom][index][help] */
 834 {
 835     const char *element_xpath = pcmk__cib_abs_xpath_for(element);
 836     const char *parent_xpath = pcmk_cib_parent_name_for(element);
 837     char *element_regex = NULL;
 838     bool rc = false;
 839     int format = 1;
 840 
 841     pcmk__assert(patchset != NULL);
 842 
 843     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 844     if (format != 2) {
 845         crm_warn("Unknown patch format: %d", format);
 846         return false;
 847     }
 848 
 849     CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
 850 
 851     /* Matches if and only if element_xpath is part of a changed path
 852      * (supported values for element never contain XML IDs with schema
 853      * validation enabled)
 854      *
 855      * @TODO Use POSIX word boundary instead of (/|$), if it works:
 856      * https://www.regular-expressions.info/wordboundaries.html.
 857      */
 858     element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
 859 
 860     for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
 861                                                       NULL, NULL);
 862          change != NULL; change = pcmk__xe_next(change, PCMK_XE_CHANGE)) {
 863 
 864         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 865         const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
 866 
 867         if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
 868             // Change to an existing element
 869             rc = true;
 870             break;
 871         }
 872 
 873         if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
 874             && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
 875             && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
 876                                                 element)) {
 877             // Newly added element
 878             rc = true;
 879             break;
 880         }
 881     }
 882 
 883     free(element_regex);
 884     return rc;
 885 }

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