LCOV - code coverage report
Current view: top level - cib - cib_utils.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 445 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 20 0.0 %

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

Generated by: LCOV version 1.14