root/lib/cib/cib_utils.c

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

DEFINITIONS

This source file includes following definitions.
  1. cib_version_details
  2. cib_diff_version_details
  3. cib__get_notify_patchset
  4. element_in_patchset_v1
  5. element_in_patchset_v2
  6. cib__element_in_patchset
  7. createEmptyCib
  8. cib_acl_enabled
  9. should_copy_cib
  10. cib_perform_op
  11. cib__create_op
  12. validate_transaction_request
  13. cib__extend_transaction
  14. cib_native_callback
  15. cib_native_notify
  16. cib_read_config
  17. cib_internal_op
  18. cib_apply_patch_event
  19. cib__signon_query
  20. cib__signon_attempts
  21. cib__clean_up_connection
  22. cib_get_generation
  23. get_object_path
  24. get_object_parent
  25. get_object_root
  26. cib_pref
  27. cib_metadata

   1 /*
   2  * Original copyright 2004 International Business Machines
   3  * Later changes copyright 2008-2024 the Pacemaker project contributors
   4  *
   5  * The version control history for this file may have further details.
   6  *
   7  * This source code is licensed under the GNU Lesser General Public License
   8  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   9  */
  10 #include <crm_internal.h>
  11 #include <unistd.h>
  12 #include <stdlib.h>
  13 #include <stdio.h>
  14 #include <stdarg.h>
  15 #include <string.h>
  16 #include <sys/utsname.h>
  17 
  18 #include <glib.h>
  19 
  20 #include <crm/crm.h>
  21 #include <crm/cib/internal.h>
  22 #include <crm/common/cib_internal.h>
  23 #include <crm/common/xml.h>
  24 #include <crm/common/xml_internal.h>
  25 #include <crm/pengine/rules.h>
  26 
  27 gboolean
  28 cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
     /* [previous][next][first][last][top][bottom][index][help] */
  29 {
  30     *epoch = -1;
  31     *updates = -1;
  32     *admin_epoch = -1;
  33 
  34     if (cib == NULL) {
  35         return FALSE;
  36 
  37     } else {
  38         crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
  39         crm_element_value_int(cib, PCMK_XA_NUM_UPDATES, updates);
  40         crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
  41     }
  42     return TRUE;
  43 }
  44 
  45 gboolean
  46 cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
     /* [previous][next][first][last][top][bottom][index][help] */
  47                          int *_admin_epoch, int *_epoch, int *_updates)
  48 {
  49     int add[] = { 0, 0, 0 };
  50     int del[] = { 0, 0, 0 };
  51 
  52     xml_patch_versions(diff, add, del);
  53 
  54     *admin_epoch = add[0];
  55     *epoch = add[1];
  56     *updates = add[2];
  57 
  58     *_admin_epoch = del[0];
  59     *_epoch = del[1];
  60     *_updates = del[2];
  61 
  62     return TRUE;
  63 }
  64 
  65 /*!
  66  * \internal
  67  * \brief Get the XML patchset from a CIB diff notification
  68  *
  69  * \param[in]  msg       CIB diff notification
  70  * \param[out] patchset  Where to store XML patchset
  71  *
  72  * \return Standard Pacemaker return code
  73  */
  74 int
  75 cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
  76 {
  77     int rc = pcmk_err_generic;
  78     xmlNode *wrapper = NULL;
  79 
  80     pcmk__assert(patchset != NULL);
  81     *patchset = NULL;
  82 
  83     if (msg == NULL) {
  84         crm_err("CIB diff notification received with no XML");
  85         return ENOMSG;
  86     }
  87 
  88     if ((crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc) != 0)
  89         || (rc != pcmk_ok)) {
  90 
  91         crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
  92                  pcmk_strerror(rc), rc);
  93         crm_log_xml_debug(msg, "failed");
  94         return pcmk_legacy2rc(rc);
  95     }
  96 
  97     wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
  98     *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
  99 
 100     if (*patchset == NULL) {
 101         crm_err("CIB diff notification received with no patchset");
 102         return ENOMSG;
 103     }
 104     return pcmk_rc_ok;
 105 }
 106 
 107 #define XPATH_DIFF_V1 "//" PCMK__XE_CIB_UPDATE_RESULT "//" PCMK__XE_DIFF_ADDED
 108 
 109 /*!
 110  * \internal
 111  * \brief Check whether a given CIB element was modified in a CIB patchset (v1)
 112  *
 113  * \param[in] patchset  CIB XML patchset
 114  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
 115  *                      to \c PCMK_XE_CIB)
 116  *
 117  * \return \c true if \p element was modified, or \c false otherwise
 118  */
 119 static bool
 120 element_in_patchset_v1(const xmlNode *patchset, const char *element)
     /* [previous][next][first][last][top][bottom][index][help] */
 121 {
 122     char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
 123                                     pcmk__s(element, PCMK_XE_CIB));
 124     xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
 125 
 126     free(xpath);
 127 
 128     if (xpath_obj == NULL) {
 129         return false;
 130     }
 131     freeXpathObject(xpath_obj);
 132     return true;
 133 }
 134 
 135 /*!
 136  * \internal
 137  * \brief Check whether a given CIB element was modified in a CIB patchset (v2)
 138  *
 139  * \param[in] patchset  CIB XML patchset
 140  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
 141  *                      to \c PCMK_XE_CIB). Supported values include any CIB
 142  *                      element supported by \c pcmk__cib_abs_xpath_for().
 143  *
 144  * \return \c true if \p element was modified, or \c false otherwise
 145  */
 146 static bool
 147 element_in_patchset_v2(const xmlNode *patchset, const char *element)
     /* [previous][next][first][last][top][bottom][index][help] */
 148 {
 149     const char *element_xpath = pcmk__cib_abs_xpath_for(element);
 150     const char *parent_xpath = pcmk_cib_parent_name_for(element);
 151     char *element_regex = NULL;
 152     bool rc = false;
 153 
 154     CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
 155 
 156     // Matches if and only if element_xpath is part of a changed path
 157     element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
 158 
 159     for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
 160                                                       NULL, NULL);
 161          change != NULL; change = pcmk__xe_next_same(change)) {
 162 
 163         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 164         const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
 165 
 166         if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
 167             // Change to an existing element
 168             rc = true;
 169             break;
 170         }
 171 
 172         if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
 173             && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
 174             && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
 175                                                 element)) {
 176 
 177             // Newly added element
 178             rc = true;
 179             break;
 180         }
 181     }
 182 
 183     free(element_regex);
 184     return rc;
 185 }
 186 
 187 /*!
 188  * \internal
 189  * \brief Check whether a given CIB element was modified in a CIB patchset
 190  *
 191  * \param[in] patchset  CIB XML patchset
 192  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
 193  *                      to \c PCMK_XE_CIB). Supported values include any CIB
 194  *                      element supported by \c pcmk__cib_abs_xpath_for().
 195  *
 196  * \return \c true if \p element was modified, or \c false otherwise
 197  */
 198 bool
 199 cib__element_in_patchset(const xmlNode *patchset, const char *element)
     /* [previous][next][first][last][top][bottom][index][help] */
 200 {
 201     int format = 1;
 202 
 203     pcmk__assert(patchset != NULL);
 204 
 205     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 206     switch (format) {
 207         case 1:
 208             return element_in_patchset_v1(patchset, element);
 209 
 210         case 2:
 211             return element_in_patchset_v2(patchset, element);
 212 
 213         default:
 214             crm_warn("Unknown patch format: %d", format);
 215             return false;
 216     }
 217 }
 218 
 219 /*!
 220  * \brief Create XML for a new (empty) CIB
 221  *
 222  * \param[in] cib_epoch  What to use as \c PCMK_XA_EPOCH CIB attribute
 223  *
 224  * \return Newly created XML for empty CIB
 225  * \note It is the caller's responsibility to free the result with free_xml().
 226  */
 227 xmlNode *
 228 createEmptyCib(int cib_epoch)
     /* [previous][next][first][last][top][bottom][index][help] */
 229 {
 230     xmlNode *cib_root = NULL, *config = NULL;
 231 
 232     cib_root = pcmk__xe_create(NULL, PCMK_XE_CIB);
 233     crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
 234     crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name());
 235 
 236     crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
 237     crm_xml_add_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
 238     crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
 239 
 240     config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION);
 241     pcmk__xe_create(cib_root, PCMK_XE_STATUS);
 242 
 243     pcmk__xe_create(config, PCMK_XE_CRM_CONFIG);
 244     pcmk__xe_create(config, PCMK_XE_NODES);
 245     pcmk__xe_create(config, PCMK_XE_RESOURCES);
 246     pcmk__xe_create(config, PCMK_XE_CONSTRAINTS);
 247 
 248 #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
 249     {
 250         xmlNode *rsc_defaults = pcmk__xe_create(config, PCMK_XE_RSC_DEFAULTS);
 251         xmlNode *meta = pcmk__xe_create(rsc_defaults, PCMK_XE_META_ATTRIBUTES);
 252         xmlNode *nvpair = pcmk__xe_create(meta, PCMK_XE_NVPAIR);
 253 
 254         crm_xml_add(meta, PCMK_XA_ID, "build-resource-defaults");
 255         crm_xml_add(nvpair, PCMK_XA_ID, "build-" PCMK_META_RESOURCE_STICKINESS);
 256         crm_xml_add(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
 257         crm_xml_add_int(nvpair, PCMK_XA_VALUE,
 258                         PCMK__RESOURCE_STICKINESS_DEFAULT);
 259     }
 260 #endif
 261     return cib_root;
 262 }
 263 
 264 static bool
 265 cib_acl_enabled(xmlNode *xml, const char *user)
     /* [previous][next][first][last][top][bottom][index][help] */
 266 {
 267     bool rc = FALSE;
 268 
 269     if(pcmk_acl_required(user)) {
 270         const char *value = NULL;
 271         GHashTable *options = pcmk__strkey_table(free, free);
 272 
 273         cib_read_config(options, xml);
 274         value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL);
 275         rc = crm_is_true(value);
 276         g_hash_table_destroy(options);
 277     }
 278 
 279     crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
 280     return rc;
 281 }
 282 
 283 /*!
 284  * \internal
 285  * \brief Determine whether to perform operations on a scratch copy of the CIB
 286  *
 287  * \param[in] op            CIB operation
 288  * \param[in] section       CIB section
 289  * \param[in] call_options  CIB call options
 290  *
 291  * \return \p true if we should make a copy of the CIB, or \p false otherwise
 292  */
 293 static bool
 294 should_copy_cib(const char *op, const char *section, int call_options)
     /* [previous][next][first][last][top][bottom][index][help] */
 295 {
 296     if (pcmk_is_set(call_options, cib_dryrun)) {
 297         // cib_dryrun implies a scratch copy by definition; no side effects
 298         return true;
 299     }
 300 
 301     if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
 302         /* Commit-transaction must make a copy for atomicity. We must revert to
 303          * the original CIB if the entire transaction cannot be applied
 304          * successfully.
 305          */
 306         return true;
 307     }
 308 
 309     if (pcmk_is_set(call_options, cib_transaction)) {
 310         /* If cib_transaction is set, then we're in the process of committing a
 311          * transaction. The commit-transaction request already made a scratch
 312          * copy, and we're accumulating changes in that copy.
 313          */
 314         return false;
 315     }
 316 
 317     if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
 318         /* Copying large CIBs accounts for a huge percentage of our CIB usage,
 319          * and this avoids some of it.
 320          *
 321          * @TODO: Is this safe? See discussion at
 322          * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
 323          */
 324         return false;
 325     }
 326 
 327     // Default behavior is to operate on a scratch copy
 328     return true;
 329 }
 330 
 331 int
 332 cib_perform_op(cib_t *cib, const char *op, uint32_t call_options,
     /* [previous][next][first][last][top][bottom][index][help] */
 333                cib__op_fn_t fn, bool is_query, const char *section,
 334                xmlNode *req, xmlNode *input, bool manage_counters,
 335                bool *config_changed, xmlNode **current_cib,
 336                xmlNode **result_cib, xmlNode **diff, xmlNode **output)
 337 {
 338     int rc = pcmk_ok;
 339     bool check_schema = true;
 340     bool make_copy = true;
 341     xmlNode *top = NULL;
 342     xmlNode *scratch = NULL;
 343     xmlNode *patchset_cib = NULL;
 344     xmlNode *local_diff = NULL;
 345 
 346     const char *user = crm_element_value(req, PCMK__XA_CIB_USER);
 347     bool with_digest = false;
 348 
 349     crm_trace("Begin %s%s%s op",
 350               (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
 351               (is_query? "read-only " : ""), op);
 352 
 353     CRM_CHECK(output != NULL, return -ENOMSG);
 354     CRM_CHECK(current_cib != NULL, return -ENOMSG);
 355     CRM_CHECK(result_cib != NULL, return -ENOMSG);
 356     CRM_CHECK(config_changed != NULL, return -ENOMSG);
 357 
 358     if(output) {
 359         *output = NULL;
 360     }
 361 
 362     *result_cib = NULL;
 363     *config_changed = false;
 364 
 365     if (fn == NULL) {
 366         return -EINVAL;
 367     }
 368 
 369     if (is_query) {
 370         xmlNode *cib_ro = *current_cib;
 371         xmlNode *cib_filtered = NULL;
 372 
 373         if (cib_acl_enabled(cib_ro, user)
 374             && xml_acl_filtered_copy(user, *current_cib, *current_cib,
 375                                      &cib_filtered)) {
 376 
 377             if (cib_filtered == NULL) {
 378                 crm_debug("Pre-filtered the entire cib");
 379                 return -EACCES;
 380             }
 381             cib_ro = cib_filtered;
 382             crm_log_xml_trace(cib_ro, "filtered");
 383         }
 384 
 385         rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
 386 
 387         if(output == NULL || *output == NULL) {
 388             /* nothing */
 389 
 390         } else if(cib_filtered == *output) {
 391             cib_filtered = NULL; /* Let them have this copy */
 392 
 393         } else if (*output == *current_cib) {
 394             /* They already know not to free it */
 395 
 396         } else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
 397             /* We're about to free the document of which *output is a part */
 398             *output = pcmk__xml_copy(NULL, *output);
 399 
 400         } else if ((*output)->doc == (*current_cib)->doc) {
 401             /* Give them a copy they can free */
 402             *output = pcmk__xml_copy(NULL, *output);
 403         }
 404 
 405         free_xml(cib_filtered);
 406         return rc;
 407     }
 408 
 409     make_copy = should_copy_cib(op, section, call_options);
 410 
 411     if (!make_copy) {
 412         /* Conditional on v2 patch style */
 413 
 414         scratch = *current_cib;
 415 
 416         // Make a copy of the top-level element to store version details
 417         top = pcmk__xe_create(NULL, (const char *) scratch->name);
 418         pcmk__xe_copy_attrs(top, scratch, pcmk__xaf_none);
 419         patchset_cib = top;
 420 
 421         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
 422         rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
 423 
 424         /* If scratch points to a new object now (for example, after an erase
 425          * operation), then *current_cib should point to the same object.
 426          */
 427         *current_cib = scratch;
 428 
 429     } else {
 430         scratch = pcmk__xml_copy(NULL, *current_cib);
 431         patchset_cib = *current_cib;
 432 
 433         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
 434         rc = (*fn) (op, call_options, section, req, input, *current_cib,
 435                     &scratch, output);
 436 
 437         if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
 438             crm_trace("Inferring changes after %s op", op);
 439             xml_track_changes(scratch, user, *current_cib,
 440                               cib_acl_enabled(*current_cib, user));
 441             xml_calculate_changes(*current_cib, scratch);
 442         }
 443         CRM_CHECK(*current_cib != scratch, return -EINVAL);
 444     }
 445 
 446     xml_acl_disable(scratch); /* Allow the system to make any additional changes */
 447 
 448     if (rc == pcmk_ok && scratch == NULL) {
 449         rc = -EINVAL;
 450         goto done;
 451 
 452     } else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
 453         crm_trace("ACL rejected part or all of the proposed changes");
 454         rc = -EACCES;
 455         goto done;
 456 
 457     } else if (rc != pcmk_ok) {
 458         goto done;
 459     }
 460 
 461     /* If the CIB is from a file, we don't need to check that the feature set is
 462      * supported.  All we care about in that case is the schema version, which
 463      * is checked elsewhere.
 464      */
 465     if (scratch && (cib == NULL || cib->variant != cib_file)) {
 466         const char *new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
 467 
 468         rc = pcmk__check_feature_set(new_version);
 469         if (rc != pcmk_rc_ok) {
 470             pcmk__config_err("Discarding update with feature set '%s' greater than our own '%s'",
 471                              new_version, CRM_FEATURE_SET);
 472             rc = pcmk_rc2legacy(rc);
 473             goto done;
 474         }
 475     }
 476 
 477     if (patchset_cib != NULL) {
 478         int old = 0;
 479         int new = 0;
 480 
 481         crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
 482         crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
 483 
 484         if (old > new) {
 485             crm_err("%s went backwards: %d -> %d (Opts: %#x)",
 486                     PCMK_XA_ADMIN_EPOCH, old, new, call_options);
 487             crm_log_xml_warn(req, "Bad Op");
 488             crm_log_xml_warn(input, "Bad Data");
 489             rc = -pcmk_err_old_data;
 490 
 491         } else if (old == new) {
 492             crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
 493             crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
 494             if (old > new) {
 495                 crm_err("%s went backwards: %d -> %d (Opts: %#x)",
 496                         PCMK_XA_EPOCH, old, new, call_options);
 497                 crm_log_xml_warn(req, "Bad Op");
 498                 crm_log_xml_warn(input, "Bad Data");
 499                 rc = -pcmk_err_old_data;
 500             }
 501         }
 502     }
 503 
 504     crm_trace("Massaging CIB contents");
 505     pcmk__strip_xml_text(scratch);
 506 
 507     if (!make_copy) {
 508         /* At this point, patchset_cib is just the PCMK_XE_CIB tag and its
 509          * properties.
 510          *
 511          * The v1 format would barf on this, but we know the v2 patch
 512          * format only needs it for the top-level version fields
 513          */
 514         local_diff = xml_create_patchset(2, patchset_cib, scratch,
 515                                          config_changed, manage_counters);
 516 
 517     } else {
 518         static time_t expires = 0;
 519         time_t tm_now = time(NULL);
 520 
 521         if (expires < tm_now) {
 522             expires = tm_now + 60;  /* Validate clients are correctly applying v2-style diffs at most once a minute */
 523             with_digest = true;
 524         }
 525 
 526         local_diff = xml_create_patchset(0, patchset_cib, scratch,
 527                                          config_changed, manage_counters);
 528     }
 529 
 530     pcmk__log_xml_changes(LOG_TRACE, scratch);
 531     xml_accept_changes(scratch);
 532 
 533     if(local_diff) {
 534         patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
 535         pcmk__log_xml_patchset(LOG_INFO, local_diff);
 536         crm_log_xml_trace(local_diff, "raw patch");
 537     }
 538 
 539     if (make_copy && (local_diff != NULL)) {
 540         // Original to compare against doesn't exist
 541         pcmk__if_tracing(
 542             {
 543                 // Validate the calculated patch set
 544                 int test_rc = pcmk_ok;
 545                 int format = 1;
 546                 xmlNode *cib_copy = pcmk__xml_copy(NULL, patchset_cib);
 547 
 548                 crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
 549                 test_rc = xml_apply_patchset(cib_copy, local_diff,
 550                                              manage_counters);
 551 
 552                 if (test_rc != pcmk_ok) {
 553                     save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
 554                     save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
 555                     save_xml_to_file(scratch, "PatchApply:actual", NULL);
 556                     save_xml_to_file(local_diff, "PatchApply:diff", NULL);
 557                     crm_err("v%d patchset error, patch failed to apply: %s "
 558                             "(%d)",
 559                             format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
 560                             test_rc);
 561                 }
 562                 free_xml(cib_copy);
 563             },
 564             {}
 565         );
 566     }
 567 
 568     if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
 569         /* Throttle the amount of costly validation we perform due to status updates
 570          * a) we don't really care whats in the status section
 571          * b) we don't validate any of its contents at the moment anyway
 572          */
 573         check_schema = false;
 574     }
 575 
 576     /* === scratch must not be modified after this point ===
 577      * Exceptions, anything in:
 578 
 579      static filter_t filter[] = {
 580      { 0, PCMK_XA_CRM_DEBUG_ORIGIN },
 581      { 0, PCMK_XA_CIB_LAST_WRITTEN },
 582      { 0, PCMK_XA_UPDATE_ORIGIN },
 583      { 0, PCMK_XA_UPDATE_CLIENT },
 584      { 0, PCMK_XA_UPDATE_USER },
 585      };
 586      */
 587 
 588     if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
 589         const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
 590 
 591         pcmk__xe_add_last_written(scratch);
 592         pcmk__warn_if_schema_deprecated(schema);
 593 
 594         /* Make values of origin, client, and user in scratch match
 595          * the ones in req (if the schema allows the attributes)
 596          */
 597         if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") >= 0) {
 598             const char *origin = crm_element_value(req, PCMK__XA_SRC);
 599             const char *client = crm_element_value(req,
 600                                                    PCMK__XA_CIB_CLIENTNAME);
 601 
 602             if (origin != NULL) {
 603                 crm_xml_add(scratch, PCMK_XA_UPDATE_ORIGIN, origin);
 604             } else {
 605                 pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_ORIGIN);
 606             }
 607 
 608             if (client != NULL) {
 609                 crm_xml_add(scratch, PCMK_XA_UPDATE_CLIENT, user);
 610             } else {
 611                 pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_CLIENT);
 612             }
 613 
 614             if (user != NULL) {
 615                 crm_xml_add(scratch, PCMK_XA_UPDATE_USER, user);
 616             } else {
 617                 pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_USER);
 618             }
 619         }
 620     }
 621 
 622     crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
 623     if ((rc == pcmk_ok) && check_schema
 624         && !pcmk__configured_schema_validates(scratch)) {
 625         const char *current_schema = crm_element_value(scratch,
 626                                                        PCMK_XA_VALIDATE_WITH);
 627 
 628         crm_warn("Updated CIB does not validate against %s schema",
 629                  pcmk__s(current_schema, "unspecified"));
 630         rc = -pcmk_err_schema_validation;
 631     }
 632 
 633   done:
 634 
 635     *result_cib = scratch;
 636 
 637     /* @TODO: This may not work correctly with !make_copy, since we don't
 638      * keep the original CIB.
 639      */
 640     if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
 641         && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
 642 
 643         if (*result_cib == NULL) {
 644             crm_debug("Pre-filtered the entire cib result");
 645         }
 646         free_xml(scratch);
 647     }
 648 
 649     if(diff) {
 650         *diff = local_diff;
 651     } else {
 652         free_xml(local_diff);
 653     }
 654 
 655     free_xml(top);
 656     crm_trace("Done");
 657     return rc;
 658 }
 659 
 660 int
 661 cib__create_op(cib_t *cib, const char *op, const char *host,
     /* [previous][next][first][last][top][bottom][index][help] */
 662                const char *section, xmlNode *data, int call_options,
 663                const char *user_name, const char *client_name,
 664                xmlNode **op_msg)
 665 {
 666     CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
 667 
 668     *op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
 669 
 670     cib->call_id++;
 671     if (cib->call_id < 1) {
 672         cib->call_id = 1;
 673     }
 674 
 675     crm_xml_add(*op_msg, PCMK__XA_T, PCMK__VALUE_CIB);
 676     crm_xml_add(*op_msg, PCMK__XA_CIB_OP, op);
 677     crm_xml_add(*op_msg, PCMK__XA_CIB_HOST, host);
 678     crm_xml_add(*op_msg, PCMK__XA_CIB_SECTION, section);
 679     crm_xml_add(*op_msg, PCMK__XA_CIB_USER, user_name);
 680     crm_xml_add(*op_msg, PCMK__XA_CIB_CLIENTNAME, client_name);
 681     crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLID, cib->call_id);
 682 
 683     crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
 684     crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options);
 685 
 686     if (data != NULL) {
 687         xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA);
 688 
 689         pcmk__xml_copy(wrapper, data);
 690     }
 691 
 692     if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
 693         CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
 694                   free_xml(*op_msg); return -EPROTO);
 695     }
 696     return pcmk_ok;
 697 }
 698 
 699 /*!
 700  * \internal
 701  * \brief Check whether a CIB request is supported in a transaction
 702  *
 703  * \param[in] request  CIB request
 704  *
 705  * \return Standard Pacemaker return code
 706  */
 707 static int
 708 validate_transaction_request(const xmlNode *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 709 {
 710     const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
 711     const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
 712     const cib__operation_t *operation = NULL;
 713     int rc = cib__get_operation(op, &operation);
 714 
 715     if (rc != pcmk_rc_ok) {
 716         // cib__get_operation() logs error
 717         return rc;
 718     }
 719 
 720     if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
 721         crm_err("Operation %s is not supported in CIB transactions", op);
 722         return EOPNOTSUPP;
 723     }
 724 
 725     if (host != NULL) {
 726         crm_err("Operation targeting a specific node (%s) is not supported in "
 727                 "a CIB transaction",
 728                 host);
 729         return EOPNOTSUPP;
 730     }
 731     return pcmk_rc_ok;
 732 }
 733 
 734 /*!
 735  * \internal
 736  * \brief Append a CIB request to a CIB transaction
 737  *
 738  * \param[in,out] cib      CIB client whose transaction to extend
 739  * \param[in,out] request  Request to add to transaction
 740  *
 741  * \return Legacy Pacemaker return code
 742  */
 743 int
 744 cib__extend_transaction(cib_t *cib, xmlNode *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 745 {
 746     int rc = pcmk_rc_ok;
 747 
 748     pcmk__assert((cib != NULL) && (request != NULL));
 749 
 750     rc = validate_transaction_request(request);
 751 
 752     if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
 753         rc = pcmk_rc_no_transaction;
 754     }
 755 
 756     if (rc == pcmk_rc_ok) {
 757         pcmk__xml_copy(cib->transaction, request);
 758 
 759     } else {
 760         const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
 761         const char *client_id = NULL;
 762 
 763         cib->cmds->client_id(cib, NULL, &client_id);
 764         crm_err("Failed to add '%s' operation to transaction for client %s: %s",
 765                 op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
 766         crm_log_xml_info(request, "failed");
 767     }
 768     return pcmk_rc2legacy(rc);
 769 }
 770 
 771 void
 772 cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
     /* [previous][next][first][last][top][bottom][index][help] */
 773 {
 774     xmlNode *output = NULL;
 775     cib_callback_client_t *blob = NULL;
 776 
 777     if (msg != NULL) {
 778         xmlNode *wrapper = NULL;
 779 
 780         crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
 781         crm_element_value_int(msg, PCMK__XA_CIB_CALLID, &call_id);
 782         wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL);
 783         output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 784     }
 785 
 786     blob = cib__lookup_id(call_id);
 787 
 788     if (blob == NULL) {
 789         crm_trace("No callback found for call %d", call_id);
 790     }
 791 
 792     if (cib == NULL) {
 793         crm_debug("No cib object supplied");
 794     }
 795 
 796     if (rc == -pcmk_err_diff_resync) {
 797         /* This is an internal value that clients do not and should not care about */
 798         rc = pcmk_ok;
 799     }
 800 
 801     if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
 802         crm_trace("Invoking callback %s for call %d",
 803                   pcmk__s(blob->id, "without ID"), call_id);
 804         blob->callback(msg, call_id, rc, output, blob->user_data);
 805 
 806     } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
 807         crm_warn("CIB command failed: %s", pcmk_strerror(rc));
 808         crm_log_xml_debug(msg, "Failed CIB Update");
 809     }
 810 
 811     /* This may free user_data, so do it after the callback */
 812     if (blob) {
 813         remove_cib_op_callback(call_id, FALSE);
 814     }
 815 
 816     if (cib && cib->op_callback != NULL) {
 817         crm_trace("Invoking global callback for call %d", call_id);
 818         cib->op_callback(msg, call_id, rc, output);
 819     }
 820     crm_trace("OP callback activated for %d", call_id);
 821 }
 822 
 823 void
 824 cib_native_notify(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 825 {
 826     xmlNode *msg = user_data;
 827     cib_notify_client_t *entry = data;
 828     const char *event = NULL;
 829 
 830     if (msg == NULL) {
 831         crm_warn("Skipping callback - NULL message");
 832         return;
 833     }
 834 
 835     event = crm_element_value(msg, PCMK__XA_SUBT);
 836 
 837     if (entry == NULL) {
 838         crm_warn("Skipping callback - NULL callback client");
 839         return;
 840 
 841     } else if (entry->callback == NULL) {
 842         crm_warn("Skipping callback - NULL callback");
 843         return;
 844 
 845     } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
 846         crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
 847         return;
 848     }
 849 
 850     crm_trace("Invoking callback for %p/%s event...", entry, event);
 851     entry->callback(event, msg);
 852     crm_trace("Callback invoked...");
 853 }
 854 
 855 gboolean
 856 cib_read_config(GHashTable * options, xmlNode * current_cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 857 {
 858     xmlNode *config = NULL;
 859     crm_time_t *now = NULL;
 860 
 861     if (options == NULL || current_cib == NULL) {
 862         return FALSE;
 863     }
 864 
 865     now = crm_time_new(NULL);
 866 
 867     g_hash_table_remove_all(options);
 868 
 869     config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG);
 870     if (config) {
 871         pe_unpack_nvpairs(current_cib, config, PCMK_XE_CLUSTER_PROPERTY_SET,
 872                           NULL, options, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, TRUE,
 873                           now, NULL);
 874     }
 875 
 876     pcmk__validate_cluster_options(options);
 877 
 878     crm_time_free(now);
 879 
 880     return TRUE;
 881 }
 882 
 883 int
 884 cib_internal_op(cib_t * cib, const char *op, const char *host,
     /* [previous][next][first][last][top][bottom][index][help] */
 885                 const char *section, xmlNode * data,
 886                 xmlNode ** output_data, int call_options, const char *user_name)
 887 {
 888     int (*delegate)(cib_t *cib, const char *op, const char *host,
 889                     const char *section, xmlNode *data, xmlNode **output_data,
 890                     int call_options, const char *user_name) = NULL;
 891 
 892     if (cib == NULL) {
 893         return -EINVAL;
 894     }
 895 
 896     delegate = cib->delegate_fn;
 897     if (delegate == NULL) {
 898         return -EPROTONOSUPPORT;
 899     }
 900     if (user_name == NULL) {
 901         user_name = getenv("CIB_user");
 902     }
 903     return delegate(cib, op, host, section, data, output_data, call_options, user_name);
 904 }
 905 
 906 /*!
 907  * \brief Apply a CIB update patch to a given CIB
 908  *
 909  * \param[in]  event   CIB update patch
 910  * \param[in]  input   CIB to patch
 911  * \param[out] output  Resulting CIB after patch
 912  * \param[in]  level   Log the patch at this log level (unless LOG_CRIT)
 913  *
 914  * \return Legacy Pacemaker return code
 915  * \note sbd calls this function
 916  */
 917 int
 918 cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
     /* [previous][next][first][last][top][bottom][index][help] */
 919                       int level)
 920 {
 921     int rc = pcmk_err_generic;
 922 
 923     xmlNode *wrapper = NULL;
 924     xmlNode *diff = NULL;
 925 
 926     pcmk__assert((event != NULL) && (input != NULL) && (output != NULL));
 927 
 928     crm_element_value_int(event, PCMK__XA_CIB_RC, &rc);
 929     wrapper = pcmk__xe_first_child(event, PCMK__XE_CIB_UPDATE_RESULT, NULL,
 930                                    NULL);
 931     diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 932 
 933     if (rc < pcmk_ok || diff == NULL) {
 934         return rc;
 935     }
 936 
 937     if (level > LOG_CRIT) {
 938         pcmk__log_xml_patchset(level, diff);
 939     }
 940 
 941     if (input != NULL) {
 942         rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
 943                               NULL);
 944 
 945         if (rc != pcmk_ok) {
 946             crm_debug("Update didn't apply: %s (%d) %p",
 947                       pcmk_strerror(rc), rc, *output);
 948 
 949             if (rc == -pcmk_err_old_data) {
 950                 crm_trace("Masking error, we already have the supplied update");
 951                 return pcmk_ok;
 952             }
 953             free_xml(*output);
 954             *output = NULL;
 955             return rc;
 956         }
 957     }
 958     return rc;
 959 }
 960 
 961 #define log_signon_query_err(out, fmt, args...) do {    \
 962         if (out != NULL) {                              \
 963             out->err(out, fmt, ##args);                 \
 964         } else {                                        \
 965             crm_err(fmt, ##args);                       \
 966         }                                               \
 967     } while (0)
 968 
 969 int
 970 cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
     /* [previous][next][first][last][top][bottom][index][help] */
 971 {
 972     int rc = pcmk_rc_ok;
 973     cib_t *cib_conn = NULL;
 974 
 975     pcmk__assert(cib_object != NULL);
 976 
 977     if (cib == NULL) {
 978         cib_conn = cib_new();
 979     } else {
 980         if (*cib == NULL) {
 981             *cib = cib_new();
 982         }
 983         cib_conn = *cib;
 984     }
 985 
 986     if (cib_conn == NULL) {
 987         return ENOMEM;
 988     }
 989 
 990     if (cib_conn->state == cib_disconnected) {
 991         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
 992         rc = pcmk_legacy2rc(rc);
 993     }
 994 
 995     if (rc != pcmk_rc_ok) {
 996         log_signon_query_err(out, "Could not connect to the CIB: %s",
 997                              pcmk_rc_str(rc));
 998         goto done;
 999     }
1000 
1001     if (out != NULL) {
1002         out->transient(out, "Querying CIB...");
1003     }
1004     rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
1005                                cib_scope_local|cib_sync_call);
1006     rc = pcmk_legacy2rc(rc);
1007 
1008     if (rc != pcmk_rc_ok) {
1009         log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
1010     }
1011 
1012 done:
1013     if (cib == NULL) {
1014         cib__clean_up_connection(&cib_conn);
1015     }
1016 
1017     if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
1018         return pcmk_rc_no_input;
1019     }
1020     return rc;
1021 }
1022 
1023 int
1024 cib__signon_attempts(cib_t *cib, const char *name, enum cib_conn_type type,
     /* [previous][next][first][last][top][bottom][index][help] */
1025                      int attempts)
1026 {
1027     int rc = pcmk_rc_ok;
1028 
1029     crm_trace("Attempting connection to CIB manager (up to %d time%s)",
1030               attempts, pcmk__plural_s(attempts));
1031 
1032     for (int remaining = attempts - 1; remaining >= 0; --remaining) {
1033         rc = cib->cmds->signon(cib, name, type);
1034 
1035         if ((rc == pcmk_rc_ok)
1036             || (remaining == 0)
1037             || ((errno != EAGAIN) && (errno != EALREADY))) {
1038             break;
1039         }
1040 
1041         // Retry after soft error (interrupted by signal, etc.)
1042         pcmk__sleep_ms((attempts - remaining) * 500);
1043         crm_debug("Re-attempting connection to CIB manager (%d attempt%s remaining)",
1044                   remaining, pcmk__plural_s(remaining));
1045     }
1046 
1047     return rc;
1048 }
1049 
1050 int
1051 cib__clean_up_connection(cib_t **cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1052 {
1053     int rc;
1054 
1055     if (*cib == NULL) {
1056         return pcmk_rc_ok;
1057     }
1058 
1059     rc = (*cib)->cmds->signoff(*cib);
1060     cib_delete(*cib);
1061     *cib = NULL;
1062     return pcmk_legacy2rc(rc);
1063 }
1064 
1065 // Deprecated functions kept only for backward API compatibility
1066 // LCOV_EXCL_START
1067 
1068 #include <crm/cib/util_compat.h>
1069 
1070 xmlNode *
1071 cib_get_generation(cib_t * cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1072 {
1073     xmlNode *the_cib = NULL;
1074     xmlNode *generation = pcmk__xe_create(NULL, PCMK__XE_GENERATION_TUPLE);
1075 
1076     cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
1077     if (the_cib != NULL) {
1078         pcmk__xe_copy_attrs(generation, the_cib, pcmk__xaf_none);
1079         free_xml(the_cib);
1080     }
1081 
1082     return generation;
1083 }
1084 
1085 const char *
1086 get_object_path(const char *object_type)
     /* [previous][next][first][last][top][bottom][index][help] */
1087 {
1088     return pcmk_cib_xpath_for(object_type);
1089 }
1090 
1091 const char *
1092 get_object_parent(const char *object_type)
     /* [previous][next][first][last][top][bottom][index][help] */
1093 {
1094     return pcmk_cib_parent_name_for(object_type);
1095 }
1096 
1097 xmlNode *
1098 get_object_root(const char *object_type, xmlNode *the_root)
     /* [previous][next][first][last][top][bottom][index][help] */
1099 {
1100     return pcmk_find_cib_element(the_root, object_type);
1101 }
1102 
1103 const char *
1104 cib_pref(GHashTable * options, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1105 {
1106     return pcmk__cluster_option(options, name);
1107 }
1108 
1109 void
1110 cib_metadata(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1111 {
1112     pcmk__output_t *out = NULL;
1113     int rc = pcmk__output_new(&out, "text", NULL, NULL);
1114 
1115     if (rc != pcmk_rc_ok) {
1116         crm_err("Unable to output metadata: %s", pcmk_rc_str(rc));
1117         return;
1118     }
1119 
1120     pcmk__daemon_metadata(out, "pacemaker-based",
1121                           "Cluster Information Base manager options",
1122                           "Cluster options used by Pacemaker's Cluster "
1123                           "Information Base manager",
1124                           pcmk__opt_based);
1125 
1126     out->finish(out, CRM_EX_OK, true, NULL);
1127     pcmk__output_free(out);
1128 }
1129 
1130 // LCOV_EXCL_STOP
1131 // End deprecated API

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