diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c
index 011c67f18c..b2b7a4c404 100644
--- a/daemons/controld/controld_te_callbacks.c
+++ b/daemons/controld/controld_te_callbacks.c
@@ -1,697 +1,697 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/msg_xml.h>
 #include <crm/cluster.h>        /* For ONLINESTATUS etc */
 
 #include <pacemaker-controld.h>
 
 void te_update_confirm(const char *event, xmlNode * msg);
 
 #define RSC_OP_PREFIX "//" PCMK__XE_DIFF_ADDED "//" PCMK_XE_CIB \
                       "//" XML_LRM_TAG_RSC_OP "[@" PCMK_XA_ID "='"
 
 // An explicit PCMK_OPT_SHUTDOWN_LOCK of 0 means the lock has been cleared
 static bool
 shutdown_lock_cleared(xmlNode *lrm_resource)
 {
     time_t shutdown_lock = 0;
 
     return (crm_element_value_epoch(lrm_resource, PCMK_OPT_SHUTDOWN_LOCK,
                                     &shutdown_lock) == pcmk_ok)
            && (shutdown_lock == 0);
 }
 
 static void
 te_update_diff_v1(const char *event, xmlNode *diff)
 {
     int lpc, max;
     xmlXPathObject *xpathObj = NULL;
     GString *rsc_op_xpath = NULL;
 
     CRM_CHECK(diff != NULL, return);
 
     pcmk__output_set_log_level(controld_globals.logger_out, LOG_TRACE);
     controld_globals.logger_out->message(controld_globals.logger_out,
                                          "xml-patchset", diff);
 
     if (cib__config_changed_v1(NULL, NULL, &diff)) {
         abort_transition(INFINITY, pcmk__graph_restart, "Non-status change",
                          diff);
         goto bail;              /* configuration changed */
     }
 
     /* Tickets Attributes - Added/Updated */
     xpathObj =
         xpath_search(diff,
                      "//" F_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_ADDED
                      "//" XML_CIB_TAG_TICKETS);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
         abort_transition(INFINITY, pcmk__graph_restart,
                          "Ticket attribute: update", aborted);
         goto bail;
 
     }
     freeXpathObject(xpathObj);
 
     /* Tickets Attributes - Removed */
     xpathObj =
         xpath_search(diff,
                      "//" F_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_REMOVED
                      "//" XML_CIB_TAG_TICKETS);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
         abort_transition(INFINITY, pcmk__graph_restart,
                          "Ticket attribute: removal", aborted);
         goto bail;
     }
     freeXpathObject(xpathObj);
 
     /* Transient Attributes - Removed */
     xpathObj =
         xpath_search(diff,
                      "//" F_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_REMOVED
                      "//" XML_TAG_TRANSIENT_NODEATTRS);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
         abort_transition(INFINITY, pcmk__graph_restart,
                          "Transient attribute: removal", aborted);
         goto bail;
 
     }
     freeXpathObject(xpathObj);
 
     // Check for lrm_resource entries
     xpathObj = xpath_search(diff,
                             "//" F_CIB_UPDATE_RESULT
                             "//" PCMK__XE_DIFF_ADDED
                             "//" XML_LRM_TAG_RESOURCE);
     max = numXpathResults(xpathObj);
 
     /*
      * Updates by, or in response to, graph actions will never affect more than
      * one resource at a time, so such updates indicate an LRM refresh. In that
      * case, start a new transition rather than check each result individually,
      * which can result in _huge_ speedups in large clusters.
      *
      * Unfortunately, we can only do so when there are no pending actions.
      * Otherwise, we could mistakenly throw away those results here, and
      * the cluster will stall waiting for them and time out the operation.
      */
     if ((controld_globals.transition_graph->pending == 0) && (max > 1)) {
         crm_debug("Ignoring resource operation updates due to history refresh of %d resources",
                   max);
         crm_log_xml_trace(diff, "lrm-refresh");
         abort_transition(INFINITY, pcmk__graph_restart, "History refresh",
                          NULL);
         goto bail;
     }
 
     if (max == 1) {
         xmlNode *lrm_resource = getXpathResult(xpathObj, 0);
 
         if (shutdown_lock_cleared(lrm_resource)) {
             // @TODO would be more efficient to abort once after transition done
             abort_transition(INFINITY, pcmk__graph_restart,
                              "Shutdown lock cleared", lrm_resource);
             // Still process results, so we stop timers and update failcounts
         }
     }
     freeXpathObject(xpathObj);
 
     /* Process operation updates */
     xpathObj =
         xpath_search(diff,
                      "//" F_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_ADDED
                      "//" XML_LRM_TAG_RSC_OP);
     max = numXpathResults(xpathObj);
     if (max > 0) {
         int lpc = 0;
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
             const char *node = get_node_id(rsc_op);
 
             process_graph_event(rsc_op, node);
         }
     }
     freeXpathObject(xpathObj);
 
     /* Detect deleted (as opposed to replaced or added) actions - eg. crm_resource -C */
     xpathObj = xpath_search(diff,
                             "//" PCMK__XE_DIFF_REMOVED "//" XML_LRM_TAG_RSC_OP);
     max = numXpathResults(xpathObj);
     for (lpc = 0; lpc < max; lpc++) {
         const char *op_id = NULL;
         xmlXPathObject *op_match = NULL;
         xmlNode *match = getXpathResult(xpathObj, lpc);
 
         CRM_LOG_ASSERT(match != NULL);
         if(match == NULL) { continue; };
 
         op_id = ID(match);
 
         if (rsc_op_xpath == NULL) {
             rsc_op_xpath = g_string_new(RSC_OP_PREFIX);
         } else {
             g_string_truncate(rsc_op_xpath, sizeof(RSC_OP_PREFIX) - 1);
         }
         pcmk__g_strcat(rsc_op_xpath, op_id, "']", NULL);
 
         op_match = xpath_search(diff, (const char *) rsc_op_xpath->str);
         if (numXpathResults(op_match) == 0) {
             /* Prevent false positives by matching cancelations too */
             const char *node = get_node_id(match);
             pcmk__graph_action_t *cancelled = get_cancel_action(op_id, node);
 
             if (cancelled == NULL) {
                 crm_debug("No match for deleted action %s (%s on %s)",
                           (const char *) rsc_op_xpath->str, op_id, node);
                 abort_transition(INFINITY, pcmk__graph_restart,
                                  "Resource op removal", match);
                 freeXpathObject(op_match);
                 goto bail;
 
             } else {
                 crm_debug("Deleted lrm_rsc_op %s on %s was for graph event %d",
                           op_id, node, cancelled->id);
             }
         }
 
         freeXpathObject(op_match);
     }
 
   bail:
     freeXpathObject(xpathObj);
     if (rsc_op_xpath != NULL) {
         g_string_free(rsc_op_xpath, TRUE);
     }
 }
 
 static void
 process_lrm_resource_diff(xmlNode *lrm_resource, const char *node)
 {
     for (xmlNode *rsc_op = pcmk__xml_first_child(lrm_resource); rsc_op != NULL;
          rsc_op = pcmk__xml_next(rsc_op)) {
         process_graph_event(rsc_op, node);
     }
     if (shutdown_lock_cleared(lrm_resource)) {
         // @TODO would be more efficient to abort once after transition done
         abort_transition(INFINITY, pcmk__graph_restart, "Shutdown lock cleared",
                          lrm_resource);
     }
 }
 
 static void
 process_resource_updates(const char *node, xmlNode *xml, xmlNode *change,
                          const char *op, const char *xpath)
 {
     xmlNode *rsc = NULL;
 
     if (xml == NULL) {
         return;
     }
 
     if (pcmk__xe_is(xml, XML_CIB_TAG_LRM)) {
         xml = first_named_child(xml, XML_LRM_TAG_RESOURCES);
         CRM_CHECK(xml != NULL, return);
     }
 
     CRM_CHECK(pcmk__xe_is(xml, XML_LRM_TAG_RESOURCES), return);
 
     /*
      * Updates by, or in response to, TE actions will never contain updates
      * for more than one resource at a time, so such updates indicate an
      * LRM refresh.
      *
      * In that case, start a new transition rather than check each result
      * individually, which can result in _huge_ speedups in large clusters.
      *
      * Unfortunately, we can only do so when there are no pending actions.
      * Otherwise, we could mistakenly throw away those results here, and
      * the cluster will stall waiting for them and time out the operation.
      */
     if ((controld_globals.transition_graph->pending == 0)
         && (xml->children != NULL) && (xml->children->next != NULL)) {
 
         crm_log_xml_trace(change, "lrm-refresh");
         abort_transition(INFINITY, pcmk__graph_restart, "History refresh",
                          NULL);
         return;
     }
 
     for (rsc = pcmk__xml_first_child(xml); rsc != NULL;
          rsc = pcmk__xml_next(rsc)) {
         crm_trace("Processing %s", ID(rsc));
         process_lrm_resource_diff(rsc, node);
     }
 }
 
 static char *extract_node_uuid(const char *xpath) 
 {
     char *mutable_path = strdup(xpath);
     char *node_uuid = NULL;
     char *search = NULL;
     char *match = NULL;
 
     match = strstr(mutable_path, "node_state[@" PCMK_XA_ID "=\'");
     if (match == NULL) {
         free(mutable_path);
         return NULL;
     }
     match += strlen("node_state[@" PCMK_XA_ID "=\'");
 
     search = strchr(match, '\'');
     if (search == NULL) {
         free(mutable_path);
         return NULL;
     }
     search[0] = 0;
 
     node_uuid = strdup(match);
     free(mutable_path);
     return node_uuid;
 }
 
 static void
 abort_unless_down(const char *xpath, const char *op, xmlNode *change,
                   const char *reason)
 {
     char *node_uuid = NULL;
     pcmk__graph_action_t *down = NULL;
 
     if(!pcmk__str_eq(op, "delete", pcmk__str_casei)) {
         abort_transition(INFINITY, pcmk__graph_restart, reason, change);
         return;
     }
 
     node_uuid = extract_node_uuid(xpath);
     if(node_uuid == NULL) {
         crm_err("Could not extract node ID from %s", xpath);
         abort_transition(INFINITY, pcmk__graph_restart, reason, change);
         return;
     }
 
     down = match_down_event(node_uuid);
     if (down == NULL) {
         crm_trace("Not expecting %s to be down (%s)", node_uuid, xpath);
         abort_transition(INFINITY, pcmk__graph_restart, reason, change);
     } else {
         crm_trace("Expecting changes to %s (%s)", node_uuid, xpath);
     }
     free(node_uuid);
 }
 
 static void
 process_op_deletion(const char *xpath, xmlNode *change)
 {
     char *mutable_key = strdup(xpath);
     char *key;
     char *node_uuid;
 
     // Extract the part of xpath between last pair of single quotes
     key = strrchr(mutable_key, '\'');
     if (key != NULL) {
         *key = '\0';
         key = strrchr(mutable_key, '\'');
     }
     if (key == NULL) {
         crm_warn("Ignoring malformed CIB update (resource deletion of %s)",
                  xpath);
         free(mutable_key);
         return;
     }
     ++key;
 
     node_uuid = extract_node_uuid(xpath);
     if (confirm_cancel_action(key, node_uuid) == FALSE) {
         abort_transition(INFINITY, pcmk__graph_restart,
                          "Resource operation removal", change);
     }
     free(mutable_key);
     free(node_uuid);
 }
 
 static void
 process_delete_diff(const char *xpath, const char *op, xmlNode *change)
 {
     if (strstr(xpath, "/" XML_LRM_TAG_RSC_OP "[")) {
         process_op_deletion(xpath, change);
 
     } else if (strstr(xpath, "/" XML_CIB_TAG_LRM "[")) {
         abort_unless_down(xpath, op, change, "Resource state removal");
 
     } else if (strstr(xpath, "/" XML_CIB_TAG_STATE "[")) {
         abort_unless_down(xpath, op, change, "Node state removal");
 
     } else {
         crm_trace("Ignoring delete of %s", xpath);
     }
 }
 
 static void
 process_node_state_diff(xmlNode *state, xmlNode *change, const char *op,
                         const char *xpath)
 {
     xmlNode *lrm = first_named_child(state, XML_CIB_TAG_LRM);
 
     process_resource_updates(ID(state), lrm, change, op, xpath);
 }
 
 static void
 process_status_diff(xmlNode *status, xmlNode *change, const char *op,
                     const char *xpath)
 {
     for (xmlNode *state = pcmk__xml_first_child(status); state != NULL;
          state = pcmk__xml_next(state)) {
         process_node_state_diff(state, change, op, xpath);
     }
 }
 
 static void
 process_cib_diff(xmlNode *cib, xmlNode *change, const char *op,
                  const char *xpath)
 {
     xmlNode *status = first_named_child(cib, PCMK_XE_STATUS);
     xmlNode *config = first_named_child(cib, PCMK_XE_CONFIGURATION);
 
     if (status) {
         process_status_diff(status, change, op, xpath);
     }
     if (config) {
         abort_transition(INFINITY, pcmk__graph_restart,
                          "Non-status-only change", change);
     }
 }
 
 static void
 te_update_diff_v2(xmlNode *diff)
 {
     crm_log_xml_trace(diff, "Patch:Raw");
 
     for (xmlNode *change = pcmk__xml_first_child(diff); change != NULL;
          change = pcmk__xml_next(change)) {
 
         xmlNode *match = NULL;
         const char *name = NULL;
         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
 
         // Possible ops: create, modify, delete, move
         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 
         // Ignore uninteresting updates
         if (op == NULL) {
             continue;
 
         } else if (xpath == NULL) {
             crm_trace("Ignoring %s change for version field", op);
             continue;
 
         } else if ((strcmp(op, "move") == 0)
                    && (strstr(xpath,
                               "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
-                              "/" XML_CIB_TAG_RESOURCES) == NULL)) {
+                              "/" PCMK_XE_RESOURCES) == NULL)) {
             /* We still need to consider moves within the resources section,
              * since they affect placement order.
              */
             crm_trace("Ignoring move change at %s", xpath);
             continue;
         }
 
         // Find the result of create/modify ops
         if (strcmp(op, "create") == 0) {
             match = change->children;
 
         } else if (strcmp(op, "modify") == 0) {
             match = first_named_child(change, XML_DIFF_RESULT);
             if(match) {
                 match = match->children;
             }
 
         } else if (!pcmk__str_any_of(op, "delete", "move", NULL)) {
             crm_warn("Ignoring malformed CIB update (%s operation on %s is unrecognized)",
                      op, xpath);
             continue;
         }
 
         if (match) {
             if (match->type == XML_COMMENT_NODE) {
                 crm_trace("Ignoring %s operation for comment at %s", op, xpath);
                 continue;
             }
             name = (const char *)match->name;
         }
 
         crm_trace("Handling %s operation for %s%s%s",
                   op, (xpath? xpath : "CIB"),
                   (name? " matched by " : ""), (name? name : ""));
 
         if (strstr(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION)) {
             abort_transition(INFINITY, pcmk__graph_restart,
                              "Configuration change", change);
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strstr(xpath, "/" XML_CIB_TAG_TICKETS)
                    || pcmk__str_eq(name, XML_CIB_TAG_TICKETS, pcmk__str_none)) {
             abort_transition(INFINITY, pcmk__graph_restart,
                              "Ticket attribute change", change);
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strstr(xpath, "/" XML_TAG_TRANSIENT_NODEATTRS "[")
                    || pcmk__str_eq(name, XML_TAG_TRANSIENT_NODEATTRS,
                                    pcmk__str_none)) {
             abort_unless_down(xpath, op, change, "Transient attribute change");
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strcmp(op, "delete") == 0) {
             process_delete_diff(xpath, op, change);
 
         } else if (name == NULL) {
             crm_warn("Ignoring malformed CIB update (%s at %s has no result)",
                      op, xpath);
 
         } else if (strcmp(name, PCMK_XE_CIB) == 0) {
             process_cib_diff(match, change, op, xpath);
 
         } else if (strcmp(name, PCMK_XE_STATUS) == 0) {
             process_status_diff(match, change, op, xpath);
 
         } else if (strcmp(name, XML_CIB_TAG_STATE) == 0) {
             process_node_state_diff(match, change, op, xpath);
 
         } else if (strcmp(name, XML_CIB_TAG_LRM) == 0) {
             process_resource_updates(ID(match), match, change, op, xpath);
 
         } else if (strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             process_resource_updates(local_node, match, change, op, xpath);
             free(local_node);
 
         } else if (strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             process_lrm_resource_diff(match, local_node);
             free(local_node);
 
         } else if (strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             process_graph_event(match, local_node);
             free(local_node);
 
         } else {
             crm_warn("Ignoring malformed CIB update (%s at %s has unrecognized result %s)",
                      op, xpath, name);
         }
     }
 }
 
 void
 te_update_diff(const char *event, xmlNode * msg)
 {
     xmlNode *diff = NULL;
     const char *op = NULL;
     int rc = -EINVAL;
     int format = 1;
     int p_add[] = { 0, 0, 0 };
     int p_del[] = { 0, 0, 0 };
 
     CRM_CHECK(msg != NULL, return);
     crm_element_value_int(msg, F_CIB_RC, &rc);
 
     if (controld_globals.transition_graph == NULL) {
         crm_trace("No graph");
         return;
 
     } else if (rc < pcmk_ok) {
         crm_trace("Filter rc=%d (%s)", rc, pcmk_strerror(rc));
         return;
 
     } else if (controld_globals.transition_graph->complete
                && (controld_globals.fsa_state != S_IDLE)
                && (controld_globals.fsa_state != S_TRANSITION_ENGINE)
                && (controld_globals.fsa_state != S_POLICY_ENGINE)) {
         crm_trace("Filter state=%s (complete)",
                   fsa_state2string(controld_globals.fsa_state));
         return;
     }
 
     op = crm_element_value(msg, F_CIB_OPERATION);
     diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     xml_patch_versions(diff, p_add, p_del);
     crm_debug("Processing (%s) diff: %d.%d.%d -> %d.%d.%d (%s)", op,
               p_del[0], p_del[1], p_del[2], p_add[0], p_add[1], p_add[2],
               fsa_state2string(controld_globals.fsa_state));
 
     crm_element_value_int(diff, PCMK_XA_FORMAT, &format);
     switch (format) {
         case 1:
             te_update_diff_v1(event, diff);
             break;
         case 2:
             te_update_diff_v2(diff);
             break;
         default:
             crm_warn("Ignoring malformed CIB update (unknown patch format %d)",
                      format);
     }
     controld_remove_all_outside_events();
 }
 
 void
 process_te_message(xmlNode * msg, xmlNode * xml_data)
 {
     const char *value = NULL;
     xmlXPathObject *xpathObj = NULL;
     int nmatches = 0;
 
     CRM_CHECK(msg != NULL, return);
 
     // Transition requests must specify transition engine as subsystem
     value = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
     if (pcmk__str_empty(value)
         || !pcmk__str_eq(value, CRM_SYSTEM_TENGINE, pcmk__str_none)) {
         crm_info("Received invalid transition request: subsystem '%s' not '"
                  CRM_SYSTEM_TENGINE "'", pcmk__s(value, ""));
         return;
     }
 
     // Only the lrm_invoke command is supported as a transition request
     value = crm_element_value(msg, PCMK__XA_CRM_TASK);
     if (!pcmk__str_eq(value, CRM_OP_INVOKE_LRM, pcmk__str_none)) {
         crm_info("Received invalid transition request: command '%s' not '"
                  CRM_OP_INVOKE_LRM "'", pcmk__s(value, ""));
         return;
     }
 
     // Transition requests must be marked as coming from the executor
     value = crm_element_value(msg, PCMK__XA_CRM_SYS_FROM);
     if (!pcmk__str_eq(value, CRM_SYSTEM_LRMD, pcmk__str_none)) {
         crm_info("Received invalid transition request: from '%s' not '"
                  CRM_SYSTEM_LRMD "'", pcmk__s(value, ""));
         return;
     }
 
     crm_debug("Processing transition request with ref='%s' origin='%s'",
               pcmk__s(crm_element_value(msg, PCMK_XA_REFERENCE), ""),
               pcmk__s(crm_element_value(msg, PCMK__XA_SRC), ""));
 
     xpathObj = xpath_search(xml_data, "//" XML_LRM_TAG_RSC_OP);
     nmatches = numXpathResults(xpathObj);
     if (nmatches == 0) {
         crm_err("Received transition request with no results (bug?)");
     } else {
         for (int lpc = 0; lpc < nmatches; lpc++) {
             xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
             const char *node = get_node_id(rsc_op);
 
             process_graph_event(rsc_op, node);
         }
     }
     freeXpathObject(xpathObj);
 }
 
 void
 cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     if (rc < pcmk_ok) {
         crm_err("Update %d FAILED: %s", call_id, pcmk_strerror(rc));
     }
 }
 
 /*!
  * \brief Handle a timeout in node-to-node communication
  *
  * \param[in,out] data  Pointer to graph action
  *
  * \return FALSE (indicating that source should be not be re-added)
  */
 gboolean
 action_timer_callback(gpointer data)
 {
     pcmk__graph_action_t *action = (pcmk__graph_action_t *) data;
     const char *task = NULL;
     const char *on_node = NULL;
     const char *via_node = NULL;
 
     CRM_CHECK(data != NULL, return FALSE);
 
     stop_te_timer(action);
 
     task = crm_element_value(action->xml, PCMK_XA_OPERATION);
     on_node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     via_node = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE);
 
     if (controld_globals.transition_graph->complete) {
         crm_notice("Node %s did not send %s result (via %s) within %dms "
                    "(ignoring because transition not in progress)",
                    (on_node? on_node : ""), (task? task : "unknown action"),
                    (via_node? via_node : "controller"), action->timeout);
     } else {
         /* fail the action */
 
         crm_err("Node %s did not send %s result (via %s) within %dms "
                 "(action timeout plus " PCMK_OPT_CLUSTER_DELAY ")",
                 (on_node? on_node : ""), (task? task : "unknown action"),
                 (via_node? via_node : "controller"),
                 (action->timeout
                  + controld_globals.transition_graph->network_delay));
         pcmk__log_graph_action(LOG_ERR, action);
 
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
 
         te_action_confirmed(action, controld_globals.transition_graph);
         abort_transition(INFINITY, pcmk__graph_restart, "Action lost", NULL);
 
         // Record timeout in the CIB if appropriate
         if ((action->type == pcmk__rsc_graph_action)
             && controld_action_is_recordable(task)) {
             controld_record_action_timeout(action);
         }
     }
 
     return FALSE;
 }
diff --git a/daemons/fenced/fenced_cib.c b/daemons/fenced/fenced_cib.c
index a089d97274..e09efac3bf 100644
--- a/daemons/fenced/fenced_cib.c
+++ b/daemons/fenced/fenced_cib.c
@@ -1,747 +1,747 @@
 /*
  * Copyright 2009-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
 */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <stdio.h>
 #include <libxml/tree.h>
 #include <libxml/xpath.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 
 #include <crm/cluster/internal.h>
 
 #include <crm/cib.h>
 #include <crm/cib/internal.h>
 
 #include <pacemaker-fenced.h>
 
 static xmlNode *local_cib = NULL;
 static cib_t *cib_api = NULL;
 static bool have_cib_devices = FALSE;
 
 /*!
  * \internal
  * \brief Check whether a node has a specific attribute name/value
  *
  * \param[in] node    Name of node to check
  * \param[in] name    Name of an attribute to look for
  * \param[in] value   The value the named attribute needs to be set to in order to be considered a match
  *
  * \return TRUE if the locally cached CIB has the specified node attribute
  */
 gboolean
 node_has_attr(const char *node, const char *name, const char *value)
 {
     GString *xpath = NULL;
     xmlNode *match;
 
     CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL)
               && (value != NULL), return FALSE);
 
     /* Search for the node's attributes in the CIB. While the schema allows
      * multiple sets of instance attributes, and allows instance attributes to
      * use id-ref to reference values elsewhere, that is intended for resources,
      * so we ignore that here.
      */
     xpath = g_string_sized_new(256);
     pcmk__g_strcat(xpath,
                    "//" XML_CIB_TAG_NODES "/" XML_CIB_TAG_NODE
                    "[@" PCMK_XA_UNAME "='", node, "']/" XML_TAG_ATTR_SETS
                    "/" XML_CIB_TAG_NVPAIR
                    "[@" PCMK_XA_NAME "='", name, "' "
                    "and @" PCMK_XA_VALUE "='", value, "']", NULL);
 
     match = get_xpath_object((const char *) xpath->str, local_cib, LOG_NEVER);
 
     g_string_free(xpath, TRUE);
     return (match != NULL);
 }
 
 static void
 add_topology_level(xmlNode *match)
 {
     char *desc = NULL;
     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
     CRM_CHECK(match != NULL, return);
 
     fenced_register_level(match, &desc, &result);
     fenced_send_level_notification(STONITH_OP_LEVEL_ADD, &result, desc);
     pcmk__reset_result(&result);
     free(desc);
 }
 
 static void
 topology_remove_helper(const char *node, int level)
 {
     char *desc = NULL;
     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
     xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
 
     crm_xml_add(data, F_STONITH_ORIGIN, __func__);
     crm_xml_add_int(data, PCMK_XA_INDEX, level);
     crm_xml_add(data, PCMK_XA_TARGET, node);
 
     fenced_unregister_level(data, &desc, &result);
     fenced_send_level_notification(STONITH_OP_LEVEL_DEL, &result, desc);
     pcmk__reset_result(&result);
     free_xml(data);
     free(desc);
 }
 
 static void
 remove_topology_level(xmlNode *match)
 {
     int index = 0;
     char *key = NULL;
 
     CRM_CHECK(match != NULL, return);
 
     key = stonith_level_key(match, fenced_target_by_unknown);
     crm_element_value_int(match, PCMK_XA_INDEX, &index);
     topology_remove_helper(key, index);
     free(key);
 }
 
 static void
 register_fencing_topology(xmlXPathObjectPtr xpathObj)
 {
     int max = numXpathResults(xpathObj), lpc = 0;
 
     for (lpc = 0; lpc < max; lpc++) {
         xmlNode *match = getXpathResult(xpathObj, lpc);
 
         remove_topology_level(match);
         add_topology_level(match);
     }
 }
 
 /* Fencing
 <diff crm_feature_set="3.0.6">
   <diff-removed>
     <fencing-topology>
       <fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
       <fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
       <fencing-level devices="disk,network" id="f-p2.1"/>
     </fencing-topology>
   </diff-removed>
   <diff-added>
     <fencing-topology>
       <fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
       <fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
       <fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
     </fencing-topology>
   </diff-added>
 </diff>
 */
 
 void
 fencing_topology_init(void)
 {
     xmlXPathObjectPtr xpathObj = NULL;
     const char *xpath = "//" XML_TAG_FENCING_LEVEL;
 
     crm_trace("Full topology refresh");
     free_topology_list();
     init_topology_list();
 
     /* Grab everything */
     xpathObj = xpath_search(local_cib, xpath);
     register_fencing_topology(xpathObj);
 
     freeXpathObject(xpathObj);
 }
 
 static void
 remove_cib_device(xmlXPathObjectPtr xpathObj)
 {
     int max = numXpathResults(xpathObj), lpc = 0;
 
     for (lpc = 0; lpc < max; lpc++) {
         const char *rsc_id = NULL;
         const char *standard = NULL;
         xmlNode *match = getXpathResult(xpathObj, lpc);
 
         CRM_LOG_ASSERT(match != NULL);
         if(match != NULL) {
             standard = crm_element_value(match, PCMK_XA_CLASS);
         }
 
         if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
             continue;
         }
 
         rsc_id = crm_element_value(match, PCMK_XA_ID);
 
         stonith_device_remove(rsc_id, true);
     }
 }
 
 #define XPATH_WATCHDOG_TIMEOUT "//" XML_CIB_TAG_NVPAIR         \
                                "[@" PCMK_XA_NAME "='"  \
                                     PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "']"
 
 static void
 update_stonith_watchdog_timeout_ms(xmlNode *cib)
 {
     long timeout_ms = 0;
     xmlNode *stonith_watchdog_xml = NULL;
     const char *value = NULL;
 
     stonith_watchdog_xml = get_xpath_object(XPATH_WATCHDOG_TIMEOUT, cib,
                                             LOG_NEVER);
     if (stonith_watchdog_xml) {
         value = crm_element_value(stonith_watchdog_xml, PCMK_XA_VALUE);
     }
     if (value) {
         timeout_ms = crm_get_msec(value);
     }
 
     if (timeout_ms < 0) {
         timeout_ms = pcmk__auto_stonith_watchdog_timeout();
     }
 
     stonith_watchdog_timeout_ms = timeout_ms;
 }
 
 /*!
  * \internal
  * \brief Update all STONITH device definitions based on current CIB
  */
 static void
 cib_devices_update(void)
 {
     GHashTableIter iter;
     stonith_device_t *device = NULL;
 
     crm_info("Updating devices to version %s.%s.%s",
              crm_element_value(local_cib, PCMK_XA_ADMIN_EPOCH),
              crm_element_value(local_cib, PCMK_XA_EPOCH),
              crm_element_value(local_cib, PCMK_XA_NUM_UPDATES));
 
     g_hash_table_iter_init(&iter, device_list);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
         if (device->cib_registered) {
             device->dirty = TRUE;
         }
     }
 
     /* have list repopulated if cib has a watchdog-fencing-resource
        TODO: keep a cached list for queries happening while we are refreshing
      */
     g_list_free_full(stonith_watchdog_targets, free);
     stonith_watchdog_targets = NULL;
 
     fenced_scheduler_run(local_cib);
 
     g_hash_table_iter_init(&iter, device_list);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
         if (device->dirty) {
             g_hash_table_iter_remove(&iter);
         }
     }
 }
 
 static void
 update_cib_stonith_devices_v1(const char *event, xmlNode * msg)
 {
     const char *reason = "none";
     gboolean needs_update = FALSE;
     xmlXPathObjectPtr xpath_obj = NULL;
 
     /* process new constraints */
     xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION);
     if (numXpathResults(xpath_obj) > 0) {
         int max = numXpathResults(xpath_obj), lpc = 0;
 
         /* Safest and simplest to always recompute */
         needs_update = TRUE;
         reason = "new location constraint";
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *match = getXpathResult(xpath_obj, lpc);
 
             crm_log_xml_trace(match, "new constraint");
         }
     }
     freeXpathObject(xpath_obj);
 
     /* process deletions */
     xpath_obj = xpath_search(msg,
                              "//" F_CIB_UPDATE_RESULT
                              "//" PCMK__XE_DIFF_REMOVED
                              "//" XML_CIB_TAG_RESOURCE);
     if (numXpathResults(xpath_obj) > 0) {
         remove_cib_device(xpath_obj);
     }
     freeXpathObject(xpath_obj);
 
     /* process additions */
     xpath_obj = xpath_search(msg,
                              "//" F_CIB_UPDATE_RESULT
                              "//" PCMK__XE_DIFF_ADDED
                              "//" XML_CIB_TAG_RESOURCE);
     if (numXpathResults(xpath_obj) > 0) {
         int max = numXpathResults(xpath_obj), lpc = 0;
 
         for (lpc = 0; lpc < max; lpc++) {
             const char *rsc_id = NULL;
             const char *standard = NULL;
             xmlNode *match = getXpathResult(xpath_obj, lpc);
 
             rsc_id = crm_element_value(match, PCMK_XA_ID);
             standard = crm_element_value(match, PCMK_XA_CLASS);
 
             if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
                 continue;
             }
 
             crm_trace("Fencing resource %s was added or modified", rsc_id);
             reason = "new resource";
             needs_update = TRUE;
         }
     }
     freeXpathObject(xpath_obj);
 
     if(needs_update) {
         crm_info("Updating device list from CIB: %s", reason);
         cib_devices_update();
     }
 }
 
 static void
 update_cib_stonith_devices_v2(const char *event, xmlNode * msg)
 {
     xmlNode *change = NULL;
     char *reason = NULL;
     bool needs_update = FALSE;
     xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     for (change = pcmk__xml_first_child(patchset); change != NULL;
          change = pcmk__xml_next(change)) {
         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
         const char *shortpath = NULL;
 
         if (pcmk__str_eq(op, "move", pcmk__str_null_matches)
             || (strstr(xpath, "/" PCMK_XE_STATUS) != NULL)) {
             continue;
         } else if (pcmk__str_eq(op, "delete", pcmk__str_casei) && strstr(xpath, "/"XML_CIB_TAG_RESOURCE)) {
             const char *rsc_id = NULL;
             char *search = NULL;
             char *mutable = NULL;
 
             if (strstr(xpath, XML_TAG_ATTR_SETS) ||
                 strstr(xpath, XML_TAG_META_SETS)) {
                 needs_update = TRUE;
                 pcmk__str_update(&reason,
                                  "(meta) attribute deleted from resource");
                 break;
             }
             pcmk__str_update(&mutable, xpath);
             rsc_id = strstr(mutable, "primitive[@" PCMK_XA_ID "=\'");
             if (rsc_id != NULL) {
                 rsc_id += strlen("primitive[@" PCMK_XA_ID "=\'");
                 search = strchr(rsc_id, '\'');
             }
             if (search != NULL) {
                 *search = 0;
                 stonith_device_remove(rsc_id, true);
                 /* watchdog_device_update called afterwards
                    to fall back to implicit definition if needed */
             } else {
                 crm_warn("Ignoring malformed CIB update (resource deletion)");
             }
             free(mutable);
 
-        } else if (strstr(xpath, "/"XML_CIB_TAG_RESOURCES) ||
+        } else if (strstr(xpath, "/" PCMK_XE_RESOURCES) ||
                    strstr(xpath, "/"XML_CIB_TAG_CONSTRAINTS) ||
                    strstr(xpath, "/"XML_CIB_TAG_RSCCONFIG)) {
             shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath);
             reason = crm_strdup_printf("%s %s", op, shortpath+1);
             needs_update = TRUE;
             break;
         }
     }
 
     if(needs_update) {
         crm_info("Updating device list from CIB: %s", reason);
         cib_devices_update();
     } else {
         crm_trace("No updates for device list found in CIB");
     }
     free(reason);
 }
 
 static void
 update_cib_stonith_devices(const char *event, xmlNode * msg)
 {
     int format = 1;
     xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     CRM_ASSERT(patchset);
     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
     switch(format) {
         case 1:
             update_cib_stonith_devices_v1(event, msg);
             break;
         case 2:
             update_cib_stonith_devices_v2(event, msg);
             break;
         default:
             crm_warn("Unknown patch format: %d", format);
     }
 }
 
 static void
 watchdog_device_update(void)
 {
     if (stonith_watchdog_timeout_ms > 0) {
         if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) &&
             !stonith_watchdog_targets) {
             /* getting here watchdog-fencing enabled, no device there yet
                and reason isn't stonith_watchdog_targets preventing that
              */
             int rc;
             xmlNode *xml;
 
             xml = create_device_registration_xml(
                     STONITH_WATCHDOG_ID,
                     st_namespace_internal,
                     STONITH_WATCHDOG_AGENT,
                     NULL, /* stonith_device_register will add our
                              own name as PCMK_STONITH_HOST_LIST param
                              so we can skip that here
                            */
                     NULL);
             rc = stonith_device_register(xml, TRUE);
             free_xml(xml);
             if (rc != pcmk_ok) {
                 rc = pcmk_legacy2rc(rc);
                 exit_code = CRM_EX_FATAL;
                 crm_crit("Cannot register watchdog pseudo fence agent: %s",
                          pcmk_rc_str(rc));
                 stonith_shutdown(0);
             }
         }
 
     } else if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) != NULL) {
         /* be silent if no device - todo parameter to stonith_device_remove */
         stonith_device_remove(STONITH_WATCHDOG_ID, true);
     }
 }
 
 /*!
  * \internal
  * \brief Query the full CIB
  *
  * \return Standard Pacemaker return code
  */
 static int
 fenced_query_cib(void)
 {
     int rc = pcmk_ok;
 
     crm_trace("Re-requesting full CIB");
     rc = cib_api->cmds->query(cib_api, NULL, &local_cib,
                               cib_scope_local|cib_sync_call);
     rc = pcmk_legacy2rc(rc);
     if (rc == pcmk_rc_ok) {
         CRM_ASSERT(local_cib != NULL);
     } else {
         crm_err("Couldn't retrieve the CIB: %s " CRM_XS " rc=%d",
                 pcmk_rc_str(rc), rc);
     }
     return rc;
 }
 
 static void
 remove_fencing_topology(xmlXPathObjectPtr xpathObj)
 {
     int max = numXpathResults(xpathObj), lpc = 0;
 
     for (lpc = 0; lpc < max; lpc++) {
         xmlNode *match = getXpathResult(xpathObj, lpc);
 
         CRM_LOG_ASSERT(match != NULL);
         if (match && crm_element_value(match, XML_DIFF_MARKER)) {
             /* Deletion */
             int index = 0;
             char *target = stonith_level_key(match, fenced_target_by_unknown);
 
             crm_element_value_int(match, PCMK_XA_INDEX, &index);
             if (target == NULL) {
                 crm_err("Invalid fencing target in element %s", ID(match));
 
             } else if (index <= 0) {
                 crm_err("Invalid level for %s in element %s", target, ID(match));
 
             } else {
                 topology_remove_helper(target, index);
             }
             /* } else { Deal with modifications during the 'addition' stage */
         }
     }
 }
 
 static void
 update_fencing_topology(const char *event, xmlNode * msg)
 {
     int format = 1;
     const char *xpath;
     xmlXPathObjectPtr xpathObj = NULL;
     xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     CRM_ASSERT(patchset);
     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 
     if(format == 1) {
         /* Process deletions (only) */
         xpath = "//" F_CIB_UPDATE_RESULT
                 "//" PCMK__XE_DIFF_REMOVED
                 "//" XML_TAG_FENCING_LEVEL;
         xpathObj = xpath_search(msg, xpath);
 
         remove_fencing_topology(xpathObj);
         freeXpathObject(xpathObj);
 
         /* Process additions and changes */
         xpath = "//" F_CIB_UPDATE_RESULT
                 "//" PCMK__XE_DIFF_ADDED
                 "//" XML_TAG_FENCING_LEVEL;
         xpathObj = xpath_search(msg, xpath);
 
         register_fencing_topology(xpathObj);
         freeXpathObject(xpathObj);
 
     } else if(format == 2) {
         xmlNode *change = NULL;
         int add[] = { 0, 0, 0 };
         int del[] = { 0, 0, 0 };
 
         xml_patch_versions(patchset, add, del);
 
         for (change = pcmk__xml_first_child(patchset); change != NULL;
              change = pcmk__xml_next(change)) {
             const char *op = crm_element_value(change, PCMK_XA_OPERATION);
             const char *xpath = crm_element_value(change, PCMK_XA_PATH);
 
             if(op == NULL) {
                 continue;
 
             } else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) {
                 /* Change to a specific entry */
 
                 crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath);
                 if(strcmp(op, "move") == 0) {
                     continue;
 
                 } else if(strcmp(op, "create") == 0) {
                     add_topology_level(change->children);
 
                 } else if(strcmp(op, "modify") == 0) {
                     xmlNode *match = first_named_child(change, XML_DIFF_RESULT);
 
                     if(match) {
                         remove_topology_level(match->children);
                         add_topology_level(match->children);
                     }
 
                 } else if(strcmp(op, "delete") == 0) {
                     /* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */
                     crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s",
                              op, add[0], add[1], add[2], xpath);
                     fencing_topology_init();
                     return;
                 }
 
             } else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) {
                 /* Change to the topology in general */
                 crm_info("Re-initializing fencing topology after top-level %s operation  %d.%d.%d for %s",
                          op, add[0], add[1], add[2], xpath);
                 fencing_topology_init();
                 return;
 
             } else if (strstr(xpath, "/" PCMK_XE_CONFIGURATION)) {
                 /* Changes to the whole config section, possibly including the topology as a whild */
                 if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) {
                     crm_trace("Nothing for us in %s operation %d.%d.%d for %s.",
                               op, add[0], add[1], add[2], xpath);
 
                 } else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) {
                     crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.",
                              op, add[0], add[1], add[2], xpath);
                     fencing_topology_init();
                     return;
                 }
 
             } else {
                 crm_trace("Nothing for us in %s operation %d.%d.%d for %s",
                           op, add[0], add[1], add[2], xpath);
             }
         }
 
     } else {
         crm_warn("Unknown patch format: %d", format);
     }
 }
 
 static void
 update_cib_cache_cb(const char *event, xmlNode * msg)
 {
     long timeout_ms_saved = stonith_watchdog_timeout_ms;
     bool need_full_refresh = false;
 
     if(!have_cib_devices) {
         crm_trace("Skipping updates until we get a full dump");
         return;
 
     } else if(msg == NULL) {
         crm_trace("Missing %s update", event);
         return;
     }
 
     /* Maintain a local copy of the CIB so that we have full access
      * to device definitions, location constraints, and node attributes
      */
     if (local_cib != NULL) {
         int rc = pcmk_ok;
         xmlNode *patchset = NULL;
 
         crm_element_value_int(msg, F_CIB_RC, &rc);
         if (rc != pcmk_ok) {
             return;
         }
 
         patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
         rc = xml_apply_patchset(local_cib, patchset, TRUE);
         switch (rc) {
             case pcmk_ok:
             case -pcmk_err_old_data:
                 break;
             case -pcmk_err_diff_resync:
             case -pcmk_err_diff_failed:
                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(local_cib);
                 local_cib = NULL;
                 break;
             default:
                 crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(local_cib);
                 local_cib = NULL;
         }
     }
 
     if (local_cib == NULL) {
         if (fenced_query_cib() != pcmk_rc_ok) {
             return;
         }
         need_full_refresh = true;
     }
 
     pcmk__refresh_node_caches_from_cib(local_cib);
     update_stonith_watchdog_timeout_ms(local_cib);
 
     if (timeout_ms_saved != stonith_watchdog_timeout_ms) {
         need_full_refresh = true;
     }
 
     if (need_full_refresh) {
         fencing_topology_init();
         cib_devices_update();
     } else {
         // Partial refresh
         update_fencing_topology(event, msg);
         update_cib_stonith_devices(event, msg);
     }
 
     watchdog_device_update();
 }
 
 static void
 init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     crm_info("Updating device list from CIB");
     have_cib_devices = TRUE;
     local_cib = copy_xml(output);
 
     pcmk__refresh_node_caches_from_cib(local_cib);
     update_stonith_watchdog_timeout_ms(local_cib);
 
     fencing_topology_init();
     cib_devices_update();
     watchdog_device_update();
 }
 
 static void
 cib_connection_destroy(gpointer user_data)
 {
     if (stonith_shutdown_flag) {
         crm_info("Connection to the CIB manager closed");
         return;
     } else {
         crm_crit("Lost connection to the CIB manager, shutting down");
     }
     if (cib_api) {
         cib_api->cmds->signoff(cib_api);
     }
     stonith_shutdown(0);
 }
 
 /*!
  * \internal
  * \brief Disconnect from CIB manager
  */
 void
 fenced_cib_cleanup(void)
 {
     if (cib_api != NULL) {
         cib_api->cmds->del_notify_callback(cib_api, T_CIB_DIFF_NOTIFY,
                                            update_cib_cache_cb);
         cib__clean_up_connection(&cib_api);
     }
     free_xml(local_cib);
     local_cib = NULL;
 }
 
 void
 setup_cib(void)
 {
     int rc, retries = 0;
 
     cib_api = cib_new();
     if (cib_api == NULL) {
         crm_err("No connection to the CIB manager");
         return;
     }
 
     do {
         sleep(retries);
         rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command);
     } while (rc == -ENOTCONN && ++retries < 5);
 
     if (rc != pcmk_ok) {
         crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc);
 
     } else if (pcmk_ok !=
                cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) {
         crm_err("Could not set CIB notification callback");
 
     } else {
         rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local);
         cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb",
                                          init_cib_cache_cb);
         cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
         crm_info("Watching for fencing topology changes");
     }
 }
diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h
index 6322c8de9c..b27323e252 100644
--- a/include/crm/msg_xml.h
+++ b/include/crm/msg_xml.h
@@ -1,293 +1,294 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_MSG_XML__H
 #  define PCMK__CRM_MSG_XML__H
 
 #  include <crm/common/xml.h>
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/msg_xml_compat.h>
 #endif
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /* This file defines constants for various XML syntax (mainly element and
  * attribute names).
  *
  * For consistency, new constants should start with "PCMK_", followed by:
  * * "XE" for XML element names
  * * "XA" for XML attribute names
  * * "OPT" for cluster option (property) names
  * * "META" for meta-attribute names
  * * "VALUE" for enumerated values for various options
  *
  * Old names that don't follow this policy should eventually be deprecated and
  * replaced with names that do.
  *
  * Symbols should be public if the user may specify them somewhere (especially
  * the CIB) or if they're part of a well-defined structure that a user may need
  * to parse. They should be internal if they're used only internally to
  * Pacemaker (such as daemon IPC/CPG message XML).
  *
  * Constants belong in the following locations:
  * * Public "XE" and "XA": msg_xml.h
  * * Internal "XE" and "XA": crm_internal.h
  * * Public "OPT", "META", and "VALUE": options.h
  * * Internal "OPT", "META", and "VALUE": options_internal.h
  *
  * For meta-attributes that can be specified as either XML attributes or nvpair
  * names, use "META" unless using both "XA" and "META" constants adds clarity.
  * An example is operation attributes, which can be specified either as
  * attributes of the PCMK_XE_OP element or as nvpairs in a meta-attribute set
  * beneath the PCMK_XE_OP element.
  */
 
 /*
  * XML elements
  */
 
 #define PCMK_XE_CIB                         "cib"
 #define PCMK_XE_CONFIGURATION               "configuration"
 #define PCMK_XE_CONTENT                     "content"
 #define PCMK_XE_DATE_EXPRESSION             "date_expression"
 #define PCMK_XE_LONGDESC                    "longdesc"
 #define PCMK_XE_OP                          "op"
 #define PCMK_XE_OPERATION                   "operation"
 #define PCMK_XE_OP_EXPRESSION               "op_expression"
 #define PCMK_XE_OPTION                      "option"
+#define PCMK_XE_RESOURCES                   "resources"
 #define PCMK_XE_ROLE                        "role"
 #define PCMK_XE_PARAMETER                   "parameter"
 #define PCMK_XE_PARAMETERS                  "parameters"
 #define PCMK_XE_RESOURCE_AGENT              "resource-agent"
 #define PCMK_XE_RSC_EXPRESSION              "rsc_expression"
 #define PCMK_XE_SHORTDESC                   "shortdesc"
 #define PCMK_XE_STATUS                      "status"
 #define PCMK_XE_VERSION                     "version"
 
 
 /*
  * XML attributes
  */
 
 #define PCMK_XA_ADMIN_EPOCH                 "admin_epoch"
 #define PCMK_XA_ATTRIBUTE                   "attribute"
 #define PCMK_XA_BOOLEAN_OP                  "boolean-op"
 #define PCMK_XA_CIB_LAST_WRITTEN            "cib-last-written"
 #define PCMK_XA_CLASS                       "class"
 #define PCMK_XA_CRM_DEBUG_ORIGIN            "crm-debug-origin"
 #define PCMK_XA_CRM_FEATURE_SET             "crm_feature_set"
 #define PCMK_XA_CRM_TIMESTAMP               "crm-timestamp"
 #define PCMK_XA_DC_UUID                     "dc-uuid"
 #define PCMK_XA_DEFAULT                     "default"
 #define PCMK_XA_DESCRIPTION                 "description"
 #define PCMK_XA_DEVICES                     "devices"
 #define PCMK_XA_EPOCH                       "epoch"
 #define PCMK_XA_EXEC_TIME                   "exec-time"
 #define PCMK_XA_EXIT_REASON                 "exit-reason"
 #define PCMK_XA_FIRST                       "first"
 #define PCMK_XA_FIRST_ACTION                "first-action"
 #define PCMK_XA_FORMAT                      "format"
 #define PCMK_XA_HAVE_QUORUM                 "have-quorum"
 #define PCMK_XA_ID                          "id"
 #define PCMK_XA_ID_REF                      "id-ref"
 #define PCMK_XA_INDEX                       "index"
 #define PCMK_XA_INFLUENCE                   "influence"
 #define PCMK_XA_KIND                        "kind"
 #define PCMK_XA_LANG                        "lang"
 #define PCMK_XA_LAST_RC_CHANGE              "last-rc-change"
 #define PCMK_XA_LOSS_POLICY                 "loss-policy"
 #define PCMK_XA_NAME                        "name"
 #define PCMK_XA_NO_QUORUM_PANIC             "no-quorum-panic"
 #define PCMK_XA_NODE_ATTRIBUTE              "node-attribute"
 #define PCMK_XA_NUM_UPDATES                 "num_updates"
 #define PCMK_XA_OBJECT_TYPE                 "object-type"
 #define PCMK_XA_OP                          "op"
 #define PCMK_XA_OPERATION                   "operation"
 #define PCMK_XA_ORIGIN                      "origin"
 #define PCMK_XA_PATH                        "path"
 #define PCMK_XA_PROVIDER                    "provider"
 #define PCMK_XA_QUEUE_TIME                  "queue-time"
 #define PCMK_XA_REASON                      "reason"
 #define PCMK_XA_REFERENCE                   "reference"
 #define PCMK_XA_REQUEST                     "request"
 #define PCMK_XA_RESOURCE_DISCOVERY          "resource-discovery"
 #define PCMK_XA_RESULT                      "result"
 #define PCMK_XA_ROLE                        "role"
 #define PCMK_XA_RSC                         "rsc"
 #define PCMK_XA_RSC_PATTERN                 "rsc-pattern"
 #define PCMK_XA_RSC_ROLE                    "rsc-role"
 #define PCMK_XA_SCORE                       "score"
 #define PCMK_XA_SCORE_ATTRIBUTE             "score-attribute"
 #define PCMK_XA_SYMMETRICAL                 "symmetrical"
 #define PCMK_XA_TARGET                      "target"
 #define PCMK_XA_TARGET_ATTRIBUTE            "target-attribute"
 #define PCMK_XA_TARGET_PATTERN              "target-pattern"
 #define PCMK_XA_TARGET_VALUE                "target-value"
 #define PCMK_XA_TICKET                      "ticket"
 #define PCMK_XA_THEN                        "then"
 #define PCMK_XA_THEN_ACTION                 "then-action"
 #define PCMK_XA_TYPE                        "type"
 #define PCMK_XA_UNAME                       "uname"
 #define PCMK_XA_UPDATE_CLIENT               "update-client"
 #define PCMK_XA_UPDATE_ORIGIN               "update-origin"
 #define PCMK_XA_UPDATE_USER                 "update-user"
 #define PCMK_XA_VALIDATE_WITH               "validate-with"
 #define PCMK_XA_VALUE                       "value"
 #define PCMK_XA_VALUE_SOURCE                "value-source"
 #define PCMK_XA_VERSION                     "version"
 #define PCMK_XA_WITH_RSC                    "with-rsc"
 #define PCMK_XA_WITH_RSC_ROLE               "with-rsc-role"
 #define PCMK_XA_XPATH                       "xpath"
 
 
 /*
  * Older constants that don't follow current naming
  */
 
 #  ifndef T_CRM
 #    define T_CRM     "crmd"
 #  endif
 
 #  ifndef T_ATTRD
 #    define T_ATTRD     "attrd"
 #  endif
 
 #  define CIB_OPTIONS_FIRST "cib-bootstrap-options"
 
 #  define F_CRM_DATA			"crm_xml"
 
 /*---- Common tags/attrs */
 #  define XML_DIFF_MARKER		"__crm_diff_marker__"
 #  define XML_TAG_FAILED		"failed"
 
 #  define XML_TAG_OPTIONS		"options"
 
 /*---- top level tags/attrs */
 #  define XML_PING_ATTR_PACEMAKERDSTATE_INIT "init"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_STARTINGDAEMONS "starting_daemons"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_WAITPING "wait_for_ping"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_RUNNING "running"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_SHUTTINGDOWN "shutting_down"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_SHUTDOWNCOMPLETE "shutdown_complete"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_REMOTE "remote"
 
 #  define XML_FAIL_TAG_CIB		"failed_update"
 
 /*---- CIB specific tags/attrs */
 #  define XML_CIB_TAG_SECTION_ALL	"all"
-#  define XML_CIB_TAG_RESOURCES		"resources"
+#  define XML_CIB_TAG_RESOURCES		PCMK_XE_RESOURCES
 #  define XML_CIB_TAG_NODES         	"nodes"
 #  define XML_CIB_TAG_CONSTRAINTS   	"constraints"
 #  define XML_CIB_TAG_CRMCONFIG   	"crm_config"
 #  define XML_CIB_TAG_OPCONFIG		"op_defaults"
 #  define XML_CIB_TAG_RSCCONFIG   	"rsc_defaults"
 #  define XML_CIB_TAG_ACLS   		"acls"
 #  define XML_CIB_TAG_ALERTS    	"alerts"
 #  define XML_CIB_TAG_ALERT   		"alert"
 #  define XML_CIB_TAG_ALERT_RECIPIENT	"recipient"
 #  define XML_CIB_TAG_ALERT_SELECT      "select"
 #  define XML_CIB_TAG_ALERT_ATTRIBUTES  "select_attributes"
 #  define XML_CIB_TAG_ALERT_FENCING     "select_fencing"
 #  define XML_CIB_TAG_ALERT_NODES       "select_nodes"
 #  define XML_CIB_TAG_ALERT_RESOURCES   "select_resources"
 #  define XML_CIB_TAG_ALERT_ATTR        "attribute"
 
 #  define XML_CIB_TAG_STATE         	"node_state"
 #  define XML_CIB_TAG_NODE          	"node"
 #  define XML_CIB_TAG_NVPAIR        	"nvpair"
 
 #  define XML_CIB_TAG_PROPSET	   	"cluster_property_set"
 #  define XML_TAG_ATTR_SETS	   	"instance_attributes"
 #  define XML_TAG_META_SETS	   	"meta_attributes"
 #  define XML_TAG_ATTRS			"attributes"
 #  define XML_TAG_PARAM			"param"
 #  define XML_TAG_UTILIZATION		"utilization"
 
 #  define XML_TAG_RESOURCE_REF		"resource_ref"
 #  define XML_CIB_TAG_RESOURCE	  	"primitive"
 #  define XML_CIB_TAG_GROUP	  	"group"
 #  define XML_CIB_TAG_INCARNATION	"clone"
 #  define XML_CIB_TAG_CONTAINER		"bundle"
 
 #  define XML_CIB_TAG_RSC_TEMPLATE	"template"
 
 #  define XML_CIB_TAG_LRM		"lrm"
 #  define XML_LRM_TAG_RESOURCES     	"lrm_resources"
 #  define XML_LRM_TAG_RESOURCE     	"lrm_resource"
 #  define XML_LRM_TAG_RSC_OP		"lrm_rsc_op"
 
 #  define XML_NODE_IS_REMOTE    	"remote_node"
 #  define XML_NODE_IS_FENCED		"node_fenced"
 #  define XML_NODE_IS_MAINTENANCE   "node_in_maintenance"
 
 #  define XML_CIB_ATTR_SHUTDOWN       	"shutdown"
 
 #  define XML_TAG_GRAPH			"transition_graph"
 #  define XML_GRAPH_TAG_RSC_OP		"rsc_op"
 #  define XML_GRAPH_TAG_PSEUDO_EVENT	"pseudo_event"
 #  define XML_GRAPH_TAG_CRM_EVENT	"crm_event"
 #  define XML_GRAPH_TAG_DOWNED            "downed"
 #  define XML_GRAPH_TAG_MAINTENANCE       "maintenance"
 
 #  define XML_TAG_RULE			"rule"
 
 #  define XML_TAG_EXPRESSION		"expression"
 
 #  define XML_CONS_TAG_RSC_DEPEND	"rsc_colocation"
 #  define XML_CONS_TAG_RSC_ORDER	"rsc_order"
 #  define XML_CONS_TAG_RSC_LOCATION	"rsc_location"
 #  define XML_CONS_TAG_RSC_TICKET	"rsc_ticket"
 #  define XML_CONS_TAG_RSC_SET		"resource_set"
 
 #  define XML_NODE_ATTR_RSC_DISCOVERY   "resource-discovery-enabled"
 
 #  define XML_CIB_TAG_GENERATION_TUPPLE	"generation_tuple"
 
 #  define XML_TAG_TRANSIENT_NODEATTRS	"transient_attributes"
 
 #  define XML_ACL_TAG_USER		"acl_target"
 #  define XML_ACL_TAG_USERv1		"acl_user"
 #  define XML_ACL_TAG_GROUP		"acl_group"
 #  define XML_ACL_TAG_ROLE		"acl_role"
 #  define XML_ACL_TAG_PERMISSION	"acl_permission"
 #  define XML_ACL_TAG_ROLE_REFv1	"role_ref"
 #  define XML_ACL_TAG_READ		"read"
 #  define XML_ACL_TAG_WRITE		"write"
 #  define XML_ACL_TAG_DENY		"deny"
 
 #  define XML_CIB_TAG_TICKETS		"tickets"
 #  define XML_CIB_TAG_TICKET_STATE	"ticket_state"
 
 #  define XML_CIB_TAG_TAGS   		"tags"
 #  define XML_CIB_TAG_TAG   		"tag"
 #  define XML_CIB_TAG_OBJ_REF 		"obj_ref"
 
 #  define XML_TAG_FENCING_TOPOLOGY      "fencing-topology"
 #  define XML_TAG_FENCING_LEVEL         "fencing-level"
 
 #  define XML_TAG_DIFF                  "diff"
 #  define XML_DIFF_VERSION              PCMK_XE_VERSION
 #  define XML_DIFF_VSOURCE              "source"
 #  define XML_DIFF_VTARGET              "target"
 #  define XML_DIFF_CHANGE               "change"
 #  define XML_DIFF_LIST                 "change-list"
 #  define XML_DIFF_ATTR                 "change-attr"
 #  define XML_DIFF_RESULT               "change-result"
 #  define XML_DIFF_POSITION             "position"
 
 #  define ID(x) crm_element_value(x, PCMK_XA_ID)
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c
index 662a25d7c8..b8d9c4c2e4 100644
--- a/lib/cib/cib_attrs.c
+++ b/lib/cib/cib_attrs.c
@@ -1,743 +1,743 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 
 #include <crm/crm.h>
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/output_internal.h>
 #include <crm/cib/internal.h>
 
 static pcmk__output_t *
 new_output_object(const char *ty)
 {
     int rc = pcmk_rc_ok;
     pcmk__output_t *out = NULL;
     const char* argv[] = { "", NULL };
     pcmk__supported_format_t formats[] = {
         PCMK__SUPPORTED_FORMAT_LOG,
         PCMK__SUPPORTED_FORMAT_TEXT,
         { NULL, NULL, NULL }
     };
 
     pcmk__register_formats(NULL, formats);
     rc = pcmk__output_new(&out, ty, NULL, (char**)argv);
     if ((rc != pcmk_rc_ok) || (out == NULL)) {
         crm_err("Can't out due to internal error: %s", pcmk_rc_str(rc));
         return NULL;
     }
 
     return out;
 }
 
 static int
 find_attr(cib_t *cib, const char *section, const char *node_uuid,
           const char *attr_set_type, const char *set_name, const char *attr_id,
           const char *attr_name, const char *user_name, xmlNode **result)
 {
     int rc = pcmk_rc_ok;
 
     const char *xpath_base = NULL;
     GString *xpath = NULL;
     xmlNode *xml_search = NULL;
     const char *set_type = NULL;
     const char *node_type = NULL;
 
     if (attr_set_type) {
         set_type = attr_set_type;
     } else {
         set_type = XML_TAG_ATTR_SETS;
     }
 
     if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
         node_uuid = NULL;
         set_type = XML_CIB_TAG_PROPSET;
 
     } else if (pcmk__strcase_any_of(section, XML_CIB_TAG_OPCONFIG, XML_CIB_TAG_RSCCONFIG,
                                     NULL)) {
         node_uuid = NULL;
         set_type = XML_TAG_META_SETS;
 
     } else if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
         node_uuid = NULL;
         section = PCMK_XE_STATUS;
         node_type = XML_CIB_TAG_TICKETS;
 
     } else if (node_uuid == NULL) {
         return EINVAL;
     }
 
     xpath_base = pcmk_cib_xpath_for(section);
     if (xpath_base == NULL) {
         crm_warn("%s CIB section not known", section);
         return ENOMSG;
     }
 
     xpath = g_string_sized_new(1024);
     g_string_append(xpath, xpath_base);
 
     if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
         pcmk__g_strcat(xpath, "//", node_type, NULL);
 
     } else if (node_uuid) {
         const char *node_type = XML_CIB_TAG_NODE;
 
         if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
             node_type = XML_CIB_TAG_STATE;
             set_type = XML_TAG_TRANSIENT_NODEATTRS;
         }
         pcmk__g_strcat(xpath,
                        "//", node_type, "[@" PCMK_XA_ID "='", node_uuid, "']",
                        NULL);
     }
 
     pcmk__g_strcat(xpath, "//", set_type, NULL);
     if (set_name) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", set_name, "']", NULL);
     }
 
     g_string_append(xpath, "//nvpair");
 
     if (attr_id && attr_name) {
         pcmk__g_strcat(xpath,
                        "[@" PCMK_XA_ID "='", attr_id, "' "
                        "and @" PCMK_XA_NAME "='", attr_name, "']", NULL);
 
     } else if (attr_id) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", attr_id, "']", NULL);
 
     } else if (attr_name) {
         pcmk__g_strcat(xpath, "[@" PCMK_XA_NAME "='", attr_name, "']", NULL);
     }
 
     rc = cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, NULL,
                          (const char *) xpath->str, NULL, &xml_search,
                          cib_sync_call|cib_scope_local|cib_xpath, user_name);
     if (rc < 0) {
         rc = pcmk_legacy2rc(rc);
         crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s, xpath=%s): %s",
                   attr_name, section, pcmk__s(node_uuid, "<null>"),
                   pcmk__s(set_name, "<null>"), (const char *) xpath->str,
                   pcmk_rc_str(rc));
     } else {
         rc = pcmk_rc_ok;
         crm_log_xml_debug(xml_search, "Match");
     }
 
     g_string_free(xpath, TRUE);
     *result = xml_search;
     return rc;
 }
 
 static int
 handle_multiples(pcmk__output_t *out, xmlNode *search, const char *attr_name)
 {
     if ((search != NULL) && (search->children != NULL)) {
         xmlNode *child = NULL;
 
         out->info(out, "Multiple attributes match name=%s", attr_name);
         for (child = pcmk__xml_first_child(search); child != NULL;
              child = pcmk__xml_next(child)) {
             out->info(out, "  Value: %s \t(id=%s)",
                       crm_element_value(child, PCMK_XA_VALUE), ID(child));
         }
         return ENOTUNIQ;
 
     } else {
         return pcmk_rc_ok;
     }
 }
 
 int
 cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section,
                       const char *node_uuid, const char *set_type, const char *set_name,
                       const char *attr_id, const char *attr_name, const char *attr_value,
                       const char *user_name, const char *node_type)
 {
     const char *tag = NULL;
     int rc = pcmk_rc_ok;
     xmlNode *xml_top = NULL;
     xmlNode *xml_obj = NULL;
     xmlNode *xml_search = NULL;
 
     char *local_attr_id = NULL;
     char *local_set_name = NULL;
 
     CRM_CHECK((out != NULL) && (cib != NULL) && (section != NULL)
               && ((attr_id != NULL) || (attr_name != NULL))
               && (attr_value != NULL), return EINVAL);
 
     rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id,
                    attr_name, user_name, &xml_search);
 
     if (rc == pcmk_rc_ok) {
         if (handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) {
             free_xml(xml_search);
             return ENOTUNIQ;
         } else {
             pcmk__str_update(&local_attr_id,
                              crm_element_value(xml_search, PCMK_XA_ID));
             attr_id = local_attr_id;
             free_xml(xml_search);
             goto do_modify;
         }
 
     } else if (rc != ENXIO) {
         free_xml(xml_search);
         return rc;
 
         /* } else if(attr_id == NULL) { */
         /*     return EINVAL; */
 
     } else {
         free_xml(xml_search);
         crm_trace("%s does not exist, create it", attr_name);
         if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
             node_uuid = NULL;
             section = PCMK_XE_STATUS;
             node_type = XML_CIB_TAG_TICKETS;
 
             xml_top = create_xml_node(xml_obj, PCMK_XE_STATUS);
             xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
 
         } else if (pcmk__str_eq(section, XML_CIB_TAG_NODES, pcmk__str_casei)) {
 
             if (node_uuid == NULL) {
                 return EINVAL;
             }
 
             if (pcmk__str_eq(node_type, "remote", pcmk__str_casei)) {
                 xml_top = create_xml_node(xml_obj, XML_CIB_TAG_NODES);
                 xml_obj = create_xml_node(xml_top, XML_CIB_TAG_NODE);
                 crm_xml_add(xml_obj, PCMK_XA_TYPE, "remote");
                 crm_xml_add(xml_obj, PCMK_XA_ID, node_uuid);
                 crm_xml_add(xml_obj, PCMK_XA_UNAME, node_uuid);
             } else {
                 tag = XML_CIB_TAG_NODE;
             }
 
         } else if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
             tag = XML_TAG_TRANSIENT_NODEATTRS;
             if (node_uuid == NULL) {
                 return EINVAL;
             }
 
             xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATE);
             crm_xml_add(xml_top, PCMK_XA_ID, node_uuid);
             xml_obj = xml_top;
 
         } else {
             tag = section;
             node_uuid = NULL;
         }
 
         if (set_name == NULL) {
             if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
                 local_set_name = strdup(CIB_OPTIONS_FIRST);
 
             } else if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
                 local_set_name = crm_strdup_printf("%s-%s", section,
                                                    XML_CIB_TAG_TICKETS);
 
             } else if (node_uuid) {
                 local_set_name = crm_strdup_printf("%s-%s", section, node_uuid);
 
                 if (set_type) {
                     char *tmp_set_name = local_set_name;
 
                     local_set_name = crm_strdup_printf("%s-%s", tmp_set_name,
                                                        set_type);
                     free(tmp_set_name);
                 }
             } else {
                 local_set_name = crm_strdup_printf("%s-options", section);
             }
             set_name = local_set_name;
         }
 
         if (attr_id == NULL) {
             local_attr_id = crm_strdup_printf("%s-%s", set_name, attr_name);
             crm_xml_sanitize_id(local_attr_id);
             attr_id = local_attr_id;
 
         } else if (attr_name == NULL) {
             attr_name = attr_id;
         }
 
         crm_trace("Creating %s/%s", section, tag);
         if (tag != NULL) {
             xml_obj = create_xml_node(xml_obj, tag);
             crm_xml_add(xml_obj, PCMK_XA_ID, node_uuid);
             if (xml_top == NULL) {
                 xml_top = xml_obj;
             }
         }
 
         if (node_uuid == NULL && !pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
             if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
                 xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_PROPSET);
             } else {
                 xml_obj = create_xml_node(xml_obj, XML_TAG_META_SETS);
             }
 
         } else if (set_type) {
             xml_obj = create_xml_node(xml_obj, set_type);
 
         } else {
             xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS);
         }
         crm_xml_add(xml_obj, PCMK_XA_ID, set_name);
 
         if (xml_top == NULL) {
             xml_top = xml_obj;
         }
     }
 
   do_modify:
     xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value);
     if (xml_top == NULL) {
         xml_top = xml_obj;
     }
 
     crm_log_xml_trace(xml_top, "update_attr");
     rc = cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, xml_top,
                          NULL, call_options, user_name);
     if (rc < 0) {
         rc = pcmk_legacy2rc(rc);
 
         out->err(out, "Error setting %s=%s (section=%s, set=%s): %s",
                  attr_name, attr_value, section, pcmk__s(set_name, "<null>"),
                  pcmk_rc_str(rc));
         crm_log_xml_info(xml_top, "Update");
     } else {
         rc = pcmk_rc_ok;
     }
 
     free(local_set_name);
     free(local_attr_id);
     free_xml(xml_top);
 
     return rc;
 }
 
 int
 cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section,
                     const char *node_uuid, const char *set_type, const char *set_name,
                     const char *attr_id, const char *attr_name, const char *user_name,
                     xmlNode **result)
 {
     int rc = pcmk_rc_ok;
 
     CRM_ASSERT(result != NULL);
     CRM_CHECK(section != NULL, return EINVAL);
 
     *result = NULL;
 
     rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, attr_name,
                    user_name, result);
 
     if (rc != pcmk_rc_ok) {
         crm_trace("Query failed for attribute %s (section=%s node=%s set=%s): %s",
                   pcmk__s(attr_name, "with unspecified name"),
                   section, pcmk__s(set_name, "<null>"),
                   pcmk__s(node_uuid, "<null>"), pcmk_rc_str(rc));
     }
 
     return rc;
 }
 
 int
 cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section,
                       const char *node_uuid, const char *set_type, const char *set_name,
                       const char *attr_id, const char *attr_name, const char *attr_value,
                       const char *user_name)
 {
     int rc = pcmk_rc_ok;
     xmlNode *xml_obj = NULL;
     xmlNode *xml_search = NULL;
     char *local_attr_id = NULL;
 
     CRM_CHECK(section != NULL, return EINVAL);
     CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL);
 
     if (attr_id == NULL) {
         rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id,
                        attr_name, user_name, &xml_search);
 
         if (rc != pcmk_rc_ok || handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) {
             free_xml(xml_search);
             return rc;
         } else {
             pcmk__str_update(&local_attr_id,
                              crm_element_value(xml_search, PCMK_XA_ID));
             attr_id = local_attr_id;
             free_xml(xml_search);
         }
     }
 
     xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, attr_value);
 
     rc = cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, xml_obj,
                          NULL, options, user_name);
     if (rc < 0) {
         rc = pcmk_legacy2rc(rc);
     } else {
         rc = pcmk_rc_ok;
         out->info(out, "Deleted %s %s: id=%s%s%s%s%s",
                   section, node_uuid ? "attribute" : "option", local_attr_id,
                   set_name ? " set=" : "", set_name ? set_name : "",
                   attr_name ? " name=" : "", attr_name ? attr_name : "");
     }
 
     free(local_attr_id);
     free_xml(xml_obj);
     return rc;
 }
 
 int
 find_nvpair_attr_delegate(cib_t *cib, const char *attr, const char *section,
                           const char *node_uuid, const char *attr_set_type, const char *set_name,
                           const char *attr_id, const char *attr_name, gboolean to_console,
                           char **value, const char *user_name)
 {
     pcmk__output_t *out = NULL;
     xmlNode *xml_search = NULL;
     int rc = pcmk_ok;
 
     out = new_output_object(to_console ? "text" : "log");
     if (out == NULL) {
         return pcmk_err_generic;
     }
 
     rc = find_attr(cib, section, node_uuid, attr_set_type, set_name, attr_id,
                    attr_name, user_name, &xml_search);
 
     if (rc == pcmk_rc_ok) {
         rc = handle_multiples(out, xml_search, attr_name);
 
         if (rc == pcmk_rc_ok) {
             pcmk__str_update(value, crm_element_value(xml_search, attr));
         }
     }
 
     out->finish(out, CRM_EX_OK, true, NULL);
     free_xml(xml_search);
     pcmk__output_free(out);
     return pcmk_rc2legacy(rc);
 }
 
 int
 update_attr_delegate(cib_t *cib, int call_options, const char *section,
                      const char *node_uuid, const char *set_type, const char *set_name,
                      const char *attr_id, const char *attr_name, const char *attr_value,
                      gboolean to_console, const char *user_name, const char *node_type)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_ok;
 
     out = new_output_object(to_console ? "text" : "log");
     if (out == NULL) {
         return pcmk_err_generic;
     }
 
     rc = cib__update_node_attr(out, cib, call_options, section, node_uuid, set_type,
                                set_name, attr_id, attr_name, attr_value, user_name,
                                node_type);
 
     out->finish(out, CRM_EX_OK, true, NULL);
     pcmk__output_free(out);
     return pcmk_rc2legacy(rc);
 }
 
 int
 read_attr_delegate(cib_t *cib, const char *section, const char *node_uuid,
                    const char *set_type, const char *set_name, const char *attr_id,
                    const char *attr_name, char **attr_value, gboolean to_console,
                    const char *user_name)
 {
     pcmk__output_t *out = NULL;
     xmlNode *result = NULL;
     int rc = pcmk_ok;
 
     out = new_output_object(to_console ? "text" : "log");
     if (out == NULL) {
         return pcmk_err_generic;
     }
 
     rc = cib__get_node_attrs(out, cib, section, node_uuid, set_type, set_name,
                              attr_id, attr_name, user_name, &result);
 
     if (rc == pcmk_rc_ok) {
         if (result->children == NULL) {
             pcmk__str_update(attr_value,
                              crm_element_value(result, PCMK_XA_VALUE));
         } else {
             rc = ENOTUNIQ;
         }
     }
 
     out->finish(out, CRM_EX_OK, true, NULL);
     free_xml(result);
     pcmk__output_free(out);
     return pcmk_rc2legacy(rc);
 }
 
 int
 delete_attr_delegate(cib_t *cib, int options, const char *section, const char *node_uuid,
                      const char *set_type, const char *set_name, const char *attr_id,
                      const char *attr_name, const char *attr_value, gboolean to_console,
                      const char *user_name)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_ok;
 
     out = new_output_object(to_console ? "text" : "log");
     if (out == NULL) {
         return pcmk_err_generic;
     }
 
     rc = cib__delete_node_attr(out, cib, options, section, node_uuid, set_type,
                                set_name, attr_id, attr_name, attr_value, user_name);
 
     out->finish(out, CRM_EX_OK, true, NULL);
     pcmk__output_free(out);
     return pcmk_rc2legacy(rc);
 }
 
 /*!
  * \internal
  * \brief Parse node UUID from search result
  *
  * \param[in]  result     XML search result
  * \param[out] uuid       If non-NULL, where to store parsed UUID
  * \param[out] is_remote  If non-NULL, set TRUE if result is remote node
  *
  * \return pcmk_ok if UUID was successfully parsed, -ENXIO otherwise
  */
 static int
 get_uuid_from_result(const xmlNode *result, char **uuid, int *is_remote)
 {
     int rc = -ENXIO;
     const char *tag;
     const char *parsed_uuid = NULL;
     int parsed_is_remote = FALSE;
 
     if (result == NULL) {
         return rc;
     }
 
     /* If there are multiple results, the first is sufficient */
     tag = (const char *) (result->name);
     if (pcmk__str_eq(tag, "xpath-query", pcmk__str_casei)) {
         result = pcmk__xml_first_child(result);
         CRM_CHECK(result != NULL, return rc);
         tag = (const char *) (result->name);
     }
 
     if (pcmk__str_eq(tag, XML_CIB_TAG_NODE, pcmk__str_casei)) {
         /* Result is <node> tag from <nodes> section */
 
         if (pcmk__str_eq(crm_element_value(result, PCMK_XA_TYPE), "remote",
                          pcmk__str_casei)) {
             parsed_uuid = crm_element_value(result, PCMK_XA_UNAME);
             parsed_is_remote = TRUE;
         } else {
             parsed_uuid = ID(result);
             parsed_is_remote = FALSE;
         }
 
     } else if (pcmk__str_eq(tag, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) {
         /* Result is <primitive> for ocf:pacemaker:remote resource */
 
         parsed_uuid = ID(result);
         parsed_is_remote = TRUE;
 
     } else if (pcmk__str_eq(tag, XML_CIB_TAG_NVPAIR, pcmk__str_casei)) {
         /* Result is PCMK_META_REMOTE_NODE parameter of <primitive> for guest
          * node
          */
 
         parsed_uuid = crm_element_value(result, PCMK_XA_VALUE);
         parsed_is_remote = TRUE;
 
     } else if (pcmk__str_eq(tag, XML_CIB_TAG_STATE, pcmk__str_casei)) {
         /* Result is <node_state> tag from <status> section */
 
         parsed_uuid = crm_element_value(result, PCMK_XA_UNAME);
         if (pcmk__xe_attr_is_true(result, XML_NODE_IS_REMOTE)) {
             parsed_is_remote = TRUE;
         }
     }
 
     if (parsed_uuid) {
         if (uuid) {
             *uuid = strdup(parsed_uuid);
         }
         if (is_remote) {
             *is_remote = parsed_is_remote;
         }
         rc = pcmk_ok;
     }
 
     return rc;
 }
 
 /* Search string to find a node by name, as:
  * - cluster or remote node in nodes section
  * - remote node in resources section
  * - guest node in resources section
  * - orphaned remote node or bundle guest node in status section
  */
 #define XPATH_UPPER_TRANS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 #define XPATH_LOWER_TRANS "abcdefghijklmnopqrstuvwxyz"
 #define XPATH_NODE \
     "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_NODES \
         "/" XML_CIB_TAG_NODE "[translate(@" PCMK_XA_UNAME ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
-    "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
+    "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES \
         "/" XML_CIB_TAG_RESOURCE \
         "[@class='ocf'][@provider='pacemaker'][@type='remote'][translate(@id,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
-    "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
+    "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES \
         "/" XML_CIB_TAG_RESOURCE "/" XML_TAG_META_SETS "/" XML_CIB_TAG_NVPAIR \
         "[@name='" PCMK_META_REMOTE_NODE "'][translate(@value,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
     "|/" PCMK_XE_CIB "/" PCMK_XE_STATUS "/" XML_CIB_TAG_STATE \
         "[@" XML_NODE_IS_REMOTE "='true'][translate(@" PCMK_XA_ID ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']"
 
 int
 query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node)
 {
     int rc = pcmk_ok;
     char *xpath_string;
     xmlNode *xml_search = NULL;
     char *host_lowercase = NULL;
 
     CRM_ASSERT(uname != NULL);
 
     host_lowercase = g_ascii_strdown(uname, -1);
 
     if (uuid) {
         *uuid = NULL;
     }
     if (is_remote_node) {
         *is_remote_node = FALSE;
     }
 
     xpath_string = crm_strdup_printf(XPATH_NODE, host_lowercase, host_lowercase, host_lowercase, host_lowercase);
     if (cib_internal_op(the_cib, PCMK__CIB_REQUEST_QUERY, NULL, xpath_string,
                         NULL, &xml_search,
                         cib_sync_call|cib_scope_local|cib_xpath,
                         NULL) == pcmk_ok) {
         rc = get_uuid_from_result(xml_search, uuid, is_remote_node);
     } else {
         rc = -ENXIO;
     }
     free(xpath_string);
     free_xml(xml_search);
     g_free(host_lowercase);
 
     if (rc != pcmk_ok) {
         crm_debug("Could not map node name '%s' to a UUID: %s",
                   uname, pcmk_strerror(rc));
     } else {
         crm_info("Mapped node name '%s' to UUID %s", uname, (uuid? *uuid : ""));
     }
     return rc;
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/cib/util_compat.h>
 
 int
 query_node_uname(cib_t * the_cib, const char *uuid, char **uname)
 {
     int rc = pcmk_ok;
     xmlNode *a_child = NULL;
     xmlNode *xml_obj = NULL;
     xmlNode *fragment = NULL;
     const char *child_name = NULL;
 
     CRM_ASSERT(uname != NULL);
     CRM_ASSERT(uuid != NULL);
 
     rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment,
                               cib_sync_call | cib_scope_local);
     if (rc != pcmk_ok) {
         return rc;
     }
 
     xml_obj = fragment;
     CRM_CHECK(pcmk__xe_is(xml_obj, XML_CIB_TAG_NODES), return -ENOMSG);
     crm_log_xml_trace(xml_obj, "Result section");
 
     rc = -ENXIO;
     *uname = NULL;
 
     for (a_child = pcmk__xml_first_child(xml_obj); a_child != NULL;
          a_child = pcmk__xml_next(a_child)) {
 
         if (pcmk__str_eq((const char *)a_child->name, XML_CIB_TAG_NODE,
                          pcmk__str_none)) {
             child_name = ID(a_child);
             if (pcmk__str_eq(uuid, child_name, pcmk__str_casei)) {
                 child_name = crm_element_value(a_child, PCMK_XA_UNAME);
                 if (child_name != NULL) {
                     *uname = strdup(child_name);
                     rc = pcmk_ok;
                 }
                 break;
             }
         }
     }
 
     free_xml(fragment);
     return rc;
 }
 
 int
 set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value)
 {
     int rc = pcmk_ok;
     char *attr_id = NULL;
 
     CRM_CHECK(uuid != NULL, return -EINVAL);
     CRM_CHECK(standby_value != NULL, return -EINVAL);
 
     if (pcmk__strcase_any_of(scope, "reboot", PCMK_XE_STATUS, NULL)) {
         scope = PCMK_XE_STATUS;
         attr_id = crm_strdup_printf("transient-standby-%.256s", uuid);
 
     } else {
         scope = XML_CIB_TAG_NODES;
         attr_id = crm_strdup_printf("standby-%.256s", uuid);
     }
 
     rc = update_attr_delegate(the_cib, cib_sync_call, scope, uuid, NULL, NULL,
                               attr_id, "standby", standby_value, TRUE, NULL, NULL);
 
     free(attr_id);
     return rc;
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 3b5e14ff19..f4074af9b9 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,1114 +1,1114 @@
 /*
  * Original copyright 2004 International Business Machines
  * Later changes copyright 2008-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 #include <crm_internal.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <sys/utsname.h>
 
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/cib/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/cib_internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/pengine/rules.h>
 
 gboolean
 cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
 {
     *epoch = -1;
     *updates = -1;
     *admin_epoch = -1;
 
     if (cib == NULL) {
         return FALSE;
 
     } else {
         crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
         crm_element_value_int(cib, PCMK_XA_NUM_UPDATES, updates);
         crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
     }
     return TRUE;
 }
 
 gboolean
 cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
                          int *_admin_epoch, int *_epoch, int *_updates)
 {
     int add[] = { 0, 0, 0 };
     int del[] = { 0, 0, 0 };
 
     xml_patch_versions(diff, add, del);
 
     *admin_epoch = add[0];
     *epoch = add[1];
     *updates = add[2];
 
     *_admin_epoch = del[0];
     *_epoch = del[1];
     *_updates = del[2];
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Get the XML patchset from a CIB diff notification
  *
  * \param[in]  msg       CIB diff notification
  * \param[out] patchset  Where to store XML patchset
  *
  * \return Standard Pacemaker return code
  */
 int
 cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
 {
     int rc = pcmk_err_generic;
 
     CRM_ASSERT(patchset != NULL);
     *patchset = NULL;
 
     if (msg == NULL) {
         crm_err("CIB diff notification received with no XML");
         return ENOMSG;
     }
 
     if ((crm_element_value_int(msg, F_CIB_RC, &rc) != 0) || (rc != pcmk_ok)) {
         crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
                  pcmk_strerror(rc), rc);
         crm_log_xml_debug(msg, "failed");
         return pcmk_legacy2rc(rc);
     }
 
     *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     if (*patchset == NULL) {
         crm_err("CIB diff notification received with no patchset");
         return ENOMSG;
     }
     return pcmk_rc_ok;
 }
 
 #define XPATH_DIFF_V1 "//" F_CIB_UPDATE_RESULT "//" PCMK__XE_DIFF_ADDED
 
 /*!
  * \internal
  * \brief Check whether a given CIB element was modified in a CIB patchset (v1)
  *
  * \param[in] patchset  CIB XML patchset
  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
  *                      to \c PCMK_XE_CIB)
  *
  * \return \c true if \p element was modified, or \c false otherwise
  */
 static bool
 element_in_patchset_v1(const xmlNode *patchset, const char *element)
 {
     char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
                                     pcmk__s(element, PCMK_XE_CIB));
     xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
 
     free(xpath);
 
     if (xpath_obj == NULL) {
         return false;
     }
     freeXpathObject(xpath_obj);
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether a given CIB element was modified in a CIB patchset (v2)
  *
  * \param[in] patchset  CIB XML patchset
  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
  *                      to \c PCMK_XE_CIB). Supported values include any CIB
  *                      element supported by \c pcmk__cib_abs_xpath_for().
  *
  * \return \c true if \p element was modified, or \c false otherwise
  */
 static bool
 element_in_patchset_v2(const xmlNode *patchset, const char *element)
 {
     const char *element_xpath = pcmk__cib_abs_xpath_for(element);
     const char *parent_xpath = pcmk_cib_parent_name_for(element);
     char *element_regex = NULL;
     bool rc = false;
 
     CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
 
     // Matches if and only if element_xpath is part of a changed path
     element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
 
     for (const xmlNode *change = first_named_child(patchset, XML_DIFF_CHANGE);
          change != NULL; change = crm_next_same_xml(change)) {
 
         const char *op = crm_element_value(change, F_CIB_OPERATION);
         const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
 
         if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
             // Change to an existing element
             rc = true;
             break;
         }
 
         if (pcmk__str_eq(op, "create", pcmk__str_none)
             && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
             && pcmk__xe_is(pcmk__xml_first_child(change), element)) {
 
             // Newly added element
             rc = true;
             break;
         }
     }
 
     free(element_regex);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Check whether a given CIB element was modified in a CIB patchset
  *
  * \param[in] patchset  CIB XML patchset
  * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
  *                      to \c PCMK_XE_CIB). Supported values include any CIB
  *                      element supported by \c pcmk__cib_abs_xpath_for().
  *
  * \return \c true if \p element was modified, or \c false otherwise
  */
 bool
 cib__element_in_patchset(const xmlNode *patchset, const char *element)
 {
     int format = 1;
 
     CRM_ASSERT(patchset != NULL);
 
     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
     switch (format) {
         case 1:
             return element_in_patchset_v1(patchset, element);
 
         case 2:
             return element_in_patchset_v2(patchset, element);
 
         default:
             crm_warn("Unknown patch format: %d", format);
             return false;
     }
 }
 
 /*!
  * \brief Create XML for a new (empty) CIB
  *
  * \param[in] cib_epoch  What to use as \c PCMK_XA_EPOCH CIB attribute
  *
  * \return Newly created XML for empty CIB
  * \note It is the caller's responsibility to free the result with free_xml().
  */
 xmlNode *
 createEmptyCib(int cib_epoch)
 {
     xmlNode *cib_root = NULL, *config = NULL;
 
     cib_root = create_xml_node(NULL, PCMK_XE_CIB);
     crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
     crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, xml_latest_schema());
 
     crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
     crm_xml_add_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
     crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
 
     config = create_xml_node(cib_root, PCMK_XE_CONFIGURATION);
     create_xml_node(cib_root, PCMK_XE_STATUS);
 
     create_xml_node(config, XML_CIB_TAG_CRMCONFIG);
     create_xml_node(config, XML_CIB_TAG_NODES);
-    create_xml_node(config, XML_CIB_TAG_RESOURCES);
+    create_xml_node(config, PCMK_XE_RESOURCES);
     create_xml_node(config, XML_CIB_TAG_CONSTRAINTS);
 
 #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
     {
         xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG);
         xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS);
         xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR);
 
         crm_xml_add(meta, PCMK_XA_ID, "build-resource-defaults");
         crm_xml_add(nvpair, PCMK_XA_ID, "build-" PCMK_META_RESOURCE_STICKINESS);
         crm_xml_add(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
         crm_xml_add_int(nvpair, PCMK_XA_VALUE,
                         PCMK__RESOURCE_STICKINESS_DEFAULT);
     }
 #endif
     return cib_root;
 }
 
 static bool
 cib_acl_enabled(xmlNode *xml, const char *user)
 {
     bool rc = FALSE;
 
     if(pcmk_acl_required(user)) {
         const char *value = NULL;
         GHashTable *options = pcmk__strkey_table(free, free);
 
         cib_read_config(options, xml);
         value = cib_pref(options, PCMK_OPT_ENABLE_ACL);
         rc = crm_is_true(value);
         g_hash_table_destroy(options);
     }
 
     crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
     return rc;
 }
 
 /*!
  * \internal
  * \brief Determine whether to perform operations on a scratch copy of the CIB
  *
  * \param[in] op            CIB operation
  * \param[in] section       CIB section
  * \param[in] call_options  CIB call options
  *
  * \return \p true if we should make a copy of the CIB, or \p false otherwise
  */
 static bool
 should_copy_cib(const char *op, const char *section, int call_options)
 {
     if (pcmk_is_set(call_options, cib_dryrun)) {
         // cib_dryrun implies a scratch copy by definition; no side effects
         return true;
     }
 
     if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
         /* Commit-transaction must make a copy for atomicity. We must revert to
          * the original CIB if the entire transaction cannot be applied
          * successfully.
          */
         return true;
     }
 
     if (pcmk_is_set(call_options, cib_transaction)) {
         /* If cib_transaction is set, then we're in the process of committing a
          * transaction. The commit-transaction request already made a scratch
          * copy, and we're accumulating changes in that copy.
          */
         return false;
     }
 
     if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
         /* Copying large CIBs accounts for a huge percentage of our CIB usage,
          * and this avoids some of it.
          *
          * @TODO: Is this safe? See discussion at
          * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
          */
         return false;
     }
 
     // Default behavior is to operate on a scratch copy
     return true;
 }
 
 int
 cib_perform_op(const char *op, int call_options, cib__op_fn_t fn, bool is_query,
                const char *section, xmlNode *req, xmlNode *input,
                bool manage_counters, bool *config_changed,
                xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff,
                xmlNode **output)
 {
     int rc = pcmk_ok;
     bool check_schema = true;
     bool make_copy = true;
     xmlNode *top = NULL;
     xmlNode *scratch = NULL;
     xmlNode *patchset_cib = NULL;
     xmlNode *local_diff = NULL;
 
     const char *new_version = NULL;
     const char *user = crm_element_value(req, F_CIB_USER);
     bool with_digest = false;
 
     crm_trace("Begin %s%s%s op",
               (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
               (is_query? "read-only " : ""), op);
 
     CRM_CHECK(output != NULL, return -ENOMSG);
     CRM_CHECK(current_cib != NULL, return -ENOMSG);
     CRM_CHECK(result_cib != NULL, return -ENOMSG);
     CRM_CHECK(config_changed != NULL, return -ENOMSG);
 
     if(output) {
         *output = NULL;
     }
 
     *result_cib = NULL;
     *config_changed = false;
 
     if (fn == NULL) {
         return -EINVAL;
     }
 
     if (is_query) {
         xmlNode *cib_ro = *current_cib;
         xmlNode *cib_filtered = NULL;
 
         if (cib_acl_enabled(cib_ro, user)
             && xml_acl_filtered_copy(user, *current_cib, *current_cib,
                                      &cib_filtered)) {
 
             if (cib_filtered == NULL) {
                 crm_debug("Pre-filtered the entire cib");
                 return -EACCES;
             }
             cib_ro = cib_filtered;
             crm_log_xml_trace(cib_ro, "filtered");
         }
 
         rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
 
         if(output == NULL || *output == NULL) {
             /* nothing */
 
         } else if(cib_filtered == *output) {
             cib_filtered = NULL; /* Let them have this copy */
 
         } else if (*output == *current_cib) {
             /* They already know not to free it */
 
         } else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
             /* We're about to free the document of which *output is a part */
             *output = copy_xml(*output);
 
         } else if ((*output)->doc == (*current_cib)->doc) {
             /* Give them a copy they can free */
             *output = copy_xml(*output);
         }
 
         free_xml(cib_filtered);
         return rc;
     }
 
     make_copy = should_copy_cib(op, section, call_options);
 
     if (!make_copy) {
         /* Conditional on v2 patch style */
 
         scratch = *current_cib;
 
         // Make a copy of the top-level element to store version details
         top = create_xml_node(NULL, (const char *) scratch->name);
         copy_in_properties(top, scratch);
         patchset_cib = top;
 
         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
         rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
 
         /* If scratch points to a new object now (for example, after an erase
          * operation), then *current_cib should point to the same object.
          */
         *current_cib = scratch;
 
     } else {
         scratch = copy_xml(*current_cib);
         patchset_cib = *current_cib;
 
         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
         rc = (*fn) (op, call_options, section, req, input, *current_cib,
                     &scratch, output);
 
         if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
             crm_trace("Inferring changes after %s op", op);
             xml_track_changes(scratch, user, *current_cib,
                               cib_acl_enabled(*current_cib, user));
             xml_calculate_changes(*current_cib, scratch);
         }
         CRM_CHECK(*current_cib != scratch, return -EINVAL);
     }
 
     xml_acl_disable(scratch); /* Allow the system to make any additional changes */
 
     if (rc == pcmk_ok && scratch == NULL) {
         rc = -EINVAL;
         goto done;
 
     } else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
         crm_trace("ACL rejected part or all of the proposed changes");
         rc = -EACCES;
         goto done;
 
     } else if (rc != pcmk_ok) {
         goto done;
     }
 
     if (scratch) {
         new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
 
         if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) {
             crm_err("Discarding update with feature set '%s' greater than our own '%s'",
                     new_version, CRM_FEATURE_SET);
             rc = -EPROTONOSUPPORT;
             goto done;
         }
     }
 
     if (patchset_cib != NULL) {
         int old = 0;
         int new = 0;
 
         crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
         crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
 
         if (old > new) {
             crm_err("%s went backwards: %d -> %d (Opts: %#x)",
                     PCMK_XA_ADMIN_EPOCH, old, new, call_options);
             crm_log_xml_warn(req, "Bad Op");
             crm_log_xml_warn(input, "Bad Data");
             rc = -pcmk_err_old_data;
 
         } else if (old == new) {
             crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
             crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
             if (old > new) {
                 crm_err("%s went backwards: %d -> %d (Opts: %#x)",
                         PCMK_XA_EPOCH, old, new, call_options);
                 crm_log_xml_warn(req, "Bad Op");
                 crm_log_xml_warn(input, "Bad Data");
                 rc = -pcmk_err_old_data;
             }
         }
     }
 
     crm_trace("Massaging CIB contents");
     pcmk__strip_xml_text(scratch);
     fix_plus_plus_recursive(scratch);
 
     if (!make_copy) {
         /* At this point, patchset_cib is just the PCMK_XE_CIB tag and its
          * properties.
          *
          * The v1 format would barf on this, but we know the v2 patch
          * format only needs it for the top-level version fields
          */
         local_diff = xml_create_patchset(2, patchset_cib, scratch,
                                          config_changed, manage_counters);
 
     } else {
         static time_t expires = 0;
         time_t tm_now = time(NULL);
 
         if (expires < tm_now) {
             expires = tm_now + 60;  /* Validate clients are correctly applying v2-style diffs at most once a minute */
             with_digest = true;
         }
 
         local_diff = xml_create_patchset(0, patchset_cib, scratch,
                                          config_changed, manage_counters);
     }
 
     pcmk__log_xml_changes(LOG_TRACE, scratch);
     xml_accept_changes(scratch);
 
     if(local_diff) {
         patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
         pcmk__log_xml_patchset(LOG_INFO, local_diff);
         crm_log_xml_trace(local_diff, "raw patch");
     }
 
     if (make_copy && (local_diff != NULL)) {
         // Original to compare against doesn't exist
         pcmk__if_tracing(
             {
                 // Validate the calculated patch set
                 int test_rc = pcmk_ok;
                 int format = 1;
                 xmlNode *cib_copy = copy_xml(patchset_cib);
 
                 crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
                 test_rc = xml_apply_patchset(cib_copy, local_diff,
                                              manage_counters);
 
                 if (test_rc != pcmk_ok) {
                     save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
                     save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
                     save_xml_to_file(scratch, "PatchApply:actual", NULL);
                     save_xml_to_file(local_diff, "PatchApply:diff", NULL);
                     crm_err("v%d patchset error, patch failed to apply: %s "
                             "(%d)",
                             format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
                             test_rc);
                 }
                 free_xml(cib_copy);
             },
             {}
         );
     }
 
     if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
         /* Throttle the amount of costly validation we perform due to status updates
          * a) we don't really care whats in the status section
          * b) we don't validate any of its contents at the moment anyway
          */
         check_schema = false;
     }
 
     /* === scratch must not be modified after this point ===
      * Exceptions, anything in:
 
      static filter_t filter[] = {
      { 0, PCMK_XA_CRM_DEBUG_ORIGIN },
      { 0, PCMK_XA_CIB_LAST_WRITTEN },
      { 0, PCMK_XA_UPDATE_ORIGIN },
      { 0, PCMK_XA_UPDATE_CLIENT },
      { 0, PCMK_XA_UPDATE_USER },
      };
      */
 
     if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
         const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
 
         pcmk__xe_add_last_written(scratch);
         if (schema) {
             static int minimum_schema = 0;
             int current_schema = get_schema_version(schema);
 
             if (minimum_schema == 0) {
                 minimum_schema = get_schema_version("pacemaker-1.2");
             }
 
             /* Does the CIB support the "update-*" attributes... */
             if (current_schema >= minimum_schema) {
                 /* Ensure values of origin, client, and user in scratch match
                  * the values in req
                  */
                 const char *origin = crm_element_value(req, PCMK__XA_SRC);
                 const char *client = crm_element_value(req, F_CIB_CLIENTNAME);
 
                 if (origin != NULL) {
                     crm_xml_add(scratch, PCMK_XA_UPDATE_ORIGIN, origin);
                 } else {
                     xml_remove_prop(scratch, PCMK_XA_UPDATE_ORIGIN);
                 }
 
                 if (client != NULL) {
                     crm_xml_add(scratch, PCMK_XA_UPDATE_CLIENT, user);
                 } else {
                     xml_remove_prop(scratch, PCMK_XA_UPDATE_CLIENT);
                 }
 
                 if (user != NULL) {
                     crm_xml_add(scratch, PCMK_XA_UPDATE_USER, user);
                 } else {
                     xml_remove_prop(scratch, PCMK_XA_UPDATE_USER);
                 }
             }
         }
     }
 
     crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
     if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, true)) {
         const char *current_schema = crm_element_value(scratch,
                                                        PCMK_XA_VALIDATE_WITH);
 
         crm_warn("Updated CIB does not validate against %s schema",
                  pcmk__s(current_schema, "unspecified"));
         rc = -pcmk_err_schema_validation;
     }
 
   done:
 
     *result_cib = scratch;
 
     /* @TODO: This may not work correctly with !make_copy, since we don't
      * keep the original CIB.
      */
     if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
         && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
 
         if (*result_cib == NULL) {
             crm_debug("Pre-filtered the entire cib result");
         }
         free_xml(scratch);
     }
 
     if(diff) {
         *diff = local_diff;
     } else {
         free_xml(local_diff);
     }
 
     free_xml(top);
     crm_trace("Done");
     return rc;
 }
 
 int
 cib__create_op(cib_t *cib, const char *op, const char *host,
                const char *section, xmlNode *data, int call_options,
                const char *user_name, const char *client_name,
                xmlNode **op_msg)
 {
     CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
 
     *op_msg = create_xml_node(NULL, T_CIB_COMMAND);
     if (*op_msg == NULL) {
         return -EPROTO;
     }
 
     cib->call_id++;
     if (cib->call_id < 1) {
         cib->call_id = 1;
     }
 
     crm_xml_add(*op_msg, PCMK__XA_T, T_CIB);
     crm_xml_add(*op_msg, F_CIB_OPERATION, op);
     crm_xml_add(*op_msg, F_CIB_HOST, host);
     crm_xml_add(*op_msg, F_CIB_SECTION, section);
     crm_xml_add(*op_msg, F_CIB_USER, user_name);
     crm_xml_add(*op_msg, F_CIB_CLIENTNAME, client_name);
     crm_xml_add_int(*op_msg, F_CIB_CALLID, cib->call_id);
 
     crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
     crm_xml_add_int(*op_msg, F_CIB_CALLOPTS, call_options);
 
     if (data != NULL) {
         add_message_xml(*op_msg, F_CIB_CALLDATA, data);
     }
 
     if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
         CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
                   free_xml(*op_msg); return -EPROTO);
     }
     return pcmk_ok;
 }
 
 /*!
  * \internal
  * \brief Check whether a CIB request is supported in a transaction
  *
  * \param[in] request  CIB request
  *
  * \return Standard Pacemaker return code
  */
 static int
 validate_transaction_request(const xmlNode *request)
 {
     const char *op = crm_element_value(request, F_CIB_OPERATION);
     const char *host = crm_element_value(request, F_CIB_HOST);
     const cib__operation_t *operation = NULL;
     int rc = cib__get_operation(op, &operation);
 
     if (rc != pcmk_rc_ok) {
         // cib__get_operation() logs error
         return rc;
     }
 
     if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
         crm_err("Operation %s is not supported in CIB transactions", op);
         return EOPNOTSUPP;
     }
 
     if (host != NULL) {
         crm_err("Operation targeting a specific node (%s) is not supported in "
                 "a CIB transaction",
                 host);
         return EOPNOTSUPP;
     }
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Append a CIB request to a CIB transaction
  *
  * \param[in,out] cib      CIB client whose transaction to extend
  * \param[in,out] request  Request to add to transaction
  *
  * \return Legacy Pacemaker return code
  */
 int
 cib__extend_transaction(cib_t *cib, xmlNode *request)
 {
     int rc = pcmk_rc_ok;
 
     CRM_ASSERT((cib != NULL) && (request != NULL));
 
     rc = validate_transaction_request(request);
 
     if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
         rc = pcmk_rc_no_transaction;
     }
 
     if (rc == pcmk_rc_ok) {
         add_node_copy(cib->transaction, request);
 
     } else {
         const char *op = crm_element_value(request, F_CIB_OPERATION);
         const char *client_id = NULL;
 
         cib->cmds->client_id(cib, NULL, &client_id);
         crm_err("Failed to add '%s' operation to transaction for client %s: %s",
                 op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
         crm_log_xml_info(request, "failed");
     }
     return pcmk_rc2legacy(rc);
 }
 
 void
 cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
 {
     xmlNode *output = NULL;
     cib_callback_client_t *blob = NULL;
 
     if (msg != NULL) {
         crm_element_value_int(msg, F_CIB_RC, &rc);
         crm_element_value_int(msg, F_CIB_CALLID, &call_id);
         output = get_message_xml(msg, F_CIB_CALLDATA);
     }
 
     blob = cib__lookup_id(call_id);
 
     if (blob == NULL) {
         crm_trace("No callback found for call %d", call_id);
     }
 
     if (cib == NULL) {
         crm_debug("No cib object supplied");
     }
 
     if (rc == -pcmk_err_diff_resync) {
         /* This is an internal value that clients do not and should not care about */
         rc = pcmk_ok;
     }
 
     if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
         crm_trace("Invoking callback %s for call %d",
                   pcmk__s(blob->id, "without ID"), call_id);
         blob->callback(msg, call_id, rc, output, blob->user_data);
 
     } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
         crm_warn("CIB command failed: %s", pcmk_strerror(rc));
         crm_log_xml_debug(msg, "Failed CIB Update");
     }
 
     /* This may free user_data, so do it after the callback */
     if (blob) {
         remove_cib_op_callback(call_id, FALSE);
     }
 
     if (cib && cib->op_callback != NULL) {
         crm_trace("Invoking global callback for call %d", call_id);
         cib->op_callback(msg, call_id, rc, output);
     }
     crm_trace("OP callback activated for %d", call_id);
 }
 
 void
 cib_native_notify(gpointer data, gpointer user_data)
 {
     xmlNode *msg = user_data;
     cib_notify_client_t *entry = data;
     const char *event = NULL;
 
     if (msg == NULL) {
         crm_warn("Skipping callback - NULL message");
         return;
     }
 
     event = crm_element_value(msg, PCMK__XA_SUBT);
 
     if (entry == NULL) {
         crm_warn("Skipping callback - NULL callback client");
         return;
 
     } else if (entry->callback == NULL) {
         crm_warn("Skipping callback - NULL callback");
         return;
 
     } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
         crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
         return;
     }
 
     crm_trace("Invoking callback for %p/%s event...", entry, event);
     entry->callback(event, msg);
     crm_trace("Callback invoked...");
 }
 
 static pcmk__cluster_option_t cib_opts[] = {
     /* name, legacy name, type, allowed values,
      * default value, validator,
      * short description,
      * long description
      */
     {
         PCMK_OPT_ENABLE_ACL, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         N_("Enable Access Control Lists (ACLs) for the CIB"),
         NULL
     },
     {
         PCMK_OPT_CLUSTER_IPC_LIMIT, NULL, "integer", NULL,
         "500", pcmk__valid_positive_int,
         N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
         N_("Raise this if log has \"Evicting client\" messages for cluster daemon"
             " PIDs (a good value is the number of resources in the cluster"
             " multiplied by the number of nodes).")
     },
 };
 
 void
 cib_metadata(void)
 {
     const char *desc_short = "Cluster Information Base manager options";
     const char *desc_long = "Cluster options used by Pacemaker's Cluster "
                             "Information Base manager";
 
     gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short,
                                             desc_long, cib_opts,
                                             PCMK__NELEM(cib_opts));
     printf("%s", s);
     g_free(s);
 }
 
 static void
 verify_cib_options(GHashTable *options)
 {
     pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts));
 }
 
 const char *
 cib_pref(GHashTable * options, const char *name)
 {
     return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts),
                                 name);
 }
 
 gboolean
 cib_read_config(GHashTable * options, xmlNode * current_cib)
 {
     xmlNode *config = NULL;
     crm_time_t *now = NULL;
 
     if (options == NULL || current_cib == NULL) {
         return FALSE;
     }
 
     now = crm_time_new(NULL);
 
     g_hash_table_remove_all(options);
 
     config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG);
     if (config) {
         pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL,
                           options, CIB_OPTIONS_FIRST, TRUE, now, NULL);
     }
 
     verify_cib_options(options);
 
     crm_time_free(now);
 
     return TRUE;
 }
 
 int
 cib_internal_op(cib_t * cib, const char *op, const char *host,
                 const char *section, xmlNode * data,
                 xmlNode ** output_data, int call_options, const char *user_name)
 {
     int (*delegate) (cib_t * cib, const char *op, const char *host,
                      const char *section, xmlNode * data,
                      xmlNode ** output_data, int call_options, const char *user_name) =
         cib->delegate_fn;
 
     if(user_name == NULL) {
         user_name = getenv("CIB_user");
     }
 
     return delegate(cib, op, host, section, data, output_data, call_options, user_name);
 }
 
 /*!
  * \brief Apply a CIB update patch to a given CIB
  *
  * \param[in]  event   CIB update patch
  * \param[in]  input   CIB to patch
  * \param[out] output  Resulting CIB after patch
  * \param[in]  level   Log the patch at this log level (unless LOG_CRIT)
  *
  * \return Legacy Pacemaker return code
  * \note sbd calls this function
  */
 int
 cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
                       int level)
 {
     int rc = pcmk_err_generic;
 
     xmlNode *diff = NULL;
 
     CRM_ASSERT(event);
     CRM_ASSERT(input);
     CRM_ASSERT(output);
 
     crm_element_value_int(event, F_CIB_RC, &rc);
     diff = get_message_xml(event, F_CIB_UPDATE_RESULT);
 
     if (rc < pcmk_ok || diff == NULL) {
         return rc;
     }
 
     if (level > LOG_CRIT) {
         pcmk__log_xml_patchset(level, diff);
     }
 
     if (input != NULL) {
         rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
                               NULL);
 
         if (rc != pcmk_ok) {
             crm_debug("Update didn't apply: %s (%d) %p",
                       pcmk_strerror(rc), rc, *output);
 
             if (rc == -pcmk_err_old_data) {
                 crm_trace("Masking error, we already have the supplied update");
                 return pcmk_ok;
             }
             free_xml(*output);
             *output = NULL;
             return rc;
         }
     }
     return rc;
 }
 
 #define log_signon_query_err(out, fmt, args...) do {    \
         if (out != NULL) {                              \
             out->err(out, fmt, ##args);                 \
         } else {                                        \
             crm_err(fmt, ##args);                       \
         }                                               \
     } while (0)
 
 int
 cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
 {
     int rc = pcmk_rc_ok;
     cib_t *cib_conn = NULL;
 
     CRM_ASSERT(cib_object != NULL);
 
     if (cib == NULL) {
         cib_conn = cib_new();
     } else {
         if (*cib == NULL) {
             *cib = cib_new();
         }
         cib_conn = *cib;
     }
 
     if (cib_conn == NULL) {
         return ENOMEM;
     }
 
     if (cib_conn->state == cib_disconnected) {
         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
     }
 
     if (rc != pcmk_rc_ok) {
         log_signon_query_err(out, "Could not connect to the CIB: %s",
                              pcmk_rc_str(rc));
         goto done;
     }
 
     if (out != NULL) {
         out->transient(out, "Querying CIB...");
     }
     rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
                                cib_scope_local|cib_sync_call);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
     }
 
 done:
     if (cib == NULL) {
         cib__clean_up_connection(&cib_conn);
     }
 
     if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
         return pcmk_rc_no_input;
     }
     return rc;
 }
 
 int
 cib__clean_up_connection(cib_t **cib)
 {
     int rc;
 
     if (*cib == NULL) {
         return pcmk_rc_ok;
     }
 
     rc = (*cib)->cmds->signoff(*cib);
     cib_delete(*cib);
     *cib = NULL;
     return pcmk_legacy2rc(rc);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/cib/util_compat.h>
 
 xmlNode *
 cib_get_generation(cib_t * cib)
 {
     xmlNode *the_cib = NULL;
     xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
 
     cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
     if (the_cib != NULL) {
         copy_in_properties(generation, the_cib);
         free_xml(the_cib);
     }
 
     return generation;
 }
 
 const char *
 get_object_path(const char *object_type)
 {
     return pcmk_cib_xpath_for(object_type);
 }
 
 const char *
 get_object_parent(const char *object_type)
 {
     return pcmk_cib_parent_name_for(object_type);
 }
 
 xmlNode *
 get_object_root(const char *object_type, xmlNode *the_root)
 {
     return pcmk_find_cib_element(the_root, object_type);
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/common/cib.c b/lib/common/cib.c
index f04e14b1f5..1e070dc018 100644
--- a/lib/common/cib.c
+++ b/lib/common/cib.c
@@ -1,175 +1,175 @@
 /*
  * Original copyright 2004 International Business Machines
  * Later changes copyright 2008-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <libxml/tree.h>    // xmlNode
 
 #include <crm/msg_xml.h>
 #include <crm/common/cib.h>
 #include <crm/common/cib_internal.h>
 
 /*
  * Functions to help find particular sections of the CIB
  */
 
 // Map CIB element names to their parent elements and XPath searches
 static struct {
     const char *name;   // Name of this CIB element
     const char *parent; // CIB element that this element is a child of
     const char *path;   // XPath to find this CIB element
 } cib_sections[] = {
     {
         // This first entry is also the default if a NULL is compared
         PCMK_XE_CIB,
         NULL,
         "//" PCMK_XE_CIB
     },
     {
         PCMK_XE_STATUS,
         "/" PCMK_XE_CIB,
         "//" PCMK_XE_CIB "/" PCMK_XE_STATUS
     },
     {
         PCMK_XE_CONFIGURATION,
         "/" PCMK_XE_CIB,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
     },
     {
         XML_CIB_TAG_CRMCONFIG,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_CRMCONFIG
     },
     {
         XML_CIB_TAG_NODES,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_NODES
     },
     {
-        XML_CIB_TAG_RESOURCES,
+        PCMK_XE_RESOURCES,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
-        "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RESOURCES
+        "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES
     },
     {
         XML_CIB_TAG_CONSTRAINTS,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_CONSTRAINTS
     },
     {
         XML_CIB_TAG_OPCONFIG,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_OPCONFIG
     },
     {
         XML_CIB_TAG_RSCCONFIG,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RSCCONFIG
     },
     {
         XML_CIB_TAG_ACLS,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_ACLS
     },
     {
         XML_TAG_FENCING_TOPOLOGY,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_TAG_FENCING_TOPOLOGY
     },
     {
         XML_CIB_TAG_TAGS,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_TAGS
     },
     {
         XML_CIB_TAG_ALERTS,
         "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION,
         "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_ALERTS
     },
     {
         XML_CIB_TAG_SECTION_ALL,
         NULL,
         "//" PCMK_XE_CIB
     },
 };
 
 /*!
  * \brief Get the relative XPath needed to find a specified CIB element name
  *
  * \param[in] element_name  Name of CIB element
  *
  * \return XPath for finding \p element_name in CIB XML (or NULL if unknown)
  * \note The return value is constant and should not be freed.
  */
 const char *
 pcmk_cib_xpath_for(const char *element_name)
 {
     for (int lpc = 0; lpc < PCMK__NELEM(cib_sections); lpc++) {
         // A NULL element_name will match the first entry
         if (pcmk__str_eq(element_name, cib_sections[lpc].name,
                          pcmk__str_null_matches)) {
             return cib_sections[lpc].path;
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get the absolute XPath needed to find a specified CIB element name
  *
  * \param[in] element  Name of CIB element
  *
  * \return XPath for finding \p element in CIB XML (or \c NULL if unknown)
  */
 const char *
 pcmk__cib_abs_xpath_for(const char *element)
 {
     const char *xpath = pcmk_cib_xpath_for(element);
 
     // XPaths returned by pcmk_cib_xpath_for() are relative (starting with "//")
     return ((xpath != NULL)? (xpath + 1) : NULL);
 }
 
 /*!
  * \brief Get the parent element name of a given CIB element name
  *
  * \param[in] element_name  Name of CIB element
  *
  * \return Parent element of \p element_name (or NULL if none or unknown)
  * \note The return value is constant and should not be freed.
  */
 const char *
 pcmk_cib_parent_name_for(const char *element_name)
 {
     for (int lpc = 0; lpc < PCMK__NELEM(cib_sections); lpc++) {
         // A NULL element_name will match the first entry
         if (pcmk__str_eq(element_name, cib_sections[lpc].name,
                          pcmk__str_null_matches)) {
             return cib_sections[lpc].parent;
         }
     }
     return NULL;
 }
 
 /*!
  * \brief Find an element in the CIB
  *
  * \param[in,out] cib           Top-level CIB XML to search
  * \param[in]     element_name  Name of CIB element to search for
  *
  * \return XML element in \p cib corresponding to \p element_name
  *         (or \p cib itself if element is unknown or not found)
  */
 xmlNode *
 pcmk_find_cib_element(xmlNode *cib, const char *element_name)
 {
     return get_xpath_object(pcmk_cib_xpath_for(element_name), cib, LOG_TRACE);
 }
diff --git a/lib/pacemaker/pcmk_resource.c b/lib/pacemaker/pcmk_resource.c
index 61f83867f4..50244d31a7 100644
--- a/lib/pacemaker/pcmk_resource.c
+++ b/lib/pacemaker/pcmk_resource.c
@@ -1,248 +1,248 @@
 /*
  * Copyright 2021-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <errno.h>
 #include <glib.h>
 #include <libxml/tree.h>
 
 #include <crm/cib/internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/results.h>
 #include <crm/common/output_internal.h>
 #include <crm/pengine/internal.h>
 
 #include <pacemaker.h>
 #include <pacemaker-internal.h>
 
 // Search path for resource operation history (takes node name and resource ID)
 #define XPATH_OP_HISTORY "//" PCMK_XE_STATUS                                \
                          "/" XML_CIB_TAG_STATE "[@" PCMK_XA_UNAME "='%s']"  \
                          "/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES      \
                          "/" XML_LRM_TAG_RESOURCE "[@" PCMK_XA_ID "='%s']"
 
 static xmlNode *
 best_op(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     char *xpath = NULL;
     xmlNode *history = NULL;
     xmlNode *best = NULL;
     bool best_effective_op = false;
     guint best_interval = 0;
     bool best_failure = false;
     const char *best_digest = NULL;
 
     // Find node's resource history
     xpath = crm_strdup_printf(XPATH_OP_HISTORY, node->details->uname, rsc->id);
     history = get_xpath_object(xpath, rsc->cluster->input, LOG_NEVER);
     free(xpath);
 
     // Examine each history entry
     for (xmlNode *lrm_rsc_op = first_named_child(history, XML_LRM_TAG_RSC_OP);
          lrm_rsc_op != NULL; lrm_rsc_op = crm_next_same_xml(lrm_rsc_op)) {
 
         const char *digest = crm_element_value(lrm_rsc_op,
                                                PCMK__XA_OP_RESTART_DIGEST);
         guint interval_ms = 0;
         const char *task = crm_element_value(lrm_rsc_op, PCMK_XA_OPERATION);
         bool effective_op = false;
         bool failure = pcmk__ends_with(ID(lrm_rsc_op), "_last_failure_0");
 
 
         crm_element_value_ms(lrm_rsc_op, PCMK_META_INTERVAL, &interval_ms);
         effective_op = interval_ms == 0
                        && pcmk__strcase_any_of(task, PCMK_ACTION_MONITOR,
                                                PCMK_ACTION_START,
                                                PCMK_ACTION_PROMOTE,
                                                PCMK_ACTION_MIGRATE_FROM, NULL);
 
         if (best == NULL) {
             goto is_best;
         }
 
         if (best_effective_op) {
             // Do not use an ineffective op if there's an effective one.
             if (!effective_op) {
                 continue;
             }
         // Do not use an ineffective non-recurring op if there's a recurring one
         } else if (best_interval != 0
                    && !effective_op
                    && interval_ms == 0) {
             continue;
         }
 
         // Do not use last failure if there's a successful one.
         if (!best_failure && failure) {
             continue;
         }
 
         // Do not use an op without a restart digest if there's one with.
         if (best_digest != NULL && digest == NULL) {
             continue;
         }
 
         // Do not use an older op if there's a newer one.
         if (pe__is_newer_op(best, lrm_rsc_op, true) > 0) {
             continue;
         }
 
 is_best:
          best = lrm_rsc_op;
          best_effective_op = effective_op;
          best_interval = interval_ms;
          best_failure = failure;
          best_digest = digest;
     }
     return best;
 }
 
 /*!
  * \internal
  * \brief Remove a resource
  *
  * \param[in,out] cib       An open connection to the CIB
  * \param[in]     cib_opts  Options to use in the CIB operation call
  * \param[in]     rsc_id    Resource to remove
  * \param[in]     rsc_type  Type of the resource ("primitive", "group", etc.)
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__resource_delete(cib_t *cib, uint32_t cib_opts, const char *rsc_id,
                       const char *rsc_type)
 {
     int rc = pcmk_rc_ok;
     xmlNode *msg_data = NULL;
 
     if (cib == NULL) {
         return ENOTCONN;
     }
 
     if (rsc_id == NULL || rsc_type == NULL) {
         return EINVAL;
     }
 
     msg_data = create_xml_node(NULL, rsc_type);
     crm_xml_add(msg_data, PCMK_XA_ID, rsc_id);
 
-    rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, msg_data, cib_opts);
+    rc = cib->cmds->remove(cib, PCMK_XE_RESOURCES, msg_data, cib_opts);
     rc = pcmk_legacy2rc(rc);
 
     free_xml(msg_data);
     return rc;
 }
 
 int
 pcmk_resource_delete(xmlNodePtr *xml, const char *rsc_id, const char *rsc_type)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
     uint32_t cib_opts = cib_sync_call;
     cib_t *cib = NULL;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     cib = cib_new();
     if (cib == NULL) {
         rc = pcmk_rc_cib_corrupt;
         goto done;
     }
 
     rc = cib->cmds->signon(cib, crm_system_name, cib_command);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         goto done;
     }
 
     rc = pcmk__resource_delete(cib, cib_opts, rsc_id, rsc_type);
 
 done:
     if (cib != NULL) {
         cib__clean_up_connection(&cib);
     }
 
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Calculate and output resource operation digests
  *
  * \param[in,out] out        Output object
  * \param[in,out] rsc        Resource to calculate digests for
  * \param[in]     node       Node whose operation history should be used
  * \param[in]     overrides  Hash table of configuration parameters to override
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__resource_digests(pcmk__output_t *out, pcmk_resource_t *rsc,
                        const pcmk_node_t *node, GHashTable *overrides)
 {
     const char *task = NULL;
     xmlNode *xml_op = NULL;
     pcmk__op_digest_t *digests = NULL;
     guint interval_ms = 0;
     int rc = pcmk_rc_ok;
 
     if ((out == NULL) || (rsc == NULL) || (node == NULL)) {
         return EINVAL;
     }
     if (rsc->variant != pcmk_rsc_variant_primitive) {
         // Only primitives get operation digests
         return EOPNOTSUPP;
     }
 
     // Find XML of operation history to use
     xml_op = best_op(rsc, node);
 
     // Generate an operation key
     if (xml_op != NULL) {
         task = crm_element_value(xml_op, PCMK_XA_OPERATION);
         crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms);
     }
     if (task == NULL) { // Assume start if no history is available
         task = PCMK_ACTION_START;
         interval_ms = 0;
     }
 
     // Calculate and show digests
     digests = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op,
                                     overrides, true, rsc->cluster);
     rc = out->message(out, "digests", rsc, node, task, interval_ms, digests);
 
     pe__free_digests(digests);
     return rc;
 }
 
 // @COMPAT The scheduler parameter is unused and can be removed at the next break
 int
 pcmk_resource_digests(xmlNodePtr *xml, pcmk_resource_t *rsc,
                       const pcmk_node_t *node, GHashTable *overrides,
                       pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
     pcmk__register_lib_messages(out);
     rc = pcmk__resource_digests(out, rsc, node, overrides);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
     return rc;
 }
diff --git a/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c b/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c
index 7d9181bd99..7fbeb5c978 100644
--- a/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c
+++ b/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c
@@ -1,185 +1,185 @@
 /*
  * Copyright 2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/cib/internal.h>
 #include <crm/common/unittest_internal.h>
 #include <crm/msg_xml.h>
 #include <pacemaker.h>
 
 static char *cib_path = NULL;
 
 static int
 setup_group(void **state)
 {
     /* This needs to be run before we attempt to read in a CIB or it will fail
      * to validate.  There's no harm in doing this before all tests.
      */
     crm_xml_init();
     return 0;
 }
 
 static void
 cib_not_connected(void **state)
 {
     xmlNode *xml = NULL;
 
     /* Without any special setup, cib_new() in pcmk_resource_delete will use the
      * native CIB which means IPC calls.  But there's nothing listening for those
      * calls, so signon() will return ENOTCONN.  Check that we handle that.
      */
     assert_int_equal(pcmk_resource_delete(&xml, "rsc", "primitive"), ENOTCONN);
     free_xml(xml);
 }
 
 static int
 setup_test(void **state)
 {
     char *in_path = crm_strdup_printf("%s/crm_mon.xml", getenv("PCMK_CTS_CLI_DIR"));
     char *contents = NULL;
     int fd;
 
     /* Copy the CIB over to a temp location so we can modify it. */
     cib_path = crm_strdup_printf("%s/test-cib.XXXXXX", pcmk__get_tmpdir());
 
     fd = mkstemp(cib_path);
     if (fd < 0) {
         free(cib_path);
         return -1;
     }
 
     if (pcmk__file_contents(in_path, &contents) != pcmk_rc_ok) {
         free(cib_path);
         close(fd);
         return -1;
     }
 
     if (pcmk__write_sync(fd, contents) != pcmk_rc_ok) {
         free(cib_path);
         free(in_path);
         free(contents);
         close(fd);
         return -1;
     }
 
     setenv("CIB_file", cib_path, 1);
     return 0;
 }
 
 static int
 teardown_test(void **state)
 {
     unlink(cib_path);
     free(cib_path);
     cib_path = NULL;
 
     unsetenv("CIB_file");
     return 0;
 }
 
 static void
 bad_input(void **state)
 {
     xmlNode *xml = NULL;
 
     /* There is a primitive resource named "Fencing", so we're just checking
      * that it returns EINVAL if both parameters aren't given.
      */
     assert_int_equal(pcmk_resource_delete(&xml, "Fencing", NULL), EINVAL);
     free_xml(xml);
     xml = NULL;
 
     assert_int_equal(pcmk_resource_delete(&xml, NULL, "primitive"), EINVAL);
     free_xml(xml);
 }
 
 static xmlNode *
 find_rsc(const char *rsc)
 {
     GString *xpath = g_string_sized_new(1024);
     xmlNode *xml_search = NULL;
     cib_t *cib = NULL;
 
     cib = cib_new();
     cib->cmds->signon(cib, crm_system_name, cib_command);
 
     pcmk__g_strcat(xpath,
-                   pcmk_cib_xpath_for(XML_CIB_TAG_RESOURCES),
+                   pcmk_cib_xpath_for(PCMK_XE_RESOURCES),
                    "//*[@" PCMK_XA_ID "=\"", rsc, "\"]", NULL);
 
     cib->cmds->query(cib, (const char *) xpath->str, &xml_search,
                      cib_xpath|cib_scope_local);
 
     g_string_free(xpath, TRUE);
     cib__clean_up_connection(&cib);
     return xml_search;
 }
 
 static void
 incorrect_type(void **state)
 {
     xmlNode *xml = NULL;
     xmlNode *result = NULL;
 
     /* cib_process_delete returns pcmk_ok even if given the wrong type so
      * we have to do an xpath query of the CIB to make sure it's still
      * there.
      */
     assert_int_equal(pcmk_resource_delete(&xml, "Fencing", "clone"), pcmk_rc_ok);
     free_xml(xml);
 
     result = find_rsc("Fencing");
     assert_non_null(result);
 
     free_xml(result);
 }
 
 static void
 correct_type(void **state)
 {
     xmlNode *xml = NULL;
     xmlNode *result = NULL;
 
     assert_int_equal(pcmk_resource_delete(&xml, "Fencing", "primitive"), pcmk_rc_ok);
     free_xml(xml);
 
     result = find_rsc("Fencing");
     assert_null(result);
 
     free_xml(result);
 }
 
 static void
 unknown_resource(void **state)
 {
     xmlNode *xml = NULL;
 
     /* cib_process_delete returns pcmk_ok even if asked to delete something
      * that doesn't exist.
      */
     assert_int_equal(pcmk_resource_delete(&xml, "no_such_resource", "primitive"), pcmk_rc_ok);
     free_xml(xml);
 }
 
 /* There are two kinds of tests in this file:
  *
  * (1) Those that test what happens if the CIB is not set up correctly, and
  * (2) Those that test what happens when run against a CIB.
  *
  * Therefore, we need two kinds of setup/teardown functions.  We only do
  * minimal overall setup for the entire group, and then setup the CIB for
  * those tests that need it.
  */
 PCMK__UNIT_TEST(setup_group, NULL,
                 cmocka_unit_test(cib_not_connected),
                 cmocka_unit_test_setup_teardown(bad_input, setup_test, teardown_test),
                 cmocka_unit_test_setup_teardown(incorrect_type, setup_test, teardown_test),
                 cmocka_unit_test_setup_teardown(correct_type, setup_test, teardown_test),
                 cmocka_unit_test_setup_teardown(unknown_resource, setup_test, teardown_test))
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index fb83deda0b..483b4036e6 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1203 +1,1203 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/scheduler_internal.h>
 
 #include "pe_status_private.h"
 
 void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length);
 
 static pcmk_node_t *active_node(const pcmk_resource_t *rsc,
                                 unsigned int *count_all,
                                 unsigned int *count_clean);
 
 pcmk_rsc_methods_t resource_class_functions[] = {
     {
          native_unpack,
          native_find_rsc,
          native_parameter,
          native_print,
          native_active,
          native_resource_state,
          native_location,
          native_free,
          pe__count_common,
          pe__native_is_filtered,
          active_node,
          pe__primitive_max_per_node,
     },
     {
          group_unpack,
          native_find_rsc,
          native_parameter,
          group_print,
          group_active,
          group_resource_state,
          native_location,
          group_free,
          pe__count_common,
          pe__group_is_filtered,
          active_node,
          pe__group_max_per_node,
     },
     {
          clone_unpack,
          native_find_rsc,
          native_parameter,
          clone_print,
          clone_active,
          clone_resource_state,
          native_location,
          clone_free,
          pe__count_common,
          pe__clone_is_filtered,
          active_node,
          pe__clone_max_per_node,
     },
     {
          pe__unpack_bundle,
          native_find_rsc,
          native_parameter,
          pe__print_bundle,
          pe__bundle_active,
          pe__bundle_resource_state,
          native_location,
          pe__free_bundle,
          pe__count_bundle,
          pe__bundle_is_filtered,
          pe__bundle_active_node,
          pe__bundle_max_per_node,
     }
 };
 
 static enum pe_obj_types
 get_resource_type(const char *name)
 {
     if (pcmk__str_eq(name, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) {
         return pcmk_rsc_variant_primitive;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_GROUP, pcmk__str_casei)) {
         return pcmk_rsc_variant_group;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_INCARNATION, pcmk__str_casei)) {
         return pcmk_rsc_variant_clone;
 
     } else if (pcmk__str_eq(name, PCMK__XE_PROMOTABLE_LEGACY,
                             pcmk__str_casei)) {
         // @COMPAT deprecated since 2.0.0
         return pcmk_rsc_variant_clone;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_CONTAINER, pcmk__str_casei)) {
         return pcmk_rsc_variant_bundle;
     }
 
     return pcmk_rsc_variant_unknown;
 }
 
 static void
 dup_attr(gpointer key, gpointer value, gpointer user_data)
 {
     add_hash_param(user_data, key, value);
 }
 
 static void
 expand_parents_fixed_nvpairs(pcmk_resource_t *rsc,
                              pe_rule_eval_data_t *rule_data,
                              GHashTable *meta_hash, pcmk_scheduler_t *scheduler)
 {
     GHashTable *parent_orig_meta = pcmk__strkey_table(free, free);
     pcmk_resource_t *p = rsc->parent;
 
     if (p == NULL) {
         return ;
     }
 
     /* Search all parent resources, get the fixed value of "meta_attributes" set only in the original xml, and stack it in the hash table. */
     /* The fixed value of the lower parent resource takes precedence and is not overwritten. */
     while(p != NULL) {
         /* A hash table for comparison is generated, including the id-ref. */
         pe__unpack_dataset_nvpairs(p->xml, XML_TAG_META_SETS, rule_data,
                                    parent_orig_meta, NULL, FALSE, scheduler);
         p = p->parent; 
     }
 
     /* If there is a fixed value of "meta_attributes" of the parent resource, it will be processed. */
     if (parent_orig_meta != NULL) {
         GHashTableIter iter;
         char *key = NULL;
         char *value = NULL;
 
         g_hash_table_iter_init(&iter, parent_orig_meta);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
             /* Parameters set in the original xml of the parent resource will also try to overwrite the child resource. */
             /* Attributes that already exist in the child lease are not updated. */
             dup_attr(key, value, meta_hash);
         }
     }
 
     if (parent_orig_meta != NULL) {
         g_hash_table_destroy(parent_orig_meta);
     }
     
     return ;
 
 }
 void
 get_meta_attributes(GHashTable * meta_hash, pcmk_resource_t * rsc,
                     pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     pe_rsc_eval_data_t rsc_rule_data = {
         .standard = crm_element_value(rsc->xml, PCMK_XA_CLASS),
         .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
         .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE)
     };
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = pcmk_role_unknown,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = &rsc_rule_data,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->xml); a != NULL; a = a->next) {
         const char *prop_name = (const char *) a->name;
         const char *prop_value = pcmk__xml_attr_value(a);
 
         add_hash_param(meta_hash, prop_name, prop_value);
     }
 
     pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_META_SETS, &rule_data,
                                meta_hash, NULL, FALSE, scheduler);
 
     /* Set the "meta_attributes" explicitly set in the parent resource to the hash table of the child resource. */
     /* If it is already explicitly set as a child, it will not be overwritten. */
     if (rsc->parent != NULL) {
         expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, scheduler);
     }
 
     /* check the defaults */
     pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, XML_TAG_META_SETS,
                                &rule_data, meta_hash, NULL, FALSE, scheduler);
 
     /* If there is "meta_attributes" that the parent resource has not explicitly set, set a value that is not set from rsc_default either. */
     /* The values already set up to this point will not be overwritten. */
     if (rsc->parent) {
         g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash);
     }
 }
 
 void
 get_rsc_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc,
                    const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = pcmk_role_unknown,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_ATTR_SETS, &rule_data,
                                meta_hash, NULL, FALSE, scheduler);
 
     /* set anything else based on the parent */
     if (rsc->parent != NULL) {
         get_rsc_attributes(meta_hash, rsc->parent, node, scheduler);
 
     } else {
         /* and finally check the defaults */
         pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, XML_TAG_ATTR_SETS,
                                    &rule_data, meta_hash, NULL, FALSE,
                                    scheduler);
     }
 }
 
 static char *
 template_op_key(xmlNode * op)
 {
     const char *name = crm_element_value(op, PCMK_XA_NAME);
     const char *role = crm_element_value(op, PCMK_XA_ROLE);
     char *key = NULL;
 
     if ((role == NULL)
         || pcmk__strcase_any_of(role, PCMK__ROLE_STARTED, PCMK__ROLE_UNPROMOTED,
                                 PCMK__ROLE_UNPROMOTED_LEGACY, NULL)) {
         role = PCMK__ROLE_UNKNOWN;
     }
 
     key = crm_strdup_printf("%s-%s", name, role);
     return key;
 }
 
 static gboolean
 unpack_template(xmlNode *xml_obj, xmlNode **expanded_xml,
                 pcmk_scheduler_t *scheduler)
 {
     xmlNode *cib_resources = NULL;
     xmlNode *template = NULL;
     xmlNode *new_xml = NULL;
     xmlNode *child_xml = NULL;
     xmlNode *rsc_ops = NULL;
     xmlNode *template_ops = NULL;
     const char *template_ref = NULL;
     const char *id = NULL;
 
     if (xml_obj == NULL) {
         pcmk__config_err("No resource object for template unpacking");
         return FALSE;
     }
 
     template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
     if (template_ref == NULL) {
         return TRUE;
     }
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("'%s' object must have a id", xml_obj->name);
         return FALSE;
     }
 
     if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
         pcmk__config_err("The resource object '%s' should not reference itself",
                          id);
         return FALSE;
     }
 
-    cib_resources = get_xpath_object("//" XML_CIB_TAG_RESOURCES,
-                                     scheduler->input, LOG_TRACE);
+    cib_resources = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input,
+                                     LOG_TRACE);
     if (cib_resources == NULL) {
         pcmk__config_err("No resources configured");
         return FALSE;
     }
 
     template = pcmk__xe_match(cib_resources, XML_CIB_TAG_RSC_TEMPLATE,
                               PCMK_XA_ID, template_ref);
     if (template == NULL) {
         pcmk__config_err("No template named '%s'", template_ref);
         return FALSE;
     }
 
     new_xml = copy_xml(template);
     xmlNodeSetName(new_xml, xml_obj->name);
     crm_xml_add(new_xml, PCMK_XA_ID, id);
     crm_xml_add(new_xml, PCMK__META_CLONE,
                 crm_element_value(xml_obj, PCMK__META_CLONE));
 
     template_ops = find_xml_node(new_xml, "operations", FALSE);
 
     for (child_xml = pcmk__xe_first_child(xml_obj); child_xml != NULL;
          child_xml = pcmk__xe_next(child_xml)) {
         xmlNode *new_child = NULL;
 
         new_child = add_node_copy(new_xml, child_xml);
 
         if (pcmk__str_eq((const char *)new_child->name, "operations", pcmk__str_none)) {
             rsc_ops = new_child;
         }
     }
 
     if (template_ops && rsc_ops) {
         xmlNode *op = NULL;
         GHashTable *rsc_ops_hash = pcmk__strkey_table(free, NULL);
 
         for (op = pcmk__xe_first_child(rsc_ops); op != NULL;
              op = pcmk__xe_next(op)) {
 
             char *key = template_op_key(op);
 
             g_hash_table_insert(rsc_ops_hash, key, op);
         }
 
         for (op = pcmk__xe_first_child(template_ops); op != NULL;
              op = pcmk__xe_next(op)) {
 
             char *key = template_op_key(op);
 
             if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
                 add_node_copy(rsc_ops, op);
             }
 
             free(key);
         }
 
         if (rsc_ops_hash) {
             g_hash_table_destroy(rsc_ops_hash);
         }
 
         free_xml(template_ops);
     }
 
     /*free_xml(*expanded_xml); */
     *expanded_xml = new_xml;
 
 #if 0 /* Disable multi-level templates for now */
     if (!unpack_template(new_xml, expanded_xml, scheduler)) {
        free_xml(*expanded_xml);
        *expanded_xml = NULL;
        return FALSE;
     }
 #endif
 
     return TRUE;
 }
 
 static gboolean
 add_template_rsc(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     const char *template_ref = NULL;
     const char *id = NULL;
 
     if (xml_obj == NULL) {
         pcmk__config_err("No resource object for processing resource list "
                          "of template");
         return FALSE;
     }
 
     template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
     if (template_ref == NULL) {
         return TRUE;
     }
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("'%s' object must have a id", xml_obj->name);
         return FALSE;
     }
 
     if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
         pcmk__config_err("The resource object '%s' should not reference itself",
                          id);
         return FALSE;
     }
 
     if (add_tag_ref(scheduler->template_rsc_sets, template_ref, id) == FALSE) {
         return FALSE;
     }
 
     return TRUE;
 }
 
 static bool
 detect_promotable(pcmk_resource_t *rsc)
 {
     const char *promotable = g_hash_table_lookup(rsc->meta,
                                                  PCMK_META_PROMOTABLE);
 
     if (crm_is_true(promotable)) {
         return TRUE;
     }
 
     // @COMPAT deprecated since 2.0.0
     if (pcmk__xe_is(rsc->xml, PCMK__XE_PROMOTABLE_LEGACY)) {
         /* @TODO in some future version, pcmk__warn_once() here,
          *       then drop support in even later version
          */
         g_hash_table_insert(rsc->meta, strdup(PCMK_META_PROMOTABLE),
                             strdup(PCMK_VALUE_TRUE));
         return TRUE;
     }
     return FALSE;
 }
 
 static void
 free_params_table(gpointer data)
 {
     g_hash_table_destroy((GHashTable *) data);
 }
 
 /*!
  * \brief Get a table of resource parameters
  *
  * \param[in,out] rsc        Resource to query
  * \param[in]     node       Node for evaluating rules (NULL for defaults)
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Hash table containing resource parameter names and values
  *         (or NULL if \p rsc or \p scheduler is NULL)
  * \note The returned table will be destroyed when the resource is freed, so
  *       callers should not destroy it.
  */
 GHashTable *
 pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node,
               pcmk_scheduler_t *scheduler)
 {
     GHashTable *params_on_node = NULL;
 
     /* A NULL node is used to request the resource's default parameters
      * (not evaluated for node), but we always want something non-NULL
      * as a hash table key.
      */
     const char *node_name = "";
 
     // Sanity check
     if ((rsc == NULL) || (scheduler == NULL)) {
         return NULL;
     }
     if ((node != NULL) && (node->details->uname != NULL)) {
         node_name = node->details->uname;
     }
 
     // Find the parameter table for given node
     if (rsc->parameter_cache == NULL) {
         rsc->parameter_cache = pcmk__strikey_table(free, free_params_table);
     } else {
         params_on_node = g_hash_table_lookup(rsc->parameter_cache, node_name);
     }
 
     // If none exists yet, create one with parameters evaluated for node
     if (params_on_node == NULL) {
         params_on_node = pcmk__strkey_table(free, free);
         get_rsc_attributes(params_on_node, rsc, node, scheduler);
         g_hash_table_insert(rsc->parameter_cache, strdup(node_name),
                             params_on_node);
     }
     return params_on_node;
 }
 
 /*!
  * \internal
  * \brief Unpack a resource's "requires" meta-attribute
  *
  * \param[in,out] rsc         Resource being unpacked
  * \param[in]     value       Value of "requires" meta-attribute
  * \param[in]     is_default  Whether \p value was selected by default
  */
 static void
 unpack_requires(pcmk_resource_t *rsc, const char *value, bool is_default)
 {
     if (pcmk__str_eq(value, PCMK__VALUE_NOTHING, pcmk__str_casei)) {
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_QUORUM, pcmk__str_casei)) {
         pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_quorum);
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_FENCING, pcmk__str_casei)) {
         pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_fencing);
         if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
             pcmk__config_warn("%s requires fencing but fencing is disabled",
                               rsc->id);
         }
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_UNFENCING, pcmk__str_casei)) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) {
             pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
                               "to \"" PCMK__VALUE_QUORUM "\" because fencing "
                               "devices cannot require unfencing", rsc->id);
             unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
             return;
 
         } else if (!pcmk_is_set(rsc->cluster->flags,
                                 pcmk_sched_fencing_enabled)) {
             pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
                               "to \"" PCMK__VALUE_QUORUM "\" because fencing "
                               "is disabled", rsc->id);
             unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
             return;
 
         } else {
             pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_fencing
                                      |pcmk_rsc_needs_unfencing);
         }
 
     } else {
         const char *orig_value = value;
 
         if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) {
             value = PCMK__VALUE_QUORUM;
 
         } else if ((rsc->variant == pcmk_rsc_variant_primitive)
                    && xml_contains_remote_node(rsc->xml)) {
             value = PCMK__VALUE_QUORUM;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_enable_unfencing)) {
             value = PCMK__VALUE_UNFENCING;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_fencing_enabled)) {
             value = PCMK__VALUE_FENCING;
 
         } else if (rsc->cluster->no_quorum_policy == pcmk_no_quorum_ignore) {
             value = PCMK__VALUE_NOTHING;
 
         } else {
             value = PCMK__VALUE_QUORUM;
         }
 
         if (orig_value != NULL) {
             pcmk__config_err("Resetting '" PCMK_META_REQUIRES "' for %s "
                              "to '%s' because '%s' is not valid",
                               rsc->id, value, orig_value);
         }
         unpack_requires(rsc, value, true);
         return;
     }
 
     pcmk__rsc_trace(rsc, "\tRequired to start: %s%s", value,
                     (is_default? " (default)" : ""));
 }
 
 #ifndef PCMK__COMPAT_2_0
 static void
 warn_about_deprecated_classes(pcmk_resource_t *rsc)
 {
     const char *std = crm_element_value(rsc->xml, PCMK_XA_CLASS);
 
     if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_none)) {
         pcmk__warn_once(pcmk__wo_upstart,
                         "Support for Upstart resources (such as %s) is "
                         "deprecated and will be removed in a future release",
                         rsc->id);
 
     } else if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_none)) {
         pcmk__warn_once(pcmk__wo_nagios,
                         "Support for Nagios resources (such as %s) is "
                         "deprecated and will be removed in a future release",
                         rsc->id);
     }
 }
 #endif
 
 /*!
  * \internal
  * \brief Unpack configuration XML for a given resource
  *
  * Unpack the XML object containing a resource's configuration into a new
  * \c pcmk_resource_t object.
  *
  * \param[in]     xml_obj    XML node containing the resource's configuration
  * \param[out]    rsc        Where to store the unpacked resource information
  * \param[in]     parent     Resource's parent, if any
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Standard Pacemaker return code
  * \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and
  *       the caller is responsible for freeing it using its variant-specific
  *       free() method. Otherwise, \p *rsc is guaranteed to be NULL.
  */
 int
 pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc,
                     pcmk_resource_t *parent, pcmk_scheduler_t *scheduler)
 {
     xmlNode *expanded_xml = NULL;
     xmlNode *ops = NULL;
     const char *value = NULL;
     const char *id = NULL;
     bool guest_node = false;
     bool remote_node = false;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = pcmk_role_unknown,
         .now = NULL,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     CRM_CHECK(rsc != NULL, return EINVAL);
     CRM_CHECK((xml_obj != NULL) && (scheduler != NULL),
               *rsc = NULL;
               return EINVAL);
 
     rule_data.now = scheduler->now;
 
     crm_log_xml_trace(xml_obj, "[raw XML]");
 
     id = crm_element_value(xml_obj, PCMK_XA_ID);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> configuration without " PCMK_XA_ID,
                          xml_obj->name);
         return pcmk_rc_unpack_error;
     }
 
     if (unpack_template(xml_obj, &expanded_xml, scheduler) == FALSE) {
         return pcmk_rc_unpack_error;
     }
 
     *rsc = calloc(1, sizeof(pcmk_resource_t));
     if (*rsc == NULL) {
         pcmk__sched_err("Unable to allocate memory for resource '%s'", id);
         return ENOMEM;
     }
     (*rsc)->cluster = scheduler;
 
     if (expanded_xml) {
         crm_log_xml_trace(expanded_xml, "[expanded XML]");
         (*rsc)->xml = expanded_xml;
         (*rsc)->orig_xml = xml_obj;
 
     } else {
         (*rsc)->xml = xml_obj;
         (*rsc)->orig_xml = NULL;
     }
 
     /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
 
     (*rsc)->parent = parent;
 
     ops = find_xml_node((*rsc)->xml, "operations", FALSE);
     (*rsc)->ops_xml = expand_idref(ops, scheduler->input);
 
     (*rsc)->variant = get_resource_type((const char *) (*rsc)->xml->name);
     if ((*rsc)->variant == pcmk_rsc_variant_unknown) {
         pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
                          id, (*rsc)->xml->name);
         common_free(*rsc);
         *rsc = NULL;
         return pcmk_rc_unpack_error;
     }
 
 #ifndef PCMK__COMPAT_2_0
     warn_about_deprecated_classes(*rsc);
 #endif
 
     (*rsc)->meta = pcmk__strkey_table(free, free);
     (*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free);
     (*rsc)->known_on = pcmk__strkey_table(NULL, free);
 
     value = crm_element_value((*rsc)->xml, PCMK__META_CLONE);
     if (value) {
         (*rsc)->id = crm_strdup_printf("%s:%s", id, value);
         add_hash_param((*rsc)->meta, PCMK__META_CLONE, value);
 
     } else {
         (*rsc)->id = strdup(id);
     }
 
     (*rsc)->fns = &resource_class_functions[(*rsc)->variant];
 
     get_meta_attributes((*rsc)->meta, *rsc, NULL, scheduler);
     (*rsc)->parameters = pe_rsc_params(*rsc, NULL, scheduler); // \deprecated
 
     (*rsc)->flags = 0;
     pcmk__set_rsc_flags(*rsc, pcmk_rsc_runnable|pcmk_rsc_unassigned);
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed);
     }
 
     (*rsc)->rsc_cons = NULL;
     (*rsc)->rsc_tickets = NULL;
     (*rsc)->actions = NULL;
     (*rsc)->role = pcmk_role_stopped;
     (*rsc)->next_role = pcmk_role_unknown;
 
     (*rsc)->recovery_type = pcmk_multiply_active_restart;
     (*rsc)->stickiness = 0;
     (*rsc)->migration_threshold = INFINITY;
     (*rsc)->failure_timeout = 0;
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_PRIORITY);
     (*rsc)->priority = char2score(value);
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_CRITICAL);
     if ((value == NULL) || crm_is_true(value)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_critical);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_NOTIFY);
     if (crm_is_true(value)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_notify);
     }
 
     if (xml_contains_remote_node((*rsc)->xml)) {
         (*rsc)->is_remote_node = TRUE;
         if (g_hash_table_lookup((*rsc)->meta, PCMK__META_CONTAINER)) {
             guest_node = true;
         } else {
             remote_node = true;
         }
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_ALLOW_MIGRATE);
     if (crm_is_true(value)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_migratable);
     } else if ((value == NULL) && remote_node) {
         /* By default, we want remote nodes to be able
          * to float around the cluster without having to stop all the
          * resources within the remote-node before moving. Allowing
          * migration support enables this feature. If this ever causes
          * problems, migration support can be explicitly turned off with
          * PCMK_META_ALLOW_MIGRATE=false.
          */
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_migratable);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_IS_MANAGED);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         if (crm_is_true(value)) {
             pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed);
         } else {
             pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
         }
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MAINTENANCE);
     if (crm_is_true(value)) {
         pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance);
     }
     if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
         pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed);
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance);
     }
 
     if (pe_rsc_is_clone(pe__const_top_resource(*rsc, false))) {
         value = g_hash_table_lookup((*rsc)->meta, PCMK_META_GLOBALLY_UNIQUE);
         if (crm_is_true(value)) {
             pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique);
         }
         if (detect_promotable(*rsc)) {
             pcmk__set_rsc_flags(*rsc, pcmk_rsc_promotable);
         }
     } else {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique);
     }
 
     // @COMPAT Deprecated meta-attribute
     value = g_hash_table_lookup((*rsc)->meta, PCMK__META_RESTART_TYPE);
     if (pcmk__str_eq(value, "restart", pcmk__str_casei)) {
         (*rsc)->restart_type = pe_restart_restart;
         pcmk__rsc_trace(*rsc, "%s dependency restart handling: restart",
                         (*rsc)->id);
         pcmk__warn_once(pcmk__wo_restart_type,
                         "Support for " PCMK__META_RESTART_TYPE " is deprecated "
                         "and will be removed in a future release");
 
     } else {
         (*rsc)->restart_type = pe_restart_ignore;
         pcmk__rsc_trace(*rsc, "%s dependency restart handling: ignore",
                         (*rsc)->id);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MULTIPLE_ACTIVE);
     if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) {
         (*rsc)->recovery_type = pcmk_multiply_active_stop;
         pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop only",
                         (*rsc)->id);
 
     } else if (pcmk__str_eq(value, "block", pcmk__str_casei)) {
         (*rsc)->recovery_type = pcmk_multiply_active_block;
         pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block",
                         (*rsc)->id);
 
     } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) {
         (*rsc)->recovery_type = pcmk_multiply_active_unexpected;
         pcmk__rsc_trace(*rsc,
                         "%s multiple running resource recovery: "
                         "stop unexpected instances",
                         (*rsc)->id);
 
     } else { // "stop_start"
         if (!pcmk__str_eq(value, "stop_start",
                           pcmk__str_casei|pcmk__str_null_matches)) {
             pcmk__config_warn("%s is not a valid value for "
                               PCMK_META_MULTIPLE_ACTIVE
                               ", using default of \"stop_start\"", value);
         }
         (*rsc)->recovery_type = pcmk_multiply_active_restart;
         pcmk__rsc_trace(*rsc,
                         "%s multiple running resource recovery: stop/start",
                         (*rsc)->id);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_RESOURCE_STICKINESS);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         (*rsc)->stickiness = char2score(value);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MIGRATION_THRESHOLD);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         (*rsc)->migration_threshold = char2score(value);
         if ((*rsc)->migration_threshold < 0) {
             /* @TODO We use 1 here to preserve previous behavior, but this
              * should probably use the default (INFINITY) or 0 (to disable)
              * instead.
              */
             pcmk__warn_once(pcmk__wo_neg_threshold,
                             PCMK_META_MIGRATION_THRESHOLD
                             " must be non-negative, using 1 instead");
             (*rsc)->migration_threshold = 1;
         }
     }
 
     if (pcmk__str_eq(crm_element_value((*rsc)->xml, PCMK_XA_CLASS),
                      PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_fencing);
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_fence_device);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_REQUIRES);
     unpack_requires(*rsc, value, false);
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_FAILURE_TIMEOUT);
     if (value != NULL) {
         guint interval_ms = 0U;
 
         // Stored as seconds
         pcmk_parse_interval_spec(value, &interval_ms);
         (*rsc)->failure_timeout = (int) (interval_ms / 1000);
     }
 
     if (remote_node) {
         GHashTable *params = pe_rsc_params(*rsc, NULL, scheduler);
 
         /* Grabbing the value now means that any rules based on node attributes
          * will evaluate to false, so such rules should not be used with
          * PCMK_REMOTE_RA_RECONNECT_INTERVAL.
          *
          * @TODO Evaluate per node before using
          */
         value = g_hash_table_lookup(params, PCMK_REMOTE_RA_RECONNECT_INTERVAL);
         if (value) {
             /* reconnect delay works by setting failure_timeout and preventing the
              * connection from starting until the failure is cleared. */
             pcmk_parse_interval_spec(value, &((*rsc)->remote_reconnect_ms));
 
             /* We want to override any default failure_timeout in use when remote
              * PCMK_REMOTE_RA_RECONNECT_INTERVAL is in use.
              */
             (*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000;
         }
     }
 
     get_target_role(*rsc, &((*rsc)->next_role));
     pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id,
                     (*rsc)->next_role != pcmk_role_unknown? role2text((*rsc)->next_role) : "default");
 
     if ((*rsc)->fns->unpack(*rsc, scheduler) == FALSE) {
         (*rsc)->fns->free(*rsc);
         *rsc = NULL;
         return pcmk_rc_unpack_error;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
         // This tag must stay exactly the same because it is tested elsewhere
         resource_location(*rsc, NULL, 0, "symmetric_default", scheduler);
     } else if (guest_node) {
         /* remote resources tied to a container resource must always be allowed
          * to opt-in to the cluster. Whether the connection resource is actually
          * allowed to be placed on a node is dependent on the container resource */
         resource_location(*rsc, NULL, 0, "remote_connection_default",
                           scheduler);
     }
 
     pcmk__rsc_trace(*rsc, "%s action notification: %s", (*rsc)->id,
                     pcmk_is_set((*rsc)->flags, pcmk_rsc_notify)? "required" : "not required");
 
     (*rsc)->utilization = pcmk__strkey_table(free, free);
 
     pe__unpack_dataset_nvpairs((*rsc)->xml, XML_TAG_UTILIZATION, &rule_data,
                                (*rsc)->utilization, NULL, FALSE, scheduler);
 
     if (expanded_xml) {
         if (add_template_rsc(xml_obj, scheduler) == FALSE) {
             (*rsc)->fns->free(*rsc);
             *rsc = NULL;
             return pcmk_rc_unpack_error;
         }
     }
     return pcmk_rc_ok;
 }
 
 gboolean
 is_parent(pcmk_resource_t *child, pcmk_resource_t *rsc)
 {
     pcmk_resource_t *parent = child;
 
     if (parent == NULL || rsc == NULL) {
         return FALSE;
     }
     while (parent->parent != NULL) {
         if (parent->parent == rsc) {
             return TRUE;
         }
         parent = parent->parent;
     }
     return FALSE;
 }
 
 pcmk_resource_t *
 uber_parent(pcmk_resource_t *rsc)
 {
     pcmk_resource_t *parent = rsc;
 
     if (parent == NULL) {
         return NULL;
     }
     while ((parent->parent != NULL)
            && (parent->parent->variant != pcmk_rsc_variant_bundle)) {
         parent = parent->parent;
     }
     return parent;
 }
 
 /*!
  * \internal
  * \brief Get the topmost parent of a resource as a const pointer
  *
  * \param[in] rsc             Resource to check
  * \param[in] include_bundle  If true, go all the way to bundle
  *
  * \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent,
  *         the bundle if \p rsc is bundled and \p include_bundle is true,
  *         otherwise the topmost parent of \p rsc up to a clone
  */
 const pcmk_resource_t *
 pe__const_top_resource(const pcmk_resource_t *rsc, bool include_bundle)
 {
     const pcmk_resource_t *parent = rsc;
 
     if (parent == NULL) {
         return NULL;
     }
     while (parent->parent != NULL) {
         if (!include_bundle
             && (parent->parent->variant == pcmk_rsc_variant_bundle)) {
             break;
         }
         parent = parent->parent;
     }
     return parent;
 }
 
 void
 common_free(pcmk_resource_t * rsc)
 {
     if (rsc == NULL) {
         return;
     }
 
     pcmk__rsc_trace(rsc, "Freeing %s %d", rsc->id, rsc->variant);
 
     g_list_free(rsc->rsc_cons);
     g_list_free(rsc->rsc_cons_lhs);
     g_list_free(rsc->rsc_tickets);
     g_list_free(rsc->dangling_migrations);
 
     if (rsc->parameter_cache != NULL) {
         g_hash_table_destroy(rsc->parameter_cache);
     }
     if (rsc->meta != NULL) {
         g_hash_table_destroy(rsc->meta);
     }
     if (rsc->utilization != NULL) {
         g_hash_table_destroy(rsc->utilization);
     }
 
     if ((rsc->parent == NULL)
         && pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
 
         free_xml(rsc->xml);
         rsc->xml = NULL;
         free_xml(rsc->orig_xml);
         rsc->orig_xml = NULL;
 
         /* if rsc->orig_xml, then rsc->xml is an expanded xml from a template */
     } else if (rsc->orig_xml) {
         free_xml(rsc->xml);
         rsc->xml = NULL;
     }
     if (rsc->running_on) {
         g_list_free(rsc->running_on);
         rsc->running_on = NULL;
     }
     if (rsc->known_on) {
         g_hash_table_destroy(rsc->known_on);
         rsc->known_on = NULL;
     }
     if (rsc->actions) {
         g_list_free(rsc->actions);
         rsc->actions = NULL;
     }
     if (rsc->allowed_nodes) {
         g_hash_table_destroy(rsc->allowed_nodes);
         rsc->allowed_nodes = NULL;
     }
     g_list_free(rsc->fillers);
     g_list_free(rsc->rsc_location);
     pcmk__rsc_trace(rsc, "Resource freed");
     free(rsc->id);
     free(rsc->clone_name);
     free(rsc->allocated_to);
     free(rsc->variant_opaque);
     free(rsc->pending_task);
     free(rsc);
 }
 
 /*!
  * \internal
  * \brief Count a node and update most preferred to it as appropriate
  *
  * \param[in]     rsc          An active resource
  * \param[in]     node         A node that \p rsc is active on
  * \param[in,out] active       This will be set to \p node if \p node is more
  *                             preferred than the current value
  * \param[in,out] count_all    If not NULL, this will be incremented
  * \param[in,out] count_clean  If not NULL, this will be incremented if \p node
  *                             is online and clean
  *
  * \return true if the count should continue, or false if sufficiently known
  */
 bool
 pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node,
                       pcmk_node_t **active, unsigned int *count_all,
                       unsigned int *count_clean)
 {
     bool keep_looking = false;
     bool is_happy = false;
 
     CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL),
               return false);
 
     is_happy = node->details->online && !node->details->unclean;
 
     if (count_all != NULL) {
         ++*count_all;
     }
     if ((count_clean != NULL) && is_happy) {
         ++*count_clean;
     }
     if ((count_all != NULL) || (count_clean != NULL)) {
         keep_looking = true; // We're counting, so go through entire list
     }
 
     if (rsc->partial_migration_source != NULL) {
         if (pcmk__same_node(node, rsc->partial_migration_source)) {
             *active = node; // This is the migration source
         } else {
             keep_looking = true;
         }
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
         if (is_happy && ((*active == NULL) || !(*active)->details->online
                          || (*active)->details->unclean)) {
             *active = node; // This is the first clean node
         } else {
             keep_looking = true;
         }
     }
     if (*active == NULL) {
         *active = node; // This is the first node checked
     }
     return keep_looking;
 }
 
 // Shared implementation of pcmk_rsc_methods_t:active_node()
 static pcmk_node_t *
 active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
             unsigned int *count_clean)
 {
     pcmk_node_t *active = NULL;
 
     if (count_all != NULL) {
         *count_all = 0;
     }
     if (count_clean != NULL) {
         *count_clean = 0;
     }
     if (rsc == NULL) {
         return NULL;
     }
     for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
         if (!pe__count_active_node(rsc, (pcmk_node_t *) iter->data, &active,
                                    count_all, count_clean)) {
             break; // Don't waste time iterating if we don't have to
         }
     }
     return active;
 }
 
 /*!
  * \brief
  * \internal Find and count active nodes according to "requires"
  *
  * \param[in]  rsc    Resource to check
  * \param[out] count  If not NULL, will be set to count of active nodes
  *
  * \return An active node (or NULL if resource is not active anywhere)
  *
  * \note This is a convenience wrapper for active_node() where the count of all
  *       active nodes or only clean active nodes is desired according to the
  *       "requires" meta-attribute.
  */
 pcmk_node_t *
 pe__find_active_requires(const pcmk_resource_t *rsc, unsigned int *count)
 {
     if (rsc == NULL) {
         if (count != NULL) {
             *count = 0;
         }
         return NULL;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
         return rsc->fns->active_node(rsc, count, NULL);
 
     } else {
         return rsc->fns->active_node(rsc, NULL, count);
     }
 }
 
 void
 pe__count_common(pcmk_resource_t *rsc)
 {
     if (rsc->children != NULL) {
         for (GList *item = rsc->children; item != NULL; item = item->next) {
             ((pcmk_resource_t *) item->data)->fns->count(item->data);
         }
 
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)
                || (rsc->role > pcmk_role_stopped)) {
         rsc->cluster->ninstances++;
         if (pe__resource_is_disabled(rsc)) {
             rsc->cluster->disabled_resources++;
         }
         if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
             rsc->cluster->blocked_resources++;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Update a resource's next role
  *
  * \param[in,out] rsc   Resource to be updated
  * \param[in]     role  Resource's new next role
  * \param[in]     why   Human-friendly reason why role is changing (for logs)
  */
 void
 pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role, const char *why)
 {
     CRM_ASSERT((rsc != NULL) && (why != NULL));
     if (rsc->next_role != role) {
         pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
                         rsc->id, role2text(rsc->next_role), role2text(role),
                         why);
         rsc->next_role = role;
     }
 }
diff --git a/lib/pengine/status.c b/lib/pengine/status.c
index c0213ab2eb..28d4e35454 100644
--- a/lib/pengine/status.c
+++ b/lib/pengine/status.c
@@ -1,484 +1,484 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 
 #include <glib.h>
 
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 
 /*!
  * \brief Create a new object to hold scheduler data
  *
  * \return New, initialized scheduler data on success, else NULL (and set errno)
  * \note Only pcmk_scheduler_t objects created with this function (as opposed
  *       to statically declared or directly allocated) should be used with the
  *       functions in this library, to allow for future extensions to the
  *       data type. The caller is responsible for freeing the memory with
  *       pe_free_working_set() when the instance is no longer needed.
  */
 pcmk_scheduler_t *
 pe_new_working_set(void)
 {
     pcmk_scheduler_t *scheduler = calloc(1, sizeof(pcmk_scheduler_t));
 
     if (scheduler != NULL) {
         set_working_set_defaults(scheduler);
     }
     return scheduler;
 }
 
 /*!
  * \brief Free scheduler data
  *
  * \param[in,out] scheduler  Scheduler data to free
  */
 void
 pe_free_working_set(pcmk_scheduler_t *scheduler)
 {
     if (scheduler != NULL) {
         pe_reset_working_set(scheduler);
         scheduler->priv = NULL;
         free(scheduler);
     }
 }
 
 /*
  * Unpack everything
  * At the end you'll have:
  *  - A list of nodes
  *  - A list of resources (each with any dependencies on other resources)
  *  - A list of constraints between resources and nodes
  *  - A list of constraints between start/stop actions
  *  - A list of nodes that need to be stonith'd
  *  - A list of nodes that need to be shutdown
  *  - A list of the possible stop/start actions (without dependencies)
  */
 gboolean
 cluster_status(pcmk_scheduler_t * scheduler)
 {
     xmlNode *section = NULL;
 
     if ((scheduler == NULL) || (scheduler->input == NULL)) {
         return FALSE;
     }
 
     crm_trace("Beginning unpack");
 
     if (scheduler->failed != NULL) {
         free_xml(scheduler->failed);
     }
     scheduler->failed = create_xml_node(NULL, "failed-ops");
 
     if (scheduler->now == NULL) {
         scheduler->now = crm_time_new(NULL);
     }
 
     if (scheduler->dc_uuid == NULL) {
         scheduler->dc_uuid = crm_element_value_copy(scheduler->input,
                                                     PCMK_XA_DC_UUID);
     }
 
     if (pcmk__xe_attr_is_true(scheduler->input, PCMK_XA_HAVE_QUORUM)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_quorate);
     } else {
         pcmk__clear_scheduler_flags(scheduler, pcmk_sched_quorate);
     }
 
     scheduler->op_defaults = get_xpath_object("//" XML_CIB_TAG_OPCONFIG,
                                               scheduler->input, LOG_NEVER);
     scheduler->rsc_defaults = get_xpath_object("//" XML_CIB_TAG_RSCCONFIG,
                                                scheduler->input, LOG_NEVER);
 
     section = get_xpath_object("//" XML_CIB_TAG_CRMCONFIG, scheduler->input,
                                LOG_TRACE);
     unpack_config(section, scheduler);
 
    if (!pcmk_any_flags_set(scheduler->flags,
                            pcmk_sched_location_only|pcmk_sched_quorate)
        && (scheduler->no_quorum_policy != pcmk_no_quorum_ignore)) {
         pcmk__sched_warn("Fencing and resource management disabled "
                          "due to lack of quorum");
     }
 
     section = get_xpath_object("//" XML_CIB_TAG_NODES, scheduler->input,
                                LOG_TRACE);
     unpack_nodes(section, scheduler);
 
-    section = get_xpath_object("//" XML_CIB_TAG_RESOURCES, scheduler->input,
+    section = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input,
                                LOG_TRACE);
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         unpack_remote_nodes(section, scheduler);
     }
     unpack_resources(section, scheduler);
 
     section = get_xpath_object("//" XML_CIB_TAG_TAGS, scheduler->input,
                                LOG_NEVER);
     unpack_tags(section, scheduler);
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         section = get_xpath_object("//" PCMK_XE_STATUS, scheduler->input,
                                    LOG_TRACE);
         unpack_status(section, scheduler);
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_no_counts)) {
         for (GList *item = scheduler->resources; item != NULL;
              item = item->next) {
             ((pcmk_resource_t *) (item->data))->fns->count(item->data);
         }
         crm_trace("Cluster resource count: %d (%d disabled, %d blocked)",
                   scheduler->ninstances, scheduler->disabled_resources,
                   scheduler->blocked_resources);
     }
 
     pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_status);
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Free a list of pcmk_resource_t
  *
  * \param[in,out] resources  List to free
  *
  * \note When the scheduler's resource list is freed, that includes the original
  *       storage for the uname and id of any Pacemaker Remote nodes in the
  *       scheduler's node list, so take care not to use those afterward.
  * \todo Refactor pcmk_node_t to strdup() the node name.
  */
 static void
 pe_free_resources(GList *resources)
 {
     pcmk_resource_t *rsc = NULL;
     GList *iterator = resources;
 
     while (iterator != NULL) {
         rsc = (pcmk_resource_t *) iterator->data;
         iterator = iterator->next;
         rsc->fns->free(rsc);
     }
     if (resources != NULL) {
         g_list_free(resources);
     }
 }
 
 static void
 pe_free_actions(GList *actions)
 {
     GList *iterator = actions;
 
     while (iterator != NULL) {
         pe_free_action(iterator->data);
         iterator = iterator->next;
     }
     if (actions != NULL) {
         g_list_free(actions);
     }
 }
 
 static void
 pe_free_nodes(GList *nodes)
 {
     for (GList *iterator = nodes; iterator != NULL; iterator = iterator->next) {
         pcmk_node_t *node = (pcmk_node_t *) iterator->data;
 
         // Shouldn't be possible, but to be safe ...
         if (node == NULL) {
             continue;
         }
         if (node->details == NULL) {
             free(node);
             continue;
         }
 
         /* This is called after pe_free_resources(), which means that we can't
          * use node->details->uname for Pacemaker Remote nodes.
          */
         crm_trace("Freeing node %s", (pe__is_guest_or_remote_node(node)?
                   "(guest or remote)" : pcmk__node_name(node)));
 
         if (node->details->attrs != NULL) {
             g_hash_table_destroy(node->details->attrs);
         }
         if (node->details->utilization != NULL) {
             g_hash_table_destroy(node->details->utilization);
         }
         if (node->details->digest_cache != NULL) {
             g_hash_table_destroy(node->details->digest_cache);
         }
         g_list_free(node->details->running_rsc);
         g_list_free(node->details->allocated_rsc);
         free(node->details);
         free(node);
     }
     if (nodes != NULL) {
         g_list_free(nodes);
     }
 }
 
 static void
 pe__free_ordering(GList *constraints)
 {
     GList *iterator = constraints;
 
     while (iterator != NULL) {
         pcmk__action_relation_t *order = iterator->data;
 
         iterator = iterator->next;
 
         free(order->task1);
         free(order->task2);
         free(order);
     }
     if (constraints != NULL) {
         g_list_free(constraints);
     }
 }
 
 static void
 pe__free_location(GList *constraints)
 {
     GList *iterator = constraints;
 
     while (iterator != NULL) {
         pcmk__location_t *cons = iterator->data;
 
         iterator = iterator->next;
 
         g_list_free_full(cons->nodes, free);
         free(cons->id);
         free(cons);
     }
     if (constraints != NULL) {
         g_list_free(constraints);
     }
 }
 
 /*!
  * \brief Reset scheduler data to defaults without freeing it or constraints
  *
  * \param[in,out] scheduler  Scheduler data to reset
  *
  * \deprecated This function is deprecated as part of the API;
  *             pe_reset_working_set() should be used instead.
  */
 void
 cleanup_calculations(pcmk_scheduler_t *scheduler)
 {
     if (scheduler == NULL) {
         return;
     }
 
     pcmk__clear_scheduler_flags(scheduler, pcmk_sched_have_status);
     if (scheduler->config_hash != NULL) {
         g_hash_table_destroy(scheduler->config_hash);
     }
 
     if (scheduler->singletons != NULL) {
         g_hash_table_destroy(scheduler->singletons);
     }
 
     if (scheduler->tickets) {
         g_hash_table_destroy(scheduler->tickets);
     }
 
     if (scheduler->template_rsc_sets) {
         g_hash_table_destroy(scheduler->template_rsc_sets);
     }
 
     if (scheduler->tags) {
         g_hash_table_destroy(scheduler->tags);
     }
 
     free(scheduler->dc_uuid);
 
     crm_trace("deleting resources");
     pe_free_resources(scheduler->resources);
 
     crm_trace("deleting actions");
     pe_free_actions(scheduler->actions);
 
     crm_trace("deleting nodes");
     pe_free_nodes(scheduler->nodes);
 
     pe__free_param_checks(scheduler);
     g_list_free(scheduler->stop_needed);
     free_xml(scheduler->graph);
     crm_time_free(scheduler->now);
     free_xml(scheduler->input);
     free_xml(scheduler->failed);
 
     set_working_set_defaults(scheduler);
 
     CRM_CHECK(scheduler->ordering_constraints == NULL,;
         );
     CRM_CHECK(scheduler->placement_constraints == NULL,;
         );
 }
 
 /*!
  * \brief Reset scheduler data to default state without freeing it
  *
  * \param[in,out] scheduler  Scheduler data to reset
  */
 void
 pe_reset_working_set(pcmk_scheduler_t *scheduler)
 {
     if (scheduler == NULL) {
         return;
     }
 
     crm_trace("Deleting %d ordering constraints",
               g_list_length(scheduler->ordering_constraints));
     pe__free_ordering(scheduler->ordering_constraints);
     scheduler->ordering_constraints = NULL;
 
     crm_trace("Deleting %d location constraints",
               g_list_length(scheduler->placement_constraints));
     pe__free_location(scheduler->placement_constraints);
     scheduler->placement_constraints = NULL;
 
     crm_trace("Deleting %d colocation constraints",
               g_list_length(scheduler->colocation_constraints));
     g_list_free_full(scheduler->colocation_constraints, free);
     scheduler->colocation_constraints = NULL;
 
     crm_trace("Deleting %d ticket constraints",
               g_list_length(scheduler->ticket_constraints));
     g_list_free_full(scheduler->ticket_constraints, free);
     scheduler->ticket_constraints = NULL;
 
     cleanup_calculations(scheduler);
 }
 
 void
 set_working_set_defaults(pcmk_scheduler_t *scheduler)
 {
     void *priv = scheduler->priv;
 
     memset(scheduler, 0, sizeof(pcmk_scheduler_t));
 
     scheduler->priv = priv;
     scheduler->order_id = 1;
     scheduler->action_id = 1;
     scheduler->no_quorum_policy = pcmk_no_quorum_stop;
 
     scheduler->flags = 0x0ULL;
 
     pcmk__set_scheduler_flags(scheduler,
                               pcmk_sched_symmetric_cluster
                               |pcmk_sched_stop_removed_resources
                               |pcmk_sched_cancel_removed_actions);
     if (!strcmp(PCMK__CONCURRENT_FENCING_DEFAULT, PCMK_VALUE_TRUE)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_concurrent_fencing);
     }
 }
 
 pcmk_resource_t *
 pe_find_resource(GList *rsc_list, const char *id)
 {
     return pe_find_resource_with_flags(rsc_list, id, pcmk_rsc_match_history);
 }
 
 pcmk_resource_t *
 pe_find_resource_with_flags(GList *rsc_list, const char *id, enum pe_find flags)
 {
     GList *rIter = NULL;
 
     for (rIter = rsc_list; id && rIter; rIter = rIter->next) {
         pcmk_resource_t *parent = rIter->data;
 
         pcmk_resource_t *match =
             parent->fns->find_rsc(parent, id, NULL, flags);
         if (match != NULL) {
             return match;
         }
     }
     crm_trace("No match for %s", id);
     return NULL;
 }
 
 /*!
  * \brief Find a node by name or ID in a list of nodes
  *
  * \param[in] nodes      List of nodes (as pcmk_node_t*)
  * \param[in] id         If not NULL, ID of node to find
  * \param[in] node_name  If not NULL, name of node to find
  *
  * \return Node from \p nodes that matches \p id if any,
  *         otherwise node from \p nodes that matches \p uname if any,
  *         otherwise NULL
  */
 pcmk_node_t *
 pe_find_node_any(const GList *nodes, const char *id, const char *uname)
 {
     pcmk_node_t *match = NULL;
 
     if (id != NULL) {
         match = pe_find_node_id(nodes, id);
     }
     if ((match == NULL) && (uname != NULL)) {
         match = pe_find_node(nodes, uname);
     }
     return match;
 }
 
 /*!
  * \brief Find a node by ID in a list of nodes
  *
  * \param[in] nodes  List of nodes (as pcmk_node_t*)
  * \param[in] id     ID of node to find
  *
  * \return Node from \p nodes that matches \p id if any, otherwise NULL
  */
 pcmk_node_t *
 pe_find_node_id(const GList *nodes, const char *id)
 {
     for (const GList *iter = nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
 
         /* @TODO Whether node IDs should be considered case-sensitive should
          * probably depend on the node type, so functionizing the comparison
          * would be worthwhile
          */
         if (pcmk__str_eq(node->details->id, id, pcmk__str_casei)) {
             return node;
         }
     }
     return NULL;
 }
 
 /*!
  * \brief Find a node by name in a list of nodes
  *
  * \param[in] nodes      List of nodes (as pcmk_node_t*)
  * \param[in] node_name  Name of node to find
  *
  * \return Node from \p nodes that matches \p node_name if any, otherwise NULL
  */
 pcmk_node_t *
 pe_find_node(const GList *nodes, const char *node_name)
 {
     for (const GList *iter = nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
 
         if (pcmk__str_eq(node->details->uname, node_name, pcmk__str_casei)) {
             return node;
         }
     }
     return NULL;
 }
diff --git a/lib/pengine/unpack.c b/lib/pengine/unpack.c
index c73cc1c21d..e601645969 100644
--- a/lib/pengine/unpack.c
+++ b/lib/pengine/unpack.c
@@ -1,5132 +1,5132 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <string.h>
 #include <glib.h>
 #include <time.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <crm/common/util.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 
 CRM_TRACE_INIT_DATA(pe_status);
 
 // A (parsed) resource action history entry
 struct action_history {
     pcmk_resource_t *rsc;       // Resource that history is for
     pcmk_node_t *node;        // Node that history is for
     xmlNode *xml;             // History entry XML
 
     // Parsed from entry XML
     const char *id;           // XML ID of history entry
     const char *key;          // Operation key of action
     const char *task;         // Action name
     const char *exit_reason;  // Exit reason given for result
     guint interval_ms;        // Action interval
     int call_id;              // Call ID of action
     int expected_exit_status; // Expected exit status of action
     int exit_status;          // Actual exit status of action
     int execution_status;     // Execution status of action
 };
 
 /* This uses pcmk__set_flags_as()/pcmk__clear_flags_as() directly rather than
  * use pcmk__set_scheduler_flags()/pcmk__clear_scheduler_flags() so that the
  * flag is stringified more readably in log messages.
  */
 #define set_config_flag(scheduler, option, flag) do {                         \
         const char *scf_value = pe_pref((scheduler)->config_hash, (option));  \
         if (scf_value != NULL) {                                              \
             if (crm_is_true(scf_value)) {                                     \
                 (scheduler)->flags = pcmk__set_flags_as(__func__, __LINE__,   \
                                     LOG_TRACE, "Scheduler",                   \
                                     crm_system_name, (scheduler)->flags,      \
                                     (flag), #flag);                           \
             } else {                                                          \
                 (scheduler)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
                                     LOG_TRACE, "Scheduler",                   \
                                     crm_system_name, (scheduler)->flags,      \
                                     (flag), #flag);                           \
             }                                                                 \
         }                                                                     \
     } while(0)
 
 static void unpack_rsc_op(pcmk_resource_t *rsc, pcmk_node_t *node,
                           xmlNode *xml_op, xmlNode **last_failure,
                           enum action_fail_response *failed);
 static void determine_remote_online_status(pcmk_scheduler_t *scheduler,
                                            pcmk_node_t *this_node);
 static void add_node_attrs(const xmlNode *xml_obj, pcmk_node_t *node,
                            bool overwrite, pcmk_scheduler_t *scheduler);
 static void determine_online_status(const xmlNode *node_state,
                                     pcmk_node_t *this_node,
                                     pcmk_scheduler_t *scheduler);
 
 static void unpack_node_lrm(pcmk_node_t *node, const xmlNode *xml,
                             pcmk_scheduler_t *scheduler);
 
 
 static gboolean
 is_dangling_guest_node(pcmk_node_t *node)
 {
     /* we are looking for a remote-node that was supposed to be mapped to a
      * container resource, but all traces of that container have disappeared 
      * from both the config and the status section. */
     if (pe__is_guest_or_remote_node(node) &&
         node->details->remote_rsc &&
         node->details->remote_rsc->container == NULL &&
         pcmk_is_set(node->details->remote_rsc->flags,
                     pcmk_rsc_removed_filler)) {
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \brief Schedule a fence action for a node
  *
  * \param[in,out] scheduler       Scheduler data
  * \param[in,out] node            Node to fence
  * \param[in]     reason          Text description of why fencing is needed
  * \param[in]     priority_delay  Whether to consider
  *                                \c PCMK_OPT_PRIORITY_FENCING_DELAY
  */
 void
 pe_fence_node(pcmk_scheduler_t *scheduler, pcmk_node_t *node,
               const char *reason, bool priority_delay)
 {
     CRM_CHECK(node, return);
 
     /* A guest node is fenced by marking its container as failed */
     if (pe__is_guest_node(node)) {
         pcmk_resource_t *rsc = node->details->remote_rsc->container;
 
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
             if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
                 crm_notice("Not fencing guest node %s "
                            "(otherwise would because %s): "
                            "its guest resource %s is unmanaged",
                            pcmk__node_name(node), reason, rsc->id);
             } else {
                 pcmk__sched_warn("Guest node %s will be fenced "
                                  "(by recovering its guest resource %s): %s",
                                  pcmk__node_name(node), rsc->id, reason);
 
                 /* We don't mark the node as unclean because that would prevent the
                  * node from running resources. We want to allow it to run resources
                  * in this transition if the recovery succeeds.
                  */
                 node->details->remote_requires_reset = TRUE;
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             }
         }
 
     } else if (is_dangling_guest_node(node)) {
         crm_info("Cleaning up dangling connection for guest node %s: "
                  "fencing was already done because %s, "
                  "and guest resource no longer exists",
                  pcmk__node_name(node), reason);
         pcmk__set_rsc_flags(node->details->remote_rsc,
                             pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
 
     } else if (pe__is_remote_node(node)) {
         pcmk_resource_t *rsc = node->details->remote_rsc;
 
         if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             crm_notice("Not fencing remote node %s "
                        "(otherwise would because %s): connection is unmanaged",
                        pcmk__node_name(node), reason);
         } else if(node->details->remote_requires_reset == FALSE) {
             node->details->remote_requires_reset = TRUE;
             pcmk__sched_warn("Remote node %s %s: %s",
                              pcmk__node_name(node),
                              pe_can_fence(scheduler, node)? "will be fenced" : "is unclean",
                              reason);
         }
         node->details->unclean = TRUE;
         // No need to apply PCMK_OPT_PRIORITY_FENCING_DELAY for remote nodes
         pe_fence_op(node, NULL, TRUE, reason, FALSE, scheduler);
 
     } else if (node->details->unclean) {
         crm_trace("Cluster node %s %s because %s",
                   pcmk__node_name(node),
                   pe_can_fence(scheduler, node)? "would also be fenced" : "also is unclean",
                   reason);
 
     } else {
         pcmk__sched_warn("Cluster node %s %s: %s",
                          pcmk__node_name(node),
                          pe_can_fence(scheduler, node)? "will be fenced" : "is unclean",
                          reason);
         node->details->unclean = TRUE;
         pe_fence_op(node, NULL, TRUE, reason, priority_delay, scheduler);
     }
 }
 
 // @TODO xpaths can't handle templates, rules, or id-refs
 
 // nvpair with provides or requires set to unfencing
 #define XPATH_UNFENCING_NVPAIR XML_CIB_TAG_NVPAIR       \
     "[(@" PCMK_XA_NAME "='" PCMK_STONITH_PROVIDES "'"   \
     "or @" PCMK_XA_NAME "='" PCMK_META_REQUIRES "') "   \
     "and @" PCMK_XA_VALUE "='" PCMK__VALUE_UNFENCING "']"
 
 // unfencing in rsc_defaults or any resource
 #define XPATH_ENABLE_UNFENCING \
-    "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RESOURCES     \
+    "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES         \
     "//" XML_TAG_META_SETS "/" XPATH_UNFENCING_NVPAIR                       \
     "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" XML_CIB_TAG_RSCCONFIG    \
     "/" XML_TAG_META_SETS "/" XPATH_UNFENCING_NVPAIR
 
 static void
 set_if_xpath(uint64_t flag, const char *xpath, pcmk_scheduler_t *scheduler)
 {
     xmlXPathObjectPtr result = NULL;
 
     if (!pcmk_is_set(scheduler->flags, flag)) {
         result = xpath_search(scheduler->input, xpath);
         if (result && (numXpathResults(result) > 0)) {
             pcmk__set_scheduler_flags(scheduler, flag);
         }
         freeXpathObject(result);
     }
 }
 
 gboolean
 unpack_config(xmlNode *config, pcmk_scheduler_t *scheduler)
 {
     const char *value = NULL;
     guint interval_ms = 0U;
     GHashTable *config_hash = pcmk__strkey_table(free, free);
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = pcmk_role_unknown,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     scheduler->config_hash = config_hash;
 
     pe__unpack_dataset_nvpairs(config, XML_CIB_TAG_PROPSET, &rule_data, config_hash,
                                CIB_OPTIONS_FIRST, FALSE, scheduler);
 
     verify_pe_options(scheduler->config_hash);
 
     set_config_flag(scheduler, PCMK_OPT_ENABLE_STARTUP_PROBES,
                     pcmk_sched_probe_resources);
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_probe_resources)) {
         crm_info("Startup probes: disabled (dangerous)");
     }
 
     value = pe_pref(scheduler->config_hash, PCMK_OPT_HAVE_WATCHDOG);
     if (value && crm_is_true(value)) {
         crm_info("Watchdog-based self-fencing will be performed via SBD if "
                  "fencing is required and " PCMK_OPT_STONITH_WATCHDOG_TIMEOUT
                  " is nonzero");
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_fencing);
     }
 
     /* Set certain flags via xpath here, so they can be used before the relevant
      * configuration sections are unpacked.
      */
     set_if_xpath(pcmk_sched_enable_unfencing, XPATH_ENABLE_UNFENCING,
                  scheduler);
 
     value = pe_pref(scheduler->config_hash, PCMK_OPT_STONITH_TIMEOUT);
     pcmk_parse_interval_spec(value, &interval_ms);
 
     if (interval_ms >= INT_MAX) {
         scheduler->stonith_timeout = INT_MAX;
     } else {
         scheduler->stonith_timeout = (int) interval_ms;
     }
     crm_debug("STONITH timeout: %d", scheduler->stonith_timeout);
 
     set_config_flag(scheduler, PCMK_OPT_STONITH_ENABLED,
                     pcmk_sched_fencing_enabled);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         crm_debug("STONITH of failed nodes is enabled");
     } else {
         crm_debug("STONITH of failed nodes is disabled");
     }
 
     scheduler->stonith_action = pe_pref(scheduler->config_hash,
                                         PCMK_OPT_STONITH_ACTION);
     if (!strcmp(scheduler->stonith_action, "poweroff")) {
         pcmk__warn_once(pcmk__wo_poweroff,
                         "Support for " PCMK_OPT_STONITH_ACTION " of "
                         "'poweroff' is deprecated and will be removed in a "
                         "future release (use 'off' instead)");
         scheduler->stonith_action = PCMK_ACTION_OFF;
     }
     crm_trace("STONITH will %s nodes", scheduler->stonith_action);
 
     set_config_flag(scheduler, PCMK_OPT_CONCURRENT_FENCING,
                     pcmk_sched_concurrent_fencing);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_concurrent_fencing)) {
         crm_debug("Concurrent fencing is enabled");
     } else {
         crm_debug("Concurrent fencing is disabled");
     }
 
     value = pe_pref(scheduler->config_hash,
                     PCMK_OPT_PRIORITY_FENCING_DELAY);
     if (value) {
         pcmk_parse_interval_spec(value, &interval_ms);
         scheduler->priority_fencing_delay = (int) (interval_ms / 1000);
         crm_trace("Priority fencing delay is %ds",
                   scheduler->priority_fencing_delay);
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ALL_RESOURCES,
                     pcmk_sched_stop_all);
     crm_debug("Stop all active resources: %s",
               pcmk__flag_text(scheduler->flags, pcmk_sched_stop_all));
 
     set_config_flag(scheduler, PCMK_OPT_SYMMETRIC_CLUSTER,
                     pcmk_sched_symmetric_cluster);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
         crm_debug("Cluster is symmetric" " - resources can run anywhere by default");
     }
 
     value = pe_pref(scheduler->config_hash, PCMK_OPT_NO_QUORUM_POLICY);
 
     if (pcmk__str_eq(value, "ignore", pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_ignore;
 
     } else if (pcmk__str_eq(value, "freeze", pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_freeze;
 
     } else if (pcmk__str_eq(value, "demote", pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_demote;
 
     } else if (pcmk__str_eq(value, "suicide", pcmk__str_casei)) {
         if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
             int do_panic = 0;
 
             crm_element_value_int(scheduler->input, PCMK_XA_NO_QUORUM_PANIC,
                                   &do_panic);
             if (do_panic || pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
                 scheduler->no_quorum_policy = pcmk_no_quorum_fence;
             } else {
                 crm_notice("Resetting " PCMK_OPT_NO_QUORUM_POLICY
                            " to 'stop': cluster has never had quorum");
                 scheduler->no_quorum_policy = pcmk_no_quorum_stop;
             }
         } else {
             pcmk__config_err("Resetting " PCMK_OPT_NO_QUORUM_POLICY
                              " to 'stop' because fencing is disabled");
             scheduler->no_quorum_policy = pcmk_no_quorum_stop;
         }
 
     } else {
         scheduler->no_quorum_policy = pcmk_no_quorum_stop;
     }
 
     switch (scheduler->no_quorum_policy) {
         case pcmk_no_quorum_freeze:
             crm_debug("On loss of quorum: Freeze resources");
             break;
         case pcmk_no_quorum_stop:
             crm_debug("On loss of quorum: Stop ALL resources");
             break;
         case pcmk_no_quorum_demote:
             crm_debug("On loss of quorum: "
                       "Demote promotable resources and stop other resources");
             break;
         case pcmk_no_quorum_fence:
             crm_notice("On loss of quorum: Fence all remaining nodes");
             break;
         case pcmk_no_quorum_ignore:
             crm_notice("On loss of quorum: Ignore");
             break;
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ORPHAN_RESOURCES,
                     pcmk_sched_stop_removed_resources);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_removed_resources)) {
         crm_trace("Orphan resources are stopped");
     } else {
         crm_trace("Orphan resources are ignored");
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ORPHAN_ACTIONS,
                     pcmk_sched_cancel_removed_actions);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_cancel_removed_actions)) {
         crm_trace("Orphan resource actions are stopped");
     } else {
         crm_trace("Orphan resource actions are ignored");
     }
 
     value = pe_pref(scheduler->config_hash, PCMK__OPT_REMOVE_AFTER_STOP);
     if (value != NULL) {
         if (crm_is_true(value)) {
             pcmk__set_scheduler_flags(scheduler, pcmk_sched_remove_after_stop);
 #ifndef PCMK__COMPAT_2_0
             pcmk__warn_once(pcmk__wo_remove_after,
                             "Support for the " PCMK__OPT_REMOVE_AFTER_STOP
                             " cluster property is deprecated and will be "
                             "removed in a future release");
 #endif
         } else {
             pcmk__clear_scheduler_flags(scheduler,
                                         pcmk_sched_remove_after_stop);
         }
     }
 
     set_config_flag(scheduler, PCMK_OPT_MAINTENANCE_MODE,
                     pcmk_sched_in_maintenance);
     crm_trace("Maintenance mode: %s",
               pcmk__flag_text(scheduler->flags, pcmk_sched_in_maintenance));
 
     set_config_flag(scheduler, PCMK_OPT_START_FAILURE_IS_FATAL,
                     pcmk_sched_start_failure_fatal);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_start_failure_fatal)) {
         crm_trace("Start failures are always fatal");
     } else {
         crm_trace("Start failures are handled by failcount");
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         set_config_flag(scheduler, PCMK_OPT_STARTUP_FENCING,
                         pcmk_sched_startup_fencing);
     }
     if (pcmk_is_set(scheduler->flags, pcmk_sched_startup_fencing)) {
         crm_trace("Unseen nodes will be fenced");
     } else {
         pcmk__warn_once(pcmk__wo_blind,
                         "Blind faith: not fencing unseen nodes");
     }
 
     pe__unpack_node_health_scores(scheduler);
 
     scheduler->placement_strategy = pe_pref(scheduler->config_hash,
                                             PCMK_OPT_PLACEMENT_STRATEGY);
     crm_trace("Placement strategy: %s", scheduler->placement_strategy);
 
     set_config_flag(scheduler, PCMK_OPT_SHUTDOWN_LOCK,
                     pcmk_sched_shutdown_lock);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         value = pe_pref(scheduler->config_hash, PCMK_OPT_SHUTDOWN_LOCK_LIMIT);
         pcmk_parse_interval_spec(value, &(scheduler->shutdown_lock));
         scheduler->shutdown_lock /= 1000;
         crm_trace("Resources will be locked to nodes that were cleanly "
                   "shut down (locks expire after %s)",
                   pcmk__readable_interval(scheduler->shutdown_lock));
     } else {
         crm_trace("Resources will not be locked to nodes that were cleanly "
                   "shut down");
     }
 
     value = pe_pref(scheduler->config_hash, PCMK_OPT_NODE_PENDING_TIMEOUT);
     pcmk_parse_interval_spec(value, &(scheduler->node_pending_timeout));
     scheduler->node_pending_timeout /= 1000;
     if (scheduler->node_pending_timeout == 0) {
         crm_trace("Do not fence pending nodes");
     } else {
         crm_trace("Fence pending nodes after %s",
                   pcmk__readable_interval(scheduler->node_pending_timeout
                                           * 1000));
     }
 
     return TRUE;
 }
 
 pcmk_node_t *
 pe_create_node(const char *id, const char *uname, const char *type,
                const char *score, pcmk_scheduler_t *scheduler)
 {
     pcmk_node_t *new_node = NULL;
 
     if (pe_find_node(scheduler->nodes, uname) != NULL) {
         pcmk__config_warn("More than one node entry has name '%s'", uname);
     }
 
     new_node = calloc(1, sizeof(pcmk_node_t));
     if (new_node == NULL) {
         pcmk__sched_err("Could not allocate memory for node %s", uname);
         return NULL;
     }
 
     new_node->weight = char2score(score);
     new_node->details = calloc(1, sizeof(struct pe_node_shared_s));
 
     if (new_node->details == NULL) {
         free(new_node);
         pcmk__sched_err("Could not allocate memory for node %s", uname);
         return NULL;
     }
 
     crm_trace("Creating node for entry %s/%s", uname, id);
     new_node->details->id = id;
     new_node->details->uname = uname;
     new_node->details->online = FALSE;
     new_node->details->shutdown = FALSE;
     new_node->details->rsc_discovery_enabled = TRUE;
     new_node->details->running_rsc = NULL;
     new_node->details->data_set = scheduler;
 
     if (pcmk__str_eq(type, "member", pcmk__str_null_matches | pcmk__str_casei)) {
         new_node->details->type = pcmk_node_variant_cluster;
 
     } else if (pcmk__str_eq(type, "remote", pcmk__str_casei)) {
         new_node->details->type = pcmk_node_variant_remote;
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_remote_nodes);
 
     } else {
         /* @COMPAT 'ping' is the default for backward compatibility, but it
          * should be changed to 'member' at a compatibility break
          */
         if (!pcmk__str_eq(type, "ping", pcmk__str_casei)) {
             pcmk__config_warn("Node %s has unrecognized type '%s', "
                               "assuming 'ping'", pcmk__s(uname, "without name"),
                               type);
         }
         pcmk__warn_once(pcmk__wo_ping_node,
                         "Support for nodes of type 'ping' (such as %s) is "
                         "deprecated and will be removed in a future release",
                         pcmk__s(uname, "unnamed node"));
         new_node->details->type = node_ping;
     }
 
     new_node->details->attrs = pcmk__strkey_table(free, free);
 
     if (pe__is_guest_or_remote_node(new_node)) {
         g_hash_table_insert(new_node->details->attrs, strdup(CRM_ATTR_KIND),
                             strdup("remote"));
     } else {
         g_hash_table_insert(new_node->details->attrs, strdup(CRM_ATTR_KIND),
                             strdup("cluster"));
     }
 
     new_node->details->utilization = pcmk__strkey_table(free, free);
     new_node->details->digest_cache = pcmk__strkey_table(free,
                                                           pe__free_digests);
 
     scheduler->nodes = g_list_insert_sorted(scheduler->nodes, new_node,
                                             pe__cmp_node_name);
     return new_node;
 }
 
 static const char *
 expand_remote_rsc_meta(xmlNode *xml_obj, xmlNode *parent, pcmk_scheduler_t *data)
 {
     xmlNode *attr_set = NULL;
     xmlNode *attr = NULL;
 
     const char *container_id = ID(xml_obj);
     const char *remote_name = NULL;
     const char *remote_server = NULL;
     const char *remote_port = NULL;
     const char *connect_timeout = "60s";
     const char *remote_allow_migrate=NULL;
     const char *is_managed = NULL;
 
     for (attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
          attr_set = pcmk__xe_next(attr_set)) {
 
         if (!pcmk__str_eq((const char *)attr_set->name, XML_TAG_META_SETS,
                           pcmk__str_casei)) {
             continue;
         }
 
         for (attr = pcmk__xe_first_child(attr_set); attr != NULL;
              attr = pcmk__xe_next(attr)) {
             const char *value = crm_element_value(attr, PCMK_XA_VALUE);
             const char *name = crm_element_value(attr, PCMK_XA_NAME);
 
             if (name == NULL) { // Sanity
                 continue;
             }
 
             if (strcmp(name, PCMK_META_REMOTE_NODE) == 0) {
                 remote_name = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_ADDR) == 0) {
                 remote_server = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_PORT) == 0) {
                 remote_port = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_CONNECT_TIMEOUT) == 0) {
                 connect_timeout = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_ALLOW_MIGRATE) == 0) {
                 remote_allow_migrate = value;
 
             } else if (strcmp(name, PCMK_META_IS_MANAGED) == 0) {
                 is_managed = value;
             }
         }
     }
 
     if (remote_name == NULL) {
         return NULL;
     }
 
     if (pe_find_resource(data->resources, remote_name) != NULL) {
         return NULL;
     }
 
     pe_create_remote_xml(parent, remote_name, container_id,
                          remote_allow_migrate, is_managed,
                          connect_timeout, remote_server, remote_port);
     return remote_name;
 }
 
 static void
 handle_startup_fencing(pcmk_scheduler_t *scheduler, pcmk_node_t *new_node)
 {
     if ((new_node->details->type == pcmk_node_variant_remote)
         && (new_node->details->remote_rsc == NULL)) {
         /* Ignore fencing for remote nodes that don't have a connection resource
          * associated with them. This happens when remote node entries get left
          * in the nodes section after the connection resource is removed.
          */
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_startup_fencing)) {
         // All nodes are unclean until we've seen their status entry
         new_node->details->unclean = TRUE;
 
     } else {
         // Blind faith ...
         new_node->details->unclean = FALSE;
     }
 
     /* We need to be able to determine if a node's status section
      * exists or not separate from whether the node is unclean. */
     new_node->details->unseen = TRUE;
 }
 
 gboolean
 unpack_nodes(xmlNode *xml_nodes, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
     pcmk_node_t *new_node = NULL;
     const char *id = NULL;
     const char *uname = NULL;
     const char *type = NULL;
     const char *score = NULL;
 
     for (xml_obj = pcmk__xe_first_child(xml_nodes); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_NODE, pcmk__str_none)) {
             new_node = NULL;
 
             id = crm_element_value(xml_obj, PCMK_XA_ID);
             uname = crm_element_value(xml_obj, PCMK_XA_UNAME);
             type = crm_element_value(xml_obj, PCMK_XA_TYPE);
             score = crm_element_value(xml_obj, PCMK_XA_SCORE);
             crm_trace("Processing node %s/%s", uname, id);
 
             if (id == NULL) {
                 pcmk__config_err("Ignoring <" XML_CIB_TAG_NODE
                                  "> entry in configuration without id");
                 continue;
             }
             new_node = pe_create_node(id, uname, type, score, scheduler);
 
             if (new_node == NULL) {
                 return FALSE;
             }
 
             handle_startup_fencing(scheduler, new_node);
 
             add_node_attrs(xml_obj, new_node, FALSE, scheduler);
 
             crm_trace("Done with node %s",
                       crm_element_value(xml_obj, PCMK_XA_UNAME));
         }
     }
 
     if (scheduler->localhost
         && (pe_find_node(scheduler->nodes, scheduler->localhost) == NULL)) {
         crm_info("Creating a fake local node");
         pe_create_node(scheduler->localhost, scheduler->localhost, NULL, 0,
                        scheduler);
     }
 
     return TRUE;
 }
 
 static void
 setup_container(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     const char *container_id = NULL;
 
     if (rsc->children) {
         g_list_foreach(rsc->children, (GFunc) setup_container, scheduler);
         return;
     }
 
     container_id = g_hash_table_lookup(rsc->meta, PCMK__META_CONTAINER);
     if (container_id && !pcmk__str_eq(container_id, rsc->id, pcmk__str_casei)) {
         pcmk_resource_t *container = pe_find_resource(scheduler->resources,
                                                       container_id);
 
         if (container) {
             rsc->container = container;
             pcmk__set_rsc_flags(container, pcmk_rsc_has_filler);
             container->fillers = g_list_append(container->fillers, rsc);
             pcmk__rsc_trace(rsc, "Resource %s's container is %s",
                             rsc->id, container_id);
         } else {
             pcmk__config_err("Resource %s: Unknown resource container (%s)",
                              rsc->id, container_id);
         }
     }
 }
 
 gboolean
 unpack_remote_nodes(xmlNode *xml_resources, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
 
     /* Create remote nodes and guest nodes from the resource configuration
      * before unpacking resources.
      */
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         const char *new_node_id = NULL;
 
         /* Check for remote nodes, which are defined by ocf:pacemaker:remote
          * primitives.
          */
         if (xml_contains_remote_node(xml_obj)) {
             new_node_id = ID(xml_obj);
             /* The "pe_find_node" check is here to make sure we don't iterate over
              * an expanded node that has already been added to the node list. */
             if (new_node_id
                 && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                 crm_trace("Found remote node %s defined by resource %s",
                           new_node_id, ID(xml_obj));
                 pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                scheduler);
             }
             continue;
         }
 
         /* Check for guest nodes, which are defined by special meta-attributes
          * of a primitive of any type (for example, VirtualDomain or Xen).
          */
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_RESOURCE, pcmk__str_none)) {
             /* This will add an ocf:pacemaker:remote primitive to the
              * configuration for the guest node's connection, to be unpacked
              * later.
              */
             new_node_id = expand_remote_rsc_meta(xml_obj, xml_resources,
                                                  scheduler);
             if (new_node_id
                 && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                 crm_trace("Found guest node %s in resource %s",
                           new_node_id, ID(xml_obj));
                 pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                scheduler);
             }
             continue;
         }
 
         /* Check for guest nodes inside a group. Clones are currently not
          * supported as guest nodes.
          */
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_GROUP, pcmk__str_none)) {
             xmlNode *xml_obj2 = NULL;
             for (xml_obj2 = pcmk__xe_first_child(xml_obj); xml_obj2 != NULL;
                  xml_obj2 = pcmk__xe_next(xml_obj2)) {
 
                 new_node_id = expand_remote_rsc_meta(xml_obj2, xml_resources,
                                                      scheduler);
 
                 if (new_node_id
                     && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                     crm_trace("Found guest node %s in resource %s inside group %s",
                               new_node_id, ID(xml_obj2), ID(xml_obj));
                     pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                    scheduler);
                 }
             }
         }
     }
     return TRUE;
 }
 
 /* Call this after all the nodes and resources have been
  * unpacked, but before the status section is read.
  *
  * A remote node's online status is reflected by the state
  * of the remote node's connection resource. We need to link
  * the remote node to this connection resource so we can have
  * easy access to the connection resource during the scheduler calculations.
  */
 static void
 link_rsc2remotenode(pcmk_scheduler_t *scheduler, pcmk_resource_t *new_rsc)
 {
     pcmk_node_t *remote_node = NULL;
 
     if (new_rsc->is_remote_node == FALSE) {
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         /* remote_nodes and remote_resources are not linked in quick location calculations */
         return;
     }
 
     remote_node = pe_find_node(scheduler->nodes, new_rsc->id);
     CRM_CHECK(remote_node != NULL, return);
 
     pcmk__rsc_trace(new_rsc, "Linking remote connection resource %s to %s",
                     new_rsc->id, pcmk__node_name(remote_node));
     remote_node->details->remote_rsc = new_rsc;
 
     if (new_rsc->container == NULL) {
         /* Handle start-up fencing for remote nodes (as opposed to guest nodes)
          * the same as is done for cluster nodes.
          */
         handle_startup_fencing(scheduler, remote_node);
 
     } else {
         /* pe_create_node() marks the new node as "remote" or "cluster"; now
          * that we know the node is a guest node, update it correctly.
          */
         g_hash_table_replace(remote_node->details->attrs, strdup(CRM_ATTR_KIND),
                              strdup("container"));
     }
 }
 
 static void
 destroy_tag(gpointer data)
 {
     pcmk_tag_t *tag = data;
 
     if (tag) {
         free(tag->id);
         g_list_free_full(tag->refs, free);
         free(tag);
     }
 }
 
 /*!
  * \internal
  * \brief Parse configuration XML for resource information
  *
  * \param[in]     xml_resources  Top of resource configuration XML
  * \param[in,out] scheduler      Scheduler data
  *
  * \return TRUE
  *
  * \note unpack_remote_nodes() MUST be called before this, so that the nodes can
  *       be used when pe__unpack_resource() calls resource_location()
  */
 gboolean
 unpack_resources(const xmlNode *xml_resources, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
     GList *gIter = NULL;
 
     scheduler->template_rsc_sets = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         pcmk_resource_t *new_rsc = NULL;
         const char *id = ID(xml_obj);
 
         if (pcmk__str_empty(id)) {
             pcmk__config_err("Ignoring <%s> resource without ID",
                              xml_obj->name);
             continue;
         }
 
         if (pcmk__str_eq((const char *) xml_obj->name, XML_CIB_TAG_RSC_TEMPLATE,
                          pcmk__str_none)) {
             if (g_hash_table_lookup_extended(scheduler->template_rsc_sets, id,
                                              NULL, NULL) == FALSE) {
                 /* Record the template's ID for the knowledge of its existence anyway. */
                 g_hash_table_insert(scheduler->template_rsc_sets, strdup(id),
                                     NULL);
             }
             continue;
         }
 
         crm_trace("Unpacking <%s " PCMK_XA_ID "='%s'>", xml_obj->name, id);
         if (pe__unpack_resource(xml_obj, &new_rsc, NULL,
                                 scheduler) == pcmk_rc_ok) {
             scheduler->resources = g_list_append(scheduler->resources, new_rsc);
             pcmk__rsc_trace(new_rsc, "Added resource %s", new_rsc->id);
 
         } else {
             pcmk__config_err("Ignoring <%s> resource '%s' "
                              "because configuration is invalid",
                              xml_obj->name, id);
         }
     }
 
     for (gIter = scheduler->resources; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
 
         setup_container(rsc, scheduler);
         link_rsc2remotenode(scheduler, rsc);
     }
 
     scheduler->resources = g_list_sort(scheduler->resources,
                                       pe__cmp_rsc_priority);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         /* Ignore */
 
     } else if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)
                && !pcmk_is_set(scheduler->flags, pcmk_sched_have_fencing)) {
 
         pcmk__config_err("Resource start-up disabled since no STONITH resources have been defined");
         pcmk__config_err("Either configure some or disable STONITH with the "
                          PCMK_OPT_STONITH_ENABLED " option");
         pcmk__config_err("NOTE: Clusters with shared data need STONITH to ensure data integrity");
     }
 
     return TRUE;
 }
 
 gboolean
 unpack_tags(xmlNode *xml_tags, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_tag = NULL;
 
     scheduler->tags = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_tag = pcmk__xe_first_child(xml_tags); xml_tag != NULL;
          xml_tag = pcmk__xe_next(xml_tag)) {
 
         xmlNode *xml_obj_ref = NULL;
         const char *tag_id = ID(xml_tag);
 
         if (!pcmk__str_eq((const char *)xml_tag->name, XML_CIB_TAG_TAG, pcmk__str_none)) {
             continue;
         }
 
         if (tag_id == NULL) {
             pcmk__config_err("Ignoring <%s> without " PCMK_XA_ID,
                              (const char *) xml_tag->name);
             continue;
         }
 
         for (xml_obj_ref = pcmk__xe_first_child(xml_tag); xml_obj_ref != NULL;
              xml_obj_ref = pcmk__xe_next(xml_obj_ref)) {
 
             const char *obj_ref = ID(xml_obj_ref);
 
             if (!pcmk__str_eq((const char *)xml_obj_ref->name, XML_CIB_TAG_OBJ_REF, pcmk__str_none)) {
                 continue;
             }
 
             if (obj_ref == NULL) {
                 pcmk__config_err("Ignoring <%s> for tag '%s' without " PCMK_XA_ID,
                                  xml_obj_ref->name, tag_id);
                 continue;
             }
 
             if (add_tag_ref(scheduler->tags, tag_id, obj_ref) == FALSE) {
                 return FALSE;
             }
         }
     }
 
     return TRUE;
 }
 
 /* The ticket state section:
  * "/cib/status/tickets/ticket_state" */
 static gboolean
 unpack_ticket_state(xmlNode *xml_ticket, pcmk_scheduler_t *scheduler)
 {
     const char *ticket_id = NULL;
     const char *granted = NULL;
     const char *last_granted = NULL;
     const char *standby = NULL;
     xmlAttrPtr xIter = NULL;
 
     pcmk_ticket_t *ticket = NULL;
 
     ticket_id = ID(xml_ticket);
     if (pcmk__str_empty(ticket_id)) {
         return FALSE;
     }
 
     crm_trace("Processing ticket state for %s", ticket_id);
 
     ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
     if (ticket == NULL) {
         ticket = ticket_new(ticket_id, scheduler);
         if (ticket == NULL) {
             return FALSE;
         }
     }
 
     for (xIter = xml_ticket->properties; xIter; xIter = xIter->next) {
         const char *prop_name = (const char *)xIter->name;
         const char *prop_value = pcmk__xml_attr_value(xIter);
 
         if (pcmk__str_eq(prop_name, PCMK_XA_ID, pcmk__str_none)) {
             continue;
         }
         g_hash_table_replace(ticket->state, strdup(prop_name), strdup(prop_value));
     }
 
     granted = g_hash_table_lookup(ticket->state, "granted");
     if (granted && crm_is_true(granted)) {
         ticket->granted = TRUE;
         crm_info("We have ticket '%s'", ticket->id);
     } else {
         ticket->granted = FALSE;
         crm_info("We do not have ticket '%s'", ticket->id);
     }
 
     last_granted = g_hash_table_lookup(ticket->state, "last-granted");
     if (last_granted) {
         long long last_granted_ll;
 
         pcmk__scan_ll(last_granted, &last_granted_ll, 0LL);
         ticket->last_granted = (time_t) last_granted_ll;
     }
 
     standby = g_hash_table_lookup(ticket->state, "standby");
     if (standby && crm_is_true(standby)) {
         ticket->standby = TRUE;
         if (ticket->granted) {
             crm_info("Granted ticket '%s' is in standby-mode", ticket->id);
         }
     } else {
         ticket->standby = FALSE;
     }
 
     crm_trace("Done with ticket state for %s", ticket_id);
 
     return TRUE;
 }
 
 static gboolean
 unpack_tickets_state(xmlNode *xml_tickets, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
 
     for (xml_obj = pcmk__xe_first_child(xml_tickets); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (!pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_TICKET_STATE, pcmk__str_none)) {
             continue;
         }
         unpack_ticket_state(xml_obj, scheduler);
     }
 
     return TRUE;
 }
 
 static void
 unpack_handle_remote_attrs(pcmk_node_t *this_node, const xmlNode *state,
                            pcmk_scheduler_t *scheduler)
 {
     const char *resource_discovery_enabled = NULL;
     const xmlNode *attrs = NULL;
     pcmk_resource_t *rsc = NULL;
 
     if (!pcmk__str_eq((const char *)state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
         return;
     }
 
     if ((this_node == NULL) || !pe__is_guest_or_remote_node(this_node)) {
         return;
     }
     crm_trace("Processing Pacemaker Remote node %s",
               pcmk__node_name(this_node));
 
     pcmk__scan_min_int(crm_element_value(state, XML_NODE_IS_MAINTENANCE),
                        &(this_node->details->remote_maintenance), 0);
 
     rsc = this_node->details->remote_rsc;
     if (this_node->details->remote_requires_reset == FALSE) {
         this_node->details->unclean = FALSE;
         this_node->details->unseen = FALSE;
     }
     attrs = find_xml_node(state, XML_TAG_TRANSIENT_NODEATTRS, FALSE);
     add_node_attrs(attrs, this_node, TRUE, scheduler);
 
     if (pe__shutdown_requested(this_node)) {
         crm_info("%s is shutting down", pcmk__node_name(this_node));
         this_node->details->shutdown = TRUE;
     }
  
     if (crm_is_true(pe_node_attribute_raw(this_node, "standby"))) {
         crm_info("%s is in standby mode", pcmk__node_name(this_node));
         this_node->details->standby = TRUE;
     }
 
     if (crm_is_true(pe_node_attribute_raw(this_node, "maintenance")) ||
         ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_managed))) {
         crm_info("%s is in maintenance mode", pcmk__node_name(this_node));
         this_node->details->maintenance = TRUE;
     }
 
     resource_discovery_enabled = pe_node_attribute_raw(this_node, XML_NODE_ATTR_RSC_DISCOVERY);
     if (resource_discovery_enabled && !crm_is_true(resource_discovery_enabled)) {
         if (pe__is_remote_node(this_node)
             && !pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
             pcmk__config_warn("Ignoring " XML_NODE_ATTR_RSC_DISCOVERY
                               " attribute on Pacemaker Remote node %s"
                               " because fencing is disabled",
                               pcmk__node_name(this_node));
         } else {
             /* This is either a remote node with fencing enabled, or a guest
              * node. We don't care whether fencing is enabled when fencing guest
              * nodes, because they are "fenced" by recovering their containing
              * resource.
              */
             crm_info("%s has resource discovery disabled",
                      pcmk__node_name(this_node));
             this_node->details->rsc_discovery_enabled = FALSE;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a cluster node's transient attributes
  *
  * \param[in]     state      CIB node state XML
  * \param[in,out] node       Cluster node whose attributes are being unpacked
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_transient_attributes(const xmlNode *state, pcmk_node_t *node,
                             pcmk_scheduler_t *scheduler)
 {
     const char *discovery = NULL;
     const xmlNode *attrs = find_xml_node(state, XML_TAG_TRANSIENT_NODEATTRS,
                                          FALSE);
 
     add_node_attrs(attrs, node, TRUE, scheduler);
 
     if (crm_is_true(pe_node_attribute_raw(node, "standby"))) {
         crm_info("%s is in standby mode", pcmk__node_name(node));
         node->details->standby = TRUE;
     }
 
     if (crm_is_true(pe_node_attribute_raw(node, "maintenance"))) {
         crm_info("%s is in maintenance mode", pcmk__node_name(node));
         node->details->maintenance = TRUE;
     }
 
     discovery = pe_node_attribute_raw(node, XML_NODE_ATTR_RSC_DISCOVERY);
     if ((discovery != NULL) && !crm_is_true(discovery)) {
         pcmk__config_warn("Ignoring " XML_NODE_ATTR_RSC_DISCOVERY " attribute "
                           "for %s because disabling resource discovery is "
                           "not allowed for cluster nodes",
                           pcmk__node_name(node));
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a node state entry (first pass)
  *
  * Unpack one node state entry from status. This unpacks information from the
  * node_state element itself and node attributes inside it, but not the
  * resource history inside it. Multiple passes through the status are needed to
  * fully unpack everything.
  *
  * \param[in]     state      CIB node state XML
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_node_state(const xmlNode *state, pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *uname = NULL;
     pcmk_node_t *this_node = NULL;
 
     id = crm_element_value(state, PCMK_XA_ID);
     if (id == NULL) {
         pcmk__config_err("Ignoring invalid " XML_CIB_TAG_STATE " entry without "
                          PCMK_XA_ID);
         crm_log_xml_info(state, "missing-id");
         return;
     }
 
     uname = crm_element_value(state, PCMK_XA_UNAME);
     if (uname == NULL) {
         /* If a joining peer makes the cluster acquire the quorum from corosync
          * meanwhile it has not joined CPG membership of pacemaker-controld yet,
          * it's possible that the created node_state entry doesn't have an uname
          * yet. We should recognize the node as `pending` and wait for it to
          * join CPG.
          */
         crm_trace("Handling " XML_CIB_TAG_STATE " entry with id=\"%s\" without "
                   PCMK_XA_UNAME, id);
     }
 
     this_node = pe_find_node_any(scheduler->nodes, id, uname);
     if (this_node == NULL) {
         pcmk__config_warn("Ignoring recorded node state for id=\"%s\" (%s) "
                           "because it is no longer in the configuration",
                           id, pcmk__s(uname, "uname unknown"));
         return;
     }
 
     if (pe__is_guest_or_remote_node(this_node)) {
         /* We can't determine the online status of Pacemaker Remote nodes until
          * after all resource history has been unpacked. In this first pass, we
          * do need to mark whether the node has been fenced, as this plays a
          * role during unpacking cluster node resource state.
          */
         pcmk__scan_min_int(crm_element_value(state, XML_NODE_IS_FENCED),
                            &(this_node->details->remote_was_fenced), 0);
         return;
     }
 
     unpack_transient_attributes(state, this_node, scheduler);
 
     /* Provisionally mark this cluster node as clean. We have at least seen it
      * in the current cluster's lifetime.
      */
     this_node->details->unclean = FALSE;
     this_node->details->unseen = FALSE;
 
     crm_trace("Determining online status of cluster node %s (id %s)",
               pcmk__node_name(this_node), id);
     determine_online_status(state, this_node, scheduler);
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_quorate)
         && this_node->details->online
         && (scheduler->no_quorum_policy == pcmk_no_quorum_fence)) {
         /* Everything else should flow from this automatically
          * (at least until the scheduler becomes able to migrate off
          * healthy resources)
          */
         pe_fence_node(scheduler, this_node, "cluster does not have quorum",
                       FALSE);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack nodes' resource history as much as possible
  *
  * Unpack as many nodes' resource history as possible in one pass through the
  * status. We need to process Pacemaker Remote nodes' connections/containers
  * before unpacking their history; the connection/container history will be
  * in another node's history, so it might take multiple passes to unpack
  * everything.
  *
  * \param[in]     status     CIB XML status section
  * \param[in]     fence      If true, treat any not-yet-unpacked nodes as unseen
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Standard Pacemaker return code (specifically pcmk_rc_ok if done,
  *         or EAGAIN if more unpacking remains to be done)
  */
 static int
 unpack_node_history(const xmlNode *status, bool fence,
                     pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
 
     // Loop through all node_state entries in CIB status
     for (const xmlNode *state = first_named_child(status, XML_CIB_TAG_STATE);
          state != NULL; state = crm_next_same_xml(state)) {
 
         const char *id = ID(state);
         const char *uname = crm_element_value(state, PCMK_XA_UNAME);
         pcmk_node_t *this_node = NULL;
 
         if ((id == NULL) || (uname == NULL)) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history from malformed "
                       XML_CIB_TAG_STATE " without id and/or uname");
             continue;
         }
 
         this_node = pe_find_node_any(scheduler->nodes, id, uname);
         if (this_node == NULL) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history for node %s because "
                       "no longer in configuration", id);
             continue;
         }
 
         if (this_node->details->unpacked) {
             crm_trace("Not unpacking resource history for node %s because "
                       "already unpacked", id);
             continue;
         }
 
         if (fence) {
             // We're processing all remaining nodes
 
         } else if (pe__is_guest_node(this_node)) {
             /* We can unpack a guest node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection and containing resource are both up.
              */
             pcmk_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL) || (rsc->role != pcmk_role_started)
                 || (rsc->container->role != pcmk_role_started)) {
                 crm_trace("Not unpacking resource history for guest node %s "
                           "because container and connection are not known to "
                           "be up", id);
                 continue;
             }
 
         } else if (pe__is_remote_node(this_node)) {
             /* We can unpack a remote node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection is up, with the exception of when shutdown locks are
              * in use.
              */
             pcmk_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL)
                 || (!pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)
                     && (rsc->role != pcmk_role_started))) {
                 crm_trace("Not unpacking resource history for remote node %s "
                           "because connection is not known to be up", id);
                 continue;
             }
 
         /* If fencing and shutdown locks are disabled and we're not processing
          * unseen nodes, then we don't want to unpack offline nodes until online
          * nodes have been unpacked. This allows us to number active clone
          * instances first.
          */
         } else if (!pcmk_any_flags_set(scheduler->flags,
                                        pcmk_sched_fencing_enabled
                                        |pcmk_sched_shutdown_lock)
                    && !this_node->details->online) {
             crm_trace("Not unpacking resource history for offline "
                       "cluster node %s", id);
             continue;
         }
 
         if (pe__is_guest_or_remote_node(this_node)) {
             determine_remote_online_status(scheduler, this_node);
             unpack_handle_remote_attrs(this_node, state, scheduler);
         }
 
         crm_trace("Unpacking resource history for %snode %s",
                   (fence? "unseen " : ""), id);
 
         this_node->details->unpacked = TRUE;
         unpack_node_lrm(this_node, state, scheduler);
 
         rc = EAGAIN; // Other node histories might depend on this one
     }
     return rc;
 }
 
 /* remove nodes that are down, stopping */
 /* create positive rsc_to_node constraints between resources and the nodes they are running on */
 /* anything else? */
 gboolean
 unpack_status(xmlNode *status, pcmk_scheduler_t *scheduler)
 {
     xmlNode *state = NULL;
 
     crm_trace("Beginning unpack");
 
     if (scheduler->tickets == NULL) {
         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     for (state = pcmk__xe_first_child(status); state != NULL;
          state = pcmk__xe_next(state)) {
 
         if (pcmk__str_eq((const char *)state->name, XML_CIB_TAG_TICKETS, pcmk__str_none)) {
             unpack_tickets_state((xmlNode *) state, scheduler);
 
         } else if (pcmk__str_eq((const char *)state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
             unpack_node_state(state, scheduler);
         }
     }
 
     while (unpack_node_history(status, FALSE, scheduler) == EAGAIN) {
         crm_trace("Another pass through node resource histories is needed");
     }
 
     // Now catch any nodes we didn't see
     unpack_node_history(status,
                         pcmk_is_set(scheduler->flags,
                                     pcmk_sched_fencing_enabled),
                         scheduler);
 
     /* Now that we know where resources are, we can schedule stops of containers
      * with failed bundle connections
      */
     if (scheduler->stop_needed != NULL) {
         for (GList *item = scheduler->stop_needed; item; item = item->next) {
             pcmk_resource_t *container = item->data;
             pcmk_node_t *node = pcmk__current_node(container);
 
             if (node) {
                 stop_action(container, node, FALSE);
             }
         }
         g_list_free(scheduler->stop_needed);
         scheduler->stop_needed = NULL;
     }
 
     /* Now that we know status of all Pacemaker Remote connections and nodes,
      * we can stop connections for node shutdowns, and check the online status
      * of remote/guest nodes that didn't have any node history to unpack.
      */
     for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
         pcmk_node_t *this_node = gIter->data;
 
         if (!pe__is_guest_or_remote_node(this_node)) {
             continue;
         }
         if (this_node->details->shutdown
             && (this_node->details->remote_rsc != NULL)) {
             pe__set_next_role(this_node->details->remote_rsc, pcmk_role_stopped,
                               "remote shutdown");
         }
         if (!this_node->details->unpacked) {
             determine_remote_online_status(scheduler, this_node);
         }
     }
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Unpack node's time when it became a member at the cluster layer
  *
  * \param[in]     node_state  Node's node_state entry
  * \param[in,out] scheduler   Scheduler data
  *
  * \return Epoch time when node became a cluster member
  *         (or scheduler effective time for legacy entries) if a member,
  *         0 if not a member, or -1 if no valid information available
  */
 static long long
 unpack_node_member(const xmlNode *node_state, pcmk_scheduler_t *scheduler)
 {
     const char *member_time = crm_element_value(node_state, PCMK__XA_IN_CCM);
     int member = 0;
 
     if (member_time == NULL) {
         return -1LL;
 
     } else if (crm_str_to_boolean(member_time, &member) == 1) {
         /* If in_ccm=0, we'll return 0 here. If in_ccm=1, either the entry was
          * recorded as a boolean for a DC < 2.1.7, or the node is pending
          * shutdown and has left the CPG, in which case it was set to 1 to avoid
          * fencing for PCMK_OPT_NODE_PENDING_TIMEOUT.
          *
          * We return the effective time for in_ccm=1 because what's important to
          * avoid fencing is that effective time minus this value is less than
          * the pending node timeout.
          */
         return member? (long long) get_effective_time(scheduler) : 0LL;
 
     } else {
         long long when_member = 0LL;
 
         if ((pcmk__scan_ll(member_time, &when_member,
                            0LL) != pcmk_rc_ok) || (when_member < 0LL)) {
             crm_warn("Unrecognized value '%s' for " PCMK__XA_IN_CCM
                      " in " XML_CIB_TAG_STATE " entry", member_time);
             return -1LL;
         }
         return when_member;
     }
 }
 
 /*!
  * \internal
  * \brief Unpack node's time when it became online in process group
  *
  * \param[in] node_state  Node's node_state entry
  *
  * \return Epoch time when node became online in process group (or 0 if not
  *         online, or 1 for legacy online entries)
  */
 static long long
 unpack_node_online(const xmlNode *node_state)
 {
     const char *peer_time = crm_element_value(node_state, PCMK__XA_CRMD);
 
     // @COMPAT Entries recorded for DCs < 2.1.7 have "online" or "offline"
     if (pcmk__str_eq(peer_time, OFFLINESTATUS,
                      pcmk__str_casei|pcmk__str_null_matches)) {
         return 0LL;
 
     } else if (pcmk__str_eq(peer_time, ONLINESTATUS, pcmk__str_casei)) {
         return 1LL;
 
     } else {
         long long when_online = 0LL;
 
         if ((pcmk__scan_ll(peer_time, &when_online, 0LL) != pcmk_rc_ok)
             || (when_online < 0)) {
             crm_warn("Unrecognized value '%s' for " PCMK__XA_CRMD " in "
                      XML_CIB_TAG_STATE " entry, assuming offline", peer_time);
             return 0LL;
         }
         return when_online;
     }
 }
 
 /*!
  * \internal
  * \brief Unpack node attribute for user-requested fencing
  *
  * \param[in] node        Node to check
  * \param[in] node_state  Node's node_state entry in CIB status
  *
  * \return \c true if fencing has been requested for \p node, otherwise \c false
  */
 static bool
 unpack_node_terminate(const pcmk_node_t *node, const xmlNode *node_state)
 {
     long long value = 0LL;
     int value_i = 0;
     const char *value_s = pe_node_attribute_raw(node, PCMK_NODE_ATTR_TERMINATE);
 
     // Value may be boolean or an epoch time
     if (crm_str_to_boolean(value_s, &value_i) == 1) {
         return (value_i != 0);
     }
     if (pcmk__scan_ll(value_s, &value, 0LL) == pcmk_rc_ok) {
         return (value > 0);
     }
     crm_warn("Ignoring unrecognized value '%s' for " PCMK_NODE_ATTR_TERMINATE
              "node attribute for %s", value_s, pcmk__node_name(node));
     return false;
 }
 
 static gboolean
 determine_online_status_no_fencing(pcmk_scheduler_t *scheduler,
                                    const xmlNode *node_state,
                                    pcmk_node_t *this_node)
 {
     gboolean online = FALSE;
     const char *join = crm_element_value(node_state, PCMK__XA_JOIN);
     const char *exp_state = crm_element_value(node_state, PCMK__XA_EXPECTED);
     long long when_member = unpack_node_member(node_state, scheduler);
     long long when_online = unpack_node_online(node_state);
 
     if (when_member <= 0) {
         crm_trace("Node %s is %sdown", pcmk__node_name(this_node),
                   ((when_member < 0)? "presumed " : ""));
 
     } else if (when_online > 0) {
         if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
             online = TRUE;
         } else {
             crm_debug("Node %s is not ready to run resources: %s",
                       pcmk__node_name(this_node), join);
         }
 
     } else if (this_node->details->expected_up == FALSE) {
         crm_trace("Node %s controller is down: "
                   "member@%lld online@%lld join=%s expected=%s",
                   pcmk__node_name(this_node), when_member, when_online,
                   pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
 
     } else {
         /* mark it unclean */
         pe_fence_node(scheduler, this_node, "peer is unexpectedly down", FALSE);
         crm_info("Node %s member@%lld online@%lld join=%s expected=%s",
                  pcmk__node_name(this_node), when_member, when_online,
                  pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
     }
     return online;
 }
 
 /*!
  * \internal
  * \brief Check whether a node has taken too long to join controller group
  *
  * \param[in,out] scheduler    Scheduler data
  * \param[in]     node         Node to check
  * \param[in]     when_member  Epoch time when node became a cluster member
  * \param[in]     when_online  Epoch time when node joined controller group
  *
  * \return true if node has been pending (on the way up) longer than
  *         \c PCMK_OPT_NODE_PENDING_TIMEOUT, otherwise false
  * \note This will also update the cluster's recheck time if appropriate.
  */
 static inline bool
 pending_too_long(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                  long long when_member, long long when_online)
 {
     if ((scheduler->node_pending_timeout > 0)
         && (when_member > 0) && (when_online <= 0)) {
         // There is a timeout on pending nodes, and node is pending
 
         time_t timeout = when_member + scheduler->node_pending_timeout;
 
         if (get_effective_time(node->details->data_set) >= timeout) {
             return true; // Node has timed out
         }
 
         // Node is pending, but still has time
         pe__update_recheck_time(timeout, scheduler, "pending node timeout");
     }
     return false;
 }
 
 static bool
 determine_online_status_fencing(pcmk_scheduler_t *scheduler,
                                 const xmlNode *node_state,
                                 pcmk_node_t *this_node)
 {
     bool termination_requested = unpack_node_terminate(this_node, node_state);
     const char *join = crm_element_value(node_state, PCMK__XA_JOIN);
     const char *exp_state = crm_element_value(node_state, PCMK__XA_EXPECTED);
     long long when_member = unpack_node_member(node_state, scheduler);
     long long when_online = unpack_node_online(node_state);
 
 /*
   - PCMK__XA_JOIN          ::= member|down|pending|banned
   - PCMK__XA_EXPECTED      ::= member|down
 
   @COMPAT with entries recorded for DCs < 2.1.7
   - PCMK__XA_IN_CCM        ::= true|false
   - PCMK__XA_CRMD          ::= online|offline
 
   Since crm_feature_set 3.18.0 (pacemaker-2.1.7):
   - PCMK__XA_IN_CCM        ::= <timestamp>|0
   Since when node has been a cluster member. A value 0 of means the node is not
   a cluster member.
 
   - PCMK__XA_CRMD          ::= <timestamp>|0
   Since when peer has been online in CPG. A value 0 means the peer is offline
   in CPG.
 */
 
     crm_trace("Node %s member@%lld online@%lld join=%s expected=%s%s",
               pcmk__node_name(this_node), when_member, when_online,
               pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"),
               (termination_requested? " (termination requested)" : ""));
 
     if (this_node->details->shutdown) {
         crm_debug("%s is shutting down", pcmk__node_name(this_node));
 
         /* Slightly different criteria since we can't shut down a dead peer */
         return (when_online > 0);
     }
 
     if (when_member < 0) {
         pe_fence_node(scheduler, this_node,
                       "peer has not been seen by the cluster", FALSE);
         return false;
     }
 
     if (pcmk__str_eq(join, CRMD_JOINSTATE_NACK, pcmk__str_none)) {
         pe_fence_node(scheduler, this_node,
                       "peer failed Pacemaker membership criteria", FALSE);
 
     } else if (termination_requested) {
         if ((when_member <= 0) && (when_online <= 0)
             && pcmk__str_eq(join, CRMD_JOINSTATE_DOWN, pcmk__str_none)) {
             crm_info("%s was fenced as requested", pcmk__node_name(this_node));
             return false;
         }
         pe_fence_node(scheduler, this_node, "fencing was requested", false);
 
     } else if (pcmk__str_eq(exp_state, CRMD_JOINSTATE_DOWN,
                             pcmk__str_null_matches)) {
 
         if (pending_too_long(scheduler, this_node, when_member, when_online)) {
             pe_fence_node(scheduler, this_node,
                           "peer pending timed out on joining the process group",
                           FALSE);
 
         } else if ((when_member > 0) || (when_online > 0)) {
             crm_info("- %s is not ready to run resources",
                      pcmk__node_name(this_node));
             this_node->details->standby = TRUE;
             this_node->details->pending = TRUE;
 
         } else {
             crm_trace("%s is down or still coming up",
                       pcmk__node_name(this_node));
         }
 
     } else if (when_member <= 0) {
         // Consider PCMK_OPT_PRIORITY_FENCING_DELAY for lost nodes
         pe_fence_node(scheduler, this_node,
                       "peer is no longer part of the cluster", TRUE);
 
     } else if (when_online <= 0) {
         pe_fence_node(scheduler, this_node,
                       "peer process is no longer available", FALSE);
 
         /* Everything is running at this point, now check join state */
 
     } else if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_none)) {
         crm_info("%s is active", pcmk__node_name(this_node));
 
     } else if (pcmk__str_any_of(join, CRMD_JOINSTATE_PENDING,
                                 CRMD_JOINSTATE_DOWN, NULL)) {
         crm_info("%s is not ready to run resources",
                  pcmk__node_name(this_node));
         this_node->details->standby = TRUE;
         this_node->details->pending = TRUE;
 
     } else {
         pe_fence_node(scheduler, this_node, "peer was in an unknown state",
                       FALSE);
     }
 
     return (when_member > 0);
 }
 
 static void
 determine_remote_online_status(pcmk_scheduler_t *scheduler,
                                pcmk_node_t *this_node)
 {
     pcmk_resource_t *rsc = this_node->details->remote_rsc;
     pcmk_resource_t *container = NULL;
     pcmk_node_t *host = NULL;
 
     /* If there is a node state entry for a (former) Pacemaker Remote node
      * but no resource creating that node, the node's connection resource will
      * be NULL. Consider it an offline remote node in that case.
      */
     if (rsc == NULL) {
         this_node->details->online = FALSE;
         goto remote_online_done;
     }
 
     container = rsc->container;
 
     if (container && pcmk__list_of_1(rsc->running_on)) {
         host = rsc->running_on->data;
     }
 
     /* If the resource is currently started, mark it online. */
     if (rsc->role == pcmk_role_started) {
         crm_trace("%s node %s presumed ONLINE because connection resource is started",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = TRUE;
     }
 
     /* consider this node shutting down if transitioning start->stop */
     if ((rsc->role == pcmk_role_started)
         && (rsc->next_role == pcmk_role_stopped)) {
 
         crm_trace("%s node %s shutting down because connection resource is stopping",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->shutdown = TRUE;
     }
 
     /* Now check all the failure conditions. */
     if(container && pcmk_is_set(container->flags, pcmk_rsc_failed)) {
         crm_trace("Guest node %s UNCLEAN because guest resource failed",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         crm_trace("%s node %s OFFLINE because connection resource failed",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
 
     } else if ((rsc->role == pcmk_role_stopped)
                || ((container != NULL)
                    && (container->role == pcmk_role_stopped))) {
 
         crm_trace("%s node %s OFFLINE because its resource is stopped",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = FALSE;
 
     } else if (host && (host->details->online == FALSE)
                && host->details->unclean) {
         crm_trace("Guest node %s UNCLEAN because host is unclean",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
     }
 
 remote_online_done:
     crm_trace("Remote node %s online=%s",
         this_node->details->id, this_node->details->online ? "TRUE" : "FALSE");
 }
 
 static void
 determine_online_status(const xmlNode *node_state, pcmk_node_t *this_node,
                         pcmk_scheduler_t *scheduler)
 {
     gboolean online = FALSE;
     const char *exp_state = crm_element_value(node_state, PCMK__XA_EXPECTED);
 
     CRM_CHECK(this_node != NULL, return);
 
     this_node->details->shutdown = FALSE;
     this_node->details->expected_up = FALSE;
 
     if (pe__shutdown_requested(this_node)) {
         this_node->details->shutdown = TRUE;
 
     } else if (pcmk__str_eq(exp_state, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
         this_node->details->expected_up = TRUE;
     }
 
     if (this_node->details->type == node_ping) {
         this_node->details->unclean = FALSE;
         online = FALSE;         /* As far as resource management is concerned,
                                  * the node is safely offline.
                                  * Anyone caught abusing this logic will be shot
                                  */
 
     } else if (!pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         online = determine_online_status_no_fencing(scheduler, node_state,
                                                     this_node);
 
     } else {
         online = determine_online_status_fencing(scheduler, node_state,
                                                  this_node);
     }
 
     if (online) {
         this_node->details->online = TRUE;
 
     } else {
         /* remove node from contention */
         this_node->fixed = TRUE; // @COMPAT deprecated and unused
         this_node->weight = -INFINITY;
     }
 
     if (online && this_node->details->shutdown) {
         /* don't run resources here */
         this_node->fixed = TRUE; // @COMPAT deprecated and unused
         this_node->weight = -INFINITY;
     }
 
     if (this_node->details->type == node_ping) {
         crm_info("%s is not a Pacemaker node", pcmk__node_name(this_node));
 
     } else if (this_node->details->unclean) {
         pcmk__sched_warn("%s is unclean", pcmk__node_name(this_node));
 
     } else if (this_node->details->online) {
         crm_info("%s is %s", pcmk__node_name(this_node),
                  this_node->details->shutdown ? "shutting down" :
                  this_node->details->pending ? "pending" :
                  this_node->details->standby ? "standby" :
                  this_node->details->maintenance ? "maintenance" : "online");
 
     } else {
         crm_trace("%s is offline", pcmk__node_name(this_node));
     }
 }
 
 /*!
  * \internal
  * \brief Find the end of a resource's name, excluding any clone suffix
  *
  * \param[in] id  Resource ID to check
  *
  * \return Pointer to last character of resource's base name
  */
 const char *
 pe_base_name_end(const char *id)
 {
     if (!pcmk__str_empty(id)) {
         const char *end = id + strlen(id) - 1;
 
         for (const char *s = end; s > id; --s) {
             switch (*s) {
                 case '0':
                 case '1':
                 case '2':
                 case '3':
                 case '4':
                 case '5':
                 case '6':
                 case '7':
                 case '8':
                 case '9':
                     break;
                 case ':':
                     return (s == end)? s : (s - 1);
                 default:
                     return end;
             }
         }
         return end;
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get a resource name excluding any clone suffix
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_strip(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     char *basename = NULL;
 
     CRM_ASSERT(end);
     basename = strndup(last_rsc_id, end - last_rsc_id + 1);
     CRM_ASSERT(basename);
     return basename;
 }
 
 /*!
  * \internal
  * \brief Get the name of the first instance of a cloned resource
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name plus :0
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_zero(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     size_t base_name_len = end - last_rsc_id + 1;
     char *zero = NULL;
 
     CRM_ASSERT(end);
     zero = calloc(base_name_len + 3, sizeof(char));
     CRM_ASSERT(zero);
     memcpy(zero, last_rsc_id, base_name_len);
     zero[base_name_len] = ':';
     zero[base_name_len + 1] = '0';
     return zero;
 }
 
 static pcmk_resource_t *
 create_fake_resource(const char *rsc_id, const xmlNode *rsc_entry,
                      pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *rsc = NULL;
     xmlNode *xml_rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE);
 
     copy_in_properties(xml_rsc, rsc_entry);
     crm_xml_add(xml_rsc, PCMK_XA_ID, rsc_id);
     crm_log_xml_debug(xml_rsc, "Orphan resource");
 
     if (pe__unpack_resource(xml_rsc, &rsc, NULL, scheduler) != pcmk_rc_ok) {
         return NULL;
     }
 
     if (xml_contains_remote_node(xml_rsc)) {
         pcmk_node_t *node;
 
         crm_debug("Detected orphaned remote node %s", rsc_id);
         node = pe_find_node(scheduler->nodes, rsc_id);
         if (node == NULL) {
 	        node = pe_create_node(rsc_id, rsc_id, "remote", NULL, scheduler);
         }
         link_rsc2remotenode(scheduler, rsc);
 
         if (node) {
             crm_trace("Setting node %s as shutting down due to orphaned connection resource", rsc_id);
             node->details->shutdown = TRUE;
         }
     }
 
     if (crm_element_value(rsc_entry, PCMK__META_CONTAINER)) {
         /* This orphaned rsc needs to be mapped to a container. */
         crm_trace("Detected orphaned container filler %s", rsc_id);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_removed_filler);
     }
     pcmk__set_rsc_flags(rsc, pcmk_rsc_removed);
     scheduler->resources = g_list_append(scheduler->resources, rsc);
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Create orphan instance for anonymous clone resource history
  *
  * \param[in,out] parent     Clone resource that orphan will be added to
  * \param[in]     rsc_id     Orphan's resource ID
  * \param[in]     node       Where orphan is active (for logging only)
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Newly added orphaned instance of \p parent
  */
 static pcmk_resource_t *
 create_anonymous_orphan(pcmk_resource_t *parent, const char *rsc_id,
                         const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *top = pe__create_clone_child(parent, scheduler);
 
     // find_rsc() because we might be a cloned group
     pcmk_resource_t *orphan = top->fns->find_rsc(top, rsc_id, NULL,
                                                pcmk_rsc_match_clone_only);
 
     pcmk__rsc_debug(parent, "Created orphan %s for %s: %s on %s",
                     top->id, parent->id, rsc_id, pcmk__node_name(node));
     return orphan;
 }
 
 /*!
  * \internal
  * \brief Check a node for an instance of an anonymous clone
  *
  * Return a child instance of the specified anonymous clone, in order of
  * preference: (1) the instance running on the specified node, if any;
  * (2) an inactive instance (i.e. within the total of \c PCMK_META_CLONE_MAX
  * instances); (3) a newly created orphan (that is, \c PCMK_META_CLONE_MAX
  * instances are already active).
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     node       Node on which to check for instance
  * \param[in,out] parent     Clone to check
  * \param[in]     rsc_id     Name of cloned resource in history (no instance)
  */
 static pcmk_resource_t *
 find_anonymous_clone(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                      pcmk_resource_t *parent, const char *rsc_id)
 {
     GList *rIter = NULL;
     pcmk_resource_t *rsc = NULL;
     pcmk_resource_t *inactive_instance = NULL;
     gboolean skip_inactive = FALSE;
 
     CRM_ASSERT(parent != NULL);
     CRM_ASSERT(pe_rsc_is_clone(parent));
     CRM_ASSERT(!pcmk_is_set(parent->flags, pcmk_rsc_unique));
 
     // Check for active (or partially active, for cloned groups) instance
     pcmk__rsc_trace(parent, "Looking for %s on %s in %s",
                     rsc_id, pcmk__node_name(node), parent->id);
     for (rIter = parent->children; rsc == NULL && rIter; rIter = rIter->next) {
         GList *locations = NULL;
         pcmk_resource_t *child = rIter->data;
 
         /* Check whether this instance is already known to be active or pending
          * anywhere, at this stage of unpacking. Because this function is called
          * for a resource before the resource's individual operation history
          * entries are unpacked, locations will generally not contain the
          * desired node.
          *
          * However, there are three exceptions:
          * (1) when child is a cloned group and we have already unpacked the
          *     history of another member of the group on the same node;
          * (2) when we've already unpacked the history of another numbered
          *     instance on the same node (which can happen if
          *     PCMK_META_GLOBALLY_UNIQUE was flipped from true to false); and
          * (3) when we re-run calculations on the same scheduler data as part of
          *     a simulation.
          */
         child->fns->location(child, &locations, 2);
         if (locations) {
             /* We should never associate the same numbered anonymous clone
              * instance with multiple nodes, and clone instances can't migrate,
              * so there must be only one location, regardless of history.
              */
             CRM_LOG_ASSERT(locations->next == NULL);
 
             if (pcmk__same_node((pcmk_node_t *) locations->data, node)) {
                 /* This child instance is active on the requested node, so check
                  * for a corresponding configured resource. We use find_rsc()
                  * instead of child because child may be a cloned group, and we
                  * need the particular member corresponding to rsc_id.
                  *
                  * If the history entry is orphaned, rsc will be NULL.
                  */
                 rsc = parent->fns->find_rsc(child, rsc_id, NULL,
                                             pcmk_rsc_match_clone_only);
                 if (rsc) {
                     /* If there are multiple instance history entries for an
                      * anonymous clone in a single node's history (which can
                      * happen if PCMK_META_GLOBALLY_UNIQUE is switched from true
                      * to false), we want to consider the instances beyond the
                      * first as orphans, even if there are inactive instance
                      * numbers available.
                      */
                     if (rsc->running_on) {
                         crm_notice("Active (now-)anonymous clone %s has "
                                    "multiple (orphan) instance histories on %s",
                                    parent->id, pcmk__node_name(node));
                         skip_inactive = TRUE;
                         rsc = NULL;
                     } else {
                         pcmk__rsc_trace(parent, "Resource %s, active", rsc->id);
                     }
                 }
             }
             g_list_free(locations);
 
         } else {
             pcmk__rsc_trace(parent, "Resource %s, skip inactive", child->id);
             if (!skip_inactive && !inactive_instance
                 && !pcmk_is_set(child->flags, pcmk_rsc_blocked)) {
                 // Remember one inactive instance in case we don't find active
                 inactive_instance = parent->fns->find_rsc(child, rsc_id, NULL,
                                                           pcmk_rsc_match_clone_only);
 
                 /* ... but don't use it if it was already associated with a
                  * pending action on another node
                  */
                 if ((inactive_instance != NULL) &&
                     (inactive_instance->pending_node != NULL) &&
                     !pcmk__same_node(inactive_instance->pending_node, node)) {
                     inactive_instance = NULL;
                 }
             }
         }
     }
 
     if ((rsc == NULL) && !skip_inactive && (inactive_instance != NULL)) {
         pcmk__rsc_trace(parent, "Resource %s, empty slot",
                         inactive_instance->id);
         rsc = inactive_instance;
     }
 
     /* If the resource has "requires" set to "quorum" or "nothing", and we don't
      * have a clone instance for every node, we don't want to consume a valid
      * instance number for unclean nodes. Such instances may appear to be active
      * according to the history, but should be considered inactive, so we can
      * start an instance elsewhere. Treat such instances as orphans.
      *
      * An exception is instances running on guest nodes -- since guest node
      * "fencing" is actually just a resource stop, requires shouldn't apply.
      *
      * @TODO Ideally, we'd use an inactive instance number if it is not needed
      * for any clean instances. However, we don't know that at this point.
      */
     if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)
         && (!node->details->online || node->details->unclean)
         && !pe__is_guest_node(node)
         && !pe__is_universal_clone(parent, scheduler)) {
 
         rsc = NULL;
     }
 
     if (rsc == NULL) {
         rsc = create_anonymous_orphan(parent, rsc_id, node, scheduler);
         pcmk__rsc_trace(parent, "Resource %s, orphan", rsc->id);
     }
     return rsc;
 }
 
 static pcmk_resource_t *
 unpack_find_resource(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                      const char *rsc_id)
 {
     pcmk_resource_t *rsc = NULL;
     pcmk_resource_t *parent = NULL;
 
     crm_trace("looking for %s", rsc_id);
     rsc = pe_find_resource(scheduler->resources, rsc_id);
 
     if (rsc == NULL) {
         /* If we didn't find the resource by its name in the operation history,
          * check it again as a clone instance. Even when PCMK_META_CLONE_MAX=0,
          * we create a single :0 orphan to match against here.
          */
         char *clone0_id = clone_zero(rsc_id);
         pcmk_resource_t *clone0 = pe_find_resource(scheduler->resources,
                                                    clone0_id);
 
         if (clone0 && !pcmk_is_set(clone0->flags, pcmk_rsc_unique)) {
             rsc = clone0;
             parent = uber_parent(clone0);
             crm_trace("%s found as %s (%s)", rsc_id, clone0_id, parent->id);
         } else {
             crm_trace("%s is not known as %s either (orphan)",
                       rsc_id, clone0_id);
         }
         free(clone0_id);
 
     } else if (rsc->variant > pcmk_rsc_variant_primitive) {
         crm_trace("Resource history for %s is orphaned because it is no longer primitive",
                   rsc_id);
         return NULL;
 
     } else {
         parent = uber_parent(rsc);
     }
 
     if (pe_rsc_is_anon_clone(parent)) {
 
         if (pe_rsc_is_bundled(parent)) {
             rsc = pe__find_bundle_replica(parent->parent, node);
         } else {
             char *base = clone_strip(rsc_id);
 
             rsc = find_anonymous_clone(scheduler, node, parent, base);
             free(base);
             CRM_ASSERT(rsc != NULL);
         }
     }
 
     if (rsc && !pcmk__str_eq(rsc_id, rsc->id, pcmk__str_casei)
         && !pcmk__str_eq(rsc_id, rsc->clone_name, pcmk__str_casei)) {
 
         pcmk__str_update(&rsc->clone_name, rsc_id);
         pcmk__rsc_debug(rsc, "Internally renamed %s on %s to %s%s",
                         rsc_id, pcmk__node_name(node), rsc->id,
                         pcmk_is_set(rsc->flags, pcmk_rsc_removed)? " (ORPHAN)" : "");
     }
     return rsc;
 }
 
 static pcmk_resource_t *
 process_orphan_resource(const xmlNode *rsc_entry, const pcmk_node_t *node,
                         pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *rsc = NULL;
     const char *rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
 
     crm_debug("Detected orphan resource %s on %s",
               rsc_id, pcmk__node_name(node));
     rsc = create_fake_resource(rsc_id, rsc_entry, scheduler);
     if (rsc == NULL) {
         return NULL;
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_stop_removed_resources)) {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
 
     } else {
         CRM_CHECK(rsc != NULL, return NULL);
         pcmk__rsc_trace(rsc, "Added orphan %s", rsc->id);
         resource_location(rsc, NULL, -INFINITY, "__orphan_do_not_run__",
                           scheduler);
     }
     return rsc;
 }
 
 static void
 process_rsc_state(pcmk_resource_t *rsc, pcmk_node_t *node,
                   enum action_fail_response on_fail)
 {
     pcmk_node_t *tmpnode = NULL;
     char *reason = NULL;
     enum action_fail_response save_on_fail = pcmk_on_fail_ignore;
 
     CRM_ASSERT(rsc);
     pcmk__rsc_trace(rsc, "Resource %s is %s on %s: on_fail=%s",
                     rsc->id, role2text(rsc->role), pcmk__node_name(node),
                     fail2text(on_fail));
 
     /* process current state */
     if (rsc->role != pcmk_role_unknown) {
         pcmk_resource_t *iter = rsc;
 
         while (iter) {
             if (g_hash_table_lookup(iter->known_on, node->details->id) == NULL) {
                 pcmk_node_t *n = pe__copy_node(node);
 
                 pcmk__rsc_trace(rsc, "%s%s%s known on %s",
                                 rsc->id,
                                 ((rsc->clone_name == NULL)? "" : " also known as "),
                                 ((rsc->clone_name == NULL)? "" : rsc->clone_name),
                                 pcmk__node_name(n));
                 g_hash_table_insert(iter->known_on, (gpointer) n->details->id, n);
             }
             if (pcmk_is_set(iter->flags, pcmk_rsc_unique)) {
                 break;
             }
             iter = iter->parent;
         }
     }
 
     /* If a managed resource is believed to be running, but node is down ... */
     if ((rsc->role > pcmk_role_stopped)
         && node->details->online == FALSE
         && node->details->maintenance == FALSE
         && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
 
         gboolean should_fence = FALSE;
 
         /* If this is a guest node, fence it (regardless of whether fencing is
          * enabled, because guest node fencing is done by recovery of the
          * container resource rather than by the fencer). Mark the resource
          * we're processing as failed. When the guest comes back up, its
          * operation history in the CIB will be cleared, freeing the affected
          * resource to run again once we are sure we know its state.
          */
         if (pe__is_guest_node(node)) {
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             should_fence = TRUE;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_fencing_enabled)) {
             if (pe__is_remote_node(node) && node->details->remote_rsc
                 && !pcmk_is_set(node->details->remote_rsc->flags,
                                 pcmk_rsc_failed)) {
 
                 /* Setting unseen means that fencing of the remote node will
                  * occur only if the connection resource is not going to start
                  * somewhere. This allows connection resources on a failed
                  * cluster node to move to another node without requiring the
                  * remote nodes to be fenced as well.
                  */
                 node->details->unseen = TRUE;
                 reason = crm_strdup_printf("%s is active there (fencing will be"
                                            " revoked if remote connection can "
                                            "be re-established elsewhere)",
                                            rsc->id);
             }
             should_fence = TRUE;
         }
 
         if (should_fence) {
             if (reason == NULL) {
                reason = crm_strdup_printf("%s is thought to be active there", rsc->id);
             }
             pe_fence_node(rsc->cluster, node, reason, FALSE);
         }
         free(reason);
     }
 
     /* In order to calculate priority_fencing_delay correctly, save the failure information and pass it to native_add_running(). */
     save_on_fail = on_fail;
 
     if (node->details->unclean) {
         /* No extra processing needed
          * Also allows resources to be started again after a node is shot
          */
         on_fail = pcmk_on_fail_ignore;
     }
 
     switch (on_fail) {
         case pcmk_on_fail_ignore:
             /* nothing to do */
             break;
 
         case pcmk_on_fail_demote:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed);
             demote_action(rsc, node, FALSE);
             break;
 
         case pcmk_on_fail_fence_node:
             /* treat it as if it is still running
              * but also mark the node as unclean
              */
             reason = crm_strdup_printf("%s failed there", rsc->id);
             pe_fence_node(rsc->cluster, node, reason, FALSE);
             free(reason);
             break;
 
         case pcmk_on_fail_standby_node:
             node->details->standby = TRUE;
             node->details->standby_onfail = TRUE;
             break;
 
         case pcmk_on_fail_block:
             /* is_managed == FALSE will prevent any
              * actions being sent for the resource
              */
             pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
             pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
             break;
 
         case pcmk_on_fail_ban:
             /* make sure it comes up somewhere else
              * or not at all
              */
             resource_location(rsc, node, -INFINITY, "__action_migration_auto__",
                               rsc->cluster);
             break;
 
         case pcmk_on_fail_stop:
             pe__set_next_role(rsc, pcmk_role_stopped, "on-fail=stop");
             break;
 
         case pcmk_on_fail_restart:
             if ((rsc->role != pcmk_role_stopped)
                 && (rsc->role != pcmk_role_unknown)) {
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case pcmk_on_fail_restart_container:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             if (rsc->container && pe_rsc_is_bundled(rsc)) {
                 /* A bundle's remote connection can run on a different node than
                  * the bundle's container. We don't necessarily know where the
                  * container is running yet, so remember it and add a stop
                  * action for it later.
                  */
                 rsc->cluster->stop_needed =
                     g_list_prepend(rsc->cluster->stop_needed, rsc->container);
             } else if (rsc->container) {
                 stop_action(rsc->container, node, FALSE);
             } else if ((rsc->role != pcmk_role_stopped)
                        && (rsc->role != pcmk_role_unknown)) {
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case pcmk_on_fail_reset_remote:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
                 tmpnode = NULL;
                 if (rsc->is_remote_node) {
                     tmpnode = pe_find_node(rsc->cluster->nodes, rsc->id);
                 }
                 if (tmpnode &&
                     pe__is_remote_node(tmpnode) &&
                     tmpnode->details->remote_was_fenced == 0) {
 
                     /* The remote connection resource failed in a way that
                      * should result in fencing the remote node.
                      */
                     pe_fence_node(rsc->cluster, tmpnode,
                                   "remote connection is unrecoverable", FALSE);
                 }
             }
 
             /* require the stop action regardless if fencing is occurring or not. */
             if (rsc->role > pcmk_role_stopped) {
                 stop_action(rsc, node, FALSE);
             }
 
             /* if reconnect delay is in use, prevent the connection from exiting the
              * "STOPPED" role until the failure is cleared by the delay timeout. */
             if (rsc->remote_reconnect_ms) {
                 pe__set_next_role(rsc, pcmk_role_stopped, "remote reset");
             }
             break;
     }
 
     /* ensure a remote-node connection failure forces an unclean remote-node
      * to be fenced. By setting unseen = FALSE, the remote-node failure will
      * result in a fencing operation regardless if we're going to attempt to 
      * reconnect to the remote-node in this transition or not. */
     if (pcmk_is_set(rsc->flags, pcmk_rsc_failed) && rsc->is_remote_node) {
         tmpnode = pe_find_node(rsc->cluster->nodes, rsc->id);
         if (tmpnode && tmpnode->details->unclean) {
             tmpnode->details->unseen = FALSE;
         }
     }
 
     if ((rsc->role != pcmk_role_stopped)
         && (rsc->role != pcmk_role_unknown)) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
             if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
                 pcmk__config_warn("Detected active orphan %s running on %s",
                                   rsc->id, pcmk__node_name(node));
             } else {
                 pcmk__config_warn("Resource '%s' must be stopped manually on "
                                   "%s because cluster is configured not to "
                                   "stop active orphans",
                                   rsc->id, pcmk__node_name(node));
             }
         }
 
         native_add_running(rsc, node, rsc->cluster,
                            (save_on_fail != pcmk_on_fail_ignore));
         switch (on_fail) {
             case pcmk_on_fail_ignore:
                 break;
             case pcmk_on_fail_demote:
             case pcmk_on_fail_block:
                 pcmk__set_rsc_flags(rsc, pcmk_rsc_failed);
                 break;
             default:
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
                 break;
         }
 
     } else if (rsc->clone_name && strchr(rsc->clone_name, ':') != NULL) {
         /* Only do this for older status sections that included instance numbers
          * Otherwise stopped instances will appear as orphans
          */
         pcmk__rsc_trace(rsc, "Resetting clone_name %s for %s (stopped)",
                         rsc->clone_name, rsc->id);
         free(rsc->clone_name);
         rsc->clone_name = NULL;
 
     } else {
         GList *possible_matches = pe__resource_actions(rsc, node,
                                                        PCMK_ACTION_STOP, FALSE);
         GList *gIter = possible_matches;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_action_t *stop = (pcmk_action_t *) gIter->data;
 
             pcmk__set_action_flags(stop, pcmk_action_optional);
         }
 
         g_list_free(possible_matches);
     }
 
     /* A successful stop after migrate_to on the migration source doesn't make
      * the partially migrated resource stopped on the migration target.
      */
     if ((rsc->role == pcmk_role_stopped)
         && rsc->partial_migration_source
         && rsc->partial_migration_source->details == node->details
         && rsc->partial_migration_target
         && rsc->running_on) {
 
         rsc->role = pcmk_role_started;
     }
 }
 
 /* create active recurring operations as optional */
 static void
 process_recurring(pcmk_node_t *node, pcmk_resource_t *rsc,
                   int start_index, int stop_index,
                   GList *sorted_op_list, pcmk_scheduler_t *scheduler)
 {
     int counter = -1;
     const char *task = NULL;
     const char *status = NULL;
     GList *gIter = sorted_op_list;
 
     CRM_ASSERT(rsc);
     pcmk__rsc_trace(rsc, "%s: Start index %d, stop index = %d",
                     rsc->id, start_index, stop_index);
 
     for (; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         guint interval_ms = 0;
         char *key = NULL;
         const char *id = ID(rsc_op);
 
         counter++;
 
         if (node->details->online == FALSE) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: node is offline",
                             rsc->id, pcmk__node_name(node));
             break;
 
             /* Need to check if there's a monitor for role="Stopped" */
         } else if (start_index < stop_index && counter <= stop_index) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: resource is not active",
                             id, pcmk__node_name(node));
             continue;
 
         } else if (counter < start_index) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: old %d",
                             id, pcmk__node_name(node), counter);
             continue;
         }
 
         crm_element_value_ms(rsc_op, PCMK_META_INTERVAL, &interval_ms);
         if (interval_ms == 0) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: non-recurring",
                             id, pcmk__node_name(node));
             continue;
         }
 
         status = crm_element_value(rsc_op, PCMK__XA_OP_STATUS);
         if (pcmk__str_eq(status, "-1", pcmk__str_casei)) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: status",
                             id, pcmk__node_name(node));
             continue;
         }
         task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
         /* create the action */
         key = pcmk__op_key(rsc->id, task, interval_ms);
         pcmk__rsc_trace(rsc, "Creating %s on %s", key, pcmk__node_name(node));
         custom_action(rsc, key, task, node, TRUE, scheduler);
     }
 }
 
 void
 calculate_active_ops(const GList *sorted_op_list, int *start_index,
                      int *stop_index)
 {
     int counter = -1;
     int implied_monitor_start = -1;
     int implied_clone_start = -1;
     const char *task = NULL;
     const char *status = NULL;
 
     *stop_index = -1;
     *start_index = -1;
 
     for (const GList *iter = sorted_op_list; iter != NULL; iter = iter->next) {
         const xmlNode *rsc_op = (const xmlNode *) iter->data;
 
         counter++;
 
         task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
         status = crm_element_value(rsc_op, PCMK__XA_OP_STATUS);
 
         if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_casei)
             && pcmk__str_eq(status, "0", pcmk__str_casei)) {
             *stop_index = counter;
 
         } else if (pcmk__strcase_any_of(task, PCMK_ACTION_START,
                                         PCMK_ACTION_MIGRATE_FROM, NULL)) {
             *start_index = counter;
 
         } else if ((implied_monitor_start <= *stop_index)
                    && pcmk__str_eq(task, PCMK_ACTION_MONITOR,
                                    pcmk__str_casei)) {
             const char *rc = crm_element_value(rsc_op, PCMK__XA_RC_CODE);
 
             if (pcmk__strcase_any_of(rc, "0", "8", NULL)) {
                 implied_monitor_start = counter;
             }
         } else if (pcmk__strcase_any_of(task, PCMK_ACTION_PROMOTE,
                                         PCMK_ACTION_DEMOTE, NULL)) {
             implied_clone_start = counter;
         }
     }
 
     if (*start_index == -1) {
         if (implied_clone_start != -1) {
             *start_index = implied_clone_start;
         } else if (implied_monitor_start != -1) {
             *start_index = implied_monitor_start;
         }
     }
 }
 
 // If resource history entry has shutdown lock, remember lock node and time
 static void
 unpack_shutdown_lock(const xmlNode *rsc_entry, pcmk_resource_t *rsc,
                      const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     time_t lock_time = 0;   // When lock started (i.e. node shutdown time)
 
     if ((crm_element_value_epoch(rsc_entry, PCMK_OPT_SHUTDOWN_LOCK,
                                  &lock_time) == pcmk_ok) && (lock_time != 0)) {
 
         if ((scheduler->shutdown_lock > 0)
             && (get_effective_time(scheduler)
                 > (lock_time + scheduler->shutdown_lock))) {
             pcmk__rsc_info(rsc, "Shutdown lock for %s on %s expired",
                            rsc->id, pcmk__node_name(node));
             pe__clear_resource_history(rsc, node);
         } else {
             /* @COMPAT I don't like breaking const signatures, but
              * rsc->lock_node should really be const -- we just can't change it
              * until the next API compatibility break.
              */
             rsc->lock_node = (pcmk_node_t *) node;
             rsc->lock_time = lock_time;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one lrm_resource entry from a node's CIB status
  *
  * \param[in,out] node       Node whose status is being unpacked
  * \param[in]     rsc_entry  lrm_resource XML being unpacked
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Resource corresponding to the entry, or NULL if no operation history
  */
 static pcmk_resource_t *
 unpack_lrm_resource(pcmk_node_t *node, const xmlNode *lrm_resource,
                     pcmk_scheduler_t *scheduler)
 {
     GList *gIter = NULL;
     int stop_index = -1;
     int start_index = -1;
     enum rsc_role_e req_role = pcmk_role_unknown;
 
     const char *rsc_id = ID(lrm_resource);
 
     pcmk_resource_t *rsc = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     xmlNode *rsc_op = NULL;
     xmlNode *last_failure = NULL;
 
     enum action_fail_response on_fail = pcmk_on_fail_ignore;
     enum rsc_role_e saved_role = pcmk_role_unknown;
 
     if (rsc_id == NULL) {
         pcmk__config_err("Ignoring invalid " XML_LRM_TAG_RESOURCE
                          " entry: No " PCMK_XA_ID);
         crm_log_xml_info(lrm_resource, "missing-id");
         return NULL;
     }
     crm_trace("Unpacking " XML_LRM_TAG_RESOURCE " for %s on %s",
               rsc_id, pcmk__node_name(node));
 
     // Build a list of individual lrm_rsc_op entries, so we can sort them
     for (rsc_op = first_named_child(lrm_resource, XML_LRM_TAG_RSC_OP);
          rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) {
 
         op_list = g_list_prepend(op_list, rsc_op);
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         }
     }
 
     /* find the resource */
     rsc = unpack_find_resource(scheduler, node, rsc_id);
     if (rsc == NULL) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         } else {
             rsc = process_orphan_resource(lrm_resource, node, scheduler);
         }
     }
     CRM_ASSERT(rsc != NULL);
 
     // Check whether the resource is "shutdown-locked" to this node
     if (pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         unpack_shutdown_lock(lrm_resource, rsc, node, scheduler);
     }
 
     /* process operations */
     saved_role = rsc->role;
     rsc->role = pcmk_role_unknown;
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         unpack_rsc_op(rsc, node, rsc_op, &last_failure, &on_fail);
     }
 
     /* create active recurring operations as optional */
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
     process_recurring(node, rsc, start_index, stop_index, sorted_op_list,
                       scheduler);
 
     /* no need to free the contents */
     g_list_free(sorted_op_list);
 
     process_rsc_state(rsc, node, on_fail);
 
     if (get_target_role(rsc, &req_role)) {
         if ((rsc->next_role == pcmk_role_unknown)
             || (req_role < rsc->next_role)) {
 
             pe__set_next_role(rsc, req_role, PCMK_META_TARGET_ROLE);
 
         } else if (req_role > rsc->next_role) {
             pcmk__rsc_info(rsc,
                            "%s: Not overwriting calculated next role %s"
                            " with requested next role %s",
                            rsc->id, role2text(rsc->next_role),
                            role2text(req_role));
         }
     }
 
     if (saved_role > rsc->role) {
         rsc->role = saved_role;
     }
 
     return rsc;
 }
 
 static void
 handle_orphaned_container_fillers(const xmlNode *lrm_rsc_list,
                                   pcmk_scheduler_t *scheduler)
 {
     for (const xmlNode *rsc_entry = pcmk__xe_first_child(lrm_rsc_list);
          rsc_entry != NULL; rsc_entry = pcmk__xe_next(rsc_entry)) {
 
         pcmk_resource_t *rsc;
         pcmk_resource_t *container;
         const char *rsc_id;
         const char *container_id;
 
         if (!pcmk__str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, pcmk__str_casei)) {
             continue;
         }
 
         container_id = crm_element_value(rsc_entry, PCMK__META_CONTAINER);
         rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
         if (container_id == NULL || rsc_id == NULL) {
             continue;
         }
 
         container = pe_find_resource(scheduler->resources, container_id);
         if (container == NULL) {
             continue;
         }
 
         rsc = pe_find_resource(scheduler->resources, rsc_id);
         if ((rsc == NULL) || (rsc->container != NULL)
             || !pcmk_is_set(rsc->flags, pcmk_rsc_removed_filler)) {
             continue;
         }
 
         pcmk__rsc_trace(rsc, "Mapped container of orphaned resource %s to %s",
                         rsc->id, container_id);
         rsc->container = container;
         container->fillers = g_list_append(container->fillers, rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one node's lrm status section
  *
  * \param[in,out] node       Node whose status is being unpacked
  * \param[in]     xml        CIB node state XML
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_node_lrm(pcmk_node_t *node, const xmlNode *xml,
                 pcmk_scheduler_t *scheduler)
 {
     bool found_orphaned_container_filler = false;
 
     // Drill down to lrm_resources section
     xml = find_xml_node(xml, XML_CIB_TAG_LRM, FALSE);
     if (xml == NULL) {
         return;
     }
     xml = find_xml_node(xml, XML_LRM_TAG_RESOURCES, FALSE);
     if (xml == NULL) {
         return;
     }
 
     // Unpack each lrm_resource entry
     for (const xmlNode *rsc_entry = first_named_child(xml, XML_LRM_TAG_RESOURCE);
          rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
 
         pcmk_resource_t *rsc = unpack_lrm_resource(node, rsc_entry, scheduler);
 
         if ((rsc != NULL)
             && pcmk_is_set(rsc->flags, pcmk_rsc_removed_filler)) {
             found_orphaned_container_filler = true;
         }
     }
 
     /* Now that all resource state has been unpacked for this node, map any
      * orphaned container fillers to their container resource.
      */
     if (found_orphaned_container_filler) {
         handle_orphaned_container_fillers(xml, scheduler);
     }
 }
 
 static void
 set_active(pcmk_resource_t *rsc)
 {
     const pcmk_resource_t *top = pe__const_top_resource(rsc, false);
 
     if (top && pcmk_is_set(top->flags, pcmk_rsc_promotable)) {
         rsc->role = pcmk_role_unpromoted;
     } else {
         rsc->role = pcmk_role_started;
     }
 }
 
 static void
 set_node_score(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk_node_t *node = value;
     int *score = user_data;
 
     node->weight = *score;
 }
 
 #define XPATH_NODE_STATE "/" PCMK_XE_CIB "/" PCMK_XE_STATUS \
                          "/" XML_CIB_TAG_STATE
 #define SUB_XPATH_LRM_RESOURCE "/" XML_CIB_TAG_LRM              \
                                "/" XML_LRM_TAG_RESOURCES        \
                                "/" XML_LRM_TAG_RESOURCE
 #define SUB_XPATH_LRM_RSC_OP "/" XML_LRM_TAG_RSC_OP
 
 static xmlNode *
 find_lrm_op(const char *resource, const char *op, const char *node, const char *source,
             int target_rc, pcmk_scheduler_t *scheduler)
 {
     GString *xpath = NULL;
     xmlNode *xml = NULL;
 
     CRM_CHECK((resource != NULL) && (op != NULL) && (node != NULL),
               return NULL);
 
     xpath = g_string_sized_new(256);
     pcmk__g_strcat(xpath,
                    XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='", node, "']"
                    SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='", resource, "']"
                    SUB_XPATH_LRM_RSC_OP "[@" PCMK_XA_OPERATION "='", op, "'",
                    NULL);
 
     /* Need to check against transition_magic too? */
     if ((source != NULL) && (strcmp(op, PCMK_ACTION_MIGRATE_TO) == 0)) {
         pcmk__g_strcat(xpath,
                        " and @" PCMK__META_MIGRATE_TARGET "='", source, "']",
                        NULL);
 
     } else if ((source != NULL)
                && (strcmp(op, PCMK_ACTION_MIGRATE_FROM) == 0)) {
         pcmk__g_strcat(xpath,
                        " and @" PCMK__META_MIGRATE_SOURCE "='", source, "']",
                        NULL);
     } else {
         g_string_append_c(xpath, ']');
     }
 
     xml = get_xpath_object((const char *) xpath->str, scheduler->input,
                            LOG_DEBUG);
     g_string_free(xpath, TRUE);
 
     if (xml && target_rc >= 0) {
         int rc = PCMK_OCF_UNKNOWN_ERROR;
         int status = PCMK_EXEC_ERROR;
 
         crm_element_value_int(xml, PCMK__XA_RC_CODE, &rc);
         crm_element_value_int(xml, PCMK__XA_OP_STATUS, &status);
         if ((rc != target_rc) || (status != PCMK_EXEC_DONE)) {
             return NULL;
         }
     }
     return xml;
 }
 
 static xmlNode *
 find_lrm_resource(const char *rsc_id, const char *node_name,
                   pcmk_scheduler_t *scheduler)
 {
     GString *xpath = NULL;
     xmlNode *xml = NULL;
 
     CRM_CHECK((rsc_id != NULL) && (node_name != NULL), return NULL);
 
     xpath = g_string_sized_new(256);
     pcmk__g_strcat(xpath,
                    XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='", node_name, "']"
                    SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='", rsc_id, "']",
                    NULL);
 
     xml = get_xpath_object((const char *) xpath->str, scheduler->input,
                            LOG_DEBUG);
 
     g_string_free(xpath, TRUE);
     return xml;
 }
 
 /*!
  * \internal
  * \brief Check whether a resource has no completed action history on a node
  *
  * \param[in,out] rsc        Resource to check
  * \param[in]     node_name  Node to check
  *
  * \return true if \p rsc_id is unknown on \p node_name, otherwise false
  */
 static bool
 unknown_on_node(pcmk_resource_t *rsc, const char *node_name)
 {
     bool result = false;
     xmlXPathObjectPtr search;
     GString *xpath = g_string_sized_new(256);
 
     pcmk__g_strcat(xpath,
                    XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='", node_name, "']"
                    SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='", rsc->id, "']"
                    SUB_XPATH_LRM_RSC_OP "[@" PCMK__XA_RC_CODE "!='193']",
                    NULL);
     search = xpath_search(rsc->cluster->input, (const char *) xpath->str);
     result = (numXpathResults(search) == 0);
     freeXpathObject(search);
     g_string_free(xpath, TRUE);
     return result;
 }
 
 /*!
  * \brief Check whether a probe/monitor indicating the resource was not running
  * on a node happened after some event
  *
  * \param[in]     rsc_id     Resource being checked
  * \param[in]     node_name  Node being checked
  * \param[in]     xml_op     Event that monitor is being compared to
  * \param[in]     same_node  Whether the operations are on the same node
  * \param[in,out] scheduler  Scheduler data
  *
  * \return true if such a monitor happened after event, false otherwise
  */
 static bool
 monitor_not_running_after(const char *rsc_id, const char *node_name,
                           const xmlNode *xml_op, bool same_node,
                           pcmk_scheduler_t *scheduler)
 {
     /* Any probe/monitor operation on the node indicating it was not running
      * there
      */
     xmlNode *monitor = find_lrm_op(rsc_id, PCMK_ACTION_MONITOR, node_name,
                                    NULL, PCMK_OCF_NOT_RUNNING, scheduler);
 
     return (monitor && pe__is_newer_op(monitor, xml_op, same_node) > 0);
 }
 
 /*!
  * \brief Check whether any non-monitor operation on a node happened after some
  * event
  *
  * \param[in]     rsc_id     Resource being checked
  * \param[in]     node_name  Node being checked
  * \param[in]     xml_op     Event that non-monitor is being compared to
  * \param[in]     same_node  Whether the operations are on the same node
  * \param[in,out] scheduler  Scheduler data
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 non_monitor_after(const char *rsc_id, const char *node_name,
                   const xmlNode *xml_op, bool same_node,
                   pcmk_scheduler_t *scheduler)
 {
     xmlNode *lrm_resource = NULL;
 
     lrm_resource = find_lrm_resource(rsc_id, node_name, scheduler);
     if (lrm_resource == NULL) {
         return false;
     }
 
     for (xmlNode *op = first_named_child(lrm_resource, XML_LRM_TAG_RSC_OP);
          op != NULL; op = crm_next_same_xml(op)) {
         const char * task = NULL;
 
         if (op == xml_op) {
             continue;
         }
 
         task = crm_element_value(op, PCMK_XA_OPERATION);
 
         if (pcmk__str_any_of(task, PCMK_ACTION_START, PCMK_ACTION_STOP,
                              PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
                              NULL)
             && pe__is_newer_op(op, xml_op, same_node) > 0) {
             return true;
         }
     }
 
     return false;
 }
 
 /*!
  * \brief Check whether the resource has newer state on a node after a migration
  * attempt
  *
  * \param[in]     rsc_id        Resource being checked
  * \param[in]     node_name     Node being checked
  * \param[in]     migrate_to    Any migrate_to event that is being compared to
  * \param[in]     migrate_from  Any migrate_from event that is being compared to
  * \param[in,out] scheduler     Scheduler data
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 newer_state_after_migrate(const char *rsc_id, const char *node_name,
                           const xmlNode *migrate_to,
                           const xmlNode *migrate_from,
                           pcmk_scheduler_t *scheduler)
 {
     const xmlNode *xml_op = migrate_to;
     const char *source = NULL;
     const char *target = NULL;
     bool same_node = false;
 
     if (migrate_from) {
         xml_op = migrate_from;
     }
 
     source = crm_element_value(xml_op, PCMK__META_MIGRATE_SOURCE);
     target = crm_element_value(xml_op, PCMK__META_MIGRATE_TARGET);
 
     /* It's preferred to compare to the migrate event on the same node if
      * existing, since call ids are more reliable.
      */
     if (pcmk__str_eq(node_name, target, pcmk__str_casei)) {
         if (migrate_from) {
            xml_op = migrate_from;
            same_node = true;
 
         } else {
            xml_op = migrate_to;
         }
 
     } else if (pcmk__str_eq(node_name, source, pcmk__str_casei)) {
         if (migrate_to) {
            xml_op = migrate_to;
            same_node = true;
 
         } else {
            xml_op = migrate_from;
         }
     }
 
     /* If there's any newer non-monitor operation on the node, or any newer
      * probe/monitor operation on the node indicating it was not running there,
      * the migration events potentially no longer matter for the node.
      */
     return non_monitor_after(rsc_id, node_name, xml_op, same_node, scheduler)
            || monitor_not_running_after(rsc_id, node_name, xml_op, same_node,
                                         scheduler);
 }
 
 /*!
  * \internal
  * \brief Parse migration source and target node names from history entry
  *
  * \param[in]  entry        Resource history entry for a migration action
  * \param[in]  source_node  If not NULL, source must match this node
  * \param[in]  target_node  If not NULL, target must match this node
  * \param[out] source_name  Where to store migration source node name
  * \param[out] target_name  Where to store migration target node name
  *
  * \return Standard Pacemaker return code
  */
 static int
 get_migration_node_names(const xmlNode *entry, const pcmk_node_t *source_node,
                          const pcmk_node_t *target_node,
                          const char **source_name, const char **target_name)
 {
     *source_name = crm_element_value(entry, PCMK__META_MIGRATE_SOURCE);
     *target_name = crm_element_value(entry, PCMK__META_MIGRATE_TARGET);
     if ((*source_name == NULL) || (*target_name == NULL)) {
         pcmk__config_err("Ignoring resource history entry %s without "
                          PCMK__META_MIGRATE_SOURCE " and "
                          PCMK__META_MIGRATE_TARGET, ID(entry));
         return pcmk_rc_unpack_error;
     }
 
     if ((source_node != NULL)
         && !pcmk__str_eq(*source_name, source_node->details->uname,
                          pcmk__str_casei|pcmk__str_null_matches)) {
         pcmk__config_err("Ignoring resource history entry %s because "
                          PCMK__META_MIGRATE_SOURCE "='%s' does not match %s",
                          ID(entry), *source_name, pcmk__node_name(source_node));
         return pcmk_rc_unpack_error;
     }
 
     if ((target_node != NULL)
         && !pcmk__str_eq(*target_name, target_node->details->uname,
                          pcmk__str_casei|pcmk__str_null_matches)) {
         pcmk__config_err("Ignoring resource history entry %s because "
                          PCMK__META_MIGRATE_TARGET "='%s' does not match %s",
                          ID(entry), *target_name, pcmk__node_name(target_node));
         return pcmk_rc_unpack_error;
     }
 
     return pcmk_rc_ok;
 }
 
 /*
  * \internal
  * \brief Add a migration source to a resource's list of dangling migrations
  *
  * If the migrate_to and migrate_from actions in a live migration both
  * succeeded, but there is no stop on the source, the migration is considered
  * "dangling." Add the source to the resource's dangling migration list, which
  * will be used to schedule a stop on the source without affecting the target.
  *
  * \param[in,out] rsc   Resource involved in migration
  * \param[in]     node  Migration source
  */
 static void
 add_dangling_migration(pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     pcmk__rsc_trace(rsc, "Dangling migration of %s requires stop on %s",
                     rsc->id, pcmk__node_name(node));
     rsc->role = pcmk_role_stopped;
     rsc->dangling_migrations = g_list_prepend(rsc->dangling_migrations,
                                               (gpointer) node);
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a successful migrate_to action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_to_success(struct action_history *history)
 {
     /* A complete migration sequence is:
      * 1. migrate_to on source node (which succeeded if we get to this function)
      * 2. migrate_from on target node
      * 3. stop on source node
      *
      * If no migrate_from has happened, the migration is considered to be
      * "partial". If the migrate_from succeeded but no stop has happened, the
      * migration is considered to be "dangling".
      *
      * If a successful migrate_to and stop have happened on the source node, we
      * still need to check for a partial migration, due to scenarios (easier to
      * produce with batch-limit=1) like:
      *
      * - A resource is migrating from node1 to node2, and a migrate_to is
      *   initiated for it on node1.
      *
      * - node2 goes into standby mode while the migrate_to is pending, which
      *   aborts the transition.
      *
      * - Upon completion of the migrate_to, a new transition schedules a stop
      *   on both nodes and a start on node1.
      *
      * - If the new transition is aborted for any reason while the resource is
      *   stopping on node1, the transition after that stop completes will see
      *   the migrate_to and stop on the source, but it's still a partial
      *   migration, and the resource must be stopped on node2 because it is
      *   potentially active there due to the migrate_to.
      *
      *   We also need to take into account that either node's history may be
      *   cleared at any point in the migration process.
      */
     int from_rc = PCMK_OCF_OK;
     int from_status = PCMK_EXEC_PENDING;
     pcmk_node_t *target_node = NULL;
     xmlNode *migrate_from = NULL;
     const char *source = NULL;
     const char *target = NULL;
     bool source_newer_op = false;
     bool target_newer_state = false;
     bool active_on_target = false;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, history->node, NULL, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     // Check for newer state on the source
     source_newer_op = non_monitor_after(history->rsc->id, source, history->xml,
                                         true, history->rsc->cluster);
 
     // Check for a migrate_from action from this source on the target
     migrate_from = find_lrm_op(history->rsc->id, PCMK_ACTION_MIGRATE_FROM,
                                target, source, -1, history->rsc->cluster);
     if (migrate_from != NULL) {
         if (source_newer_op) {
             /* There's a newer non-monitor operation on the source and a
              * migrate_from on the target, so this migrate_to is irrelevant to
              * the resource's state.
              */
             return;
         }
         crm_element_value_int(migrate_from, PCMK__XA_RC_CODE, &from_rc);
         crm_element_value_int(migrate_from, PCMK__XA_OP_STATUS, &from_status);
     }
 
     /* If the resource has newer state on both the source and target after the
      * migration events, this migrate_to is irrelevant to the resource's state.
      */
     target_newer_state = newer_state_after_migrate(history->rsc->id, target,
                                                    history->xml, migrate_from,
                                                    history->rsc->cluster);
     if (source_newer_op && target_newer_state) {
         return;
     }
 
     /* Check for dangling migration (migrate_from succeeded but stop not done).
      * We know there's no stop because we already returned if the target has a
      * migrate_from and the source has any newer non-monitor operation.
      */
     if ((from_rc == PCMK_OCF_OK) && (from_status == PCMK_EXEC_DONE)) {
         add_dangling_migration(history->rsc, history->node);
         return;
     }
 
     /* Without newer state, this migrate_to implies the resource is active.
      * (Clones are not allowed to migrate, so role can't be promoted.)
      */
     history->rsc->role = pcmk_role_started;
 
     target_node = pe_find_node(history->rsc->cluster->nodes, target);
     active_on_target = !target_newer_state && (target_node != NULL)
                        && target_node->details->online;
 
     if (from_status != PCMK_EXEC_PENDING) { // migrate_from failed on target
         if (active_on_target) {
             native_add_running(history->rsc, target_node, history->rsc->cluster,
                                TRUE);
         } else {
             // Mark resource as failed, require recovery, and prevent migration
             pcmk__set_rsc_flags(history->rsc,
                                 pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_migratable);
         }
         return;
     }
 
     // The migrate_from is pending, complete but erased, or to be scheduled
 
     /* If there is no history at all for the resource on an online target, then
      * it was likely cleaned. Just return, and we'll schedule a probe. Once we
      * have the probe result, it will be reflected in target_newer_state.
      */
     if ((target_node != NULL) && target_node->details->online
         && unknown_on_node(history->rsc, target)) {
         return;
     }
 
     if (active_on_target) {
         pcmk_node_t *source_node = pe_find_node(history->rsc->cluster->nodes,
                                                 source);
 
         native_add_running(history->rsc, target_node, history->rsc->cluster,
                            FALSE);
         if ((source_node != NULL) && source_node->details->online) {
             /* This is a partial migration: the migrate_to completed
              * successfully on the source, but the migrate_from has not
              * completed. Remember the source and target; if the newly
              * chosen target remains the same when we schedule actions
              * later, we may continue with the migration.
              */
             history->rsc->partial_migration_target = target_node;
             history->rsc->partial_migration_source = source_node;
         }
 
     } else if (!source_newer_op) {
         // Mark resource as failed, require recovery, and prevent migration
         pcmk__set_rsc_flags(history->rsc,
                             pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
         pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_migratable);
     }
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a failed migrate_to action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_to_failure(struct action_history *history)
 {
     xmlNode *target_migrate_from = NULL;
     const char *source = NULL;
     const char *target = NULL;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, history->node, NULL, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     history->rsc->role = pcmk_role_started;
 
     // Check for migrate_from on the target
     target_migrate_from = find_lrm_op(history->rsc->id,
                                       PCMK_ACTION_MIGRATE_FROM, target, source,
                                       PCMK_OCF_OK, history->rsc->cluster);
 
     if (/* If the resource state is unknown on the target, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(history->rsc, target)
         /* If the resource has newer state on the target after the migration
          * events, this migrate_to no longer matters for the target.
          */
         && !newer_state_after_migrate(history->rsc->id, target, history->xml,
                                       target_migrate_from,
                                       history->rsc->cluster)) {
         /* The resource has no newer state on the target, so assume it's still
          * active there.
          * (if it is up).
          */
         pcmk_node_t *target_node = pe_find_node(history->rsc->cluster->nodes,
                                                 target);
 
         if (target_node && target_node->details->online) {
             native_add_running(history->rsc, target_node, history->rsc->cluster,
                                FALSE);
         }
 
     } else if (!non_monitor_after(history->rsc->id, source, history->xml, true,
                                   history->rsc->cluster)) {
         /* We know the resource has newer state on the target, but this
          * migrate_to still matters for the source as long as there's no newer
          * non-monitor operation there.
          */
 
         // Mark node as having dangling migration so we can force a stop later
         history->rsc->dangling_migrations =
             g_list_prepend(history->rsc->dangling_migrations,
                            (gpointer) history->node);
     }
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a failed migrate_from action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_from_failure(struct action_history *history)
 {
     xmlNode *source_migrate_to = NULL;
     const char *source = NULL;
     const char *target = NULL;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, NULL, history->node, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     history->rsc->role = pcmk_role_started;
 
     // Check for a migrate_to on the source
     source_migrate_to = find_lrm_op(history->rsc->id, PCMK_ACTION_MIGRATE_TO,
                                     source, target, PCMK_OCF_OK,
                                     history->rsc->cluster);
 
     if (/* If the resource state is unknown on the source, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(history->rsc, source)
         /* If the resource has newer state on the source after the migration
          * events, this migrate_from no longer matters for the source.
          */
         && !newer_state_after_migrate(history->rsc->id, source,
                                       source_migrate_to, history->xml,
                                       history->rsc->cluster)) {
         /* The resource has no newer state on the source, so assume it's still
          * active there (if it is up).
          */
         pcmk_node_t *source_node = pe_find_node(history->rsc->cluster->nodes,
                                                 source);
 
         if (source_node && source_node->details->online) {
             native_add_running(history->rsc, source_node, history->rsc->cluster,
                                TRUE);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Add an action to cluster's list of failed actions
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 record_failed_op(struct action_history *history)
 {
     if (!(history->node->details->online)) {
         return;
     }
 
     for (const xmlNode *xIter = history->rsc->cluster->failed->children;
          xIter != NULL; xIter = xIter->next) {
 
         const char *key = pcmk__xe_history_key(xIter);
         const char *uname = crm_element_value(xIter, PCMK_XA_UNAME);
 
         if (pcmk__str_eq(history->key, key, pcmk__str_none)
             && pcmk__str_eq(uname, history->node->details->uname,
                             pcmk__str_casei)) {
             crm_trace("Skipping duplicate entry %s on %s",
                       history->key, pcmk__node_name(history->node));
             return;
         }
     }
 
     crm_trace("Adding entry for %s on %s to failed action list",
               history->key, pcmk__node_name(history->node));
     crm_xml_add(history->xml, PCMK_XA_UNAME, history->node->details->uname);
     crm_xml_add(history->xml, PCMK__XA_RSC_ID, history->rsc->id);
     add_node_copy(history->rsc->cluster->failed, history->xml);
 }
 
 static char *
 last_change_str(const xmlNode *xml_op)
 {
     time_t when;
     char *result = NULL;
 
     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
                                 &when) == pcmk_ok) {
         char *when_s = pcmk__epoch2str(&when, 0);
         const char *p = strchr(when_s, ' ');
 
         // Skip day of week to make message shorter
         if ((p != NULL) && (*(++p) != '\0')) {
             result = strdup(p);
             CRM_ASSERT(result != NULL);
         }
         free(when_s);
     }
 
     if (result == NULL) {
         result = strdup("unknown time");
         CRM_ASSERT(result != NULL);
     }
 
     return result;
 }
 
 /*!
  * \internal
  * \brief Compare two on-fail values
  *
  * \param[in] first   One on-fail value to compare
  * \param[in] second  The other on-fail value to compare
  *
  * \return A negative number if second is more severe than first, zero if they
  *         are equal, or a positive number if first is more severe than second.
  * \note This is only needed until the action_fail_response values can be
  *       renumbered at the next API compatibility break.
  */
 static int
 cmp_on_fail(enum action_fail_response first, enum action_fail_response second)
 {
     switch (first) {
         case pcmk_on_fail_demote:
             switch (second) {
                 case pcmk_on_fail_ignore:
                     return 1;
                 case pcmk_on_fail_demote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case pcmk_on_fail_reset_remote:
             switch (second) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                     return 1;
                 case pcmk_on_fail_reset_remote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case pcmk_on_fail_restart_container:
             switch (second) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                 case pcmk_on_fail_reset_remote:
                     return 1;
                 case pcmk_on_fail_restart_container:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         default:
             break;
     }
     switch (second) {
         case pcmk_on_fail_demote:
             return (first == pcmk_on_fail_ignore)? -1 : 1;
 
         case pcmk_on_fail_reset_remote:
             switch (first) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         case pcmk_on_fail_restart_container:
             switch (first) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                 case pcmk_on_fail_reset_remote:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         default:
             break;
     }
     return first - second;
 }
 
 /*!
  * \internal
  * \brief Ban a resource (or its clone if an anonymous instance) from all nodes
  *
  * \param[in,out] rsc  Resource to ban
  */
 static void
 ban_from_all_nodes(pcmk_resource_t *rsc)
 {
     int score = -INFINITY;
     pcmk_resource_t *fail_rsc = rsc;
 
     if (fail_rsc->parent != NULL) {
         pcmk_resource_t *parent = uber_parent(fail_rsc);
 
         if (pe_rsc_is_anon_clone(parent)) {
             /* For anonymous clones, if an operation with on-fail=stop fails for
              * any instance, the entire clone must stop.
              */
             fail_rsc = parent;
         }
     }
 
     // Ban the resource from all nodes
     crm_notice("%s will not be started under current conditions", fail_rsc->id);
     if (fail_rsc->allowed_nodes != NULL) {
         g_hash_table_destroy(fail_rsc->allowed_nodes);
     }
     fail_rsc->allowed_nodes = pe__node_list2table(rsc->cluster->nodes);
     g_hash_table_foreach(fail_rsc->allowed_nodes, set_node_score, &score);
 }
 
 /*!
  * \internal
  * \brief Get configured failure handling and role after failure for an action
  *
  * \param[in,out] history    Unpacked action history entry
  * \param[out]    on_fail    Where to set configured failure handling
  * \param[out]    fail_role  Where to set to role after failure
  */
 static void
 unpack_failure_handling(struct action_history *history,
                         enum action_fail_response *on_fail,
                         enum rsc_role_e *fail_role)
 {
     xmlNode *config = pcmk__find_action_config(history->rsc, history->task,
                                                history->interval_ms, true);
 
     GHashTable *meta = pcmk__unpack_action_meta(history->rsc, history->node,
                                                 history->task,
                                                 history->interval_ms, config);
 
     const char *on_fail_str = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
 
     *on_fail = pcmk__parse_on_fail(history->rsc, history->task,
                                    history->interval_ms, on_fail_str);
     *fail_role = pcmk__role_after_failure(history->rsc, history->task, *on_fail,
                                           meta);
     g_hash_table_destroy(meta);
 }
 
 /*!
  * \internal
  * \brief Update resource role, failure handling, etc., after a failed action
  *
  * \param[in,out] history         Parsed action result history
  * \param[in]     config_on_fail  Action failure handling from configuration
  * \param[in]     fail_role       Resource's role after failure of this action
  * \param[out]    last_failure    This will be set to the history XML
  * \param[in,out] on_fail         Actual handling of action result
  */
 static void
 unpack_rsc_op_failure(struct action_history *history,
                       enum action_fail_response config_on_fail,
                       enum rsc_role_e fail_role, xmlNode **last_failure,
                       enum action_fail_response *on_fail)
 {
     bool is_probe = false;
     char *last_change_s = NULL;
 
     *last_failure = history->xml;
 
     is_probe = pcmk_xe_is_probe(history->xml);
     last_change_s = last_change_str(history->xml);
 
     if (!pcmk_is_set(history->rsc->cluster->flags, pcmk_sched_symmetric_cluster)
         && (history->exit_status == PCMK_OCF_NOT_INSTALLED)) {
         crm_trace("Unexpected result (%s%s%s) was recorded for "
                   "%s of %s on %s at %s " CRM_XS " exit-status=%d id=%s",
                   services_ocf_exitcode_str(history->exit_status),
                   (pcmk__str_empty(history->exit_reason)? "" : ": "),
                   pcmk__s(history->exit_reason, ""),
                   (is_probe? "probe" : history->task), history->rsc->id,
                   pcmk__node_name(history->node), last_change_s,
                   history->exit_status, history->id);
     } else {
         pcmk__sched_warn("Unexpected result (%s%s%s) was recorded for %s of "
                          "%s on %s at %s " CRM_XS " exit-status=%d id=%s",
                          services_ocf_exitcode_str(history->exit_status),
                          (pcmk__str_empty(history->exit_reason)? "" : ": "),
                          pcmk__s(history->exit_reason, ""),
                          (is_probe? "probe" : history->task), history->rsc->id,
                          pcmk__node_name(history->node), last_change_s,
                          history->exit_status, history->id);
 
         if (is_probe && (history->exit_status != PCMK_OCF_OK)
             && (history->exit_status != PCMK_OCF_NOT_RUNNING)
             && (history->exit_status != PCMK_OCF_RUNNING_PROMOTED)) {
 
             /* A failed (not just unexpected) probe result could mean the user
              * didn't know resources will be probed even where they can't run.
              */
             crm_notice("If it is not possible for %s to run on %s, see "
                        "the " PCMK_XA_RESOURCE_DISCOVERY " option for location "
                        "constraints",
                        history->rsc->id, pcmk__node_name(history->node));
         }
 
         record_failed_op(history);
     }
 
     free(last_change_s);
 
     if (cmp_on_fail(*on_fail, config_on_fail) < 0) {
         pcmk__rsc_trace(history->rsc, "on-fail %s -> %s for %s",
                         fail2text(*on_fail), fail2text(config_on_fail),
                         history->key);
         *on_fail = config_on_fail;
     }
 
     if (strcmp(history->task, PCMK_ACTION_STOP) == 0) {
         resource_location(history->rsc, history->node, -INFINITY,
                           "__stop_fail__", history->rsc->cluster);
 
     } else if (strcmp(history->task, PCMK_ACTION_MIGRATE_TO) == 0) {
         unpack_migrate_to_failure(history);
 
     } else if (strcmp(history->task, PCMK_ACTION_MIGRATE_FROM) == 0) {
         unpack_migrate_from_failure(history);
 
     } else if (strcmp(history->task, PCMK_ACTION_PROMOTE) == 0) {
         history->rsc->role = pcmk_role_promoted;
 
     } else if (strcmp(history->task, PCMK_ACTION_DEMOTE) == 0) {
         if (config_on_fail == pcmk_on_fail_block) {
             history->rsc->role = pcmk_role_promoted;
             pe__set_next_role(history->rsc, pcmk_role_stopped,
                               "demote with on-fail=block");
 
         } else if (history->exit_status == PCMK_OCF_NOT_RUNNING) {
             history->rsc->role = pcmk_role_stopped;
 
         } else {
             /* Staying in the promoted role would put the scheduler and
              * controller into a loop. Setting the role to unpromoted is not
              * dangerous because the resource will be stopped as part of
              * recovery, and any promotion will be ordered after that stop.
              */
             history->rsc->role = pcmk_role_unpromoted;
         }
     }
 
     if (is_probe && (history->exit_status == PCMK_OCF_NOT_INSTALLED)) {
         /* leave stopped */
         pcmk__rsc_trace(history->rsc, "Leaving %s stopped", history->rsc->id);
         history->rsc->role = pcmk_role_stopped;
 
     } else if (history->rsc->role < pcmk_role_started) {
         pcmk__rsc_trace(history->rsc, "Setting %s active", history->rsc->id);
         set_active(history->rsc);
     }
 
     pcmk__rsc_trace(history->rsc,
                     "Resource %s: role=%s unclean=%s on_fail=%s fail_role=%s",
                     history->rsc->id, role2text(history->rsc->role),
                     pcmk__btoa(history->node->details->unclean),
                     fail2text(config_on_fail), role2text(fail_role));
 
     if ((fail_role != pcmk_role_started)
         && (history->rsc->next_role < fail_role)) {
         pe__set_next_role(history->rsc, fail_role, "failure");
     }
 
     if (fail_role == pcmk_role_stopped) {
         ban_from_all_nodes(history->rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Block a resource with a failed action if it cannot be recovered
  *
  * If resource action is a failed stop and fencing is not possible, mark the
  * resource as unmanaged and blocked, since recovery cannot be done.
  *
  * \param[in,out] history  Parsed action history entry
  */
 static void
 block_if_unrecoverable(struct action_history *history)
 {
     char *last_change_s = NULL;
 
     if (strcmp(history->task, PCMK_ACTION_STOP) != 0) {
         return; // All actions besides stop are always recoverable
     }
     if (pe_can_fence(history->node->details->data_set, history->node)) {
         return; // Failed stops are recoverable via fencing
     }
 
     last_change_s = last_change_str(history->xml);
     pcmk__sched_err("No further recovery can be attempted for %s "
                     "because %s on %s failed (%s%s%s) at %s "
                     CRM_XS " rc=%d id=%s",
                     history->rsc->id, history->task,
                     pcmk__node_name(history->node),
                     services_ocf_exitcode_str(history->exit_status),
                     (pcmk__str_empty(history->exit_reason)? "" : ": "),
                     pcmk__s(history->exit_reason, ""),
                     last_change_s, history->exit_status, history->id);
 
     free(last_change_s);
 
     pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_managed);
     pcmk__set_rsc_flags(history->rsc, pcmk_rsc_blocked);
 }
 
 /*!
  * \internal
  * \brief Update action history's execution status and why
  *
  * \param[in,out] history  Parsed action history entry
  * \param[out]    why      Where to store reason for update
  * \param[in]     value    New value
  * \param[in]     reason   Description of why value was changed
  */
 static inline void
 remap_because(struct action_history *history, const char **why, int value,
               const char *reason)
 {
     if (history->execution_status != value) {
         history->execution_status = value;
         *why = reason;
     }
 }
 
 /*!
  * \internal
  * \brief Remap informational monitor results and operation status
  *
  * For the monitor results, certain OCF codes are for providing extended information
  * to the user about services that aren't yet failed but not entirely healthy either.
  * These must be treated as the "normal" result by Pacemaker.
  *
  * For operation status, the action result can be used to determine an appropriate
  * status for the purposes of responding to the action.  The status provided by the
  * executor is not directly usable since the executor does not know what was expected.
  *
  * \param[in,out] history  Parsed action history entry
  * \param[in,out] on_fail  What should be done about the result
  * \param[in]     expired  Whether result is expired
  *
  * \note If the result is remapped and the node is not shutting down or failed,
  *       the operation will be recorded in the scheduler data's list of failed
  *       operations to highlight it for the user.
  *
  * \note This may update the resource's current and next role.
  */
 static void
 remap_operation(struct action_history *history,
                 enum action_fail_response *on_fail, bool expired)
 {
     bool is_probe = false;
     int orig_exit_status = history->exit_status;
     int orig_exec_status = history->execution_status;
     const char *why = NULL;
     const char *task = history->task;
 
     // Remap degraded results to their successful counterparts
     history->exit_status = pcmk__effective_rc(history->exit_status);
     if (history->exit_status != orig_exit_status) {
         why = "degraded result";
         if (!expired && (!history->node->details->shutdown
                          || history->node->details->online)) {
             record_failed_op(history);
         }
     }
 
     if (!pe_rsc_is_bundled(history->rsc)
         && pcmk_xe_mask_probe_failure(history->xml)
         && ((history->execution_status != PCMK_EXEC_DONE)
             || (history->exit_status != PCMK_OCF_NOT_RUNNING))) {
         history->execution_status = PCMK_EXEC_DONE;
         history->exit_status = PCMK_OCF_NOT_RUNNING;
         why = "equivalent probe result";
     }
 
     /* If the executor reported an execution status of anything but done or
      * error, consider that final. But for done or error, we know better whether
      * it should be treated as a failure or not, because we know the expected
      * result.
      */
     switch (history->execution_status) {
         case PCMK_EXEC_DONE:
         case PCMK_EXEC_ERROR:
             break;
 
         // These should be treated as node-fatal
         case PCMK_EXEC_NO_FENCE_DEVICE:
         case PCMK_EXEC_NO_SECRETS:
             remap_because(history, &why, PCMK_EXEC_ERROR_HARD,
                           "node-fatal error");
             goto remap_done;
 
         default:
             goto remap_done;
     }
 
     is_probe = pcmk_xe_is_probe(history->xml);
     if (is_probe) {
         task = "probe";
     }
 
     if (history->expected_exit_status < 0) {
         /* Pre-1.0 Pacemaker versions, and Pacemaker 1.1.6 or earlier with
          * Heartbeat 2.0.7 or earlier as the cluster layer, did not include the
          * expected exit status in the transition key, which (along with the
          * similar case of a corrupted transition key in the CIB) will be
          * reported to this function as -1. Pacemaker 2.0+ does not support
          * rolling upgrades from those versions or processing of saved CIB files
          * from those versions, so we do not need to care much about this case.
          */
         remap_because(history, &why, PCMK_EXEC_ERROR,
                       "obsolete history format");
         pcmk__config_warn("Expected result not found for %s on %s "
                           "(corrupt or obsolete CIB?)",
                           history->key, pcmk__node_name(history->node));
 
     } else if (history->exit_status == history->expected_exit_status) {
         remap_because(history, &why, PCMK_EXEC_DONE, "expected result");
 
     } else {
         remap_because(history, &why, PCMK_EXEC_ERROR, "unexpected result");
         pcmk__rsc_debug(history->rsc,
                         "%s on %s: expected %d (%s), got %d (%s%s%s)",
                         history->key, pcmk__node_name(history->node),
                         history->expected_exit_status,
                         services_ocf_exitcode_str(history->expected_exit_status),
                         history->exit_status,
                         services_ocf_exitcode_str(history->exit_status),
                         (pcmk__str_empty(history->exit_reason)? "" : ": "),
                         pcmk__s(history->exit_reason, ""));
     }
 
     switch (history->exit_status) {
         case PCMK_OCF_OK:
             if (is_probe
                 && (history->expected_exit_status == PCMK_OCF_NOT_RUNNING)) {
                 char *last_change_s = last_change_str(history->xml);
 
                 remap_because(history, &why, PCMK_EXEC_DONE, "probe");
                 pcmk__rsc_info(history->rsc,
                                "Probe found %s active on %s at %s",
                                history->rsc->id, pcmk__node_name(history->node),
                                last_change_s);
                 free(last_change_s);
             }
             break;
 
         case PCMK_OCF_NOT_RUNNING:
             if (is_probe
                 || (history->expected_exit_status == history->exit_status)
                 || !pcmk_is_set(history->rsc->flags, pcmk_rsc_managed)) {
 
                 /* For probes, recurring monitors for the Stopped role, and
                  * unmanaged resources, "not running" is not considered a
                  * failure.
                  */
                 remap_because(history, &why, PCMK_EXEC_DONE, "exit status");
                 history->rsc->role = pcmk_role_stopped;
                 *on_fail = pcmk_on_fail_ignore;
                 pe__set_next_role(history->rsc, pcmk_role_unknown,
                                   "not running");
             }
             break;
 
         case PCMK_OCF_RUNNING_PROMOTED:
             if (is_probe
                 && (history->exit_status != history->expected_exit_status)) {
                 char *last_change_s = last_change_str(history->xml);
 
                 remap_because(history, &why, PCMK_EXEC_DONE, "probe");
                 pcmk__rsc_info(history->rsc,
                                "Probe found %s active and promoted on %s at %s",
                                 history->rsc->id,
                                 pcmk__node_name(history->node), last_change_s);
                 free(last_change_s);
             }
             if (!expired
                 || (history->exit_status == history->expected_exit_status)) {
                 history->rsc->role = pcmk_role_promoted;
             }
             break;
 
         case PCMK_OCF_FAILED_PROMOTED:
             if (!expired) {
                 history->rsc->role = pcmk_role_promoted;
             }
             remap_because(history, &why, PCMK_EXEC_ERROR, "exit status");
             break;
 
         case PCMK_OCF_NOT_CONFIGURED:
             remap_because(history, &why, PCMK_EXEC_ERROR_FATAL, "exit status");
             break;
 
         case PCMK_OCF_UNIMPLEMENT_FEATURE:
             {
                 guint interval_ms = 0;
                 crm_element_value_ms(history->xml, PCMK_META_INTERVAL,
                                      &interval_ms);
 
                 if (interval_ms == 0) {
                     if (!expired) {
                         block_if_unrecoverable(history);
                     }
                     remap_because(history, &why, PCMK_EXEC_ERROR_HARD,
                                   "exit status");
                 } else {
                     remap_because(history, &why, PCMK_EXEC_NOT_SUPPORTED,
                                   "exit status");
                 }
             }
             break;
 
         case PCMK_OCF_NOT_INSTALLED:
         case PCMK_OCF_INVALID_PARAM:
         case PCMK_OCF_INSUFFICIENT_PRIV:
             if (!expired) {
                 block_if_unrecoverable(history);
             }
             remap_because(history, &why, PCMK_EXEC_ERROR_HARD, "exit status");
             break;
 
         default:
             if (history->execution_status == PCMK_EXEC_DONE) {
                 char *last_change_s = last_change_str(history->xml);
 
                 crm_info("Treating unknown exit status %d from %s of %s "
                          "on %s at %s as failure",
                          history->exit_status, task, history->rsc->id,
                          pcmk__node_name(history->node), last_change_s);
                 remap_because(history, &why, PCMK_EXEC_ERROR,
                               "unknown exit status");
                 free(last_change_s);
             }
             break;
     }
 
 remap_done:
     if (why != NULL) {
         pcmk__rsc_trace(history->rsc,
                         "Remapped %s result from [%s: %s] to [%s: %s] "
                         "because of %s",
                         history->key, pcmk_exec_status_str(orig_exec_status),
                         crm_exit_str(orig_exit_status),
                         pcmk_exec_status_str(history->execution_status),
                         crm_exit_str(history->exit_status), why);
     }
 }
 
 // return TRUE if start or monitor last failure but parameters changed
 static bool
 should_clear_for_param_change(const xmlNode *xml_op, const char *task,
                               pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     if (pcmk__str_any_of(task, PCMK_ACTION_START, PCMK_ACTION_MONITOR, NULL)) {
         if (pe__bundle_needs_remote_name(rsc)) {
             /* We haven't allocated resources yet, so we can't reliably
              * substitute addr parameters for the REMOTE_CONTAINER_HACK.
              * When that's needed, defer the check until later.
              */
             pe__add_param_check(xml_op, rsc, node, pcmk__check_last_failure,
                                 rsc->cluster);
 
         } else {
             pcmk__op_digest_t *digest_data = NULL;
 
             digest_data = rsc_action_digest_cmp(rsc, xml_op, node,
                                                 rsc->cluster);
             switch (digest_data->rc) {
                 case pcmk__digest_unknown:
                     crm_trace("Resource %s history entry %s on %s"
                               " has no digest to compare",
                               rsc->id, pcmk__xe_history_key(xml_op),
                               node->details->id);
                     break;
                 case pcmk__digest_match:
                     break;
                 default:
                     return TRUE;
             }
         }
     }
     return FALSE;
 }
 
 // Order action after fencing of remote node, given connection rsc
 static void
 order_after_remote_fencing(pcmk_action_t *action, pcmk_resource_t *remote_conn,
                            pcmk_scheduler_t *scheduler)
 {
     pcmk_node_t *remote_node = pe_find_node(scheduler->nodes, remote_conn->id);
 
     if (remote_node) {
         pcmk_action_t *fence = pe_fence_op(remote_node, NULL, TRUE, NULL,
                                            FALSE, scheduler);
 
         order_actions(fence, action, pcmk__ar_first_implies_then);
     }
 }
 
 static bool
 should_ignore_failure_timeout(const pcmk_resource_t *rsc, const char *task,
                               guint interval_ms, bool is_last_failure)
 {
     /* Clearing failures of recurring monitors has special concerns. The
      * executor reports only changes in the monitor result, so if the
      * monitor is still active and still getting the same failure result,
      * that will go undetected after the failure is cleared.
      *
      * Also, the operation history will have the time when the recurring
      * monitor result changed to the given code, not the time when the
      * result last happened.
      *
      * @TODO We probably should clear such failures only when the failure
      * timeout has passed since the last occurrence of the failed result.
      * However we don't record that information. We could maybe approximate
      * that by clearing only if there is a more recent successful monitor or
      * stop result, but we don't even have that information at this point
      * since we are still unpacking the resource's operation history.
      *
      * This is especially important for remote connection resources with a
      * reconnect interval, so in that case, we skip clearing failures
      * if the remote node hasn't been fenced.
      */
     if (rsc->remote_reconnect_ms
         && pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)
         && (interval_ms != 0)
         && pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
 
         pcmk_node_t *remote_node = pe_find_node(rsc->cluster->nodes, rsc->id);
 
         if (remote_node && !remote_node->details->remote_was_fenced) {
             if (is_last_failure) {
                 crm_info("Waiting to clear monitor failure for remote node %s"
                          " until fencing has occurred", rsc->id);
             }
             return TRUE;
         }
     }
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Check operation age and schedule failure clearing when appropriate
  *
  * This function has two distinct purposes. The first is to check whether an
  * operation history entry is expired (i.e. the resource has a failure timeout,
  * the entry is older than the timeout, and the resource either has no fail
  * count or its fail count is entirely older than the timeout). The second is to
  * schedule fail count clearing when appropriate (i.e. the operation is expired
  * and either the resource has an expired fail count or the operation is a
  * last_failure for a remote connection resource with a reconnect interval,
  * or the operation is a last_failure for a start or monitor operation and the
  * resource's parameters have changed since the operation).
  *
  * \param[in,out] history  Parsed action result history
  *
  * \return true if operation history entry is expired, otherwise false
  */
 static bool
 check_operation_expiry(struct action_history *history)
 {
     bool expired = false;
     bool is_last_failure = pcmk__ends_with(history->id, "_last_failure_0");
     time_t last_run = 0;
     int unexpired_fail_count = 0;
     const char *clear_reason = NULL;
 
     if (history->execution_status == PCMK_EXEC_NOT_INSTALLED) {
         pcmk__rsc_trace(history->rsc,
                         "Resource history entry %s on %s is not expired: "
                         "Not Installed does not expire",
                         history->id, pcmk__node_name(history->node));
         return false; // "Not installed" must always be cleared manually
     }
 
     if ((history->rsc->failure_timeout > 0)
         && (crm_element_value_epoch(history->xml, PCMK_XA_LAST_RC_CHANGE,
                                     &last_run) == 0)) {
 
         /* Resource has a PCMK_META_FAILURE_TIMEOUT and history entry has a
          * timestamp
          */
 
         time_t now = get_effective_time(history->rsc->cluster);
         time_t last_failure = 0;
 
         // Is this particular operation history older than the failure timeout?
         if ((now >= (last_run + history->rsc->failure_timeout))
             && !should_ignore_failure_timeout(history->rsc, history->task,
                                               history->interval_ms,
                                               is_last_failure)) {
             expired = true;
         }
 
         // Does the resource as a whole have an unexpired fail count?
         unexpired_fail_count = pe_get_failcount(history->node, history->rsc,
                                                 &last_failure,
                                                 pcmk__fc_effective,
                                                 history->xml);
 
         // Update scheduler recheck time according to *last* failure
         crm_trace("%s@%lld is %sexpired @%lld with unexpired_failures=%d timeout=%ds"
                   " last-failure@%lld",
                   history->id, (long long) last_run, (expired? "" : "not "),
                   (long long) now, unexpired_fail_count,
                   history->rsc->failure_timeout, (long long) last_failure);
         last_failure += history->rsc->failure_timeout + 1;
         if (unexpired_fail_count && (now < last_failure)) {
             pe__update_recheck_time(last_failure, history->rsc->cluster,
                                     "fail count expiration");
         }
     }
 
     if (expired) {
         if (pe_get_failcount(history->node, history->rsc, NULL,
                              pcmk__fc_default, history->xml)) {
             // There is a fail count ignoring timeout
 
             if (unexpired_fail_count == 0) {
                 // There is no fail count considering timeout
                 clear_reason = "it expired";
 
             } else {
                 /* This operation is old, but there is an unexpired fail count.
                  * In a properly functioning cluster, this should only be
                  * possible if this operation is not a failure (otherwise the
                  * fail count should be expired too), so this is really just a
                  * failsafe.
                  */
                 pcmk__rsc_trace(history->rsc,
                                 "Resource history entry %s on %s is not "
                                 "expired: Unexpired fail count",
                                 history->id, pcmk__node_name(history->node));
                 expired = false;
             }
 
         } else if (is_last_failure
                    && (history->rsc->remote_reconnect_ms != 0)) {
             /* Clear any expired last failure when reconnect interval is set,
              * even if there is no fail count.
              */
             clear_reason = "reconnect interval is set";
         }
     }
 
     if (!expired && is_last_failure
         && should_clear_for_param_change(history->xml, history->task,
                                          history->rsc, history->node)) {
         clear_reason = "resource parameters have changed";
     }
 
     if (clear_reason != NULL) {
         pcmk_action_t *clear_op = NULL;
 
         // Schedule clearing of the fail count
         clear_op = pe__clear_failcount(history->rsc, history->node,
                                        clear_reason, history->rsc->cluster);
 
         if (pcmk_is_set(history->rsc->cluster->flags,
                         pcmk_sched_fencing_enabled)
             && (history->rsc->remote_reconnect_ms != 0)) {
             /* If we're clearing a remote connection due to a reconnect
              * interval, we want to wait until any scheduled fencing
              * completes.
              *
              * We could limit this to remote_node->details->unclean, but at
              * this point, that's always true (it won't be reliable until
              * after unpack_node_history() is done).
              */
             crm_info("Clearing %s failure will wait until any scheduled "
                      "fencing of %s completes",
                      history->task, history->rsc->id);
             order_after_remote_fencing(clear_op, history->rsc,
                                        history->rsc->cluster);
         }
     }
 
     if (expired && (history->interval_ms == 0)
         && pcmk__str_eq(history->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
         switch (history->exit_status) {
             case PCMK_OCF_OK:
             case PCMK_OCF_NOT_RUNNING:
             case PCMK_OCF_RUNNING_PROMOTED:
             case PCMK_OCF_DEGRADED:
             case PCMK_OCF_DEGRADED_PROMOTED:
                 // Don't expire probes that return these values
                 pcmk__rsc_trace(history->rsc,
                                 "Resource history entry %s on %s is not "
                                 "expired: Probe result",
                              history->id, pcmk__node_name(history->node));
                 expired = false;
                 break;
         }
     }
 
     return expired;
 }
 
 int
 pe__target_rc_from_xml(const xmlNode *xml_op)
 {
     int target_rc = 0;
     const char *key = crm_element_value(xml_op, PCMK__XA_TRANSITION_KEY);
 
     if (key == NULL) {
         return -1;
     }
     decode_transition_key(key, NULL, NULL, NULL, &target_rc);
     return target_rc;
 }
 
 /*!
  * \internal
  * \brief Update a resource's state for an action result
  *
  * \param[in,out] history       Parsed action history entry
  * \param[in]     exit_status   Exit status to base new state on
  * \param[in]     last_failure  Resource's last_failure entry, if known
  * \param[in,out] on_fail       Resource's current failure handling
  */
 static void
 update_resource_state(struct action_history *history, int exit_status,
                       const xmlNode *last_failure,
                       enum action_fail_response *on_fail)
 {
     bool clear_past_failure = false;
 
     if ((exit_status == PCMK_OCF_NOT_INSTALLED)
         || (!pe_rsc_is_bundled(history->rsc)
             && pcmk_xe_mask_probe_failure(history->xml))) {
         history->rsc->role = pcmk_role_stopped;
 
     } else if (exit_status == PCMK_OCF_NOT_RUNNING) {
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MONITOR,
                             pcmk__str_none)) {
         if ((last_failure != NULL)
             && pcmk__str_eq(history->key, pcmk__xe_history_key(last_failure),
                             pcmk__str_none)) {
             clear_past_failure = true;
         }
         if (history->rsc->role < pcmk_role_started) {
             set_active(history->rsc);
         }
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_START, pcmk__str_none)) {
         history->rsc->role = pcmk_role_started;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_STOP, pcmk__str_none)) {
         history->rsc->role = pcmk_role_stopped;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_PROMOTE,
                             pcmk__str_none)) {
         history->rsc->role = pcmk_role_promoted;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_DEMOTE,
                             pcmk__str_none)) {
         if (*on_fail == pcmk_on_fail_demote) {
             // Demote clears an error only if on-fail=demote
             clear_past_failure = true;
         }
         history->rsc->role = pcmk_role_unpromoted;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MIGRATE_FROM,
                             pcmk__str_none)) {
         history->rsc->role = pcmk_role_started;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MIGRATE_TO,
                             pcmk__str_none)) {
         unpack_migrate_to_success(history);
 
     } else if (history->rsc->role < pcmk_role_started) {
         pcmk__rsc_trace(history->rsc, "%s active on %s",
                         history->rsc->id, pcmk__node_name(history->node));
         set_active(history->rsc);
     }
 
     if (!clear_past_failure) {
         return;
     }
 
     switch (*on_fail) {
         case pcmk_on_fail_stop:
         case pcmk_on_fail_ban:
         case pcmk_on_fail_standby_node:
         case pcmk_on_fail_fence_node:
             pcmk__rsc_trace(history->rsc,
                             "%s (%s) is not cleared by a completed %s",
                             history->rsc->id, fail2text(*on_fail),
                             history->task);
             break;
 
         case pcmk_on_fail_block:
         case pcmk_on_fail_ignore:
         case pcmk_on_fail_demote:
         case pcmk_on_fail_restart:
         case pcmk_on_fail_restart_container:
             *on_fail = pcmk_on_fail_ignore;
             pe__set_next_role(history->rsc, pcmk_role_unknown,
                               "clear past failures");
             break;
 
         case pcmk_on_fail_reset_remote:
             if (history->rsc->remote_reconnect_ms == 0) {
                 /* With no reconnect interval, the connection is allowed to
                  * start again after the remote node is fenced and
                  * completely stopped. (With a reconnect interval, we wait
                  * for the failure to be cleared entirely before attempting
                  * to reconnect.)
                  */
                 *on_fail = pcmk_on_fail_ignore;
                 pe__set_next_role(history->rsc, pcmk_role_unknown,
                                   "clear past failures and reset remote");
             }
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a given history entry matters for resource state
  *
  * \param[in] history  Parsed action history entry
  *
  * \return true if action can affect resource state, otherwise false
  */
 static inline bool
 can_affect_state(struct action_history *history)
 {
 #if 0
     /* @COMPAT It might be better to parse only actions we know we're interested
      * in, rather than exclude a couple we don't. However that would be a
      * behavioral change that should be done at a major or minor series release.
      * Currently, unknown operations can affect whether a resource is considered
      * active and/or failed.
      */
      return pcmk__str_any_of(history->task, PCMK_ACTION_MONITOR,
                              PCMK_ACTION_START, PCMK_ACTION_STOP,
                              PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
                              PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
                              "asyncmon", NULL);
 #else
      return !pcmk__str_any_of(history->task, PCMK_ACTION_NOTIFY,
                               PCMK_ACTION_META_DATA, NULL);
 #endif
 }
 
 /*!
  * \internal
  * \brief Unpack execution/exit status and exit reason from a history entry
  *
  * \param[in,out] history  Action history entry to unpack
  *
  * \return Standard Pacemaker return code
  */
 static int
 unpack_action_result(struct action_history *history)
 {
     if ((crm_element_value_int(history->xml, PCMK__XA_OP_STATUS,
                                &(history->execution_status)) < 0)
         || (history->execution_status < PCMK_EXEC_PENDING)
         || (history->execution_status > PCMK_EXEC_MAX)
         || (history->execution_status == PCMK_EXEC_CANCELLED)) {
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "with invalid " PCMK__XA_OP_STATUS " '%s'",
                          history->id, history->rsc->id,
                          pcmk__node_name(history->node),
                          pcmk__s(crm_element_value(history->xml,
                                                    PCMK__XA_OP_STATUS),
                                  ""));
         return pcmk_rc_unpack_error;
     }
     if ((crm_element_value_int(history->xml, PCMK__XA_RC_CODE,
                                &(history->exit_status)) < 0)
         || (history->exit_status < 0) || (history->exit_status > CRM_EX_MAX)) {
 #if 0
         /* @COMPAT We should ignore malformed entries, but since that would
          * change behavior, it should be done at a major or minor series
          * release.
          */
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "with invalid " PCMK__XA_RC_CODE " '%s'",
                          history->id, history->rsc->id,
                          pcmk__node_name(history->node),
                          pcmk__s(crm_element_value(history->xml,
                                                    PCMK__XA_RC_CODE),
                                  ""));
         return pcmk_rc_unpack_error;
 #else
         history->exit_status = CRM_EX_ERROR;
 #endif
     }
     history->exit_reason = crm_element_value(history->xml, PCMK_XA_EXIT_REASON);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Process an action history entry whose result expired
  *
  * \param[in,out] history           Parsed action history entry
  * \param[in]     orig_exit_status  Action exit status before remapping
  *
  * \return Standard Pacemaker return code (in particular, pcmk_rc_ok means the
  *         entry needs no further processing)
  */
 static int
 process_expired_result(struct action_history *history, int orig_exit_status)
 {
     if (!pe_rsc_is_bundled(history->rsc)
         && pcmk_xe_mask_probe_failure(history->xml)
         && (orig_exit_status != history->expected_exit_status)) {
 
         if (history->rsc->role <= pcmk_role_stopped) {
             history->rsc->role = pcmk_role_unknown;
         }
         crm_trace("Ignoring resource history entry %s for probe of %s on %s: "
                   "Masked failure expired",
                   history->id, history->rsc->id,
                   pcmk__node_name(history->node));
         return pcmk_rc_ok;
     }
 
     if (history->exit_status == history->expected_exit_status) {
         return pcmk_rc_undetermined; // Only failures expire
     }
 
     if (history->interval_ms == 0) {
         crm_notice("Ignoring resource history entry %s for %s of %s on %s: "
                    "Expired failure",
                    history->id, history->task, history->rsc->id,
                    pcmk__node_name(history->node));
         return pcmk_rc_ok;
     }
 
     if (history->node->details->online && !history->node->details->unclean) {
         /* Reschedule the recurring action. schedule_cancel() won't work at
          * this stage, so as a hacky workaround, forcibly change the restart
          * digest so pcmk__check_action_config() does what we want later.
          *
          * @TODO We should skip this if there is a newer successful monitor.
          *       Also, this causes rescheduling only if the history entry
          *       has a PCMK__XA_OP_DIGEST (which the expire-non-blocked-failure
          *       scheduler regression test doesn't, but that may not be a
          *       realistic scenario in production).
          */
         crm_notice("Rescheduling %s-interval %s of %s on %s "
                    "after failure expired",
                    pcmk__readable_interval(history->interval_ms), history->task,
                    history->rsc->id, pcmk__node_name(history->node));
         crm_xml_add(history->xml, PCMK__XA_OP_RESTART_DIGEST,
                     "calculated-failure-timeout");
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_undetermined;
 }
 
 /*!
  * \internal
  * \brief Process a masked probe failure
  *
  * \param[in,out] history           Parsed action history entry
  * \param[in]     orig_exit_status  Action exit status before remapping
  * \param[in]     last_failure      Resource's last_failure entry, if known
  * \param[in,out] on_fail           Resource's current failure handling
  */
 static void
 mask_probe_failure(struct action_history *history, int orig_exit_status,
                    const xmlNode *last_failure,
                    enum action_fail_response *on_fail)
 {
     pcmk_resource_t *ban_rsc = history->rsc;
 
     if (!pcmk_is_set(history->rsc->flags, pcmk_rsc_unique)) {
         ban_rsc = uber_parent(history->rsc);
     }
 
     crm_notice("Treating probe result '%s' for %s on %s as 'not running'",
                services_ocf_exitcode_str(orig_exit_status), history->rsc->id,
                pcmk__node_name(history->node));
     update_resource_state(history, history->expected_exit_status, last_failure,
                           on_fail);
     crm_xml_add(history->xml, PCMK_XA_UNAME, history->node->details->uname);
 
     record_failed_op(history);
     resource_location(ban_rsc, history->node, -INFINITY, "masked-probe-failure",
                       history->rsc->cluster);
 }
 
 /*!
  * \internal Check whether a given failure is for a given pending action
  *
  * \param[in] history       Parsed history entry for pending action
  * \param[in] last_failure  Resource's last_failure entry, if known
  *
  * \return true if \p last_failure is failure of pending action in \p history,
  *         otherwise false
  * \note Both \p history and \p last_failure must come from the same
  *       lrm_resource block, as node and resource are assumed to be the same.
  */
 static bool
 failure_is_newer(const struct action_history *history,
                  const xmlNode *last_failure)
 {
     guint failure_interval_ms = 0U;
     long long failure_change = 0LL;
     long long this_change = 0LL;
 
     if (last_failure == NULL) {
         return false; // Resource has no last_failure entry
     }
 
     if (!pcmk__str_eq(history->task,
                       crm_element_value(last_failure, PCMK_XA_OPERATION),
                       pcmk__str_none)) {
         return false; // last_failure is for different action
     }
 
     if ((crm_element_value_ms(last_failure, PCMK_META_INTERVAL,
                               &failure_interval_ms) != pcmk_ok)
         || (history->interval_ms != failure_interval_ms)) {
         return false; // last_failure is for action with different interval
     }
 
     if ((pcmk__scan_ll(crm_element_value(history->xml, PCMK_XA_LAST_RC_CHANGE),
                        &this_change, 0LL) != pcmk_rc_ok)
         || (pcmk__scan_ll(crm_element_value(last_failure,
                                             PCMK_XA_LAST_RC_CHANGE),
                           &failure_change, 0LL) != pcmk_rc_ok)
         || (failure_change < this_change)) {
         return false; // Failure is not known to be newer
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Update a resource's role etc. for a pending action
  *
  * \param[in,out] history       Parsed history entry for pending action
  * \param[in]     last_failure  Resource's last_failure entry, if known
  */
 static void
 process_pending_action(struct action_history *history,
                        const xmlNode *last_failure)
 {
     /* For recurring monitors, a failure is recorded only in RSC_last_failure_0,
      * and there might be a RSC_monitor_INTERVAL entry with the last successful
      * or pending result.
      *
      * If last_failure contains the failure of the pending recurring monitor
      * we're processing here, and is newer, the action is no longer pending.
      * (Pending results have call ID -1, which sorts last, so the last failure
      * if any should be known.)
      */
     if (failure_is_newer(history, last_failure)) {
         return;
     }
 
     if (strcmp(history->task, PCMK_ACTION_START) == 0) {
         pcmk__set_rsc_flags(history->rsc, pcmk_rsc_start_pending);
         set_active(history->rsc);
 
     } else if (strcmp(history->task, PCMK_ACTION_PROMOTE) == 0) {
         history->rsc->role = pcmk_role_promoted;
 
     } else if ((strcmp(history->task, PCMK_ACTION_MIGRATE_TO) == 0)
                && history->node->details->unclean) {
         /* A migrate_to action is pending on a unclean source, so force a stop
          * on the target.
          */
         const char *migrate_target = NULL;
         pcmk_node_t *target = NULL;
 
         migrate_target = crm_element_value(history->xml,
                                            PCMK__META_MIGRATE_TARGET);
         target = pe_find_node(history->rsc->cluster->nodes, migrate_target);
         if (target != NULL) {
             stop_action(history->rsc, target, FALSE);
         }
     }
 
     if (history->rsc->pending_task != NULL) {
         /* There should never be multiple pending actions, but as a failsafe,
          * just remember the first one processed for display purposes.
          */
         return;
     }
 
     if (pcmk_is_probe(history->task, history->interval_ms)) {
         /* Pending probes are currently never displayed, even if pending
          * operations are requested. If we ever want to change that,
          * enable the below and the corresponding part of
          * native.c:native_pending_task().
          */
 #if 0
         history->rsc->pending_task = strdup("probe");
         history->rsc->pending_node = history->node;
 #endif
     } else {
         history->rsc->pending_task = strdup(history->task);
         history->rsc->pending_node = history->node;
     }
 }
 
 static void
 unpack_rsc_op(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *xml_op,
               xmlNode **last_failure, enum action_fail_response *on_fail)
 {
     int old_rc = 0;
     bool expired = false;
     pcmk_resource_t *parent = rsc;
     enum rsc_role_e fail_role = pcmk_role_unknown;
     enum action_fail_response failure_strategy = pcmk_on_fail_restart;
 
     struct action_history history = {
         .rsc = rsc,
         .node = node,
         .xml = xml_op,
         .execution_status = PCMK_EXEC_UNKNOWN,
     };
 
     CRM_CHECK(rsc && node && xml_op, return);
 
     history.id = ID(xml_op);
     if (history.id == NULL) {
         pcmk__config_err("Ignoring resource history entry for %s on %s "
                          "without ID", rsc->id, pcmk__node_name(node));
         return;
     }
 
     // Task and interval
     history.task = crm_element_value(xml_op, PCMK_XA_OPERATION);
     if (history.task == NULL) {
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "without " PCMK_XA_OPERATION,
                          history.id, rsc->id, pcmk__node_name(node));
         return;
     }
     crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &(history.interval_ms));
     if (!can_affect_state(&history)) {
         pcmk__rsc_trace(rsc,
                         "Ignoring resource history entry %s for %s on %s "
                         "with irrelevant action '%s'",
                         history.id, rsc->id, pcmk__node_name(node),
                         history.task);
         return;
     }
 
     if (unpack_action_result(&history) != pcmk_rc_ok) {
         return; // Error already logged
     }
 
     history.expected_exit_status = pe__target_rc_from_xml(xml_op);
     history.key = pcmk__xe_history_key(xml_op);
     crm_element_value_int(xml_op, PCMK__XA_CALL_ID, &(history.call_id));
 
     pcmk__rsc_trace(rsc, "Unpacking %s (%s call %d on %s): %s (%s)",
                     history.id, history.task, history.call_id,
                     pcmk__node_name(node),
                     pcmk_exec_status_str(history.execution_status),
                     crm_exit_str(history.exit_status));
 
     if (node->details->unclean) {
         pcmk__rsc_trace(rsc,
                         "%s is running on %s, which is unclean (further action "
                         "depends on value of stop's on-fail attribute)",
                         rsc->id, pcmk__node_name(node));
     }
 
     expired = check_operation_expiry(&history);
     old_rc = history.exit_status;
 
     remap_operation(&history, on_fail, expired);
 
     if (expired && (process_expired_result(&history, old_rc) == pcmk_rc_ok)) {
         goto done;
     }
 
     if (!pe_rsc_is_bundled(rsc) && pcmk_xe_mask_probe_failure(xml_op)) {
         mask_probe_failure(&history, old_rc, *last_failure, on_fail);
         goto done;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         parent = uber_parent(rsc);
     }
 
     switch (history.execution_status) {
         case PCMK_EXEC_PENDING:
             process_pending_action(&history, *last_failure);
             goto done;
 
         case PCMK_EXEC_DONE:
             update_resource_state(&history, history.exit_status, *last_failure,
                                   on_fail);
             goto done;
 
         case PCMK_EXEC_NOT_INSTALLED:
             unpack_failure_handling(&history, &failure_strategy, &fail_role);
             if (failure_strategy == pcmk_on_fail_ignore) {
                 crm_warn("Cannot ignore failed %s of %s on %s: "
                          "Resource agent doesn't exist "
                          CRM_XS " status=%d rc=%d id=%s",
                          history.task, rsc->id, pcmk__node_name(node),
                          history.execution_status, history.exit_status,
                          history.id);
                 /* Also for printing it as "FAILED" by marking it as
                  * pcmk_rsc_failed later
                  */
                 *on_fail = pcmk_on_fail_ban;
             }
             resource_location(parent, node, -INFINITY, "hard-error",
                               rsc->cluster);
             unpack_rsc_op_failure(&history, failure_strategy, fail_role,
                                   last_failure, on_fail);
             goto done;
 
         case PCMK_EXEC_NOT_CONNECTED:
             if (pe__is_guest_or_remote_node(node)
                 && pcmk_is_set(node->details->remote_rsc->flags,
                                pcmk_rsc_managed)) {
                 /* We should never get into a situation where a managed remote
                  * connection resource is considered OK but a resource action
                  * behind the connection gets a "not connected" status. But as a
                  * fail-safe in case a bug or unusual circumstances do lead to
                  * that, ensure the remote connection is considered failed.
                  */
                 pcmk__set_rsc_flags(node->details->remote_rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             }
             break; // Not done, do error handling
 
         case PCMK_EXEC_ERROR:
         case PCMK_EXEC_ERROR_HARD:
         case PCMK_EXEC_ERROR_FATAL:
         case PCMK_EXEC_TIMEOUT:
         case PCMK_EXEC_NOT_SUPPORTED:
         case PCMK_EXEC_INVALID:
             break; // Not done, do error handling
 
         default: // No other value should be possible at this point
             break;
     }
 
     unpack_failure_handling(&history, &failure_strategy, &fail_role);
     if ((failure_strategy == pcmk_on_fail_ignore)
         || ((failure_strategy == pcmk_on_fail_restart_container)
             && (strcmp(history.task, PCMK_ACTION_STOP) == 0))) {
 
         char *last_change_s = last_change_str(xml_op);
 
         crm_warn("Pretending failed %s (%s%s%s) of %s on %s at %s succeeded "
                  CRM_XS " %s",
                  history.task, services_ocf_exitcode_str(history.exit_status),
                  (pcmk__str_empty(history.exit_reason)? "" : ": "),
                  pcmk__s(history.exit_reason, ""), rsc->id,
                  pcmk__node_name(node), last_change_s, history.id);
         free(last_change_s);
 
         update_resource_state(&history, history.expected_exit_status,
                               *last_failure, on_fail);
         crm_xml_add(xml_op, PCMK_XA_UNAME, node->details->uname);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_ignore_failure);
 
         record_failed_op(&history);
 
         if ((failure_strategy == pcmk_on_fail_restart_container)
             && cmp_on_fail(*on_fail, pcmk_on_fail_restart) <= 0) {
             *on_fail = failure_strategy;
         }
 
     } else {
         unpack_rsc_op_failure(&history, failure_strategy, fail_role,
                               last_failure, on_fail);
 
         if (history.execution_status == PCMK_EXEC_ERROR_HARD) {
             uint8_t log_level = LOG_ERR;
 
             if (history.exit_status == PCMK_OCF_NOT_INSTALLED) {
                 log_level = LOG_NOTICE;
             }
             do_crm_log(log_level,
                        "Preventing %s from restarting on %s because "
                        "of hard failure (%s%s%s) " CRM_XS " %s",
                        parent->id, pcmk__node_name(node),
                        services_ocf_exitcode_str(history.exit_status),
                        (pcmk__str_empty(history.exit_reason)? "" : ": "),
                        pcmk__s(history.exit_reason, ""), history.id);
             resource_location(parent, node, -INFINITY, "hard-error",
                               rsc->cluster);
 
         } else if (history.execution_status == PCMK_EXEC_ERROR_FATAL) {
             pcmk__sched_err("Preventing %s from restarting anywhere because "
                             "of fatal failure (%s%s%s) " CRM_XS " %s",
                             parent->id,
                             services_ocf_exitcode_str(history.exit_status),
                             (pcmk__str_empty(history.exit_reason)? "" : ": "),
                             pcmk__s(history.exit_reason, ""), history.id);
             resource_location(parent, NULL, -INFINITY, "fatal-error",
                               rsc->cluster);
         }
     }
 
 done:
     pcmk__rsc_trace(rsc, "%s role on %s after %s is %s (next %s)",
                     rsc->id, pcmk__node_name(node), history.id,
                     role2text(rsc->role), role2text(rsc->next_role));
 }
 
 static void
 add_node_attrs(const xmlNode *xml_obj, pcmk_node_t *node, bool overwrite,
                pcmk_scheduler_t *scheduler)
 {
     const char *cluster_name = NULL;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = pcmk_role_unknown,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     g_hash_table_insert(node->details->attrs,
                         strdup(CRM_ATTR_UNAME), strdup(node->details->uname));
 
     g_hash_table_insert(node->details->attrs, strdup(CRM_ATTR_ID),
                         strdup(node->details->id));
     if (pcmk__str_eq(node->details->id, scheduler->dc_uuid, pcmk__str_casei)) {
         scheduler->dc_node = node;
         node->details->is_dc = TRUE;
         g_hash_table_insert(node->details->attrs,
                             strdup(CRM_ATTR_IS_DC), strdup(PCMK_VALUE_TRUE));
     } else {
         g_hash_table_insert(node->details->attrs,
                             strdup(CRM_ATTR_IS_DC), strdup(PCMK_VALUE_FALSE));
     }
 
     cluster_name = g_hash_table_lookup(scheduler->config_hash,
                                        PCMK_OPT_CLUSTER_NAME);
     if (cluster_name) {
         g_hash_table_insert(node->details->attrs, strdup(CRM_ATTR_CLUSTER_NAME),
                             strdup(cluster_name));
     }
 
     pe__unpack_dataset_nvpairs(xml_obj, XML_TAG_ATTR_SETS, &rule_data,
                                node->details->attrs, NULL, overwrite,
                                scheduler);
 
     pe__unpack_dataset_nvpairs(xml_obj, XML_TAG_UTILIZATION, &rule_data,
                                node->details->utilization, NULL,
                                FALSE, scheduler);
 
     if (pe_node_attribute_raw(node, CRM_ATTR_SITE_NAME) == NULL) {
         const char *site_name = pe_node_attribute_raw(node, "site-name");
 
         if (site_name) {
             g_hash_table_insert(node->details->attrs,
                                 strdup(CRM_ATTR_SITE_NAME),
                                 strdup(site_name));
 
         } else if (cluster_name) {
             /* Default to cluster-name if unset */
             g_hash_table_insert(node->details->attrs,
                                 strdup(CRM_ATTR_SITE_NAME),
                                 strdup(cluster_name));
         }
     }
 }
 
 static GList *
 extract_operations(const char *node, const char *rsc, xmlNode * rsc_entry, gboolean active_filter)
 {
     int counter = -1;
     int stop_index = -1;
     int start_index = -1;
 
     xmlNode *rsc_op = NULL;
 
     GList *gIter = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     /* extract operations */
     op_list = NULL;
     sorted_op_list = NULL;
 
     for (rsc_op = pcmk__xe_first_child(rsc_entry);
          rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) {
 
         if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP,
                          pcmk__str_none)) {
             crm_xml_add(rsc_op, "resource", rsc);
             crm_xml_add(rsc_op, PCMK_XA_UNAME, node);
             op_list = g_list_prepend(op_list, rsc_op);
         }
     }
 
     if (op_list == NULL) {
         /* if there are no operations, there is nothing to do */
         return NULL;
     }
 
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     /* create active recurring operations as optional */
     if (active_filter == FALSE) {
         return sorted_op_list;
     }
 
     op_list = NULL;
 
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         counter++;
 
         if (start_index < stop_index) {
             crm_trace("Skipping %s: not active", ID(rsc_entry));
             break;
 
         } else if (counter < start_index) {
             crm_trace("Skipping %s: old", ID(rsc_op));
             continue;
         }
         op_list = g_list_append(op_list, rsc_op);
     }
 
     g_list_free(sorted_op_list);
     return op_list;
 }
 
 GList *
 find_operations(const char *rsc, const char *node, gboolean active_filter,
                 pcmk_scheduler_t *scheduler)
 {
     GList *output = NULL;
     GList *intermediate = NULL;
 
     xmlNode *tmp = NULL;
     xmlNode *status = find_xml_node(scheduler->input, PCMK_XE_STATUS, TRUE);
 
     pcmk_node_t *this_node = NULL;
 
     xmlNode *node_state = NULL;
 
     for (node_state = pcmk__xe_first_child(status); node_state != NULL;
          node_state = pcmk__xe_next(node_state)) {
 
         if (pcmk__str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
             const char *uname = crm_element_value(node_state, PCMK_XA_UNAME);
 
             if (node != NULL && !pcmk__str_eq(uname, node, pcmk__str_casei)) {
                 continue;
             }
 
             this_node = pe_find_node(scheduler->nodes, uname);
             if(this_node == NULL) {
                 CRM_LOG_ASSERT(this_node != NULL);
                 continue;
 
             } else if (pe__is_guest_or_remote_node(this_node)) {
                 determine_remote_online_status(scheduler, this_node);
 
             } else {
                 determine_online_status(node_state, this_node, scheduler);
             }
 
             if (this_node->details->online
                 || pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
                 /* offline nodes run no resources...
                  * unless stonith is enabled in which case we need to
                  *   make sure rsc start events happen after the stonith
                  */
                 xmlNode *lrm_rsc = NULL;
 
                 tmp = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
                 tmp = find_xml_node(tmp, XML_LRM_TAG_RESOURCES, FALSE);
 
                 for (lrm_rsc = pcmk__xe_first_child(tmp); lrm_rsc != NULL;
                      lrm_rsc = pcmk__xe_next(lrm_rsc)) {
 
                     if (pcmk__str_eq((const char *)lrm_rsc->name,
                                      XML_LRM_TAG_RESOURCE, pcmk__str_none)) {
 
                         const char *rsc_id = crm_element_value(lrm_rsc,
                                                                PCMK_XA_ID);
 
                         if (rsc != NULL && !pcmk__str_eq(rsc_id, rsc, pcmk__str_casei)) {
                             continue;
                         }
 
                         intermediate = extract_operations(uname, rsc_id, lrm_rsc, active_filter);
                         output = g_list_concat(output, intermediate);
                     }
                 }
             }
         }
     }
 
     return output;
 }
diff --git a/tools/cibadmin.c b/tools/cibadmin.c
index 527e875b21..2dc5803adc 100644
--- a/tools/cibadmin.c
+++ b/tools/cibadmin.c
@@ -1,952 +1,952 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <stdio.h>
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/ipc.h>
 #include <crm/common/xml.h>
 #include <crm/cib/internal.h>
 
 #include <pacemaker-internal.h>
 
 #define SUMMARY "query and edit the Pacemaker configuration"
 
 #define INDENT "                                "
 
 enum cibadmin_section_type {
     cibadmin_section_all = 0,
     cibadmin_section_scope,
     cibadmin_section_xpath,
 };
 
 static int request_id = 0;
 
 static cib_t *the_cib = NULL;
 static GMainLoop *mainloop = NULL;
 static crm_exit_t exit_code = CRM_EX_OK;
 
 static struct {
     const char *cib_action;
     int cmd_options;
     enum cibadmin_section_type section_type;
     char *cib_section;
     char *validate_with;
     gint message_timeout_sec;
     enum pcmk__acl_render_how acl_render_mode;
     gchar *cib_user;
     gchar *dest_node;
     gchar *input_file;
     gchar *input_xml;
     gboolean input_stdin;
     bool delete_all;
     gboolean allow_create;
     gboolean force;
     gboolean get_node_path;
     gboolean local;
     gboolean no_children;
     gboolean sync_call;
 
     /* @COMPAT: For "-!" version option. Not advertised nor marked as
      * deprecated, but accepted.
      */
     gboolean extended_version;
 
     //! \deprecated
     gboolean no_bcast;
 } options;
 
 int do_init(void);
 static int do_work(xmlNode *input, xmlNode **output);
 void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
                           void *user_data);
 
 static void
 print_xml_output(xmlNode * xml)
 {
     if (!xml) {
         return;
     } else if (xml->type != XML_ELEMENT_NODE) {
         return;
     }
 
     if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
         const char *id = crm_element_value(xml, PCMK_XA_ID);
 
         if (pcmk__str_eq((const char *)xml->name, "xpath-query", pcmk__str_casei)) {
             xmlNode *child = NULL;
 
             for (child = xml->children; child; child = child->next) {
                 print_xml_output(child);
             }
 
         } else if (id) {
             printf("%s\n", id);
         }
 
     } else {
         char *buffer = dump_xml_formatted(xml);
         fprintf(stdout, "%s", buffer);
         free(buffer);
     }
 }
 
 // Upgrade requested but already at latest schema
 static void
 report_schema_unchanged(void)
 {
     const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
 
     crm_info("Upgrade unnecessary: %s\n", err);
     printf("Upgrade unnecessary: %s\n", err);
     exit_code = CRM_EX_OK;
 }
 
 /*!
  * \internal
  * \brief Check whether the current CIB action is dangerous
  * \return true if \p options.cib_action is dangerous, or false otherwise
  */
 static inline bool
 cib_action_is_dangerous(void)
 {
     return options.no_bcast || options.delete_all
            || pcmk__str_any_of(options.cib_action,
                                PCMK__CIB_REQUEST_UPGRADE,
                                PCMK__CIB_REQUEST_ERASE,
                                NULL);
 }
 
 /*!
  * \internal
  * \brief Determine whether the given CIB scope is valid for \p cibadmin
  *
  * \param[in] scope  Scope to validate
  *
  * \return true if \p scope is valid, or false otherwise
  * \note An invalid scope applies the operation to the entire CIB.
  */
 static inline bool
 scope_is_valid(const char *scope)
 {
     return pcmk__str_any_of(scope,
                             PCMK_XE_CONFIGURATION,
                             XML_CIB_TAG_NODES,
-                            XML_CIB_TAG_RESOURCES,
+                            PCMK_XE_RESOURCES,
                             XML_CIB_TAG_CONSTRAINTS,
                             XML_CIB_TAG_CRMCONFIG,
                             XML_CIB_TAG_RSCCONFIG,
                             XML_CIB_TAG_OPCONFIG,
                             XML_CIB_TAG_ACLS,
                             XML_TAG_FENCING_TOPOLOGY,
                             XML_CIB_TAG_TAGS,
                             XML_CIB_TAG_ALERTS,
                             PCMK_XE_STATUS,
                             NULL);
 }
 
 static gboolean
 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
            GError **error)
 {
     options.delete_all = false;
 
     if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_UPGRADE;
 
     } else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_QUERY;
 
     } else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_ERASE;
 
     } else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_BUMP;
 
     } else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_CREATE;
 
     } else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_MODIFY;
 
     } else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH;
 
     } else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_REPLACE;
 
     } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_DELETE;
 
     } else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) {
         options.cib_action = PCMK__CIB_REQUEST_DELETE;
         options.delete_all = true;
 
     } else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) {
         options.cib_action = "empty";
         pcmk__str_update(&options.validate_with, optarg);
 
     } else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) {
         options.cib_action = "md5-sum";
 
     } else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned",
                                 NULL)) {
         options.cib_action = "md5-sum-versioned";
 
     } else {
         // Should be impossible
         return FALSE;
     }
 
     return TRUE;
 }
 
 static gboolean
 show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                GError **error)
 {
     if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) {
         options.acl_render_mode = pcmk__acl_render_default;
 
     } else if (g_strcmp0(optarg, "namespace") == 0) {
         options.acl_render_mode = pcmk__acl_render_namespace;
 
     } else if (g_strcmp0(optarg, "text") == 0) {
         options.acl_render_mode = pcmk__acl_render_text;
 
     } else if (g_strcmp0(optarg, "color") == 0) {
         options.acl_render_mode = pcmk__acl_render_color;
 
     } else {
         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     "Invalid value '%s' for option '%s'",
                     optarg, option_name);
         return FALSE;
     }
     return TRUE;
 }
 
 static gboolean
 section_cb(const gchar *option_name, const gchar *optarg, gpointer data,
            GError **error)
 {
     if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) {
         options.section_type = cibadmin_section_scope;
 
     } else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) {
         options.section_type = cibadmin_section_xpath;
 
     } else {
         // Should be impossible
         return FALSE;
     }
 
     pcmk__str_update(&options.cib_section, optarg);
     return TRUE;
 }
 
 static GOptionEntry command_entries[] = {
     { "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Upgrade the configuration to the latest syntax", NULL },
 
     { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Query the contents of the CIB", NULL },
 
     { "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Erase the contents of the whole CIB", NULL },
 
     { "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Increase the CIB's epoch value by 1", NULL },
 
     { "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Create an object in the CIB (will fail if object already exists)",
       NULL },
 
     { "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Find object somewhere in CIB's XML tree and update it (fails if object "
       "does not exist unless -c is also specified)",
       NULL },
 
     { "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Supply an update in the form of an XML diff (see crm_diff(8))", NULL },
 
     { "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Recursively replace an object in the CIB", NULL },
 
     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Delete first object matching supplied criteria (for example, "
       "<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" "
           PCMK_XA_NAME "=\"monitor\"/>).\n"
       INDENT "The XML element name and all attributes must match in order for "
       "the element to be deleted.",
       NULL },
 
     { "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       command_cb,
       "When used with --xpath, remove all matching objects in the "
       "configuration instead of just the first one",
       NULL },
 
     { "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
       command_cb,
       "Output an empty CIB. Accepts an optional schema name argument to use as "
       "the " PCMK_XA_VALIDATE_WITH " value.\n"
       INDENT "If no schema is given, the latest will be used.",
       "[schema]" },
 
     { "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Calculate the on-disk CIB digest", NULL },
 
     { "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       command_cb, "Calculate an on-the-wire versioned CIB digest", NULL },
 
     { NULL }
 };
 
 static GOptionEntry data_entries[] = {
     /* @COMPAT: These arguments should be last-wins. We can have an enum option
      * that stores the input type, along with a single string option that stores
      * the XML string for --xml-text, filename for --xml-file, or NULL for
      * --xml-pipe.
      */
     { "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
       &options.input_xml, "Retrieve XML from the supplied string", "value" },
 
     { "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME,
       &options.input_file, "Retrieve XML from the named file", "value" },
 
     { "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.input_stdin, "Retrieve XML from stdin", NULL },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
       "Force the action to be performed", NULL },
 
     { "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT,
       &options.message_timeout_sec,
       "Time (in seconds) to wait before declaring the operation failed",
       "value" },
 
     { "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user,
       "Run the command with permissions of the named user (valid only for the "
       "root and " CRM_DAEMON_USER " accounts)", "value" },
 
     { "sync-call", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.sync_call, "Wait for call to complete before returning", NULL },
 
     { "local", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.local,
       "Command takes effect locally (should be used only for queries)", NULL },
 
     { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
       "Limit scope of operation to specific section of CIB\n"
       INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " XML_CIB_TAG_NODES
-      ", " XML_CIB_TAG_RESOURCES ", " XML_CIB_TAG_CONSTRAINTS
+      ", " PCMK_XE_RESOURCES ", " XML_CIB_TAG_CONSTRAINTS
       ", " XML_CIB_TAG_CRMCONFIG ", " XML_CIB_TAG_RSCCONFIG ",\n"
       INDENT "              " XML_CIB_TAG_OPCONFIG ", " XML_CIB_TAG_ACLS
       ", " XML_TAG_FENCING_TOPOLOGY ", " XML_CIB_TAG_TAGS
       ", " XML_CIB_TAG_ALERTS ", " PCMK_XE_STATUS "\n"
       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
       "appear takes effect",
       "value" },
 
     { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
       "A valid XPath to use instead of --scope/-o\n"
       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
       "appear takes effect",
       "value" },
 
     { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.get_node_path,
       "When performing XPath queries, return paths of any matches found\n"
       INDENT "(for example, "
       "\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
-      "/" XML_CIB_TAG_RESOURCES "/" XML_CIB_TAG_INCARNATION
+      "/" PCMK_XE_RESOURCES "/" XML_CIB_TAG_INCARNATION
       "[@" PCMK_XA_ID "='dummy-clone']"
       "/" XML_CIB_TAG_RESOURCE "[@" PCMK_XA_ID "='dummy']\")",
       NULL },
 
     { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
       show_access_cb,
       "Whether to use syntax highlighting for ACLs (with -Q/--query and "
       "-U/--user)\n"
       INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
       "default for non-terminal),\n"
       INDENT "                'namespace', or 'auto' (use default value)\n"
       INDENT "Default value: 'auto'",
       "[value]" },
 
     { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.allow_create,
       "(Advanced) Allow target of --modify/-M to be created if it does not "
       "exist",
       NULL },
 
     { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.no_children,
       "(Advanced) When querying an object, do not include its children in the "
       "result",
       NULL },
 
     { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
       "(Advanced) Send command to the specified host", "value" },
 
     // @COMPAT: Deprecated
     { "no-bcast", 'b', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
       &options.no_bcast, "deprecated", NULL },
 
     // @COMPAT: Deprecated
     { "host", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
       &options.dest_node, "deprecated", NULL },
 
     { NULL }
 };
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args)
 {
     const char *desc = NULL;
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         // @COMPAT: Deprecated
         { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
           &options.extended_version, "deprecated", NULL },
 
         { NULL }
     };
 
     desc = "Examples:\n\n"
            "Query the configuration from the local node:\n\n"
            "\t# cibadmin --query --local\n\n"
            "Query just the cluster options configuration:\n\n"
            "\t# cibadmin --query --scope " XML_CIB_TAG_CRMCONFIG "\n\n"
            "Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n"
            "\t# cibadmin --query --xpath "
                "\"//" XML_CIB_TAG_NVPAIR
                "[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\""
                "\n\n"
            "Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n"
            "\t# cibadmin --delete-all --xpath "
                "\"//" XML_CIB_TAG_NVPAIR
                "[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n"
            "Remove the resource named 'old':\n\n"
            "\t# cibadmin --delete --xml-text "
                "'<" XML_CIB_TAG_RESOURCE " " PCMK_XA_ID "=\"old\"/>'\n\n"
            "Remove all resources from the configuration:\n\n"
-           "\t# cibadmin --replace --scope " XML_CIB_TAG_RESOURCES
-               " --xml-text '<" XML_CIB_TAG_RESOURCES "/>'\n\n"
+           "\t# cibadmin --replace --scope " PCMK_XE_RESOURCES
+               " --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n"
            "Replace complete configuration with contents of "
                "$HOME/pacemaker.xml:\n\n"
            "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
            "Replace " XML_CIB_TAG_CONSTRAINTS " section of configuration with "
                "contents of $HOME/constraints.xml:\n\n"
            "\t# cibadmin --replace --scope " XML_CIB_TAG_CONSTRAINTS
                " --xml-file $HOME/constraints.xml\n\n"
            "Increase configuration version to prevent old configurations from "
                "being loaded accidentally:\n\n"
            "\t# cibadmin --modify --xml-text "
                "'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH
                    "=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n"
            "Edit the configuration with your favorite $EDITOR:\n\n"
            "\t# cibadmin --query > $HOME/local.xml\n\n"
            "\t# $EDITOR $HOME/local.xml\n\n"
            "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
            "Assuming terminal, render configuration in color (green for "
                "writable, blue for readable, red for\n"
                "denied) to visualize permissions for user tony:\n\n"
            "\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
            "SEE ALSO:\n"
            " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
 
     context = pcmk__build_arg_context(args, NULL, NULL, "<command>");
     g_option_context_set_description(context, desc);
 
     pcmk__add_main_args(context, extra_prog_entries);
 
     pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
                         command_entries);
     pcmk__add_arg_group(context, "data", "Data:", "Show data help",
                         data_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_rc_ok;
     const char *source = NULL;
     xmlNode *output = NULL;
     xmlNode *input = NULL;
     gchar *acl_cred = NULL;
 
     GError *error = NULL;
 
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
     gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
     GOptionContext *context = build_arg_context(args);
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     if (g_strv_length(processed_args) > 1) {
         gchar *help = g_option_context_get_help(context, TRUE, NULL);
         GString *extra = g_string_sized_new(128);
 
         for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
             if (extra->len > 0) {
                 g_string_append_c(extra, ' ');
             }
             g_string_append(extra, processed_args[lpc]);
         }
 
         exit_code = CRM_EX_USAGE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "non-option ARGV-elements: %s\n\n%s", extra->str, help);
         g_free(help);
         g_string_free(extra, TRUE);
         goto done;
     }
 
     if (args->version || options.extended_version) {
         g_strfreev(processed_args);
         pcmk__free_arg_context(context);
 
         /* FIXME: When cibadmin is converted to use formatted output, this can
          * be replaced by out->version with the appropriate boolean flag.
          *
          * options.extended_version is deprecated and will be removed in a
          * future release.
          */
         pcmk__cli_help(options.extended_version? '!' : 'v');
     }
 
     /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
      *
      * (func@file:line)      error: CIB <op> failures   <XML>
      *
      * In cibadmin we explicitly output the XML portion without the prefixes. So
      * we default to LOG_CRIT.
      */
     pcmk__cli_init_logging("cibadmin", 0);
     set_crm_log_level(LOG_CRIT);
 
     if (args->verbosity > 0) {
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_verbose);
 
         for (int i = 0; i < args->verbosity; i++) {
             crm_bump_log_level(argc, argv);
         }
     }
 
     if (options.cib_action == NULL) {
         // @COMPAT: Create a default command if other tools have one
         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 
         exit_code = CRM_EX_USAGE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "Must specify a command option\n\n%s", help);
         g_free(help);
         goto done;
     }
 
     if (strcmp(options.cib_action, "empty") == 0) {
         // Output an empty CIB
         char *buf = NULL;
 
         output = createEmptyCib(1);
         crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
         buf = dump_xml_formatted(output);
         fprintf(stdout, "%s", buf);
         free(buf);
         goto done;
     }
 
     if (cib_action_is_dangerous() && !options.force) {
         exit_code = CRM_EX_UNSAFE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "The supplied command is considered dangerous. To prevent "
                     "accidental destruction of the cluster, the --force flag "
                     "is required in order to proceed.");
         goto done;
     }
 
     if (options.message_timeout_sec < 1) {
         // Set default timeout
         options.message_timeout_sec = 30;
     }
 
     if (options.section_type == cibadmin_section_xpath) {
         // Enable getting section by XPath
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_xpath);
 
     } else if (options.section_type == cibadmin_section_scope) {
         if (!scope_is_valid(options.cib_section)) {
             // @COMPAT: Consider requiring --force to proceed
             fprintf(stderr,
                     "Invalid value '%s' for '--scope'. Operation will apply "
                     "to the entire CIB.\n", options.cib_section);
         }
     }
 
     if (options.allow_create) {
         // Allow target of --modify/-M to be created if it does not exist
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_can_create);
     }
 
     if (options.delete_all) {
         // With cibadmin_section_xpath, remove all matching objects
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_multiple);
     }
 
     if (options.get_node_path) {
         /* Enable getting node path of XPath query matches.
          * Meaningful only if options.section_type == cibadmin_section_xpath.
          */
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_xpath_address);
     }
 
     if (options.local) {
         // Configure command to take effect only locally
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_scope_local);
     }
 
     // @COMPAT: Deprecated option
     if (options.no_bcast) {
         // Configure command to take effect only locally and not to broadcast
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_inhibit_bcast|cib_scope_local);
     }
 
     if (options.no_children) {
         // When querying an object, don't include its children in the result
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_no_children);
     }
 
     if (options.sync_call
         || (options.acl_render_mode != pcmk__acl_render_none)) {
         /* Wait for call to complete before returning.
          *
          * The ACL render modes work only with sync calls due to differences in
          * output handling between sync/async. It shouldn't matter to the user
          * whether the call is synchronous; for a CIB query, we have to wait for
          * the result in order to display it in any case.
          */
         cib__set_call_options(options.cmd_options, crm_system_name,
                               cib_sync_call);
     }
 
     if (options.input_file != NULL) {
         input = filename2xml(options.input_file);
         source = options.input_file;
 
     } else if (options.input_xml != NULL) {
         input = string2xml(options.input_xml);
         source = "input string";
 
     } else if (options.input_stdin) {
         source = "STDIN";
         input = stdin2xml();
 
     } else if (options.acl_render_mode != pcmk__acl_render_none) {
         char *username = pcmk__uid2username(geteuid());
         bool required = pcmk_acl_required(username);
 
         free(username);
 
         if (required) {
             if (options.force) {
                 fprintf(stderr, "The supplied command can provide skewed"
                                  " result since it is run under user that also"
                                  " gets guarded per ACLs on their own right."
                                  " Continuing since --force flag was"
                                  " provided.\n");
 
             } else {
                 exit_code = CRM_EX_UNSAFE;
                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                             "The supplied command can provide skewed result "
                             "since it is run under user that also gets guarded "
                             "per ACLs in their own right. To accept the risk "
                             "of such a possible distortion (without even "
                             "knowing it at this time), use the --force flag.");
                 goto done;
             }
         }
 
         if (options.cib_user == NULL) {
             exit_code = CRM_EX_USAGE;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         "The supplied command requires -U user specified.");
             goto done;
         }
 
         /* We already stopped/warned ACL-controlled users about consequences.
          *
          * Note: acl_cred takes ownership of options.cib_user here.
          * options.cib_user is set to NULL so that the CIB is obtained as the
          * user running the cibadmin command. The CIB must be obtained as a user
          * with full permissions in order to show the CIB correctly annotated
          * for the options.cib_user's permissions.
          */
         acl_cred = options.cib_user;
         options.cib_user = NULL;
     }
 
     if (input != NULL) {
         crm_log_xml_debug(input, "[admin input]");
 
     } else if (source != NULL) {
         exit_code = CRM_EX_CONFIG;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "Couldn't parse input from %s.", source);
         goto done;
     }
 
     if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) {
         char *digest = NULL;
 
         if (input == NULL) {
             exit_code = CRM_EX_USAGE;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         "Please supply XML to process with -X, -x, or -p");
             goto done;
         }
 
         digest = calculate_on_disk_digest(input);
         fprintf(stderr, "Digest: ");
         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
         free(digest);
         goto done;
 
     } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
         char *digest = NULL;
         const char *version = NULL;
 
         if (input == NULL) {
             exit_code = CRM_EX_USAGE;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         "Please supply XML to process with -X, -x, or -p");
             goto done;
         }
 
         version = crm_element_value(input, PCMK_XA_CRM_FEATURE_SET);
         digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version);
         fprintf(stderr, "Versioned (%s) digest: ", version);
         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
         free(digest);
         goto done;
     }
 
     rc = do_init();
     if (rc != pcmk_ok) {
         rc = pcmk_legacy2rc(rc);
         exit_code = pcmk_rc2exitc(rc);
 
         crm_err("Init failed, could not perform requested operations: %s",
                 pcmk_rc_str(rc));
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "Init failed, could not perform requested operations: %s",
                     pcmk_rc_str(rc));
         goto done;
     }
 
     rc = do_work(input, &output);
     if (rc > 0) {
         /* wait for the reply by creating a mainloop and running it until
          * the callbacks are invoked...
          */
         request_id = rc;
 
         the_cib->cmds->register_callback(the_cib, request_id,
                                          options.message_timeout_sec, FALSE,
                                          NULL, "cibadmin_op_callback",
                                          cibadmin_op_callback);
 
         mainloop = g_main_loop_new(NULL, FALSE);
 
         crm_trace("%s waiting for reply from the local CIB", crm_system_name);
 
         crm_info("Starting mainloop");
         g_main_loop_run(mainloop);
 
     } else if ((rc == -pcmk_err_schema_unchanged)
                && (strcmp(options.cib_action,
                           PCMK__CIB_REQUEST_UPGRADE) == 0)) {
         report_schema_unchanged();
 
     } else if (rc < 0) {
         rc = pcmk_legacy2rc(rc);
         crm_err("Call failed: %s", pcmk_rc_str(rc));
         fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
 
         if (rc == pcmk_rc_schema_validation) {
             if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) {
                 xmlNode *obj = NULL;
                 int version = 0;
 
                 if (the_cib->cmds->query(the_cib, NULL, &obj,
                                          options.cmd_options) == pcmk_ok) {
                     update_validation(&obj, &version, 0, TRUE, FALSE);
                 }
                 free_xml(obj);
 
             } else if (output) {
                 validate_xml_verbose(output);
             }
         }
         exit_code = pcmk_rc2exitc(rc);
     }
 
     if ((output != NULL)
         && (options.acl_render_mode != pcmk__acl_render_none)) {
 
         xmlDoc *acl_evaled_doc;
         rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
         if (rc == pcmk_rc_ok) {
             xmlChar *rendered = NULL;
 
             rc = pcmk__acl_evaled_render(acl_evaled_doc,
                                          options.acl_render_mode, &rendered);
             if (rc != pcmk_rc_ok) {
                 exit_code = CRM_EX_CONFIG;
                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                             "Could not render evaluated access: %s",
                             pcmk_rc_str(rc));
                 goto done;
             }
             printf("%s\n", (char *) rendered);
             free(rendered);
 
         } else {
             exit_code = CRM_EX_CONFIG;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         "Could not evaluate access per request (%s, error: %s)",
                         acl_cred, pcmk_rc_str(rc));
             goto done;
         }
 
     } else if (output != NULL) {
         print_xml_output(output);
     }
 
     crm_trace("%s exiting normally", crm_system_name);
 
 done:
     g_strfreev(processed_args);
     pcmk__free_arg_context(context);
 
     g_free(options.cib_user);
     g_free(options.dest_node);
     g_free(options.input_file);
     g_free(options.input_xml);
     free(options.cib_section);
     free(options.validate_with);
 
     g_free(acl_cred);
     free_xml(input);
     free_xml(output);
 
     rc = cib__clean_up_connection(&the_cib);
     if (exit_code == CRM_EX_OK) {
         exit_code = pcmk_rc2exitc(rc);
     }
 
     pcmk__output_and_clear_error(&error, NULL);
     crm_exit(exit_code);
 }
 
 static int
 do_work(xmlNode *input, xmlNode **output)
 {
     /* construct the request */
     the_cib->call_timeout = options.message_timeout_sec;
     if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
         && pcmk__xe_is(input, PCMK_XE_CIB)) {
         xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS);
 
         if (status == NULL) {
             create_xml_node(input, PCMK_XE_STATUS);
         }
     }
 
     crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
     return cib_internal_op(the_cib, options.cib_action, options.dest_node,
                            options.cib_section, input, output,
                            options.cmd_options, options.cib_user);
 }
 
 int
 do_init(void)
 {
     int rc = pcmk_ok;
 
     the_cib = cib_new();
     rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
     if (rc != pcmk_ok) {
         crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
         fprintf(stderr, "Could not connect to the CIB: %s\n",
                 pcmk_strerror(rc));
     }
 
     return rc;
 }
 
 void
 cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     rc = pcmk_legacy2rc(rc);
     exit_code = pcmk_rc2exitc(rc);
 
     if (rc == pcmk_rc_schema_unchanged) {
         report_schema_unchanged();
 
     } else if (rc != pcmk_rc_ok) {
         crm_warn("Call %s failed: %s " CRM_XS " rc=%d",
                  options.cib_action, pcmk_rc_str(rc), rc);
         fprintf(stderr, "Call %s failed: %s\n",
                 options.cib_action, pcmk_rc_str(rc));
         print_xml_output(output);
 
     } else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0)
                && (output == NULL)) {
         crm_err("Query returned no output");
         crm_log_xml_err(msg, "no output");
 
     } else if (output == NULL) {
         crm_info("Call passed");
 
     } else {
         crm_info("Call passed");
         print_xml_output(output);
     }
 
     if (call_id == request_id) {
         g_main_loop_quit(mainloop);
 
     } else {
         crm_info("Message was not the response we were looking for (%d vs. %d)",
                  call_id, request_id);
     }
 }
diff --git a/tools/crm_resource.c b/tools/crm_resource.c
index 1e8a13ffcc..b845f836d9 100644
--- a/tools/crm_resource.c
+++ b/tools/crm_resource.c
@@ -1,2086 +1,2086 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm_resource.h>
 #include <crm/lrmd_internal.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/ipc_attrd_internal.h>
 #include <crm/common/lists_internal.h>
 #include <crm/common/output.h>
 #include <pacemaker-internal.h>
 
 #include <sys/param.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <time.h>
 
 #include <crm/crm.h>
 #include <crm/stonith-ng.h>
 #include <crm/common/ipc_controld.h>
 #include <crm/cib/internal.h>
 
 #define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources"
 
 enum rsc_command {
     cmd_none = 0,           // No command option given (yet)
     cmd_ban,
     cmd_cleanup,
     cmd_clear,
     cmd_colocations,
     cmd_cts,
     cmd_delete,
     cmd_delete_param,
     cmd_digests,
     cmd_execute_agent,
     cmd_fail,
     cmd_get_param,
     cmd_get_property,
     cmd_list_active_ops,
     cmd_list_agents,
     cmd_list_all_ops,
     cmd_list_alternatives,
     cmd_list_instances,
     cmd_list_providers,
     cmd_list_resources,
     cmd_list_standards,
     cmd_locate,
     cmd_metadata,
     cmd_move,
     cmd_query_raw_xml,
     cmd_query_xml,
     cmd_refresh,
     cmd_restart,
     cmd_set_param,
     cmd_set_property,
     cmd_wait,
     cmd_why,
 };
 
 struct {
     enum rsc_command rsc_cmd;     // crm_resource command to perform
 
     // Infrastructure that given command needs to work
     gboolean require_cib;         // Whether command requires CIB IPC
     int cib_options;              // Options to use with CIB IPC calls
     gboolean require_crmd;        // Whether command requires controller IPC
     gboolean require_scheduler;   // Whether command requires scheduler data
     gboolean require_resource;    // Whether command requires resource specified
     gboolean require_node;        // Whether command requires node specified
     int find_flags;               // Flags to use when searching for resource
 
     // Command-line option values
     gchar *rsc_id;                // Value of --resource
     gchar *rsc_type;              // Value of --resource-type
     gboolean force;               // --force was given
     gboolean clear_expired;       // --expired was given
     gboolean recursive;           // --recursive was given
     gboolean promoted_role_only;  // --promoted was given
     gchar *host_uname;            // Value of --node
     gchar *interval_spec;         // Value of --interval
     gchar *move_lifetime;         // Value of --lifetime
     gchar *operation;             // Value of --operation
     const char *attr_set_type;    // Instance, meta, utilization, or element attribute
     gchar *prop_id;               // --nvpair (attribute XML ID)
     char *prop_name;              // Attribute name
     gchar *prop_set;              // --set-name (attribute block XML ID)
     gchar *prop_value;            // --parameter-value (attribute value)
     int timeout_ms;               // Parsed from --timeout value
     char *agent_spec;             // Standard and/or provider and/or agent
     gchar *xml_file;              // Value of (deprecated) --xml-file
     int check_level;              // Optional value of --validate or --force-check
 
     // Resource configuration specified via command-line arguments
     gboolean cmdline_config;      // Resource configuration was via arguments
     char *v_agent;                // Value of --agent
     char *v_class;                // Value of --class
     char *v_provider;             // Value of --provider
     GHashTable *cmdline_params;   // Resource parameters specified
 
     // Positional command-line arguments
     gchar **remainder;            // Positional arguments as given
     GHashTable *override_params;  // Resource parameter values that override config
 } options = {
     .attr_set_type = XML_TAG_ATTR_SETS,
     .check_level = -1,
     .cib_options = cib_sync_call,
     .require_cib = TRUE,
     .require_scheduler = TRUE,
     .require_resource = TRUE,
 };
 
 #if 0
 // @COMPAT @TODO enable this at next backward compatibility break
 #define SET_COMMAND(cmd) do {                                               \
         if (options.rsc_cmd != cmd_none) {                                  \
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,             \
                         "Only one command option may be specified");        \
             return FALSE;                                                   \
         }                                                                   \
         options.rsc_cmd = (cmd);                                            \
     } while (0)
 #else
 #define SET_COMMAND(cmd) do {                                               \
         if (options.rsc_cmd != cmd_none) {                                  \
             reset_options();                                                \
         }                                                                   \
         options.rsc_cmd = (cmd);                                            \
     } while (0)
 #endif
 
 gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean list_agents_cb(const gchar *option_name, const gchar *optarg,
                         gpointer data, GError **error);
 gboolean list_providers_cb(const gchar *option_name, const gchar *optarg,
                            gpointer data, GError **error);
 gboolean list_standards_cb(const gchar *option_name, const gchar *optarg,
                            gpointer data, GError **error);
 gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg,
                               gpointer data, GError **error);
 gboolean metadata_cb(const gchar *option_name, const gchar *optarg,
                      gpointer data, GError **error);
 gboolean option_cb(const gchar *option_name, const gchar *optarg,
                    gpointer data, GError **error);
 gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg,
                               gpointer data, GError **error);
 gboolean restart_cb(const gchar *option_name, const gchar *optarg,
                     gpointer data, GError **error);
 gboolean digests_cb(const gchar *option_name, const gchar *optarg,
                     gpointer data, GError **error);
 gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 static crm_exit_t exit_code = CRM_EX_OK;
 static pcmk__output_t *out = NULL;
 static pcmk__common_args_t *args = NULL;
 
 // Things that should be cleaned up on exit
 static GError *error = NULL;
 static GMainLoop *mainloop = NULL;
 static cib_t *cib_conn = NULL;
 static pcmk_ipc_api_t *controld_api = NULL;
 static pcmk_scheduler_t *scheduler = NULL;
 
 #define MESSAGE_TIMEOUT_S 60
 
 #define INDENT "                                    "
 
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 // Clean up and exit
 static crm_exit_t
 bye(crm_exit_t ec)
 {
     pcmk__output_and_clear_error(&error, out);
 
     if (out != NULL) {
         out->finish(out, ec, true, NULL);
         pcmk__output_free(out);
     }
     pcmk__unregister_formats();
 
     if (cib_conn != NULL) {
         cib_t *save_cib_conn = cib_conn;
 
         cib_conn = NULL; // Ensure we can't free this twice
         cib__clean_up_connection(&save_cib_conn);
     }
 
     if (controld_api != NULL) {
         pcmk_ipc_api_t *save_controld_api = controld_api;
 
         controld_api = NULL; // Ensure we can't free this twice
         pcmk_free_ipc_api(save_controld_api);
     }
 
     if (mainloop != NULL) {
         g_main_loop_unref(mainloop);
         mainloop = NULL;
     }
 
     pe_free_working_set(scheduler);
     scheduler = NULL;
     crm_exit(ec);
     return ec;
 }
 
 static void
 quit_main_loop(crm_exit_t ec)
 {
     exit_code = ec;
     if (mainloop != NULL) {
         GMainLoop *mloop = mainloop;
 
         mainloop = NULL; // Don't re-enter this block
         pcmk_quit_main_loop(mloop, 10);
         g_main_loop_unref(mloop);
     }
 }
 
 static gboolean
 resource_ipc_timeout(gpointer data)
 {
     // Start with newline because "Waiting for ..." message doesn't have one
     if (error != NULL) {
         g_clear_error(&error);
     }
 
     g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT,
                 _("Aborting because no messages received in %d seconds"), MESSAGE_TIMEOUT_S);
 
     quit_main_loop(CRM_EX_TIMEOUT);
     return FALSE;
 }
 
 static void
 controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type,
                           crm_exit_t status, void *event_data, void *user_data)
 {
     switch (event_type) {
         case pcmk_ipc_event_disconnect:
             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
                 crm_info("Connection to controller was terminated");
             }
             quit_main_loop(exit_code);
             break;
 
         case pcmk_ipc_event_reply:
             if (status != CRM_EX_OK) {
                 out->err(out, "Error: bad reply from controller: %s",
                          crm_exit_str(status));
                 pcmk_disconnect_ipc(api);
                 quit_main_loop(status);
             } else {
                 if ((pcmk_controld_api_replies_expected(api) == 0)
                     && mainloop && g_main_loop_is_running(mainloop)) {
                     out->info(out, "... got reply (done)");
                     crm_debug("Got all the replies we expected");
                     pcmk_disconnect_ipc(api);
                     quit_main_loop(CRM_EX_OK);
                 } else {
                     out->info(out, "... got reply");
                 }
             }
             break;
 
         default:
             break;
     }
 }
 
 static void
 start_mainloop(pcmk_ipc_api_t *capi)
 {
     unsigned int count = pcmk_controld_api_replies_expected(capi);
 
     if (count > 0) {
         out->info(out, "Waiting for %u %s from the controller",
                   count, pcmk__plural_alt(count, "reply", "replies"));
         exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
         mainloop = g_main_loop_new(NULL, FALSE);
         g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL);
         g_main_loop_run(mainloop);
     }
 }
 
 static int
 compare_id(gconstpointer a, gconstpointer b)
 {
     return strcmp((const char *)a, (const char *)b);
 }
 
 static GList *
 build_constraint_list(xmlNode *root)
 {
     GList *retval = NULL;
     xmlNode *cib_constraints = NULL;
     xmlXPathObjectPtr xpathObj = NULL;
     int ndx = 0;
 
     cib_constraints = pcmk_find_cib_element(root, XML_CIB_TAG_CONSTRAINTS);
     xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION);
 
     for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) {
         xmlNode *match = getXpathResult(xpathObj, ndx);
         retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id);
     }
 
     freeXpathObject(xpathObj);
     return retval;
 }
 
 /* short option letters still available: eEJkKXyYZ */
 
 static GOptionEntry query_entries[] = {
     { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
       "List all cluster resources with status",
       NULL },
     { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
       "List IDs of all instantiated resources (individual members\n"
       INDENT "rather than groups etc.)",
       NULL },
     { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
       NULL,
       NULL },
     { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
       "List active resource operations, optionally filtered by\n"
       INDENT "--resource and/or --node",
       NULL },
     { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
       "List all resource operations, optionally filtered by\n"
       INDENT "--resource and/or --node",
       NULL },
     { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       list_standards_cb,
       "List supported standards",
       NULL },
     { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       list_providers_cb,
       "List all available OCF providers",
       NULL },
     { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
       list_agents_cb,
       "List all agents available for the named standard and/or provider",
       "STD:PROV" },
     { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
       list_alternatives_cb,
       "List all available providers for the named OCF agent",
       "AGENT" },
     { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
       metadata_cb,
       "Show the metadata for the named class:provider:agent",
       "SPEC" },
     { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Show XML configuration of resource (after any template expansion)",
       NULL },
     { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Show XML configuration of resource (before any template expansion)",
       NULL },
     { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
       "Display named parameter for resource (use instance attribute\n"
       INDENT "unless --element, --meta, or --utilization is specified)",
       "PARAM" },
     { "get-property", 'G', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
       "Display named property of resource ('class', 'type', or 'provider') "
       "(requires --resource)",
       "PROPERTY" },
     { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Show node(s) currently running resource",
       NULL },
     { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Display the location and colocation constraints that apply to a\n"
       INDENT "resource, and if --recursive is specified, to the resources\n"
       INDENT "directly or indirectly involved in those colocations.\n"
       INDENT "If the named resource is part of a group, or a clone or\n"
       INDENT "bundle instance, constraints for the collective resource\n"
       INDENT "will be shown unless --force is given.",
       NULL },
     { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Equivalent to --constraints --recursive",
       NULL },
     { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, why_cb,
       "Show why resources are not running, optionally filtered by\n"
       INDENT "--resource and/or --node",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry command_entries[] = {
     { "validate", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "Validate resource configuration by calling agent's validate-all\n"
       INDENT "action. The configuration may be specified either by giving an\n"
       INDENT "existing resource name with -r, or by specifying --class,\n"
       INDENT "--agent, and --provider arguments, along with any number of\n"
       INDENT "--option arguments. An optional LEVEL argument can be given\n"
       INDENT "to control the level of checking performed.",
       "LEVEL" },
     { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
       "If resource has any past failures, clear its history and fail\n"
       INDENT "count. Optionally filtered by --resource, --node, --operation\n"
       INDENT "and --interval (otherwise all). --operation and --interval\n"
       INDENT "apply to fail counts, but entire history is always clear, to\n"
       INDENT "allow current state to be rechecked. If the named resource is\n"
       INDENT "part of a group, or one numbered instance of a clone or bundled\n"
       INDENT "resource, the clean-up applies to the whole collective resource\n"
       INDENT "unless --force is given.",
       NULL },
     { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
       "Delete resource's history (including failures) so its current state\n"
       INDENT "is rechecked. Optionally filtered by --resource and --node\n"
       INDENT "(otherwise all). If the named resource is part of a group, or one\n"
       INDENT "numbered instance of a clone or bundled resource, the refresh\n"
       INDENT "applies to the whole collective resource unless --force is given.",
       NULL },
     { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
       "Set named parameter for resource (requires -v). Use instance\n"
       INDENT "attribute unless --element, --meta, or --utilization is "
       "specified.",
       "PARAM" },
     { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
       "Delete named parameter for resource. Use instance attribute\n"
       INDENT "unless --element, --meta or, --utilization is specified.",
       "PARAM" },
     { "set-property", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, set_prop_cb,
       "Set named property of resource ('class', 'type', or 'provider') "
       "(requires -r, -t, -v)",
       "PROPERTY" },
 
     { NULL }
 };
 
 static GOptionEntry location_entries[] = {
     { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Create a constraint to move resource. If --node is specified,\n"
       INDENT "the constraint will be to move to that node, otherwise it\n"
       INDENT "will be to ban the current node. Unless --force is specified\n"
       INDENT "this will return an error if the resource is already running\n"
       INDENT "on the specified node. If --force is specified, this will\n"
       INDENT "always ban the current node.\n"
       INDENT "Optional: --lifetime, --promoted. NOTE: This may prevent the\n"
       INDENT "resource from running on its previous location until the\n"
       INDENT "implicit constraint expires or is removed with --clear.",
       NULL },
     { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Create a constraint to keep resource off a node.\n"
       INDENT "Optional: --node, --lifetime, --promoted.\n"
       INDENT "NOTE: This will prevent the resource from running on the\n"
       INDENT "affected node until the implicit constraint expires or is\n"
       INDENT "removed with --clear. If --node is not specified, it defaults\n"
       INDENT "to the node currently running the resource for primitives\n"
       INDENT "and groups, or the promoted instance of promotable clones with\n"
       INDENT PCMK_META_PROMOTED_MAX "=1 (all other situations result in an\n"
       INDENT "error as there is no sane default).",
       NULL },
     { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
       "Remove all constraints created by the --ban and/or --move\n"
       INDENT "commands. Requires: --resource. Optional: --node, --promoted,\n"
       INDENT "--expired. If --node is not specified, all constraints created\n"
       INDENT "by --ban and --move will be removed for the named resource. If\n"
       INDENT "--node and --force are specified, any constraint created by\n"
       INDENT "--move will be cleared, even if it is not for the specified\n"
       INDENT "node. If --expired is specified, only those constraints whose\n"
       INDENT "lifetimes have expired will be removed.",
       NULL },
     { "expired", 'e', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, expired_cb,
       "Modifies the --clear argument to remove constraints with\n"
       INDENT "expired lifetimes.",
       NULL },
     { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime,
       "Lifespan (as ISO 8601 duration) of created constraints (with\n"
       INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)",
       "TIMESPEC" },
     { "promoted", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.promoted_role_only,
       "Limit scope of command to promoted role (with -B, -M, -U). For\n"
       INDENT "-B and -M, previously promoted instances may remain\n"
       INDENT "active in the unpromoted role.",
       NULL },
 
     // Deprecated since 2.1.0
     { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &options.promoted_role_only,
       "Deprecated: Use --promoted instead", NULL },
 
     { NULL }
 };
 
 static GOptionEntry advanced_entries[] = {
     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
       "(Advanced) Delete a resource from the CIB. Required: -t",
       NULL },
     { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, fail_cb,
       "(Advanced) Tell the cluster this resource has failed",
       NULL },
     { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, restart_cb,
       "(Advanced) Tell the cluster to restart this resource and\n"
       INDENT "anything that depends on it",
       NULL },
     { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, wait_cb,
       "(Advanced) Wait until the cluster settles into a stable state",
       NULL },
     { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, digests_cb,
       "(Advanced) Show parameter hashes that Pacemaker uses to detect\n"
       INDENT "configuration changes (only accurate if there is resource\n"
       INDENT "history on the specified node). Required: --resource, --node.\n"
       INDENT "Optional: any NAME=VALUE parameters will be used to override\n"
       INDENT "the configuration (to see what the hash would be with those\n"
       INDENT "changes).",
       NULL },
     { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "(Advanced) Bypass the cluster and demote a resource on the local\n"
       INDENT "node. Unless --force is specified, this will refuse to do so if\n"
       INDENT "the cluster believes the resource is a clone instance already\n"
       INDENT "running on the local node.",
       NULL },
     { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "(Advanced) Bypass the cluster and stop a resource on the local node",
       NULL },
     { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "(Advanced) Bypass the cluster and start a resource on the local\n"
       INDENT "node. Unless --force is specified, this will refuse to do so if\n"
       INDENT "the cluster believes the resource is a clone instance already\n"
       INDENT "running on the local node.",
       NULL },
     { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "(Advanced) Bypass the cluster and promote a resource on the local\n"
       INDENT "node. Unless --force is specified, this will refuse to do so if\n"
       INDENT "the cluster believes the resource is a clone instance already\n"
       INDENT "running on the local node.",
       NULL },
     { "force-check", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
       validate_or_force_cb,
       "(Advanced) Bypass the cluster and check the state of a resource on\n"
       INDENT "the local node. An optional LEVEL argument can be given\n"
       INDENT "to control the level of checking performed.",
       "LEVEL" },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname,
       "Node name",
       "NAME" },
     { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive,
       "Follow colocation chains when using --set-parameter or --constraints",
       NULL },
     { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type,
       "Resource XML element (primitive, group, etc.) (with -D)",
       "ELEMENT" },
     { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value,
       "Value to use with -p",
       "PARAM" },
     { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
       "Use resource meta-attribute instead of instance attribute\n"
       INDENT "(with -p, -g, -d)",
       NULL },
     { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
       "Use resource utilization attribute instead of instance attribute\n"
       INDENT "(with -p, -g, -d)",
       NULL },
     { "element", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
       "Use resource element attribute instead of instance attribute\n"
       INDENT "(with -p, -g, -d)",
       NULL },
     { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation,
       "Operation to clear instead of all (with -C -r)",
       "OPERATION" },
     { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec,
       "Interval of operation to clear (default 0) (with -C -r -n)",
       "N" },
     { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb,
       "The standard the resource agent conforms to (for example, ocf).\n"
       INDENT "Use with --agent, --provider, --option, and --validate.",
       "CLASS" },
     { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb,
       "The agent to use (for example, IPaddr). Use with --class,\n"
       INDENT "--provider, --option, and --validate.",
       "AGENT" },
     { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb,
       "The vendor that supplies the resource agent (for example,\n"
       INDENT "heartbeat). Use with --class, --agent, --option, and --validate.",
       "PROVIDER" },
     { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb,
       "Specify a device configuration parameter as NAME=VALUE (may be\n"
       INDENT "specified multiple times). Use with --validate and without the\n"
       INDENT "-r option.",
       "PARAM" },
     { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set,
       "(Advanced) XML ID of attributes element to use (with -p, -d)",
       "ID" },
     { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id,
       "(Advanced) XML ID of nvpair element to use (with -p, -d)",
       "ID" },
     { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb,
       "(Advanced) Abort if command does not finish in this time (with\n"
       INDENT "--restart, --wait, --force-*)",
       "N" },
     { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
       "Force the action to be performed. See help for individual commands for\n"
       INDENT "additional behavior.",
       NULL },
     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &options.xml_file,
       NULL,
       "FILE" },
     { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname,
       NULL,
       "HOST" },
 
     { NULL }
 };
 
 static void
 reset_options(void) {
     options.require_crmd = FALSE;
     options.require_node = FALSE;
 
     options.require_cib = TRUE;
     options.require_scheduler = TRUE;
     options.require_resource = TRUE;
 
     options.find_flags = 0;
 }
 
 gboolean
 agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.cmdline_config = TRUE;
     options.require_resource = FALSE;
 
     if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) {
         pcmk__str_update(&options.v_provider, optarg);
     } else {
         pcmk__str_update(&options.v_agent, optarg);
     }
 
     return TRUE;
 }
 
 gboolean
 attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) {
         options.attr_set_type = XML_TAG_META_SETS;
     } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) {
         options.attr_set_type = XML_TAG_UTILIZATION;
     } else if (pcmk__str_eq(option_name, "--element", pcmk__str_casei)) {
         options.attr_set_type = ATTR_SET_ELEMENT;
     }
     return TRUE;
 }
 
 gboolean
 class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     pcmk__str_update(&options.v_class, optarg);
     options.cmdline_config = TRUE;
     options.require_resource = FALSE;
     return TRUE;
 }
 
 gboolean
 cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) {
         SET_COMMAND(cmd_cleanup);
     } else {
         SET_COMMAND(cmd_refresh);
     }
 
     options.require_resource = FALSE;
     if (getenv("CIB_file") == NULL) {
         options.require_crmd = TRUE;
     }
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename;
     return TRUE;
 }
 
 gboolean
 delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     SET_COMMAND(cmd_delete);
     options.require_scheduler = FALSE;
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     return TRUE;
 }
 
 gboolean
 expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.clear_expired = TRUE;
     options.require_resource = FALSE;
     return TRUE;
 }
 
 static void
 get_agent_spec(const gchar *optarg)
 {
     options.require_cib = FALSE;
     options.require_scheduler = FALSE;
     options.require_resource = FALSE;
     pcmk__str_update(&options.agent_spec, optarg);
 }
 
 gboolean
 list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                GError **error)
 {
     SET_COMMAND(cmd_list_agents);
     get_agent_spec(optarg);
     return TRUE;
 }
 
 gboolean
 list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                   GError **error)
 {
     SET_COMMAND(cmd_list_providers);
     get_agent_spec(optarg);
     return TRUE;
 }
 
 gboolean
 list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                   GError **error)
 {
     SET_COMMAND(cmd_list_standards);
     options.require_cib = FALSE;
     options.require_scheduler = FALSE;
     options.require_resource = FALSE;
     return TRUE;
 }
 
 gboolean
 list_alternatives_cb(const gchar *option_name, const gchar *optarg,
                      gpointer data, GError **error)
 {
     SET_COMMAND(cmd_list_alternatives);
     get_agent_spec(optarg);
     return TRUE;
 }
 
 gboolean
 metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data,
             GError **error)
 {
     SET_COMMAND(cmd_metadata);
     get_agent_spec(optarg);
     return TRUE;
 }
 
 gboolean
 option_cb(const gchar *option_name, const gchar *optarg, gpointer data,
           GError **error)
 {
     char *name = NULL;
     char *value = NULL;
 
     if (pcmk__scan_nvpair(optarg, &name, &value) != 2) {
         return FALSE;
     }
     if (options.cmdline_params == NULL) {
         options.cmdline_params = pcmk__strkey_table(free, free);
     }
     g_hash_table_replace(options.cmdline_params, name, value);
     return TRUE;
 }
 
 gboolean
 fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     SET_COMMAND(cmd_fail);
     options.require_crmd = TRUE;
     options.require_node = TRUE;
     return TRUE;
 }
 
 gboolean
 flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) {
         SET_COMMAND(cmd_clear);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
     } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) {
         SET_COMMAND(cmd_ban);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
     } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) {
         SET_COMMAND(cmd_move);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
     } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) {
         SET_COMMAND(cmd_query_xml);
         options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) {
         SET_COMMAND(cmd_query_raw_xml);
         options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) {
         SET_COMMAND(cmd_locate);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
 
     } else if (pcmk__str_any_of(option_name, "-a", "--constraints", NULL)) {
         SET_COMMAND(cmd_colocations);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
 
     } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) {
         SET_COMMAND(cmd_colocations);
         options.find_flags = pcmk_rsc_match_history
                              |pcmk_rsc_match_anon_basename;
         options.recursive = TRUE;
     }
 
     return TRUE;
 }
 
 gboolean
 get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) {
         SET_COMMAND(cmd_get_param);
     } else {
         SET_COMMAND(cmd_get_property);
     }
 
     pcmk__str_update(&options.prop_name, optarg);
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     return TRUE;
 }
 
 gboolean
 list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) {
         SET_COMMAND(cmd_cts);
     } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) {
         SET_COMMAND(cmd_list_resources);
     } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) {
         SET_COMMAND(cmd_list_instances);
     } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) {
         SET_COMMAND(cmd_list_active_ops);
     } else {
         SET_COMMAND(cmd_list_all_ops);
     }
 
     options.require_resource = FALSE;
     return TRUE;
 }
 
 gboolean
 set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) {
         SET_COMMAND(cmd_set_param);
     } else {
         SET_COMMAND(cmd_delete_param);
     }
 
     pcmk__str_update(&options.prop_name, optarg);
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     return TRUE;
 }
 
 gboolean
 set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     SET_COMMAND(cmd_set_property);
     options.require_scheduler = FALSE;
     pcmk__str_update(&options.prop_name, optarg);
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
     return TRUE;
 }
 
 gboolean
 timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.timeout_ms = crm_get_msec(optarg);
     return TRUE;
 }
 
 gboolean
 validate_or_force_cb(const gchar *option_name, const gchar *optarg,
                      gpointer data, GError **error)
 {
     SET_COMMAND(cmd_execute_agent);
     if (options.operation) {
         g_free(options.operation);
     }
     options.operation = g_strdup(option_name + 2); // skip "--"
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename;
     if (options.override_params == NULL) {
         options.override_params = pcmk__strkey_table(free, free);
     }
 
     if (optarg != NULL) {
         if (pcmk__scan_min_int(optarg, &options.check_level, 0) != pcmk_rc_ok) {
             g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM,
                         _("Invalid check level setting: %s"), optarg);
             return FALSE;
         }
     }
 
     return TRUE;
 }
 
 gboolean
 restart_cb(const gchar *option_name, const gchar *optarg, gpointer data,
            GError **error)
 {
     SET_COMMAND(cmd_restart);
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename;
     return TRUE;
 }
 
 gboolean
 digests_cb(const gchar *option_name, const gchar *optarg, gpointer data,
            GError **error)
 {
     SET_COMMAND(cmd_digests);
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename;
     if (options.override_params == NULL) {
         options.override_params = pcmk__strkey_table(free, free);
     }
     options.require_node = TRUE;
     options.require_scheduler = TRUE;
     return TRUE;
 }
 
 gboolean
 wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     SET_COMMAND(cmd_wait);
     options.require_resource = FALSE;
     options.require_scheduler = FALSE;
     return TRUE;
 }
 
 gboolean
 why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     SET_COMMAND(cmd_why);
     options.require_resource = FALSE;
     options.find_flags = pcmk_rsc_match_history|pcmk_rsc_match_anon_basename;
     return TRUE;
 }
 
 static int
 ban_or_move(pcmk__output_t *out, pcmk_resource_t *rsc,
             const char *move_lifetime)
 {
     int rc = pcmk_rc_ok;
     pcmk_node_t *current = NULL;
     unsigned int nactive = 0;
 
     CRM_CHECK(rsc != NULL, return EINVAL);
 
     current = pe__find_active_requires(rsc, &nactive);
 
     if (nactive == 1) {
         rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime,
                               cib_conn, options.cib_options, options.promoted_role_only,
                               PCMK__ROLE_PROMOTED);
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         int count = 0;
         GList *iter = NULL;
 
         current = NULL;
         for(iter = rsc->children; iter; iter = iter->next) {
             pcmk_resource_t *child = (pcmk_resource_t *)iter->data;
             enum rsc_role_e child_role = child->fns->state(child, TRUE);
 
             if (child_role == pcmk_role_promoted) {
                 count++;
                 current = pcmk__current_node(child);
             }
         }
 
         if(count == 1 && current) {
             rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime,
                                   cib_conn, options.cib_options, options.promoted_role_only,
                                   PCMK__ROLE_PROMOTED);
 
         } else {
             rc = EINVAL;
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         _("Resource '%s' not moved: active in %d locations (promoted in %d).\n"
                         "To prevent '%s' from running on a specific location, "
                         "specify a node."
                         "To prevent '%s' from being promoted at a specific "
                         "location, specify a node and the --promoted option."),
                         options.rsc_id, nactive, count, options.rsc_id, options.rsc_id);
         }
 
     } else {
         rc = EINVAL;
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("Resource '%s' not moved: active in %d locations.\n"
                     "To prevent '%s' from running on a specific location, "
                     "specify a node."),
                     options.rsc_id, nactive, options.rsc_id);
     }
 
     return rc;
 }
 
 static void
 cleanup(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     int rc = pcmk_rc_ok;
 
     if (options.force == FALSE) {
         rsc = uber_parent(rsc);
     }
 
     crm_debug("Erasing failures of %s (%s requested) on %s",
               rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
     rc = cli_resource_delete(controld_api, options.host_uname, rsc,
                              options.operation, options.interval_spec, TRUE,
                              scheduler, options.force);
 
     if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
         // Show any reasons why resource might stay stopped
         cli_resource_check(out, rsc, node);
     }
 
     if (rc == pcmk_rc_ok) {
         start_mainloop(controld_api);
     }
 }
 
 static int
 clear_constraints(pcmk__output_t *out, xmlNodePtr *cib_xml_copy)
 {
     GList *before = NULL;
     GList *after = NULL;
     GList *remaining = NULL;
     GList *ele = NULL;
     pcmk_node_t *dest = NULL;
     int rc = pcmk_rc_ok;
 
     if (!out->is_quiet(out)) {
         before = build_constraint_list(scheduler->input);
     }
 
     if (options.clear_expired) {
         rc = cli_resource_clear_all_expired(scheduler->input, cib_conn,
                                             options.cib_options, options.rsc_id,
                                             options.host_uname,
                                             options.promoted_role_only);
 
     } else if (options.host_uname) {
         dest = pe_find_node(scheduler->nodes, options.host_uname);
         if (dest == NULL) {
             rc = pcmk_rc_node_unknown;
             if (!out->is_quiet(out)) {
                 g_list_free(before);
             }
             return rc;
         }
         rc = cli_resource_clear(options.rsc_id, dest->details->uname, NULL,
                                 cib_conn, options.cib_options, TRUE, options.force);
 
     } else {
         rc = cli_resource_clear(options.rsc_id, NULL, scheduler->nodes,
                                 cib_conn, options.cib_options, TRUE, options.force);
     }
 
     if (!out->is_quiet(out)) {
         rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
         rc = pcmk_legacy2rc(rc);
 
         if (rc != pcmk_rc_ok) {
             g_set_error(&error, PCMK__RC_ERROR, rc,
                         _("Could not get modified CIB: %s\n"), pcmk_rc_str(rc));
             g_list_free(before);
             free_xml(*cib_xml_copy);
             *cib_xml_copy = NULL;
             return rc;
         }
 
         scheduler->input = *cib_xml_copy;
         cluster_status(scheduler);
 
         after = build_constraint_list(scheduler->input);
         remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp);
 
         for (ele = remaining; ele != NULL; ele = ele->next) {
             out->info(out, "Removing constraint: %s", (char *) ele->data);
         }
 
         g_list_free(before);
         g_list_free(after);
         g_list_free(remaining);
     }
 
     return rc;
 }
 
 static int
 initialize_scheduler_data(xmlNodePtr *cib_xml_copy)
 {
     int rc = pcmk_rc_ok;
 
     if (options.xml_file != NULL) {
         *cib_xml_copy = filename2xml(options.xml_file);
         if (*cib_xml_copy == NULL) {
             rc = pcmk_rc_cib_corrupt;
         }
     } else {
         rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
         rc = pcmk_legacy2rc(rc);
     }
 
     if (rc == pcmk_rc_ok) {
         scheduler = pe_new_working_set();
         if (scheduler == NULL) {
             rc = ENOMEM;
         } else {
             pcmk__set_scheduler_flags(scheduler,
                                       pcmk_sched_no_counts
                                       |pcmk_sched_no_compat);
             scheduler->priv = out;
             rc = update_scheduler_input(scheduler, cib_xml_copy);
         }
     }
 
     if (rc != pcmk_rc_ok) {
         free_xml(*cib_xml_copy);
         *cib_xml_copy = NULL;
         return rc;
     }
 
     cluster_status(scheduler);
     return pcmk_rc_ok;
 }
 
 static int
 refresh(pcmk__output_t *out)
 {
     int rc = pcmk_rc_ok;
     const char *router_node = options.host_uname;
     int attr_options = pcmk__node_attr_none;
 
     if (options.host_uname) {
         pcmk_node_t *node = pe_find_node(scheduler->nodes, options.host_uname);
 
         if (pe__is_guest_or_remote_node(node)) {
             node = pcmk__current_node(node->details->remote_rsc);
             if (node == NULL) {
                 rc = ENXIO;
                 g_set_error(&error, PCMK__RC_ERROR, rc,
                             _("No cluster connection to Pacemaker Remote node %s detected"),
                             options.host_uname);
                 return rc;
             }
             router_node = node->details->uname;
             attr_options |= pcmk__node_attr_remote;
         }
     }
 
     if (controld_api == NULL) {
         out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
                   options.host_uname? options.host_uname : "all nodes");
         rc = pcmk_rc_ok;
         return rc;
     }
 
     crm_debug("Re-checking the state of all resources on %s", options.host_uname?options.host_uname:"all nodes");
 
     rc = pcmk__attrd_api_clear_failures(NULL, options.host_uname, NULL,
                                         NULL, NULL, NULL, attr_options);
 
     if (pcmk_controld_api_reprobe(controld_api, options.host_uname,
                                   router_node) == pcmk_rc_ok) {
         start_mainloop(controld_api);
     }
 
     return rc;
 }
 
 static void
 refresh_resource(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     int rc = pcmk_rc_ok;
 
     if (options.force == FALSE) {
         rsc = uber_parent(rsc);
     }
 
     crm_debug("Re-checking the state of %s (%s requested) on %s",
               rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
     rc = cli_resource_delete(controld_api, options.host_uname, rsc, NULL, 0,
                              FALSE, scheduler, options.force);
 
     if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
         // Show any reasons why resource might stay stopped
         cli_resource_check(out, rsc, node);
     }
 
     if (rc == pcmk_rc_ok) {
         start_mainloop(controld_api);
     }
 }
 
 static int
 set_property(void)
 {
     int rc = pcmk_rc_ok;
     xmlNode *msg_data = NULL;
 
     if (pcmk__str_empty(options.rsc_type)) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("Must specify -t with resource type"));
         rc = ENXIO;
         return rc;
 
     } else if (pcmk__str_empty(options.prop_value)) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("Must supply -v with new value"));
         rc = ENXIO;
         return rc;
     }
 
     CRM_LOG_ASSERT(options.prop_name != NULL);
 
     msg_data = create_xml_node(NULL, options.rsc_type);
     crm_xml_add(msg_data, PCMK_XA_ID, options.rsc_id);
     crm_xml_add(msg_data, options.prop_name, options.prop_value);
 
-    rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data,
+    rc = cib_conn->cmds->modify(cib_conn, PCMK_XE_RESOURCES, msg_data,
                                 options.cib_options);
     rc = pcmk_legacy2rc(rc);
     free_xml(msg_data);
 
     return rc;
 }
 
 static int
 show_metadata(pcmk__output_t *out, const char *agent_spec)
 {
     int rc = pcmk_rc_ok;
     char *standard = NULL;
     char *provider = NULL;
     char *type = NULL;
     char *metadata = NULL;
     lrmd_t *lrmd_conn = NULL;
 
     rc = lrmd__new(&lrmd_conn, NULL, NULL, 0);
     if (rc != pcmk_rc_ok) {
         g_set_error(&error, PCMK__RC_ERROR, rc,
                     _("Could not create executor connection"));
         lrmd_api_delete(lrmd_conn);
         return rc;
     }
 
     rc = crm_parse_agent_spec(agent_spec, &standard, &provider, &type);
     rc = pcmk_legacy2rc(rc);
 
     if (rc == pcmk_rc_ok) {
         rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard,
                                            provider, type,
                                            &metadata, 0);
         rc = pcmk_legacy2rc(rc);
 
         if (metadata) {
             out->output_xml(out, "metadata", metadata);
             free(metadata);
         } else {
             /* We were given a validly formatted spec, but it doesn't necessarily
              * match up with anything that exists.  Use ENXIO as the return code
              * here because that maps to an exit code of CRM_EX_NOSUCH, which
              * probably is the most common reason to get here.
              */
             rc = ENXIO;
             g_set_error(&error, PCMK__RC_ERROR, rc,
                         _("Metadata query for %s failed: %s"),
                         agent_spec, pcmk_rc_str(rc));
         }
     } else {
         rc = ENXIO;
         g_set_error(&error, PCMK__RC_ERROR, rc,
                     _("'%s' is not a valid agent specification"), agent_spec);
     }
 
     lrmd_api_delete(lrmd_conn);
     return rc;
 }
 
 static void
 validate_cmdline_config(void)
 {
     // Cannot use both --resource and command-line resource configuration
     if (options.rsc_id != NULL) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("--resource cannot be used with --class, --agent, and --provider"));
 
     // Not all commands support command-line resource configuration
     } else if (options.rsc_cmd != cmd_execute_agent) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("--class, --agent, and --provider can only be used with "
                     "--validate and --force-*"));
 
     // Not all of --class, --agent, and --provider need to be given.  Not all
     // classes support the concept of a provider.  Check that what we were given
     // is valid.
     } else if (pcmk__str_eq(options.v_class, "stonith", pcmk__str_none)) {
         if (options.v_provider != NULL) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         _("stonith does not support providers"));
 
         } else if (stonith_agent_exists(options.v_agent, 0) == FALSE) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         _("%s is not a known stonith agent"), options.v_agent ? options.v_agent : "");
         }
 
     } else if (resources_agent_exists(options.v_class, options.v_provider, options.v_agent) == FALSE) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                     _("%s:%s:%s is not a known resource"),
                     options.v_class ? options.v_class : "",
                     options.v_provider ? options.v_provider : "",
                     options.v_agent ? options.v_agent : "");
     }
 
     if (error != NULL) {
         return;
     }
 
     if (options.cmdline_params == NULL) {
         options.cmdline_params = pcmk__strkey_table(free, free);
     }
     options.require_resource = FALSE;
     options.require_scheduler = FALSE;
     options.require_cib = FALSE;
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
         { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id,
           "Resource ID",
           "ID" },
         { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder,
           NULL,
           NULL },
 
         { NULL }
     };
 
     const char *description = "Examples:\n\n"
                               "List the available OCF agents:\n\n"
                               "\t# crm_resource --list-agents ocf\n\n"
                               "List the available OCF agents from the linux-ha project:\n\n"
                               "\t# crm_resource --list-agents ocf:heartbeat\n\n"
                               "Move 'myResource' to a specific node:\n\n"
                               "\t# crm_resource --resource myResource --move --node altNode\n\n"
                               "Allow (but not force) 'myResource' to move back to its original "
                               "location:\n\n"
                               "\t# crm_resource --resource myResource --clear\n\n"
                               "Stop 'myResource' (and anything that depends on it):\n\n"
                               "\t# crm_resource --resource myResource --set-parameter "
                               PCMK_META_TARGET_ROLE "--meta --parameter-value Stopped\n\n"
                               "Tell the cluster not to manage 'myResource' (the cluster will not "
                               "attempt to start or stop the\n"
                               "resource under any circumstances; useful when performing maintenance "
                               "tasks on a resource):\n\n"
                               "\t# crm_resource --resource myResource --set-parameter "
                               PCMK_META_IS_MANAGED "--meta --parameter-value false\n\n"
                               "Erase the operation history of 'myResource' on 'aNode' (the cluster "
                               "will 'forget' the existing\n"
                               "resource state, including any errors, and attempt to recover the"
                               "resource; useful when a resource\n"
                               "had failed permanently and has been repaired by an administrator):\n\n"
                               "\t# crm_resource --resource myResource --cleanup --node aNode\n\n";
 
     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
     g_option_context_set_description(context, description);
 
     /* Add the -Q option, which cannot be part of the globally supported options
      * because some tools use that flag for something else.
      */
     pcmk__add_main_args(context, extra_prog_entries);
 
     pcmk__add_arg_group(context, "queries", "Queries:",
                         "Show query help", query_entries);
     pcmk__add_arg_group(context, "commands", "Commands:",
                         "Show command help", command_entries);
     pcmk__add_arg_group(context, "locations", "Locations:",
                         "Show location help", location_entries);
     pcmk__add_arg_group(context, "advanced", "Advanced:",
                         "Show advanced option help", advanced_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     xmlNode *cib_xml_copy = NULL;
     pcmk_resource_t *rsc = NULL;
     pcmk_node_t *node = NULL;
     int rc = pcmk_rc_ok;
 
     GOptionGroup *output_group = NULL;
     gchar **processed_args = NULL;
     GOptionContext *context = NULL;
 
     /*
      * Parse command line arguments
      */
 
     args = pcmk__new_common_args(SUMMARY);
     processed_args = pcmk__cmdline_preproc(argv, "GHINSTdginpstuvx");
     context = build_arg_context(args, &output_group);
 
     pcmk__register_formats(output_group, formats);
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     pcmk__cli_init_logging("crm_resource", args->verbosity);
 
     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     if (rc != pcmk_rc_ok) {
         exit_code = CRM_EX_ERROR;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error creating output format %s: %s"),
                     args->output_ty, pcmk_rc_str(rc));
         goto done;
     }
 
     pe__register_messages(out);
     crm_resource_register_messages(out);
     lrmd__register_messages(out);
     pcmk__register_lib_messages(out);
 
     out->quiet = args->quiet;
 
     crm_log_args(argc, argv);
 
     /*
      * Validate option combinations
      */
 
     // If the user didn't explicitly specify a command, list resources
     if (options.rsc_cmd == cmd_none) {
         options.rsc_cmd = cmd_list_resources;
         options.require_resource = FALSE;
     }
 
     // --expired without --clear/-U doesn't make sense
     if (options.clear_expired && (options.rsc_cmd != cmd_clear)) {
         exit_code = CRM_EX_USAGE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--expired requires --clear or -U"));
         goto done;
     }
 
     if ((options.remainder != NULL) && (options.override_params != NULL)) {
         // Commands that use positional arguments will create override_params
         for (gchar **s = options.remainder; *s; s++) {
             char *name = calloc(1, strlen(*s));
             char *value = calloc(1, strlen(*s));
             int rc = sscanf(*s, "%[^=]=%s", name, value);
 
             if (rc == 2) {
                 g_hash_table_replace(options.override_params, name, value);
 
             } else {
                 exit_code = CRM_EX_USAGE;
                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                             _("Error parsing '%s' as a name=value pair"),
                             argv[optind]);
                 free(value);
                 free(name);
                 goto done;
             }
         }
 
     } else if (options.remainder != NULL) {
         gchar **strv = NULL;
         gchar *msg = NULL;
         int i = 1;
         int len = 0;
 
         for (gchar **s = options.remainder; *s; s++) {
             len++;
         }
 
         CRM_ASSERT(len > 0);
 
         /* Add 1 for the strv[0] string below, and add another 1 for the NULL
          * at the end of the array so g_strjoinv knows when to stop.
          */
         strv = calloc(len+2, sizeof(char *));
         strv[0] = strdup("non-option ARGV-elements:\n");
 
         for (gchar **s = options.remainder; *s; s++) {
             strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s);
             i++;
         }
 
         strv[i] = NULL;
 
         exit_code = CRM_EX_USAGE;
         msg = g_strjoinv("", strv);
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
         g_free(msg);
 
         /* Don't try to free the last element, which is just NULL. */
         for(i = 0; i < len+1; i++) {
             free(strv[i]);
         }
         free(strv);
 
         goto done;
     }
 
     if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
         /* Kind of a hack to display XML lists using a real tag instead of <list>.  This just
          * saves from having to write custom messages to build the lists around all these things
          */
         switch (options.rsc_cmd) {
             case cmd_execute_agent:
             case cmd_list_resources:
             case cmd_query_xml:
             case cmd_query_raw_xml:
             case cmd_list_active_ops:
             case cmd_list_all_ops:
             case cmd_colocations:
                 pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
                 break;
 
             default:
                 pcmk__force_args(context, &error, "%s --xml-substitute", g_get_prgname());
                 break;
         }
     } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) {
         if ((options.rsc_cmd == cmd_colocations) ||
             options.rsc_cmd == cmd_list_resources) {
             pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
         }
     }
 
     if (args->version) {
         out->version(out, false);
         goto done;
     }
 
     if (options.cmdline_config) {
         /* A resource configuration was given on the command line. Sanity-check
          * the values and set error if they don't make sense.
          */
         validate_cmdline_config();
         if (error != NULL) {
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
     } else if (options.cmdline_params != NULL) {
         // @COMPAT @TODO error out here when we can break backward compatibility
         g_hash_table_destroy(options.cmdline_params);
         options.cmdline_params = NULL;
     }
 
     if (options.require_resource && (options.rsc_id == NULL)) {
         exit_code = CRM_EX_USAGE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     _("Must supply a resource id with -r"));
         goto done;
     }
     if (options.require_node && (options.host_uname == NULL)) {
         exit_code = CRM_EX_USAGE;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     _("Must supply a node name with -N"));
         goto done;
     }
 
     /*
      * Set up necessary connections
      */
 
     if (options.find_flags && options.rsc_id) {
         options.require_scheduler = TRUE;
     }
 
     // Establish a connection to the CIB if needed
     if (options.require_cib) {
         cib_conn = cib_new();
         if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) {
             exit_code = CRM_EX_DISCONNECT;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Could not create CIB connection"));
             goto done;
         }
         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
         if (rc != pcmk_rc_ok) {
             exit_code = pcmk_rc2exitc(rc);
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Could not connect to the CIB: %s"), pcmk_rc_str(rc));
             goto done;
         }
     }
 
     // Populate scheduler data from XML file if specified or CIB query otherwise
     if (options.require_scheduler) {
         rc = initialize_scheduler_data(&cib_xml_copy);
         if (rc != pcmk_rc_ok) {
             exit_code = pcmk_rc2exitc(rc);
             goto done;
         }
     }
 
     // If command requires that resource exist if specified, find it
     if (options.find_flags && options.rsc_id) {
         rsc = pe_find_resource_with_flags(scheduler->resources, options.rsc_id,
                                           options.find_flags);
         if (rsc == NULL) {
             exit_code = CRM_EX_NOSUCH;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Resource '%s' not found"), options.rsc_id);
             goto done;
         }
 
         /* The --ban, --clear, --move, and --restart commands do not work with
          * instances of clone resourcs.
          */
         if (strchr(options.rsc_id, ':') != NULL && pe_rsc_is_clone(rsc->parent) &&
             (options.rsc_cmd == cmd_ban || options.rsc_cmd == cmd_clear ||
              options.rsc_cmd == cmd_move || options.rsc_cmd == cmd_restart)) {
             exit_code = CRM_EX_INVALID_PARAM;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Cannot operate on clone resource instance '%s'"), options.rsc_id);
             goto done;
         }
     }
 
     // If user supplied a node name, check whether it exists
     if ((options.host_uname != NULL) && (scheduler != NULL)) {
         node = pe_find_node(scheduler->nodes, options.host_uname);
 
         if (node == NULL) {
             exit_code = CRM_EX_NOSUCH;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Node '%s' not found"), options.host_uname);
             goto done;
         }
     }
 
     // Establish a connection to the controller if needed
     if (options.require_crmd) {
         rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
         if (rc != pcmk_rc_ok) {
             exit_code = pcmk_rc2exitc(rc);
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Error connecting to the controller: %s"), pcmk_rc_str(rc));
             goto done;
         }
         pcmk_register_ipc_callback(controld_api, controller_event_callback,
                                    NULL);
         rc = pcmk__connect_ipc(controld_api, pcmk_ipc_dispatch_main, 5);
         if (rc != pcmk_rc_ok) {
             exit_code = pcmk_rc2exitc(rc);
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Error connecting to %s: %s"),
                         pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc));
             goto done;
         }
     }
 
     /*
      * Handle requested command
      */
 
     switch (options.rsc_cmd) {
         case cmd_list_resources: {
             GList *all = NULL;
             all = g_list_prepend(all, (gpointer) "*");
             rc = out->message(out, "resource-list", scheduler,
                               pcmk_show_inactive_rscs | pcmk_show_rsc_only | pcmk_show_pending,
                               true, all, all, false);
             g_list_free(all);
 
             if (rc == pcmk_rc_no_output) {
                 rc = ENXIO;
             }
             break;
         }
 
         case cmd_list_instances:
             rc = out->message(out, "resource-names-list", scheduler->resources);
 
             if (rc != pcmk_rc_ok) {
                 rc = ENXIO;
             }
 
             break;
 
         case cmd_list_alternatives:
             rc = pcmk__list_alternatives(out, options.agent_spec);
             break;
 
         case cmd_list_agents:
             rc = pcmk__list_agents(out, options.agent_spec);
             break;
 
         case cmd_list_standards:
             rc = pcmk__list_standards(out);
             break;
 
         case cmd_list_providers:
             rc = pcmk__list_providers(out, options.agent_spec);
             break;
 
         case cmd_metadata:
             rc = show_metadata(out, options.agent_spec);
             break;
 
         case cmd_restart:
             /* We don't pass scheduler because rsc needs to stay valid for the
              * entire lifetime of cli_resource_restart(), but it will reset and
              * update the scheduler data multiple times, so it needs to use its
              * own copy.
              */
             rc = cli_resource_restart(out, rsc, node, options.move_lifetime,
                                       options.timeout_ms, cib_conn,
                                       options.cib_options, options.promoted_role_only,
                                       options.force);
             break;
 
         case cmd_wait:
             rc = wait_till_stable(out, options.timeout_ms, cib_conn);
             break;
 
         case cmd_execute_agent:
             if (options.cmdline_config) {
                 exit_code = cli_resource_execute_from_params(out, NULL,
                     options.v_class, options.v_provider, options.v_agent,
                     options.operation, options.cmdline_params,
                     options.override_params, options.timeout_ms,
                     args->verbosity, options.force, options.check_level);
             } else {
                 exit_code = cli_resource_execute(rsc, options.rsc_id,
                     options.operation, options.override_params,
                     options.timeout_ms, cib_conn, scheduler,
                     args->verbosity, options.force, options.check_level);
             }
             goto done;
 
         case cmd_digests:
             node = pe_find_node(scheduler->nodes, options.host_uname);
             if (node == NULL) {
                 rc = pcmk_rc_node_unknown;
             } else {
                 rc = pcmk__resource_digests(out, rsc, node,
                                             options.override_params);
             }
             break;
 
         case cmd_colocations:
             rc = out->message(out, "locations-and-colocations", rsc,
                               options.recursive, (bool) options.force);
             break;
 
         case cmd_cts:
             rc = pcmk_rc_ok;
             g_list_foreach(scheduler->resources, (GFunc) cli_resource_print_cts,
                            out);
             cli_resource_print_cts_constraints(scheduler);
             break;
 
         case cmd_fail:
             rc = cli_resource_fail(controld_api, options.host_uname,
                                    options.rsc_id, scheduler);
             if (rc == pcmk_rc_ok) {
                 start_mainloop(controld_api);
             }
             break;
 
         case cmd_list_active_ops:
             rc = cli_resource_print_operations(options.rsc_id,
                                                options.host_uname, TRUE,
                                                scheduler);
             break;
 
         case cmd_list_all_ops:
             rc = cli_resource_print_operations(options.rsc_id,
                                                options.host_uname, FALSE,
                                                scheduler);
             break;
 
         case cmd_locate: {
             GList *nodes = cli_resource_search(rsc, options.rsc_id, scheduler);
             rc = out->message(out, "resource-search-list", nodes, options.rsc_id);
             g_list_free_full(nodes, free);
             break;
         }
 
         case cmd_query_xml:
             rc = cli_resource_print(rsc, scheduler, true);
             break;
 
         case cmd_query_raw_xml:
             rc = cli_resource_print(rsc, scheduler, false);
             break;
 
         case cmd_why:
             if ((options.host_uname != NULL) && (node == NULL)) {
                 rc = pcmk_rc_node_unknown;
             } else {
                 rc = out->message(out, "resource-reasons-list",
                                   scheduler->resources, rsc, node);
             }
             break;
 
         case cmd_clear:
             rc = clear_constraints(out, &cib_xml_copy);
             break;
 
         case cmd_move:
             if (options.host_uname == NULL) {
                 rc = ban_or_move(out, rsc, options.move_lifetime);
             } else {
                 rc = cli_resource_move(rsc, options.rsc_id, options.host_uname,
                                        options.move_lifetime, cib_conn,
                                        options.cib_options, scheduler,
                                        options.promoted_role_only,
                                        options.force);
             }
 
             if (rc == EINVAL) {
                 exit_code = CRM_EX_USAGE;
                 goto done;
             }
 
             break;
 
         case cmd_ban:
             if (options.host_uname == NULL) {
                 rc = ban_or_move(out, rsc, options.move_lifetime);
             } else if (node == NULL) {
                 rc = pcmk_rc_node_unknown;
             } else {
                 rc = cli_resource_ban(out, options.rsc_id, node->details->uname,
                                       options.move_lifetime, cib_conn,
                                       options.cib_options,
                                       options.promoted_role_only,
                                       PCMK__ROLE_PROMOTED);
             }
 
             if (rc == EINVAL) {
                 exit_code = CRM_EX_USAGE;
                 goto done;
             }
 
             break;
 
         case cmd_get_property:
             rc = out->message(out, "property-list", rsc, options.prop_name);
             if (rc == pcmk_rc_no_output) {
                 rc = ENXIO;
             }
 
             break;
 
         case cmd_set_property:
             rc = set_property();
             break;
 
         case cmd_get_param: {
             unsigned int count = 0;
             GHashTable *params = NULL;
             pcmk_node_t *current = rsc->fns->active_node(rsc, &count, NULL);
             bool free_params = true;
             const char* value = NULL;
 
             if (count > 1) {
                 out->err(out, "%s is active on more than one node,"
                          " returning the default value for %s", rsc->id,
                          pcmk__s(options.prop_name, "unspecified property"));
                 current = NULL;
             }
 
             crm_debug("Looking up %s in %s", options.prop_name, rsc->id);
 
             if (pcmk__str_eq(options.attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_none)) {
                 params = pe_rsc_params(rsc, current, scheduler);
                 free_params = false;
 
                 value = g_hash_table_lookup(params, options.prop_name);
 
             } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_none)) {
                 params = pcmk__strkey_table(free, free);
                 get_meta_attributes(params, rsc, current, scheduler);
 
                 value = g_hash_table_lookup(params, options.prop_name);
 
             } else if (pcmk__str_eq(options.attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
 
                 value = crm_element_value(rsc->xml, options.prop_name);
                 free_params = false;
 
             } else {
                 params = pcmk__strkey_table(free, free);
                 pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params,
                                            NULL, FALSE, scheduler);
 
                 value = g_hash_table_lookup(params, options.prop_name);
             }
 
             rc = out->message(out, "attribute-list", rsc, options.prop_name, value);
             if (free_params) {
                 g_hash_table_destroy(params);
             }
 
             break;
         }
 
         case cmd_set_param:
             if (pcmk__str_empty(options.prop_value)) {
                 exit_code = CRM_EX_USAGE;
                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                             _("You need to supply a value with the -v option"));
                 goto done;
             }
 
             /* coverity[var_deref_model] False positive */
             rc = cli_resource_update_attribute(rsc, options.rsc_id,
                                                options.prop_set,
                                                options.attr_set_type,
                                                options.prop_id,
                                                options.prop_name,
                                                options.prop_value,
                                                options.recursive, cib_conn,
                                                options.cib_options,
                                                options.force);
             break;
 
         case cmd_delete_param:
             /* coverity[var_deref_model] False positive */
             rc = cli_resource_delete_attribute(rsc, options.rsc_id,
                                                options.prop_set,
                                                options.attr_set_type,
                                                options.prop_id,
                                                options.prop_name, cib_conn,
                                                options.cib_options,
                                                options.force);
             break;
 
         case cmd_cleanup:
             if (rsc == NULL) {
                 rc = cli_cleanup_all(controld_api, options.host_uname,
                                      options.operation, options.interval_spec,
                                      scheduler);
                 if (rc == pcmk_rc_ok) {
                     start_mainloop(controld_api);
                 }
             } else {
                 cleanup(out, rsc, node);
             }
             break;
 
         case cmd_refresh:
             if (rsc == NULL) {
                 rc = refresh(out);
             } else {
                 refresh_resource(out, rsc, node);
             }
             break;
 
         case cmd_delete:
             /* rsc_id was already checked for NULL much earlier when validating
              * command line arguments.
              */
             if (options.rsc_type == NULL) {
                 // @COMPAT @TODO change this to exit_code = CRM_EX_USAGE
                 rc = ENXIO;
                 g_set_error(&error, PCMK__RC_ERROR, rc,
                             _("You need to specify a resource type with -t"));
             } else {
                 rc = pcmk__resource_delete(cib_conn, options.cib_options,
                                            options.rsc_id, options.rsc_type);
 
                 if (rc != pcmk_rc_ok) {
                     g_set_error(&error, PCMK__RC_ERROR, rc,
                                 _("Could not delete resource %s: %s"),
                                 options.rsc_id, pcmk_rc_str(rc));
                 }
             }
 
             break;
 
         default:
             exit_code = CRM_EX_USAGE;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Unimplemented command: %d"), (int) options.rsc_cmd);
             goto done;
     }
 
     /* Convert rc into an exit code. */
     if (rc != pcmk_rc_ok && rc != pcmk_rc_no_output) {
         exit_code = pcmk_rc2exitc(rc);
     }
 
     /*
      * Clean up and exit
      */
 
 done:
     /* When we get here, exit_code has been set one of two ways - either at one of
      * the spots where there's a "goto done" (which itself could have happened either
      * directly or by calling pcmk_rc2exitc), or just up above after any of the break
      * statements.
      *
      * Thus, we can use just exit_code here to decide what to do.
      */
     if (exit_code != CRM_EX_OK && exit_code != CRM_EX_USAGE) {
         if (error != NULL) {
             char *msg = crm_strdup_printf("%s\nError performing operation: %s",
                                           error->message, crm_exit_str(exit_code));
             g_clear_error(&error);
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
             free(msg);
         } else {
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         _("Error performing operation: %s"), crm_exit_str(exit_code));
         }
     }
 
     g_free(options.host_uname);
     g_free(options.interval_spec);
     g_free(options.move_lifetime);
     g_free(options.operation);
     g_free(options.prop_id);
     free(options.prop_name);
     g_free(options.prop_set);
     g_free(options.prop_value);
     g_free(options.rsc_id);
     g_free(options.rsc_type);
     free(options.agent_spec);
     free(options.v_agent);
     free(options.v_class);
     free(options.v_provider);
     g_free(options.xml_file);
     g_strfreev(options.remainder);
 
     if (options.override_params != NULL) {
         g_hash_table_destroy(options.override_params);
     }
 
     /* options.cmdline_params does not need to be destroyed here.  See the
      * comments in cli_resource_execute_from_params.
      */
 
     g_strfreev(processed_args);
     g_option_context_free(context);
 
     return bye(exit_code);
 }
diff --git a/tools/crm_resource_print.c b/tools/crm_resource_print.c
index 24c55b7150..c00aaa6d1c 100644
--- a/tools/crm_resource_print.c
+++ b/tools/crm_resource_print.c
@@ -1,822 +1,822 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdint.h>
 
 #include <crm_resource.h>
 #include <crm/common/lists_internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 
 #define cons_string(x) x?x:"NA"
 static int
 print_constraint(xmlNode *xml_obj, void *userdata)
 {
     pcmk_scheduler_t *scheduler = (pcmk_scheduler_t *) userdata;
     pcmk__output_t *out = scheduler->priv;
     xmlNode *lifetime = NULL;
     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
 
     if (id == NULL) {
         return pcmk_rc_ok;
     }
 
     // @COMPAT lifetime is deprecated
     lifetime = first_named_child(xml_obj, "lifetime");
     if (pe_evaluate_rules(lifetime, NULL, scheduler->now, NULL) == FALSE) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__xe_is(xml_obj, XML_CONS_TAG_RSC_DEPEND)) {
         return pcmk_rc_ok;
     }
 
     out->info(out, "Constraint %s %s %s %s %s %s %s",
               xml_obj->name,
               cons_string(crm_element_value(xml_obj, PCMK_XA_ID)),
               cons_string(crm_element_value(xml_obj, PCMK_XA_RSC)),
               cons_string(crm_element_value(xml_obj, PCMK_XA_WITH_RSC)),
               cons_string(crm_element_value(xml_obj, PCMK_XA_SCORE)),
               cons_string(crm_element_value(xml_obj, PCMK_XA_RSC_ROLE)),
               cons_string(crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE)));
 
     return pcmk_rc_ok;
 }
 
 void
 cli_resource_print_cts_constraints(pcmk_scheduler_t *scheduler)
 {
     pcmk__xe_foreach_child(pcmk_find_cib_element(scheduler->input,
                                                  XML_CIB_TAG_CONSTRAINTS),
                            NULL, print_constraint, scheduler);
 }
 
 void
 cli_resource_print_cts(pcmk_resource_t *rsc, pcmk__output_t *out)
 {
     const char *host = NULL;
     bool needs_quorum = TRUE;
     const char *rtype = crm_element_value(rsc->xml, PCMK_XA_TYPE);
     const char *rprov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
     const char *rclass = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     pcmk_node_t *node = pcmk__current_node(rsc);
 
     if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         needs_quorum = FALSE;
     } else {
         // @TODO check requires in resource meta-data and rsc_defaults
     }
 
     if (node != NULL) {
         host = node->details->uname;
     }
 
     out->info(out, "Resource: %s %s %s %s %s %s %s %s %d %lld %#.16llx",
               rsc->xml->name, rsc->id,
               rsc->clone_name ? rsc->clone_name : rsc->id, rsc->parent ? rsc->parent->id : "NA",
               rprov ? rprov : "NA", rclass, rtype, host ? host : "NA", needs_quorum, rsc->flags,
               rsc->flags);
 
     g_list_foreach(rsc->children, (GFunc) cli_resource_print_cts, out);
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_print_operations(const char *rsc_id, const char *host_uname,
                               bool active, pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_no_output;
     GList *ops = find_operations(rsc_id, host_uname, active, scheduler);
 
     if (!ops) {
         return rc;
     }
 
     out->begin_list(out, NULL, NULL, "Resource Operations");
     rc = pcmk_rc_ok;
 
     for (GList *lpc = ops; lpc != NULL; lpc = lpc->next) {
         xmlNode *xml_op = (xmlNode *) lpc->data;
         out->message(out, "node-and-op", scheduler, xml_op);
     }
 
     out->end_list(out);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_print(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler,
                    bool expanded)
 {
     pcmk__output_t *out = scheduler->priv;
     uint32_t show_opts = pcmk_show_pending;
     GList *all = NULL;
 
     all = g_list_prepend(all, (gpointer) "*");
 
     out->begin_list(out, NULL, NULL, "Resource Config");
     out->message(out, crm_map_element_name(rsc->xml), show_opts, rsc, all, all);
     out->message(out, "resource-config", rsc, !expanded);
     out->end_list(out);
 
     g_list_free(all);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("attribute-list", "pcmk_resource_t *", "const char *",
                   "const char *")
 static int
 attribute_list_default(pcmk__output_t *out, va_list args) {
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     const char *attr = va_arg(args, char *);
     const char *value = va_arg(args, const char *);
 
     if (value != NULL) {
         out->begin_list(out, NULL, NULL, "Attributes");
         out->list_item(out, attr, "%s", value);
         out->end_list(out);
         return pcmk_rc_ok;
     } else {
         out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id);
     }
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("agent-status", "int", "const char *", "const char *", "const char *",
                   "const char *", "const char *", "crm_exit_t", "const char *")
 static int
 agent_status_default(pcmk__output_t *out, va_list args) {
     int status = va_arg(args, int);
     const char *action = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *class = va_arg(args, const char *);
     const char *provider = va_arg(args, const char *);
     const char *type = va_arg(args, const char *);
     crm_exit_t rc = va_arg(args, crm_exit_t);
     const char *exit_reason = va_arg(args, const char *);
 
     if (status == PCMK_EXEC_DONE) {
         /* Operation <action> [for <resource>] (<class>[:<provider>]:<agent>)
          * returned <exit-code> (<exit-description>[: <exit-reason>])
          */
         out->info(out, "Operation %s%s%s (%s%s%s:%s) returned %d (%s%s%s)",
                   action,
                   ((name == NULL)? "" : " for "), ((name == NULL)? "" : name),
                   class,
                   ((provider == NULL)? "" : ":"),
                   ((provider == NULL)? "" : provider),
                   type, (int) rc, services_ocf_exitcode_str((int) rc),
                   ((exit_reason == NULL)? "" : ": "),
                   ((exit_reason == NULL)? "" : exit_reason));
     } else {
         /* Operation <action> [for <resource>] (<class>[:<provider>]:<agent>)
          * could not be executed (<execution-status>[: <exit-reason>])
          */
         out->err(out,
                  "Operation %s%s%s (%s%s%s:%s) could not be executed (%s%s%s)",
                  action,
                  ((name == NULL)? "" : " for "), ((name == NULL)? "" : name),
                  class,
                  ((provider == NULL)? "" : ":"),
                  ((provider == NULL)? "" : provider),
                  type, pcmk_exec_status_str(status),
                  ((exit_reason == NULL)? "" : ": "),
                  ((exit_reason == NULL)? "" : exit_reason));
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("agent-status", "int", "const char *", "const char *", "const char *",
                   "const char *", "const char *", "crm_exit_t", "const char *")
 static int
 agent_status_xml(pcmk__output_t *out, va_list args) {
     int status = va_arg(args, int);
     const char *action G_GNUC_UNUSED = va_arg(args, const char *);
     const char *name G_GNUC_UNUSED = va_arg(args, const char *);
     const char *class G_GNUC_UNUSED = va_arg(args, const char *);
     const char *provider G_GNUC_UNUSED = va_arg(args, const char *);
     const char *type G_GNUC_UNUSED = va_arg(args, const char *);
     crm_exit_t rc = va_arg(args, crm_exit_t);
     const char *exit_reason = va_arg(args, const char *);
 
     char *exit_str = pcmk__itoa(rc);
     char *status_str = pcmk__itoa(status);
 
     pcmk__output_create_xml_node(out, "agent-status",
                                  "code", exit_str,
                                  "message", services_ocf_exitcode_str((int) rc),
                                  "execution_code", status_str,
                                  "execution_message", pcmk_exec_status_str(status),
                                  PCMK_XA_REASON, exit_reason,
                                  NULL);
 
     free(exit_str);
     free(status_str);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("attribute-list", "pcmk_resource_t *", "const char *",
                   "const char *")
 static int
 attribute_list_text(pcmk__output_t *out, va_list args) {
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     const char *attr = va_arg(args, char *);
     const char *value = va_arg(args, const char *);
 
     if (value != NULL) {
         pcmk__formatted_printf(out, "%s\n", value);
         return pcmk_rc_ok;
     } else {
         out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id);
     }
     return pcmk_rc_ok;
 }
 PCMK__OUTPUT_ARGS("override", "const char *", "const char *", "const char *")
 static int
 override_default(pcmk__output_t *out, va_list args) {
     const char *rsc_name = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
 
     if (rsc_name == NULL) {
         out->list_item(out, NULL, "Overriding the cluster configuration with '%s' = '%s'",
                        name, value);
     } else {
         out->list_item(out, NULL, "Overriding the cluster configuration for '%s' with '%s' = '%s'",
                        rsc_name, name, value);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("override", "const char *", "const char *", "const char *")
 static int
 override_xml(pcmk__output_t *out, va_list args) {
     const char *rsc_name = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "override",
                                                    PCMK_XA_NAME, name,
                                                    PCMK_XA_VALUE, value,
                                                    NULL);
 
     if (rsc_name != NULL) {
         crm_xml_add(node, PCMK_XA_RSC, rsc_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("property-list", "pcmk_resource_t *", "const char *")
 static int
 property_list_default(pcmk__output_t *out, va_list args) {
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     const char *attr = va_arg(args, char *);
 
     const char *value = crm_element_value(rsc->xml, attr);
 
     if (value != NULL) {
         out->begin_list(out, NULL, NULL, "Properties");
         out->list_item(out, attr, "%s", value);
         out->end_list(out);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("property-list", "pcmk_resource_t *", "const char *")
 static int
 property_list_text(pcmk__output_t *out, va_list args) {
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     const char *attr = va_arg(args, const char *);
 
     const char *value = crm_element_value(rsc->xml, attr);
 
     if (value != NULL) {
         pcmk__formatted_printf(out, "%s\n", value);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-agent-action", "int", "const char *", "const char *",
                   "const char *", "const char *", "const char *", "GHashTable *",
                   "crm_exit_t", "int", "const char *", "const char *", "const char *")
 static int
 resource_agent_action_default(pcmk__output_t *out, va_list args) {
     int verbose = va_arg(args, int);
 
     const char *class = va_arg(args, const char *);
     const char *provider = va_arg(args, const char *);
     const char *type = va_arg(args, const char *);
     const char *rsc_name = va_arg(args, const char *);
     const char *action = va_arg(args, const char *);
     GHashTable *overrides = va_arg(args, GHashTable *);
     crm_exit_t rc = va_arg(args, crm_exit_t);
     int status = va_arg(args, int);
     const char *exit_reason = va_arg(args, const char *);
     const char *stdout_data = va_arg(args, const char *);
     const char *stderr_data = va_arg(args, const char *);
 
     if (overrides) {
         GHashTableIter iter;
         const char *name = NULL;
         const char *value = NULL;
 
         out->begin_list(out, NULL, NULL, "overrides");
 
         g_hash_table_iter_init(&iter, overrides);
         while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &value)) {
             out->message(out, "override", rsc_name, name, value);
         }
 
         out->end_list(out);
     }
 
     out->message(out, "agent-status", status, action, rsc_name, class, provider,
                  type, rc, exit_reason);
 
     /* hide output for validate-all if not in verbose */
     if ((verbose == 0)
         && pcmk__str_eq(action, PCMK_ACTION_VALIDATE_ALL, pcmk__str_casei)) {
         return pcmk_rc_ok;
     }
 
     if (stdout_data || stderr_data) {
         xmlNodePtr doc = NULL;
 
         if (stdout_data != NULL) {
             doc = string2xml(stdout_data);
         }
         if (doc != NULL) {
             out->output_xml(out, "command", stdout_data);
             xmlFreeNode(doc);
         } else {
             out->subprocess_output(out, rc, stdout_data, stderr_data);
         }
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-agent-action", "int", "const char *", "const char *",
                   "const char *", "const char *", "const char *", "GHashTable *",
                   "crm_exit_t", "int", "const char *", "const char *", "const char *")
 static int
 resource_agent_action_xml(pcmk__output_t *out, va_list args) {
     int verbose G_GNUC_UNUSED = va_arg(args, int);
 
     const char *class = va_arg(args, const char *);
     const char *provider = va_arg(args, const char *);
     const char *type = va_arg(args, const char *);
     const char *rsc_name = va_arg(args, const char *);
     const char *action = va_arg(args, const char *);
     GHashTable *overrides = va_arg(args, GHashTable *);
     crm_exit_t rc = va_arg(args, crm_exit_t);
     int status = va_arg(args, int);
     const char *exit_reason = va_arg(args, const char *);
     const char *stdout_data = va_arg(args, const char *);
     const char *stderr_data = va_arg(args, const char *);
 
     xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource-agent-action",
                                                      "action", action,
                                                      PCMK_XA_CLASS, class,
                                                      PCMK_XA_TYPE, type,
                                                      NULL);
 
     if (rsc_name) {
         crm_xml_add(node, PCMK_XA_RSC, rsc_name);
     }
 
     crm_xml_add(node, PCMK_XA_PROVIDER, provider);
 
     if (overrides) {
         GHashTableIter iter;
         const char *name = NULL;
         const char *value = NULL;
 
         out->begin_list(out, NULL, NULL, "overrides");
 
         g_hash_table_iter_init(&iter, overrides);
         while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &value)) {
             out->message(out, "override", rsc_name, name, value);
         }
 
         out->end_list(out);
     }
 
     out->message(out, "agent-status", status, action, rsc_name, class, provider,
                  type, rc, exit_reason);
 
     if (stdout_data || stderr_data) {
         xmlNodePtr doc = NULL;
 
         if (stdout_data != NULL) {
             doc = string2xml(stdout_data);
         }
         if (doc != NULL) {
             out->output_xml(out, "command", stdout_data);
             xmlFreeNode(doc);
         } else {
             out->subprocess_output(out, rc, stdout_data, stderr_data);
         }
     }
 
     pcmk__output_xml_pop_parent(out);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-check-list", "resource_checks_t *")
 static int
 resource_check_list_default(pcmk__output_t *out, va_list args) {
     resource_checks_t *checks = va_arg(args, resource_checks_t *);
 
     const pcmk_resource_t *parent = pe__const_top_resource(checks->rsc, false);
 
     if (checks->flags == 0) {
         return pcmk_rc_no_output;
     }
 
     out->begin_list(out, NULL, NULL, "Resource Checks");
 
     if (pcmk_is_set(checks->flags, rsc_remain_stopped)) {
         out->list_item(out, "check", "Configuration specifies '%s' should remain stopped",
                        parent->id);
     }
 
     if (pcmk_is_set(checks->flags, rsc_unpromotable)) {
         out->list_item(out, "check", "Configuration specifies '%s' should not be promoted",
                        parent->id);
     }
 
     if (pcmk_is_set(checks->flags, rsc_unmanaged)) {
         out->list_item(out, "check", "Configuration prevents cluster from stopping or starting unmanaged '%s'",
                        parent->id);
     }
 
     if (pcmk_is_set(checks->flags, rsc_locked)) {
         out->list_item(out, "check", "'%s' is locked to node %s due to shutdown",
                        parent->id, checks->lock_node);
     }
 
     if (pcmk_is_set(checks->flags, rsc_node_health)) {
         out->list_item(out, "check",
                        "'%s' cannot run on unhealthy nodes due to "
                        PCMK_OPT_NODE_HEALTH_STRATEGY "='%s'",
                        parent->id,
                        pe_pref(checks->rsc->cluster->config_hash,
                                PCMK_OPT_NODE_HEALTH_STRATEGY));
     }
 
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-check-list", "resource_checks_t *")
 static int
 resource_check_list_xml(pcmk__output_t *out, va_list args) {
     resource_checks_t *checks = va_arg(args, resource_checks_t *);
 
     const pcmk_resource_t *parent = pe__const_top_resource(checks->rsc, false);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "check",
                                                    PCMK_XA_ID, parent->id,
                                                    NULL);
 
     if (pcmk_is_set(checks->flags, rsc_remain_stopped)) {
         pcmk__xe_set_bool_attr(node, "remain_stopped", true);
     }
 
     if (pcmk_is_set(checks->flags, rsc_unpromotable)) {
         pcmk__xe_set_bool_attr(node, "promotable", false);
     }
 
     if (pcmk_is_set(checks->flags, rsc_unmanaged)) {
         pcmk__xe_set_bool_attr(node, "unmanaged", true);
     }
 
     if (pcmk_is_set(checks->flags, rsc_locked)) {
         crm_xml_add(node, "locked-to", checks->lock_node);
     }
 
     if (pcmk_is_set(checks->flags, rsc_node_health)) {
         pcmk__xe_set_bool_attr(node, "unhealthy", true);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-search-list", "GList *", "const char *")
 static int
 resource_search_list_default(pcmk__output_t *out, va_list args)
 {
     GList *nodes = va_arg(args, GList *);
     const char *requested_name = va_arg(args, const char *);
 
     bool printed = false;
     int rc = pcmk_rc_no_output;
 
     if (!out->is_quiet(out) && nodes == NULL) {
         out->err(out, "resource %s is NOT running", requested_name);
         return rc;
     }
 
     for (GList *lpc = nodes; lpc != NULL; lpc = lpc->next) {
         node_info_t *ni = (node_info_t *) lpc->data;
 
         if (!printed) {
             out->begin_list(out, NULL, NULL, "Nodes");
             printed = true;
             rc = pcmk_rc_ok;
         }
 
         if (out->is_quiet(out)) {
             out->list_item(out, "node", "%s", ni->node_name);
         } else {
             const char *role_text = "";
 
             if (ni->promoted) {
 #ifdef PCMK__COMPAT_2_0
                 role_text = " " PCMK__ROLE_PROMOTED_LEGACY;
 #else
                 role_text = " " PCMK__ROLE_PROMOTED;
 #endif
             }
             out->list_item(out, "node", "resource %s is running on: %s%s",
                            requested_name, ni->node_name, role_text);
         }
     }
 
     if (printed) {
         out->end_list(out);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("resource-search-list", "GList *", "const char *")
 static int
 resource_search_list_xml(pcmk__output_t *out, va_list args)
 {
     GList *nodes = va_arg(args, GList *);
     const char *requested_name = va_arg(args, const char *);
 
     pcmk__output_xml_create_parent(out, "nodes",
                                    "resource", requested_name,
                                    NULL);
 
     for (GList *lpc = nodes; lpc != NULL; lpc = lpc->next) {
         node_info_t *ni = (node_info_t *) lpc->data;
         xmlNodePtr sub_node = pcmk__output_create_xml_text_node(out, "node", ni->node_name);
 
         if (ni->promoted) {
             crm_xml_add(sub_node, "state", "promoted");
         }
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-reasons-list", "GList *", "pcmk_resource_t *",
                   "pcmk_node_t *")
 static int
 resource_reasons_list_default(pcmk__output_t *out, va_list args)
 {
     GList *resources = va_arg(args, GList *);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
 
     const char *host_uname = (node == NULL)? NULL : node->details->uname;
 
     out->begin_list(out, NULL, NULL, "Resource Reasons");
 
     if ((rsc == NULL) && (host_uname == NULL)) {
         GList *lpc = NULL;
         GList *hosts = NULL;
 
         for (lpc = resources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
             rsc->fns->location(rsc, &hosts, TRUE);
 
             if (hosts == NULL) {
                 out->list_item(out, "reason", "Resource %s is not running", rsc->id);
             } else {
                 out->list_item(out, "reason", "Resource %s is running", rsc->id);
             }
 
             cli_resource_check(out, rsc, NULL);
             g_list_free(hosts);
             hosts = NULL;
         }
 
     } else if ((rsc != NULL) && (host_uname != NULL)) {
         if (resource_is_running_on(rsc, host_uname)) {
             out->list_item(out, "reason", "Resource %s is running on host %s",
                            rsc->id, host_uname);
         } else {
             out->list_item(out, "reason", "Resource %s is not running on host %s",
                            rsc->id, host_uname);
         }
 
         cli_resource_check(out, rsc, node);
 
     } else if ((rsc == NULL) && (host_uname != NULL)) {
         const char* host_uname =  node->details->uname;
         GList *allResources = node->details->allocated_rsc;
         GList *activeResources = node->details->running_rsc;
         GList *unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp);
         GList *lpc = NULL;
 
         for (lpc = activeResources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
             out->list_item(out, "reason", "Resource %s is running on host %s",
                            rsc->id, host_uname);
             cli_resource_check(out, rsc, node);
         }
 
         for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
             out->list_item(out, "reason", "Resource %s is assigned to host %s but not running",
                            rsc->id, host_uname);
             cli_resource_check(out, rsc, node);
         }
 
         g_list_free(allResources);
         g_list_free(activeResources);
         g_list_free(unactiveResources);
 
     } else if ((rsc != NULL) && (host_uname == NULL)) {
         GList *hosts = NULL;
 
         rsc->fns->location(rsc, &hosts, TRUE);
         out->list_item(out, "reason", "Resource %s is %srunning",
                        rsc->id, (hosts? "" : "not "));
         cli_resource_check(out, rsc, NULL);
         g_list_free(hosts);
     }
 
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-reasons-list", "GList *", "pcmk_resource_t *",
                   "pcmk_node_t *")
 static int
 resource_reasons_list_xml(pcmk__output_t *out, va_list args)
 {
     GList *resources = va_arg(args, GList *);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
 
     const char *host_uname = (node == NULL)? NULL : node->details->uname;
 
     xmlNodePtr xml_node = pcmk__output_xml_create_parent(out, "reason", NULL);
 
     if ((rsc == NULL) && (host_uname == NULL)) {
         GList *lpc = NULL;
         GList *hosts = NULL;
 
-        pcmk__output_xml_create_parent(out, "resources", NULL);
+        pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL);
 
         for (lpc = resources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
 
             rsc->fns->location(rsc, &hosts, TRUE);
 
             pcmk__output_xml_create_parent(out, "resource",
                                            PCMK_XA_ID, rsc->id,
                                            "running", pcmk__btoa(hosts != NULL),
                                            NULL);
 
             cli_resource_check(out, rsc, NULL);
             pcmk__output_xml_pop_parent(out);
             g_list_free(hosts);
             hosts = NULL;
         }
 
         pcmk__output_xml_pop_parent(out);
 
     } else if ((rsc != NULL) && (host_uname != NULL)) {
         if (resource_is_running_on(rsc, host_uname)) {
             crm_xml_add(xml_node, "running_on", host_uname);
         }
 
         cli_resource_check(out, rsc, node);
 
     } else if ((rsc == NULL) && (host_uname != NULL)) {
         const char* host_uname =  node->details->uname;
         GList *allResources = node->details->allocated_rsc;
         GList *activeResources = node->details->running_rsc;
         GList *unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp);
         GList *lpc = NULL;
 
-        pcmk__output_xml_create_parent(out, "resources", NULL);
+        pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL);
 
         for (lpc = activeResources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
 
             pcmk__output_xml_create_parent(out, "resource",
                                            PCMK_XA_ID, rsc->id,
                                            "running", PCMK_VALUE_TRUE,
                                            "host", host_uname,
                                            NULL);
 
             cli_resource_check(out, rsc, node);
             pcmk__output_xml_pop_parent(out);
         }
 
         for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
 
             pcmk__output_xml_create_parent(out, "resource",
                                            PCMK_XA_ID, rsc->id,
                                            "running", PCMK_VALUE_FALSE,
                                            "host", host_uname,
                                            NULL);
 
             cli_resource_check(out, rsc, node);
             pcmk__output_xml_pop_parent(out);
         }
 
         pcmk__output_xml_pop_parent(out);
         g_list_free(allResources);
         g_list_free(activeResources);
         g_list_free(unactiveResources);
 
     } else if ((rsc != NULL) && (host_uname == NULL)) {
         GList *hosts = NULL;
 
         rsc->fns->location(rsc, &hosts, TRUE);
         crm_xml_add(xml_node, "running", pcmk__btoa(hosts != NULL));
         cli_resource_check(out, rsc, NULL);
         g_list_free(hosts);
     }
 
     return pcmk_rc_ok;
 }
 
 static void
 add_resource_name(pcmk_resource_t *rsc, pcmk__output_t *out)
 {
     if (rsc->children == NULL) {
         out->list_item(out, "resource", "%s", rsc->id);
     } else {
         g_list_foreach(rsc->children, (GFunc) add_resource_name, out);
     }
 }
 
 PCMK__OUTPUT_ARGS("resource-names-list", "GList *")
 static int
 resource_names(pcmk__output_t *out, va_list args) {
     GList *resources = va_arg(args, GList *);
 
     if (resources == NULL) {
         out->err(out, "NO resources configured\n");
         return pcmk_rc_no_output;
     }
 
     out->begin_list(out, NULL, NULL, "Resource Names");
     g_list_foreach(resources, (GFunc) add_resource_name, out);
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "agent-status", "default", agent_status_default },
     { "agent-status", "xml", agent_status_xml },
     { "attribute-list", "default", attribute_list_default },
     { "attribute-list", "text", attribute_list_text },
     { "override", "default", override_default },
     { "override", "xml", override_xml },
     { "property-list", "default", property_list_default },
     { "property-list", "text", property_list_text },
     { "resource-agent-action", "default", resource_agent_action_default },
     { "resource-agent-action", "xml", resource_agent_action_xml },
     { "resource-check-list", "default", resource_check_list_default },
     { "resource-check-list", "xml", resource_check_list_xml },
     { "resource-search-list", "default", resource_search_list_default },
     { "resource-search-list", "xml", resource_search_list_xml },
     { "resource-reasons-list", "default", resource_reasons_list_default },
     { "resource-reasons-list", "xml", resource_reasons_list_xml },
     { "resource-names-list", "default", resource_names },
 
     { NULL, NULL, NULL }
 };
 
 void
 crm_resource_register_messages(pcmk__output_t *out) {
     pcmk__register_messages(out, fmt_functions);
 }
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 226112513f..f5945f946e 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,2256 +1,2252 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm_resource.h>
 #include <crm/common/ipc_attrd_internal.h>
 #include <crm/common/ipc_controld.h>
 #include <crm/common/lists_internal.h>
 #include <crm/services_internal.h>
 
 static GList *
 build_node_info_list(const pcmk_resource_t *rsc)
 {
     GList *retval = NULL;
 
     for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
 
         for (const GList *iter2 = child->running_on;
              iter2 != NULL; iter2 = iter2->next) {
 
             const pcmk_node_t *node = (const pcmk_node_t *) iter2->data;
             node_info_t *ni = calloc(1, sizeof(node_info_t));
 
             ni->node_name = node->details->uname;
             ni->promoted = pcmk_is_set(rsc->flags, pcmk_rsc_promotable) &&
                            child->fns->state(child, TRUE) == pcmk_role_promoted;
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 GList *
 cli_resource_search(pcmk_resource_t *rsc, const char *requested_name,
                     pcmk_scheduler_t *scheduler)
 {
     GList *retval = NULL;
     const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
 
     if (pe_rsc_is_clone(rsc)) {
         retval = build_node_info_list(rsc);
 
     /* The anonymous clone children's common ID is supplied */
     } else if (pe_rsc_is_clone(parent)
                && !pcmk_is_set(rsc->flags, pcmk_rsc_unique)
                && rsc->clone_name
                && pcmk__str_eq(requested_name, rsc->clone_name, pcmk__str_casei)
                && !pcmk__str_eq(requested_name, rsc->id, pcmk__str_casei)) {
 
         retval = build_node_info_list(parent);
 
     } else if (rsc->running_on != NULL) {
         for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
             pcmk_node_t *node = (pcmk_node_t *) iter->data;
             node_info_t *ni = calloc(1, sizeof(node_info_t));
             ni->node_name = node->details->uname;
             ni->promoted = (rsc->fns->state(rsc, TRUE) == pcmk_role_promoted);
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 // \return Standard Pacemaker return code
 static int
 find_resource_attr(pcmk__output_t *out, cib_t * the_cib, const char *attr,
                    const char *rsc, const char *attr_set_type, const char *set_name,
                    const char *attr_id, const char *attr_name, char **value)
 {
     int rc = pcmk_rc_ok;
     xmlNode *xml_search = NULL;
     GString *xpath = NULL;
     const char *xpath_base = NULL;
 
     if(value) {
         *value = NULL;
     }
 
     if(the_cib == NULL) {
         return ENOTCONN;
     }
 
-    xpath_base = pcmk_cib_xpath_for(XML_CIB_TAG_RESOURCES);
+    xpath_base = pcmk_cib_xpath_for(PCMK_XE_RESOURCES);
     if (xpath_base == NULL) {
-        crm_err(XML_CIB_TAG_RESOURCES " CIB element not known (bug?)");
+        crm_err(PCMK_XE_RESOURCES " CIB element not known (bug?)");
         return ENOMSG;
     }
 
     xpath = g_string_sized_new(1024);
     pcmk__g_strcat(xpath,
                    xpath_base, "//*[@" PCMK_XA_ID "=\"", rsc, "\"]", NULL);
 
     if (attr_set_type != NULL) {
         pcmk__g_strcat(xpath, "/", attr_set_type, NULL);
         if (set_name != NULL) {
             pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "=\"", set_name, "\"]",
                            NULL);
         }
     }
 
     g_string_append(xpath, "//" XML_CIB_TAG_NVPAIR "[");
     if (attr_id != NULL) {
         pcmk__g_strcat(xpath, "@" PCMK_XA_ID "=\"", attr_id, "\"", NULL);
     }
 
     if (attr_name != NULL) {
         if (attr_id != NULL) {
             g_string_append(xpath, " and ");
         }
         pcmk__g_strcat(xpath, "@" PCMK_XA_NAME "=\"", attr_name, "\"", NULL);
     }
     g_string_append_c(xpath, ']');
 
     rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search,
                               cib_sync_call | cib_scope_local | cib_xpath);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         goto done;
     }
 
     crm_log_xml_debug(xml_search, "Match");
     if (xml_search->children != NULL) {
         xmlNode *child = NULL;
 
         rc = ENOTUNIQ;
         out->info(out, "Multiple attributes match name=%s", attr_name);
 
         for (child = pcmk__xml_first_child(xml_search); child != NULL;
              child = pcmk__xml_next(child)) {
             out->info(out, "  Value: %s \t(id=%s)",
                       crm_element_value(child, PCMK_XA_VALUE), ID(child));
         }
 
         out->spacer(out);
 
     } else if(value) {
         pcmk__str_update(value, crm_element_value(xml_search, attr));
     }
 
   done:
     g_string_free(xpath, TRUE);
     free_xml(xml_search);
     return rc;
 }
 
 /* PRIVATE. Use the find_matching_attr_resources instead. */
 static void
 find_matching_attr_resources_recursive(pcmk__output_t *out,
                                        GList /* <pcmk_resource_t*> */ **result,
                                        pcmk_resource_t *rsc, const char *rsc_id,
                                        const char * attr_set, const char * attr_set_type,
                                        const char * attr_id, const char * attr_name,
                                        cib_t * cib, const char * cmd, int depth)
 {
     int rc = pcmk_rc_ok;
     char *lookup_id = clone_strip(rsc->id);
     char *local_attr_id = NULL;
 
     /* visit the children */
     for(GList *gIter = rsc->children; gIter; gIter = gIter->next) {
         find_matching_attr_resources_recursive(out, result,
                                                (pcmk_resource_t *) gIter->data,
                                                rsc_id, attr_set, attr_set_type,
                                                attr_id, attr_name, cib, cmd, depth+1);
         /* do it only once for clones */
         if (rsc->variant == pcmk_rsc_variant_clone) {
             break;
         }
     }
 
     rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                             attr_set, attr_id, attr_name, &local_attr_id);
     /* Post-order traversal.
      * The root is always on the list and it is the last item. */
     if((0 == depth) || (pcmk_rc_ok == rc)) {
         /* push the head */
         *result = g_list_append(*result, rsc);
     }
 
     free(local_attr_id);
     free(lookup_id);
 }
 
 
 /* The result is a linearized pre-ordered tree of resources. */
 static GList/*<pcmk_resource_t*>*/ *
 find_matching_attr_resources(pcmk__output_t *out, pcmk_resource_t *rsc,
                              const char * rsc_id, const char * attr_set,
                              const char * attr_set_type, const char * attr_id,
                              const char * attr_name, cib_t * cib, const char * cmd,
                              gboolean force)
 {
     int rc = pcmk_rc_ok;
     char *lookup_id = NULL;
     char *local_attr_id = NULL;
     GList * result = NULL;
     /* If --force is used, update only the requested resource (clone or primitive).
      * Otherwise, if the primitive has the attribute, use that.
      * Otherwise use the clone. */
     if(force == TRUE) {
         return g_list_append(result, rsc);
     }
     if ((rsc->parent != NULL)
         && (rsc->parent->variant == pcmk_rsc_variant_clone)) {
         int rc = pcmk_rc_ok;
         char *local_attr_id = NULL;
         rc = find_resource_attr(out, cib, PCMK_XA_ID, rsc_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &local_attr_id);
         free(local_attr_id);
 
         if(rc != pcmk_rc_ok) {
             rsc = rsc->parent;
             out->info(out, "Performing %s of '%s' on '%s', the parent of '%s'",
                       cmd, attr_name, rsc->id, rsc_id);
         }
         return g_list_append(result, rsc);
 
     } else if ((rsc->parent == NULL) && (rsc->children != NULL)
                && (rsc->variant == pcmk_rsc_variant_clone)) {
         pcmk_resource_t *child = rsc->children->data;
 
         if (child->variant == pcmk_rsc_variant_primitive) {
             lookup_id = clone_strip(child->id); /* Could be a cloned group! */
             rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id,
                                     attr_set_type, attr_set, attr_id, attr_name,
                                     &local_attr_id);
 
             if(rc == pcmk_rc_ok) {
                 rsc = child;
                 out->info(out, "A value for '%s' already exists in child '%s', performing %s on that instead of '%s'",
                           attr_name, lookup_id, cmd, rsc_id);
             }
 
             free(local_attr_id);
             free(lookup_id);
         }
         return g_list_append(result, rsc);
     }
     /* If the resource is a group ==> children inherit the attribute if defined. */
     find_matching_attr_resources_recursive(out, &result, rsc, rsc_id, attr_set,
                                            attr_set_type, attr_id, attr_name,
                                            cib, cmd, 0);
     return result;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_update_attribute(pcmk_resource_t *rsc, const char *requested_name,
                               const char *attr_set, const char *attr_set_type,
                               const char *attr_id, const char *attr_name,
                               const char *attr_value, gboolean recursive,
                               cib_t *cib, int cib_options, gboolean force)
 {
     pcmk__output_t *out = rsc->cluster->priv;
     int rc = pcmk_rc_ok;
 
     char *found_attr_id = NULL;
 
     GList/*<pcmk_resource_t*>*/ *resources = NULL;
     const char *top_id = pe__const_top_resource(rsc, false)->id;
 
     if ((attr_id == NULL) && !force) {
         find_resource_attr(out, cib, PCMK_XA_ID, top_id, NULL, NULL, NULL,
                            attr_name, NULL);
     }
 
     if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) {
         if (!force) {
             rc = find_resource_attr(out, cib, PCMK_XA_ID, top_id,
                                     XML_TAG_META_SETS, attr_set, attr_id,
                                     attr_name, &found_attr_id);
             if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
                 out->err(out,
                          "WARNING: There is already a meta attribute "
                          "for '%s' called '%s' (id=%s)",
                          top_id, attr_name, found_attr_id);
                 out->err(out,
                          "         Delete '%s' first or use the force option "
                          "to override", found_attr_id);
             }
             free(found_attr_id);
             if (rc == pcmk_rc_ok) {
                 return ENOTUNIQ;
             }
         }
         resources = g_list_append(resources, rsc);
 
     } else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
         crm_xml_add(rsc->xml, attr_name, attr_value);
         CRM_ASSERT(cib != NULL);
-        rc = cib->cmds->replace(cib, XML_CIB_TAG_RESOURCES, rsc->xml,
-                                cib_options);
+        rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_options);
         rc = pcmk_legacy2rc(rc);
         if (rc == pcmk_rc_ok) {
             out->info(out, "Set attribute: name=%s value=%s",
                       attr_name, attr_value);
         }
         return rc;
 
     } else {
         resources = find_matching_attr_resources(out, rsc, requested_name,
                                                  attr_set, attr_set_type,
                                                  attr_id, attr_name, cib,
                                                  "update", force);
     }
 
     /* If the user specified attr_set or attr_id, the intent is to modify a
      * single resource, which will be the last item in the list.
      */
     if ((attr_set != NULL) || (attr_id != NULL)) {
         GList *last = g_list_last(resources);
 
         resources = g_list_remove_link(resources, last);
         g_list_free(resources);
         resources = last;
     }
 
     for (GList *iter = resources; iter != NULL; iter = iter->next) {
         char *lookup_id = NULL;
         char *local_attr_set = NULL;
         const char *rsc_attr_id = attr_id;
         const char *rsc_attr_set = attr_set;
 
         xmlNode *xml_top = NULL;
         xmlNode *xml_obj = NULL;
         found_attr_id = NULL;
 
         rsc = (pcmk_resource_t *) iter->data;
 
         lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
         rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &found_attr_id);
 
         switch (rc) {
             case pcmk_rc_ok:
                 crm_debug("Found a match for name=%s: id=%s",
                           attr_name, found_attr_id);
                 rsc_attr_id = found_attr_id;
                 break;
 
             case ENXIO:
                 if (rsc_attr_set == NULL) {
                     local_attr_set = crm_strdup_printf("%s-%s", lookup_id,
                                                        attr_set_type);
                     rsc_attr_set = local_attr_set;
                 }
                 if (rsc_attr_id == NULL) {
                     found_attr_id = crm_strdup_printf("%s-%s",
                                                       rsc_attr_set, attr_name);
                     rsc_attr_id = found_attr_id;
                 }
 
                 xml_top = create_xml_node(NULL, (const char *) rsc->xml->name);
                 crm_xml_add(xml_top, PCMK_XA_ID, lookup_id);
 
                 xml_obj = create_xml_node(xml_top, attr_set_type);
                 crm_xml_add(xml_obj, PCMK_XA_ID, rsc_attr_set);
                 break;
 
             default:
                 free(lookup_id);
                 free(found_attr_id);
                 g_list_free(resources);
                 return rc;
         }
 
         xml_obj = crm_create_nvpair_xml(xml_obj, rsc_attr_id, attr_name,
                                         attr_value);
         if (xml_top == NULL) {
             xml_top = xml_obj;
         }
 
         crm_log_xml_debug(xml_top, "Update");
 
-        rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top,
-                               cib_options);
+        rc = cib->cmds->modify(cib, PCMK_XE_RESOURCES, xml_top, cib_options);
         rc = pcmk_legacy2rc(rc);
         if (rc == pcmk_rc_ok) {
             out->info(out, "Set '%s' option: id=%s%s%s%s%s value=%s",
                       lookup_id, found_attr_id,
                       ((rsc_attr_set == NULL)? "" : " set="),
                       pcmk__s(rsc_attr_set, ""),
                       ((attr_name == NULL)? "" : " name="),
                       pcmk__s(attr_name, ""), attr_value);
         }
 
         free_xml(xml_top);
 
         free(lookup_id);
         free(found_attr_id);
         free(local_attr_set);
 
         if (recursive
             && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS,
                             pcmk__str_casei)) {
             GList *lpc = NULL;
             static bool need_init = true;
 
             if (need_init) {
                 need_init = false;
                 pcmk__unpack_constraints(rsc->cluster);
                 pe__clear_resource_flags_on_all(rsc->cluster,
                                                 pcmk_rsc_detect_loop);
             }
 
             /* We want to set the attribute only on resources explicitly
              * colocated with this one, so we use rsc->rsc_cons_lhs directly
              * rather than the with_this_colocations() method.
              */
             pcmk__set_rsc_flags(rsc, pcmk_rsc_detect_loop);
             for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
                 pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
                 crm_debug("Checking %s %d", cons->id, cons->score);
                 if (!pcmk_is_set(cons->dependent->flags, pcmk_rsc_detect_loop)
                     && (cons->score > 0)) {
                     crm_debug("Setting %s=%s for dependent resource %s",
                               attr_name, attr_value, cons->dependent->id);
                     cli_resource_update_attribute(cons->dependent,
                                                   cons->dependent->id, NULL,
                                                   attr_set_type, NULL,
                                                   attr_name, attr_value,
                                                   recursive, cib, cib_options,
                                                   force);
                 }
             }
         }
     }
     g_list_free(resources);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_delete_attribute(pcmk_resource_t *rsc, const char *requested_name,
                               const char *attr_set, const char *attr_set_type,
                               const char *attr_id, const char *attr_name,
                               cib_t *cib, int cib_options, gboolean force)
 {
     pcmk__output_t *out = rsc->cluster->priv;
     int rc = pcmk_rc_ok;
     GList/*<pcmk_resource_t*>*/ *resources = NULL;
 
     if ((attr_id == NULL) && !force) {
         find_resource_attr(out, cib, PCMK_XA_ID,
                            pe__const_top_resource(rsc, false)->id, NULL,
                            NULL, NULL, attr_name, NULL);
     }
 
     if (pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) {
         resources = find_matching_attr_resources(out, rsc, requested_name,
                                                  attr_set, attr_set_type,
                                                  attr_id, attr_name, cib,
                                                  "delete", force);
 
     } else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
         xml_remove_prop(rsc->xml, attr_name);
         CRM_ASSERT(cib != NULL);
-        rc = cib->cmds->replace(cib, XML_CIB_TAG_RESOURCES, rsc->xml,
-                                cib_options);
+        rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_options);
         rc = pcmk_legacy2rc(rc);
         if (rc == pcmk_rc_ok) {
             out->info(out, "Deleted attribute: %s", attr_name);
         }
         return rc;
 
     } else {
         resources = g_list_append(resources, rsc);
     }
 
     for (GList *iter = resources; iter != NULL; iter = iter->next) {
         char *lookup_id = NULL;
         xmlNode *xml_obj = NULL;
         char *found_attr_id = NULL;
         const char *rsc_attr_id = attr_id;
 
         rsc = (pcmk_resource_t *) iter->data;
 
         lookup_id = clone_strip(rsc->id);
         rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &found_attr_id);
         switch (rc) {
             case pcmk_rc_ok:
                 break;
 
             case ENXIO:
                 free(lookup_id);
                 rc = pcmk_rc_ok;
                 continue;
 
             default:
                 free(lookup_id);
                 g_list_free(resources);
                 return rc;
         }
 
         if (rsc_attr_id == NULL) {
             rsc_attr_id = found_attr_id;
         }
 
         xml_obj = crm_create_nvpair_xml(NULL, rsc_attr_id, attr_name, NULL);
         crm_log_xml_debug(xml_obj, "Delete");
 
         CRM_ASSERT(cib);
-        rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, xml_obj,
-                               cib_options);
+        rc = cib->cmds->remove(cib, PCMK_XE_RESOURCES, xml_obj, cib_options);
         rc = pcmk_legacy2rc(rc);
 
         if (rc == pcmk_rc_ok) {
             out->info(out, "Deleted '%s' option: id=%s%s%s%s%s",
                       lookup_id, found_attr_id,
                       ((attr_set == NULL)? "" : " set="),
                       pcmk__s(attr_set, ""),
                       ((attr_name == NULL)? "" : " name="),
                       pcmk__s(attr_name, ""));
         }
 
         free(lookup_id);
         free_xml(xml_obj);
         free(found_attr_id);
     }
     g_list_free(resources);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource,
                 const char *host_uname, const char *rsc_id,
                 pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     const char *router_node = host_uname;
     const char *rsc_api_id = NULL;
     const char *rsc_long_id = NULL;
     const char *rsc_class = NULL;
     const char *rsc_provider = NULL;
     const char *rsc_type = NULL;
     bool cib_only = false;
     pcmk_resource_t *rsc = pe_find_resource(scheduler->resources, rsc_id);
 
     if (rsc == NULL) {
         out->err(out, "Resource %s not found", rsc_id);
         return ENXIO;
 
     } else if (rsc->variant != pcmk_rsc_variant_primitive) {
         out->err(out, "We can only process primitive resources, not %s", rsc_id);
         return EINVAL;
     }
 
     rsc_class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     rsc_provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
     rsc_type = crm_element_value(rsc->xml, PCMK_XA_TYPE);
     if ((rsc_class == NULL) || (rsc_type == NULL)) {
         out->err(out, "Resource %s does not have a class and type", rsc_id);
         return EINVAL;
     }
 
     {
         pcmk_node_t *node = pe_find_node(scheduler->nodes, host_uname);
 
         if (node == NULL) {
             out->err(out, "Node %s not found", host_uname);
             return pcmk_rc_node_unknown;
         }
 
         if (!(node->details->online)) {
             if (do_fail_resource) {
                 out->err(out, "Node %s is not online", host_uname);
                 return ENOTCONN;
             } else {
                 cib_only = true;
             }
         }
         if (!cib_only && pe__is_guest_or_remote_node(node)) {
             node = pcmk__current_node(node->details->remote_rsc);
             if (node == NULL) {
                 out->err(out, "No cluster connection to Pacemaker Remote node %s detected",
                          host_uname);
                 return ENOTCONN;
             }
             router_node = node->details->uname;
         }
     }
 
     if (rsc->clone_name) {
         rsc_api_id = rsc->clone_name;
         rsc_long_id = rsc->id;
     } else {
         rsc_api_id = rsc->id;
     }
     if (do_fail_resource) {
         return pcmk_controld_api_fail(controld_api, host_uname, router_node,
                                       rsc_api_id, rsc_long_id,
                                       rsc_class, rsc_provider, rsc_type);
     } else {
         return pcmk_controld_api_refresh(controld_api, host_uname, router_node,
                                          rsc_api_id, rsc_long_id, rsc_class,
                                          rsc_provider, rsc_type, cib_only);
     }
 }
 
 /*!
  * \internal
  * \brief Get resource name as used in failure-related node attributes
  *
  * \param[in] rsc  Resource to check
  *
  * \return Newly allocated string containing resource's fail name
  * \note The caller is responsible for freeing the result.
  */
 static inline char *
 rsc_fail_name(const pcmk_resource_t *rsc)
 {
     const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         return strdup(name);
     }
     return clone_strip(name);
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname,
                   const char *rsc_id, pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
 
     /* Erase the resource's entire LRM history in the CIB, even if we're only
      * clearing a single operation's fail count. If we erased only entries for a
      * single operation, we might wind up with a wrong idea of the current
      * resource state, and we might not re-probe the resource.
      */
     rc = send_lrm_rsc_op(controld_api, false, host_uname, rsc_id, scheduler);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     crm_trace("Processing %d mainloop inputs",
               pcmk_controld_api_replies_expected(controld_api));
     while (g_main_context_iteration(NULL, FALSE)) {
         crm_trace("Processed mainloop input, %d still remaining",
                   pcmk_controld_api_replies_expected(controld_api));
     }
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_failures(pcmk__output_t *out, pcmk_ipc_api_t *controld_api,
                    const char *node_name, const char *rsc_id, const char *operation,
                    const char *interval_spec, pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
     const char *failed_value = NULL;
     const char *failed_id = NULL;
     char *interval_ms_s = NULL;
     GHashTable *rscs = NULL;
     GHashTableIter iter;
 
     /* Create a hash table to use as a set of resources to clean. This lets us
      * clean each resource only once (per node) regardless of how many failed
      * operations it has.
      */
     rscs = pcmk__strkey_table(NULL, NULL);
 
     // Normalize interval to milliseconds for comparison to history entry
     if (operation) {
         guint interval_ms = 0U;
 
         pcmk_parse_interval_spec(interval_spec, &interval_ms);
         interval_ms_s = crm_strdup_printf("%u", interval_ms);
     }
 
     for (xmlNode *xml_op = pcmk__xml_first_child(scheduler->failed);
          xml_op != NULL;
          xml_op = pcmk__xml_next(xml_op)) {
 
         failed_id = crm_element_value(xml_op, PCMK__XA_RSC_ID);
         if (failed_id == NULL) {
             // Malformed history entry, should never happen
             continue;
         }
 
         // No resource specified means all resources match
         if (rsc_id) {
             pcmk_resource_t *fail_rsc = NULL;
 
             fail_rsc = pe_find_resource_with_flags(scheduler->resources,
                                                    failed_id,
                                                    pcmk_rsc_match_history
                                                    |pcmk_rsc_match_anon_basename);
             if (!fail_rsc || !pcmk__str_eq(rsc_id, fail_rsc->id, pcmk__str_casei)) {
                 continue;
             }
         }
 
         // Host name should always have been provided by this point
         failed_value = crm_element_value(xml_op, PCMK_XA_UNAME);
         if (!pcmk__str_eq(node_name, failed_value, pcmk__str_casei)) {
             continue;
         }
 
         // No operation specified means all operations match
         if (operation) {
             failed_value = crm_element_value(xml_op, PCMK_XA_OPERATION);
             if (!pcmk__str_eq(operation, failed_value, pcmk__str_casei)) {
                 continue;
             }
 
             // Interval (if operation was specified) defaults to 0 (not all)
             failed_value = crm_element_value(xml_op, PCMK_META_INTERVAL);
             if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) {
                 continue;
             }
         }
 
         g_hash_table_add(rscs, (gpointer) failed_id);
     }
 
     free(interval_ms_s);
 
     g_hash_table_iter_init(&iter, rscs);
     while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) {
         crm_debug("Erasing failures of %s on %s", failed_id, node_name);
         rc = clear_rsc_history(controld_api, node_name, failed_id, scheduler);
         if (rc != pcmk_rc_ok) {
             return rc;
         }
     }
     g_hash_table_destroy(rscs);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 clear_rsc_fail_attrs(const pcmk_resource_t *rsc, const char *operation,
                      const char *interval_spec, const pcmk_node_t *node)
 {
     int rc = pcmk_rc_ok;
     int attr_options = pcmk__node_attr_none;
     char *rsc_name = rsc_fail_name(rsc);
 
     if (pe__is_guest_or_remote_node(node)) {
         attr_options |= pcmk__node_attr_remote;
     }
 
     rc = pcmk__attrd_api_clear_failures(NULL, node->details->uname, rsc_name,
                                         operation, interval_spec, NULL,
                                         attr_options);
     free(rsc_name);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname,
                     const pcmk_resource_t *rsc, const char *operation,
                     const char *interval_spec, bool just_failures,
                     pcmk_scheduler_t *scheduler, gboolean force)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     pcmk_node_t *node = NULL;
 
     if (rsc == NULL) {
         return ENXIO;
 
     } else if (rsc->children) {
 
         for (const GList *lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
             const pcmk_resource_t *child = (const pcmk_resource_t *) lpc->data;
 
             rc = cli_resource_delete(controld_api, host_uname, child, operation,
                                      interval_spec, just_failures, scheduler,
                                      force);
             if (rc != pcmk_rc_ok) {
                 return rc;
             }
         }
         return pcmk_rc_ok;
 
     } else if (host_uname == NULL) {
         GList *lpc = NULL;
         GList *nodes = g_hash_table_get_values(rsc->known_on);
 
         if(nodes == NULL && force) {
             nodes = pcmk__copy_node_list(scheduler->nodes, false);
 
         } else if(nodes == NULL && rsc->exclusive_discover) {
             GHashTableIter iter;
             pcmk_node_t *node = NULL;
 
             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
             while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) {
                 if(node->weight >= 0) {
                     nodes = g_list_prepend(nodes, node);
                 }
             }
 
         } else if(nodes == NULL) {
             nodes = g_hash_table_get_values(rsc->allowed_nodes);
         }
 
         for (lpc = nodes; lpc != NULL; lpc = lpc->next) {
             node = (pcmk_node_t *) lpc->data;
 
             if (node->details->online) {
                 rc = cli_resource_delete(controld_api, node->details->uname, rsc,
                                          operation, interval_spec, just_failures,
                                          scheduler, force);
             }
             if (rc != pcmk_rc_ok) {
                 g_list_free(nodes);
                 return rc;
             }
         }
 
         g_list_free(nodes);
         return pcmk_rc_ok;
     }
 
     node = pe_find_node(scheduler->nodes, host_uname);
 
     if (node == NULL) {
         out->err(out, "Unable to clean up %s because node %s not found",
                  rsc->id, host_uname);
         return ENODEV;
     }
 
     if (!node->details->rsc_discovery_enabled) {
         out->err(out, "Unable to clean up %s because resource discovery disabled on %s",
                  rsc->id, host_uname);
         return EOPNOTSUPP;
     }
 
     if (controld_api == NULL) {
         out->err(out, "Dry run: skipping clean-up of %s on %s due to CIB_file",
                  rsc->id, host_uname);
         return pcmk_rc_ok;
     }
 
     rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Unable to clean up %s failures on %s: %s",
                  rsc->id, host_uname, pcmk_rc_str(rc));
         return rc;
     }
 
     if (just_failures) {
         rc = clear_rsc_failures(out, controld_api, host_uname, rsc->id, operation,
                                 interval_spec, scheduler);
     } else {
         rc = clear_rsc_history(controld_api, host_uname, rsc->id, scheduler);
     }
     if (rc != pcmk_rc_ok) {
         out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s",
                  rsc->id, host_uname, pcmk_rc_str(rc));
     } else {
         out->info(out, "Cleaned up %s on %s", rsc->id, host_uname);
     }
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name,
                 const char *operation, const char *interval_spec,
                 pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     int attr_options = pcmk__node_attr_none;
     const char *display_name = node_name? node_name : "all nodes";
 
     if (controld_api == NULL) {
         out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
                   display_name);
         return rc;
     }
 
     if (node_name) {
         pcmk_node_t *node = pe_find_node(scheduler->nodes, node_name);
 
         if (node == NULL) {
             out->err(out, "Unknown node: %s", node_name);
             return ENXIO;
         }
         if (pe__is_guest_or_remote_node(node)) {
             attr_options |= pcmk__node_attr_remote;
         }
     }
 
     rc = pcmk__attrd_api_clear_failures(NULL, node_name, NULL, operation,
                                         interval_spec, NULL, attr_options);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Unable to clean up all failures on %s: %s",
                  display_name, pcmk_rc_str(rc));
         return rc;
     }
 
     if (node_name) {
         rc = clear_rsc_failures(out, controld_api, node_name, NULL,
                                 operation, interval_spec, scheduler);
         if (rc != pcmk_rc_ok) {
             out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s",
                      node_name, pcmk_rc_str(rc));
             return rc;
         }
     } else {
         for (GList *iter = scheduler->nodes; iter; iter = iter->next) {
             pcmk_node_t *node = (pcmk_node_t *) iter->data;
 
             rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL,
                                     operation, interval_spec, scheduler);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s",
                          pcmk_rc_str(rc));
                 return rc;
             }
         }
     }
 
     out->info(out, "Cleaned up all resources on %s", display_name);
     return rc;
 }
 
 static void
 check_role(resource_checks_t *checks)
 {
     const char *role_s = g_hash_table_lookup(checks->rsc->meta,
                                              PCMK_META_TARGET_ROLE);
 
     if (role_s == NULL) {
         return;
     }
     switch (text2role(role_s)) {
         case pcmk_role_stopped:
             checks->flags |= rsc_remain_stopped;
             break;
 
         case pcmk_role_unpromoted:
             if (pcmk_is_set(pe__const_top_resource(checks->rsc, false)->flags,
                             pcmk_rsc_promotable)) {
                 checks->flags |= rsc_unpromotable;
             }
             break;
 
         default:
             break;
     }
 }
 
 static void
 check_managed(resource_checks_t *checks)
 {
     const char *managed_s = g_hash_table_lookup(checks->rsc->meta,
                                                 PCMK_META_IS_MANAGED);
 
     if ((managed_s != NULL) && !crm_is_true(managed_s)) {
         checks->flags |= rsc_unmanaged;
     }
 }
 
 static void
 check_locked(resource_checks_t *checks)
 {
     if (checks->rsc->lock_node != NULL) {
         checks->flags |= rsc_locked;
         checks->lock_node = checks->rsc->lock_node->details->uname;
     }
 }
 
 static bool
 node_is_unhealthy(pcmk_node_t *node)
 {
     switch (pe__health_strategy(node->details->data_set)) {
         case pcmk__health_strategy_none:
             break;
 
         case pcmk__health_strategy_no_red:
             if (pe__node_health(node) < 0) {
                 return true;
             }
             break;
 
         case pcmk__health_strategy_only_green:
             if (pe__node_health(node) <= 0) {
                 return true;
             }
             break;
 
         case pcmk__health_strategy_progressive:
         case pcmk__health_strategy_custom:
             /* @TODO These are finite scores, possibly with rules, and possibly
              * combining with other scores, so attributing these as a cause is
              * nontrivial.
              */
             break;
     }
     return false;
 }
 
 static void
 check_node_health(resource_checks_t *checks, pcmk_node_t *node)
 {
     if (node == NULL) {
         GHashTableIter iter;
         bool allowed = false;
         bool all_nodes_unhealthy = true;
 
         g_hash_table_iter_init(&iter, checks->rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             allowed = true;
             if (!node_is_unhealthy(node)) {
                 all_nodes_unhealthy = false;
                 break;
             }
         }
         if (allowed && all_nodes_unhealthy) {
             checks->flags |= rsc_node_health;
         }
 
     } else if (node_is_unhealthy(node)) {
         checks->flags |= rsc_node_health;
     }
 }
 
 int
 cli_resource_check(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     resource_checks_t checks = { .rsc = rsc };
 
     check_role(&checks);
     check_managed(&checks);
     check_locked(&checks);
     check_node_health(&checks, node);
 
     return out->message(out, "resource-check-list", &checks);
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
                   const char *rsc_id, pcmk_scheduler_t *scheduler)
 {
     crm_notice("Failing %s on %s", rsc_id, host_uname);
     return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, scheduler);
 }
 
 static GHashTable *
 generate_resource_params(pcmk_resource_t *rsc, pcmk_node_t *node,
                          pcmk_scheduler_t *scheduler)
 {
     GHashTable *params = NULL;
     GHashTable *meta = NULL;
     GHashTable *combined = NULL;
     GHashTableIter iter;
     char *key = NULL;
     char *value = NULL;
 
     combined = pcmk__strkey_table(free, free);
 
     params = pe_rsc_params(rsc, node, scheduler);
     if (params != NULL) {
         g_hash_table_iter_init(&iter, params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             g_hash_table_insert(combined, strdup(key), strdup(value));
         }
     }
 
     meta = pcmk__strkey_table(free, free);
     get_meta_attributes(meta, rsc, node, scheduler);
     if (meta != NULL) {
         g_hash_table_iter_init(&iter, meta);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             char *crm_name = crm_meta_name(key);
 
             g_hash_table_insert(combined, crm_name, strdup(value));
         }
         g_hash_table_destroy(meta);
     }
 
     return combined;
 }
 
 bool resource_is_running_on(pcmk_resource_t *rsc, const char *host)
 {
     bool found = true;
     GList *hIter = NULL;
     GList *hosts = NULL;
 
     if (rsc == NULL) {
         return false;
     }
 
     rsc->fns->location(rsc, &hosts, TRUE);
     for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
         pcmk_node_t *node = (pcmk_node_t *) hIter->data;
 
         if (pcmk__strcase_any_of(host, node->details->uname, node->details->id, NULL)) {
             crm_trace("Resource %s is running on %s\n", rsc->id, host);
             goto done;
         }
     }
 
     if (host != NULL) {
         crm_trace("Resource %s is not running on: %s\n", rsc->id, host);
         found = false;
 
     } else if(host == NULL && hosts == NULL) {
         crm_trace("Resource %s is not running\n", rsc->id);
         found = false;
     }
 
   done:
     g_list_free(hosts);
     return found;
 }
 
 /*!
  * \internal
  * \brief Create a list of all resources active on host from a given list
  *
  * \param[in] host      Name of host to check whether resources are active
  * \param[in] rsc_list  List of resources to check
  *
  * \return New list of resources from list that are active on host
  */
 static GList *
 get_active_resources(const char *host, GList *rsc_list)
 {
     GList *rIter = NULL;
     GList *active = NULL;
 
     for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) rIter->data;
 
         /* Expand groups to their members, because if we're restarting a member
          * other than the first, we can't otherwise tell which resources are
          * stopping and starting.
          */
         if (rsc->variant == pcmk_rsc_variant_group) {
             active = g_list_concat(active,
                                    get_active_resources(host, rsc->children));
         } else if (resource_is_running_on(rsc, host)) {
             active = g_list_append(active, strdup(rsc->id));
         }
     }
     return active;
 }
 
 static void dump_list(GList *items, const char *tag)
 {
     int lpc = 0;
     GList *item = NULL;
 
     for (item = items; item != NULL; item = item->next) {
         crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data);
         lpc++;
     }
 }
 
 static void display_list(pcmk__output_t *out, GList *items, const char *tag)
 {
     GList *item = NULL;
 
     for (item = items; item != NULL; item = item->next) {
         out->info(out, "%s%s", tag, (const char *)item->data);
     }
 }
 
 /*!
  * \internal
  * \brief Upgrade XML to latest schema version and use it as scheduler input
  *
  * This also updates the scheduler timestamp to the current time.
  *
  * \param[in,out] scheduler  Scheduler data to update
  * \param[in,out] xml        XML to use as input
  *
  * \return Standard Pacemaker return code
  * \note On success, caller is responsible for freeing memory allocated for
  *       scheduler->now.
  * \todo This follows the example of other callers of cli_config_update()
  *       and returns ENOKEY ("Required key not available") if that fails,
  *       but perhaps pcmk_rc_schema_validation would be better in that case.
  */
 int
 update_scheduler_input(pcmk_scheduler_t *scheduler, xmlNode **xml)
 {
     if (cli_config_update(xml, NULL, FALSE) == FALSE) {
         return ENOKEY;
     }
     scheduler->input = *xml;
     scheduler->now = crm_time_new(NULL);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Update scheduler XML input based on a CIB query
  *
  * \param[in] scheduler  Scheduler data to initialize
  * \param[in] cib        Connection to the CIB manager
  *
  * \return Standard Pacemaker return code
  * \note On success, caller is responsible for freeing memory allocated for
  *       scheduler->input and scheduler->now.
  */
 static int
 update_scheduler_input_to_cib(pcmk__output_t *out, pcmk_scheduler_t *scheduler,
                               cib_t *cib)
 {
     xmlNode *cib_xml_copy = NULL;
     int rc = pcmk_rc_ok;
 
     rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_rc_str(rc), rc);
         return rc;
     }
     rc = update_scheduler_input(scheduler, &cib_xml_copy);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Could not upgrade the current CIB XML");
         free_xml(cib_xml_copy);
         return rc;
     }
 
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 update_dataset(cib_t *cib, pcmk_scheduler_t *scheduler, bool simulate)
 {
     char *pid = NULL;
     char *shadow_file = NULL;
     cib_t *shadow_cib = NULL;
     int rc = pcmk_rc_ok;
 
     pcmk__output_t *out = scheduler->priv;
 
     pe_reset_working_set(scheduler);
     pcmk__set_scheduler_flags(scheduler,
                               pcmk_sched_no_counts|pcmk_sched_no_compat);
     rc = update_scheduler_input_to_cib(out, scheduler, cib);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     if(simulate) {
         bool prev_quiet = false;
 
         pid = pcmk__getpid_s();
         shadow_cib = cib_shadow_new(pid);
         shadow_file = get_shadow_file(pid);
 
         if (shadow_cib == NULL) {
             out->err(out, "Could not create shadow cib: '%s'", pid);
             rc = ENXIO;
             goto done;
         }
 
         rc = write_xml_file(scheduler->input, shadow_file, FALSE);
 
         if (rc < 0) {
             out->err(out, "Could not populate shadow cib: %s (%d)", pcmk_strerror(rc), rc);
             goto done;
         }
 
         rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
 
         if (rc != pcmk_rc_ok) {
             out->err(out, "Could not connect to shadow cib: %s (%d)", pcmk_rc_str(rc), rc);
             goto done;
         }
 
         pcmk__schedule_actions(scheduler->input,
                                pcmk_sched_no_counts|pcmk_sched_no_compat,
                                scheduler);
 
         prev_quiet = out->is_quiet(out);
         out->quiet = true;
         pcmk__simulate_transition(scheduler, shadow_cib, NULL);
         out->quiet = prev_quiet;
 
         rc = update_dataset(shadow_cib, scheduler, false);
 
     } else {
         cluster_status(scheduler);
     }
 
   done:
     // Do not free scheduler->input here, we need rsc->xml to be valid later on
     cib_delete(shadow_cib);
     free(pid);
 
     if(shadow_file) {
         unlink(shadow_file);
         free(shadow_file);
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Find the maximum stop timeout of a resource and its children (if any)
  *
  * \param[in,out] rsc  Resource to get timeout for
  *
  * \return Maximum stop timeout for \p rsc (in milliseconds)
  */
 static int
 max_rsc_stop_timeout(pcmk_resource_t *rsc)
 {
     long long result_ll;
     int max_delay = 0;
     xmlNode *config = NULL;
     GHashTable *meta = NULL;
 
     if (rsc == NULL) {
         return 0;
     }
 
     // If resource is collective, use maximum of its children's stop timeouts
     if (rsc->children != NULL) {
         for (GList *iter = rsc->children; iter; iter = iter->next) {
             pcmk_resource_t *child = iter->data;
             int delay = max_rsc_stop_timeout(child);
 
             if (delay > max_delay) {
                 pcmk__rsc_trace(rsc,
                                 "Maximum stop timeout for %s is now %s "
                                 "due to %s", rsc->id,
                                 pcmk__readable_interval(delay), child->id);
                 max_delay = delay;
             }
         }
         return max_delay;
     }
 
     // Get resource's stop action configuration from CIB
     config = pcmk__find_action_config(rsc, PCMK_ACTION_STOP, 0, true);
 
     /* Get configured timeout for stop action (fully evaluated for rules,
      * defaults, etc.).
      *
      * @TODO This currently ignores node (which might matter for rules)
      */
     meta = pcmk__unpack_action_meta(rsc, NULL, PCMK_ACTION_STOP, 0, config);
     if ((pcmk__scan_ll(g_hash_table_lookup(meta, PCMK_META_TIMEOUT),
                        &result_ll, -1LL) == pcmk_rc_ok)
         && (result_ll >= 0) && (result_ll <= INT_MAX)) {
         max_delay = (int) result_ll;
     }
     g_hash_table_destroy(meta);
 
     return max_delay;
 }
 
 /*!
  * \internal
  * \brief Find a reasonable waiting time for stopping any one resource in a list
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     resources  List of names of resources that will be stopped
  *
  * \return Rough estimate of a reasonable time to wait (in seconds) to stop any
  *         one resource in \p resources
  * \note This estimate is very rough, simply the maximum stop timeout of all
  *       given resources and their children, plus a small fudge factor. It does
  *       not account for children that must be stopped in sequence, action
  *       throttling, or any demotions needed. It checks the stop timeout, even
  *       if the resources in question are actually being started.
  */
 static int
 wait_time_estimate(pcmk_scheduler_t *scheduler, const GList *resources)
 {
     int max_delay = 0;
 
     // Find maximum stop timeout in milliseconds
     for (const GList *item = resources; item != NULL; item = item->next) {
         pcmk_resource_t *rsc = pe_find_resource(scheduler->resources,
                                                 (const char *) item->data);
         int delay = max_rsc_stop_timeout(rsc);
 
         if (delay > max_delay) {
             pcmk__rsc_trace(rsc,
                             "Wait time is now %s due to %s",
                             pcmk__readable_interval(delay), rsc->id);
             max_delay = delay;
         }
     }
 
     return (max_delay / 1000) + 5;
 }
 
 #define waiting_for_starts(d, r, h) ((d != NULL) || \
                                     (!resource_is_running_on((r), (h))))
 
 /*!
  * \internal
  * \brief Restart a resource (on a particular host if requested).
  *
  * \param[in,out] out                 Output object
  * \param[in,out] rsc                 The resource to restart
  * \param[in]     node                Node to restart resource on (NULL for all)
  * \param[in]     move_lifetime       If not NULL, how long constraint should
  *                                    remain in effect (as ISO 8601 string)
  * \param[in]     timeout_ms          Consider failed if actions do not complete
  *                                    in this time (specified in milliseconds,
  *                                    but a two-second granularity is actually
  *                                    used; if 0, it will be calculated based on
  *                                    the resource timeout)
  * \param[in,out] cib                 Connection to the CIB manager
  * \param[in]     cib_options         Group of enum cib_call_options flags to
  *                                    use with CIB calls
  * \param[in]     promoted_role_only  If true, limit to promoted instances
  * \param[in]     force               If true, apply only to requested instance
  *                                    if part of a collective resource
  *
  * \return Standard Pacemaker return code (exits on certain failures)
  */
 int
 cli_resource_restart(pcmk__output_t *out, pcmk_resource_t *rsc,
                      const pcmk_node_t *node, const char *move_lifetime,
                      int timeout_ms, cib_t *cib, int cib_options,
                      gboolean promoted_role_only, gboolean force)
 {
     int rc = pcmk_rc_ok;
     int lpc = 0;
     int before = 0;
     int step_timeout_s = 0;
     int sleep_interval = 2;
     int timeout = timeout_ms / 1000;
 
     bool stop_via_ban = false;
     char *rsc_id = NULL;
     char *lookup_id = NULL;
     char *orig_target_role = NULL;
 
     GList *list_delta = NULL;
     GList *target_active = NULL;
     GList *current_active = NULL;
     GList *restart_target_active = NULL;
 
     pcmk_scheduler_t *scheduler = NULL;
     pcmk_resource_t *parent = uber_parent(rsc);
 
     bool running = false;
     const char *id = rsc->clone_name ? rsc->clone_name : rsc->id;
     const char *host = node ? node->details->uname : NULL;
 
     /* If the implicit resource or primitive resource of a bundle is given, operate on the
      * bundle itself instead.
      */
     if (pe_rsc_is_bundled(rsc)) {
         rsc = parent->parent;
     }
 
     running = resource_is_running_on(rsc, host);
 
     if (pe_rsc_is_clone(parent) && !running) {
         if (pe_rsc_is_unique_clone(parent)) {
             lookup_id = strdup(rsc->id);
         } else {
             lookup_id = clone_strip(rsc->id);
         }
 
         rsc = parent->fns->find_rsc(parent, lookup_id, node,
                                     pcmk_rsc_match_basename
                                     |pcmk_rsc_match_current_node);
         free(lookup_id);
         running = resource_is_running_on(rsc, host);
     }
 
     if (!running) {
         if (host) {
             out->err(out, "%s is not running on %s and so cannot be restarted", id, host);
         } else {
             out->err(out, "%s is not running anywhere and so cannot be restarted", id);
         }
         return ENXIO;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         out->err(out, "Unmanaged resources cannot be restarted.");
         return EAGAIN;
     }
 
     rsc_id = strdup(rsc->id);
 
     if (pe_rsc_is_unique_clone(parent)) {
         lookup_id = strdup(rsc->id);
     } else {
         lookup_id = clone_strip(rsc->id);
     }
 
     if (host) {
         if (pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) {
             stop_via_ban = true;
         } else if (pe_rsc_is_clone(parent)) {
             stop_via_ban = true;
             free(lookup_id);
             lookup_id = strdup(parent->id);
         }
     }
 
     /*
       grab full cib
       determine originally active resources
       disable or ban
       poll cib and watch for affected resources to get stopped
       without --timeout, calculate the stop timeout for each step and wait for that
       if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down
       if everything stopped, re-enable or un-ban
       poll cib and watch for affected resources to get started
       without --timeout, calculate the start timeout for each step and wait for that
       if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up
       report success
 
       Optimizations:
       - use constraints to determine ordered list of affected resources
       - Allow a --no-deps option (aka. --force-restart)
     */
 
     scheduler = pe_new_working_set();
     if (scheduler == NULL) {
         rc = errno;
         out->err(out, "Could not allocate scheduler data: %s", pcmk_rc_str(rc));
         goto done;
     }
 
     scheduler->priv = out;
     rc = update_dataset(cib, scheduler, false);
 
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not get new resource list: %s (%d)", pcmk_rc_str(rc), rc);
         goto done;
     }
 
     restart_target_active = get_active_resources(host, scheduler->resources);
     current_active = get_active_resources(host, scheduler->resources);
 
     dump_list(current_active, "Origin");
 
     if (stop_via_ban) {
         /* Stop the clone or bundle instance by banning it from the host */
         out->quiet = true;
         rc = cli_resource_ban(out, lookup_id, host, move_lifetime, cib,
                               cib_options, promoted_role_only,
                               PCMK__ROLE_PROMOTED);
     } else {
         /* Stop the resource by setting PCMK_META_TARGET_ROLE to Stopped.
          * Remember any existing PCMK_META_TARGET_ROLE so we can restore it
          * later (though it only makes any difference if it's Unpromoted).
          */
 
         find_resource_attr(out, cib, PCMK_XA_VALUE, lookup_id, NULL, NULL, NULL,
                            PCMK_META_TARGET_ROLE, &orig_target_role);
         rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                            NULL, PCMK_META_TARGET_ROLE,
                                            PCMK_ACTION_STOPPED, FALSE, cib,
                                            cib_options, force);
     }
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not set " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
                  rsc_id, pcmk_rc_str(rc), rc);
         if (current_active != NULL) {
             g_list_free_full(current_active, free);
             current_active = NULL;
         }
         if (restart_target_active != NULL) {
             g_list_free_full(restart_target_active, free);
             restart_target_active = NULL;
         }
         goto done;
     }
 
     rc = update_dataset(cib, scheduler, true);
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not determine which resources would be stopped");
         goto failure;
     }
 
     target_active = get_active_resources(host, scheduler->resources);
     dump_list(target_active, "Target");
 
     list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
     out->info(out, "Waiting for %d resources to stop:", g_list_length(list_delta));
     display_list(out, list_delta, " * ");
 
     step_timeout_s = timeout / sleep_interval;
     while (list_delta != NULL) {
         before = g_list_length(list_delta);
         if(timeout_ms == 0) {
             step_timeout_s = wait_time_estimate(scheduler, list_delta)
                              / sleep_interval;
         }
 
         /* We probably don't need the entire step timeout */
         for(lpc = 0; (lpc < step_timeout_s) && (list_delta != NULL); lpc++) {
             sleep(sleep_interval);
             if(timeout) {
                 timeout -= sleep_interval;
                 crm_trace("%ds remaining", timeout);
             }
             rc = update_dataset(cib, scheduler, FALSE);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were stopped");
                 goto failure;
             }
 
             if (current_active != NULL) {
                 g_list_free_full(current_active, free);
             }
             current_active = get_active_resources(host, scheduler->resources);
 
             g_list_free(list_delta);
             list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
 
             dump_list(current_active, "Current");
             dump_list(list_delta, "Delta");
         }
 
         crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before);
         if(before == g_list_length(list_delta)) {
             /* aborted during stop phase, print the contents of list_delta */
             out->err(out, "Could not complete shutdown of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
             display_list(out, list_delta, " * ");
             rc = ETIME;
             goto failure;
         }
 
     }
 
     if (stop_via_ban) {
         rc = cli_resource_clear(lookup_id, host, NULL, cib, cib_options, true, force);
 
     } else if (orig_target_role) {
         rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                            NULL, PCMK_META_TARGET_ROLE,
                                            orig_target_role, FALSE, cib,
                                            cib_options, force);
         free(orig_target_role);
         orig_target_role = NULL;
     } else {
         rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                            NULL, PCMK_META_TARGET_ROLE, cib,
                                            cib_options, force);
     }
 
     if(rc != pcmk_rc_ok) {
         out->err(out,
                  "Could not unset " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
                  rsc_id, pcmk_rc_str(rc), rc);
         goto done;
     }
 
     if (target_active != NULL) {
         g_list_free_full(target_active, free);
     }
     target_active = restart_target_active;
 
     list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
     out->info(out, "Waiting for %d resources to start again:", g_list_length(list_delta));
     display_list(out, list_delta, " * ");
 
     step_timeout_s = timeout / sleep_interval;
     while (waiting_for_starts(list_delta, rsc, host)) {
         before = g_list_length(list_delta);
         if(timeout_ms == 0) {
             step_timeout_s = wait_time_estimate(scheduler, list_delta)
                              / sleep_interval;
         }
 
         /* We probably don't need the entire step timeout */
         for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) {
 
             sleep(sleep_interval);
             if(timeout) {
                 timeout -= sleep_interval;
                 crm_trace("%ds remaining", timeout);
             }
 
             rc = update_dataset(cib, scheduler, false);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were started");
                 goto failure;
             }
 
             /* It's OK if dependent resources moved to a different node,
              * so we check active resources on all nodes.
              */
             if (current_active != NULL) {
                 g_list_free_full(current_active, free);
             }
             current_active = get_active_resources(NULL, scheduler->resources);
 
             g_list_free(list_delta);
             list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
             dump_list(current_active, "Current");
             dump_list(list_delta, "Delta");
         }
 
         if(before == g_list_length(list_delta)) {
             /* aborted during start phase, print the contents of list_delta */
             out->err(out, "Could not complete restart of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
             display_list(out, list_delta, " * ");
             rc = ETIME;
             goto failure;
         }
 
     }
 
     rc = pcmk_rc_ok;
     goto done;
 
   failure:
     if (stop_via_ban) {
         cli_resource_clear(lookup_id, host, NULL, cib, cib_options, true, force);
     } else if (orig_target_role) {
         cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL,
                                       PCMK_META_TARGET_ROLE, orig_target_role,
                                       FALSE, cib, cib_options, force);
         free(orig_target_role);
     } else {
         cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                       NULL, PCMK_META_TARGET_ROLE, cib,
                                       cib_options, force);
     }
 
 done:
     if (list_delta != NULL) {
         g_list_free(list_delta);
     }
     if (current_active != NULL) {
         g_list_free_full(current_active, free);
     }
     if (target_active != NULL && (target_active != restart_target_active)) {
         g_list_free_full(target_active, free);
     }
     if (restart_target_active != NULL) {
         g_list_free_full(restart_target_active, free);
     }
     free(rsc_id);
     free(lookup_id);
     pe_free_working_set(scheduler);
     return rc;
 }
 
 static inline bool
 action_is_pending(const pcmk_action_t *action)
 {
     if (pcmk_any_flags_set(action->flags,
                            pcmk_action_optional|pcmk_action_pseudo)
         || !pcmk_is_set(action->flags, pcmk_action_runnable)
         || pcmk__str_eq(PCMK_ACTION_NOTIFY, action->task, pcmk__str_casei)) {
         return false;
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether any actions in a list are pending
  *
  * \param[in] actions   List of actions to check
  *
  * \return true if any actions in the list are pending, otherwise false
  */
 static bool
 actions_are_pending(const GList *actions)
 {
     for (const GList *action = actions; action != NULL; action = action->next) {
         const pcmk_action_t *a = (const pcmk_action_t *) action->data;
 
         if (action_is_pending(a)) {
             crm_notice("Waiting for %s (flags=%#.8x)", a->uuid, a->flags);
             return true;
         }
     }
     return false;
 }
 
 static void
 print_pending_actions(pcmk__output_t *out, GList *actions)
 {
     GList *action;
 
     out->info(out, "Pending actions:");
     for (action = actions; action != NULL; action = action->next) {
         pcmk_action_t *a = (pcmk_action_t *) action->data;
 
         if (!action_is_pending(a)) {
             continue;
         }
 
         if (a->node) {
             out->info(out, "\tAction %d: %s\ton %s",
                       a->id, a->uuid, pcmk__node_name(a->node));
         } else {
             out->info(out, "\tAction %d: %s", a->id, a->uuid);
         }
     }
 }
 
 /* For --wait, timeout (in seconds) to use if caller doesn't specify one */
 #define WAIT_DEFAULT_TIMEOUT_S (60 * 60)
 
 /* For --wait, how long to sleep between cluster state checks */
 #define WAIT_SLEEP_S (2)
 
 /*!
  * \internal
  * \brief Wait until all pending cluster actions are complete
  *
  * This waits until either the CIB's transition graph is idle or a timeout is
  * reached.
  *
  * \param[in,out] out          Output object
  * \param[in]     timeout_ms   Consider failed if actions do not complete in
  *                             this time (specified in milliseconds, but
  *                             one-second granularity is actually used; if 0, a
  *                             default will be used)
  * \param[in,out] cib          Connection to the CIB manager
  *
  * \return Standard Pacemaker return code
  */
 int
 wait_till_stable(pcmk__output_t *out, int timeout_ms, cib_t * cib)
 {
     pcmk_scheduler_t *scheduler = NULL;
     xmlXPathObjectPtr search;
     int rc = pcmk_rc_ok;
     bool pending_unknown_state_resources;
     int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S;
     time_t expire_time = time(NULL) + timeout_s;
     time_t time_diff;
     bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet
 
     scheduler = pe_new_working_set();
     if (scheduler == NULL) {
         return ENOMEM;
     }
 
     do {
         /* Abort if timeout is reached */
         time_diff = expire_time - time(NULL);
         if (time_diff > 0) {
             crm_info("Waiting up to %lld seconds for cluster actions to complete", (long long) time_diff);
         } else {
             print_pending_actions(out, scheduler->actions);
             pe_free_working_set(scheduler);
             return ETIME;
         }
         if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */
             sleep(WAIT_SLEEP_S);
         }
 
         /* Get latest transition graph */
         pe_reset_working_set(scheduler);
         rc = update_scheduler_input_to_cib(out, scheduler, cib);
         if (rc != pcmk_rc_ok) {
             pe_free_working_set(scheduler);
             return rc;
         }
         pcmk__schedule_actions(scheduler->input,
                                pcmk_sched_no_counts|pcmk_sched_no_compat,
                                scheduler);
 
         if (!printed_version_warning) {
             /* If the DC has a different version than the local node, the two
              * could come to different conclusions about what actions need to be
              * done. Warn the user in this case.
              *
              * @TODO A possible long-term solution would be to reimplement the
              * wait as a new controller operation that would be forwarded to the
              * DC. However, that would have potential problems of its own.
              */
             const char *dc_version = g_hash_table_lookup(scheduler->config_hash,
                                                          PCMK_OPT_DC_VERSION);
 
             if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) {
                 out->info(out, "warning: wait option may not work properly in "
                           "mixed-version cluster");
                 printed_version_warning = true;
             }
         }
 
         search = xpath_search(scheduler->input,
                               "/cib/status/node_state/lrm/lrm_resources"
                               "/lrm_resource/" XML_LRM_TAG_RSC_OP
                               "[@" PCMK__XA_RC_CODE "='193']");
         pending_unknown_state_resources = (numXpathResults(search) > 0);
         freeXpathObject(search);
     } while (actions_are_pending(scheduler->actions) || pending_unknown_state_resources);
 
     pe_free_working_set(scheduler);
     return rc;
 }
 
 static const char *
 get_action(const char *rsc_action) {
     const char *action = NULL;
 
     if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) {
         action = PCMK_ACTION_VALIDATE_ALL;
 
     } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
         action = PCMK_ACTION_MONITOR;
 
     } else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-stop",
                                     "force-demote", "force-promote", NULL)) {
         action = rsc_action+6;
     } else {
         action = rsc_action;
     }
 
     return action;
 }
 
 /*!
  * \brief Set up environment variables as expected by resource agents
  *
  * When the cluster executes resource agents, it adds certain environment
  * variables (directly or via resource meta-attributes) expected by some
  * resource agents. Add the essential ones that many resource agents expect, so
  * the behavior is the same for command-line execution.
  *
  * \param[in,out] params       Resource parameters that will be passed to agent
  * \param[in]     timeout_ms   Action timeout (in milliseconds)
  * \param[in]     check_level  OCF check level
  * \param[in]     verbosity    Verbosity level
  */
 static void
 set_agent_environment(GHashTable *params, int timeout_ms, int check_level,
                       int verbosity)
 {
     g_hash_table_insert(params, crm_meta_name(PCMK_META_TIMEOUT),
                         crm_strdup_printf("%d", timeout_ms));
 
     g_hash_table_insert(params, strdup(PCMK_XA_CRM_FEATURE_SET),
                         strdup(CRM_FEATURE_SET));
 
     if (check_level >= 0) {
         char *level = crm_strdup_printf("%d", check_level);
 
         setenv("OCF_CHECK_LEVEL", level, 1);
         free(level);
     }
 
     pcmk__set_env_option(PCMK__ENV_DEBUG, ((verbosity > 0)? "1" : "0"), true);
     if (verbosity > 1) {
         setenv("OCF_TRACE_RA", "1", 1);
     }
 
     /* A resource agent using the standard ocf-shellfuncs library will not print
      * messages to stderr if it doesn't have a controlling terminal (e.g. if
      * crm_resource is called via script or ssh). This forces it to do so.
      */
     setenv("OCF_TRACE_FILE", "/dev/stderr", 0);
 }
 
 /*!
  * \internal
  * \brief Apply command-line overrides to resource parameters
  *
  * \param[in,out] params     Parameters to be passed to agent
  * \param[in]     overrides  Parameters to override (or NULL if none)
  */
 static void
 apply_overrides(GHashTable *params, GHashTable *overrides)
 {
     if (overrides != NULL) {
         GHashTableIter iter;
         char *name = NULL;
         char *value = NULL;
 
         g_hash_table_iter_init(&iter, overrides);
         while (g_hash_table_iter_next(&iter, (gpointer *) &name,
                                       (gpointer *) &value)) {
             g_hash_table_replace(params, strdup(name), strdup(value));
         }
     }
 }
 
 crm_exit_t
 cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name,
                                  const char *rsc_class, const char *rsc_prov,
                                  const char *rsc_type, const char *rsc_action,
                                  GHashTable *params, GHashTable *override_hash,
                                  int timeout_ms, int resource_verbose, gboolean force,
                                  int check_level)
 {
     const char *class = rsc_class;
     const char *action = get_action(rsc_action);
     crm_exit_t exit_code = CRM_EX_OK;
     svc_action_t *op = NULL;
 
     // If no timeout was provided, use the same default as the cluster
     if (timeout_ms == 0) {
         timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
     }
 
     set_agent_environment(params, timeout_ms, check_level, resource_verbose);
     apply_overrides(params, override_hash);
 
     op = services__create_resource_action(rsc_name? rsc_name : "test",
                                           rsc_class, rsc_prov, rsc_type, action,
                                           0, timeout_ms, params, 0);
     if (op == NULL) {
         out->err(out, "Could not execute %s using %s%s%s:%s: %s",
                  action, rsc_class, (rsc_prov? ":" : ""),
                  (rsc_prov? rsc_prov : ""), rsc_type, strerror(ENOMEM));
         g_hash_table_destroy(params);
         return CRM_EX_OSERR;
     }
 
     if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
         class = resources_find_service_class(rsc_type);
     }
     if (!pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_OCF,
                               PCMK_RESOURCE_CLASS_LSB, NULL)) {
         services__format_result(op, CRM_EX_UNIMPLEMENT_FEATURE, PCMK_EXEC_ERROR,
                                 "Manual execution of the %s standard is "
                                 "unsupported", pcmk__s(class, "unspecified"));
     }
 
     if (op->rc != PCMK_OCF_UNKNOWN) {
         exit_code = op->rc;
         goto done;
     }
 
     services_action_sync(op);
 
     // Map results to OCF codes for consistent reporting to user
     {
         enum ocf_exitcode ocf_code = services_result2ocf(class, action, op->rc);
 
         // Cast variable instead of function return to keep compilers happy
         exit_code = (crm_exit_t) ocf_code;
     }
 
 done:
     out->message(out, "resource-agent-action", resource_verbose, rsc_class,
                  rsc_prov, rsc_type, rsc_name, rsc_action, override_hash,
                  exit_code, op->status, services__exit_reason(op),
                  op->stdout_data, op->stderr_data);
     services_action_free(op);
     return exit_code;
 }
 
 crm_exit_t
 cli_resource_execute(pcmk_resource_t *rsc, const char *requested_name,
                      const char *rsc_action, GHashTable *override_hash,
                      int timeout_ms, cib_t *cib, pcmk_scheduler_t *scheduler,
                      int resource_verbose, gboolean force, int check_level)
 {
     pcmk__output_t *out = scheduler->priv;
     crm_exit_t exit_code = CRM_EX_OK;
     const char *rid = NULL;
     const char *rtype = NULL;
     const char *rprov = NULL;
     const char *rclass = NULL;
     GHashTable *params = NULL;
 
     if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote",
                                     "force-promote", NULL)) {
         if(pe_rsc_is_clone(rsc)) {
             GList *nodes = cli_resource_search(rsc, requested_name, scheduler);
             if(nodes != NULL && force == FALSE) {
                 out->err(out, "It is not safe to %s %s here: the cluster claims it is already active",
                          rsc_action, rsc->id);
                 out->err(out,
                          "Try setting "
                          PCMK_META_TARGET_ROLE "=" PCMK__ROLE_STOPPED
                          " first or specifying the force option");
                 return CRM_EX_UNSAFE;
             }
 
             g_list_free_full(nodes, free);
         }
     }
 
     if(pe_rsc_is_clone(rsc)) {
         /* Grab the first child resource in the hope it's not a group */
         rsc = rsc->children->data;
     }
 
     if (rsc->variant == pcmk_rsc_variant_group) {
         out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action);
         return CRM_EX_UNIMPLEMENT_FEATURE;
     } else if (pe_rsc_is_bundled(rsc)) {
         out->err(out, "Sorry, the %s option doesn't support bundled resources", rsc_action);
         return CRM_EX_UNIMPLEMENT_FEATURE;
     }
 
     rclass = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     rprov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
     rtype = crm_element_value(rsc->xml, PCMK_XA_TYPE);
 
     params = generate_resource_params(rsc, NULL /* @TODO use local node */,
                                       scheduler);
 
     if (timeout_ms == 0) {
         timeout_ms = pe_get_configured_timeout(rsc, get_action(rsc_action),
                                                scheduler);
     }
 
     rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id;
 
     exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, rsc_action,
                                                  params, override_hash, timeout_ms,
                                                  resource_verbose, force, check_level);
     return exit_code;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_move(const pcmk_resource_t *rsc, const char *rsc_id,
                   const char *host_name, const char *move_lifetime, cib_t *cib,
                   int cib_options, pcmk_scheduler_t *scheduler,
                   gboolean promoted_role_only, gboolean force)
 {
     pcmk__output_t *out = scheduler->priv;
     int rc = pcmk_rc_ok;
     unsigned int count = 0;
     pcmk_node_t *current = NULL;
     pcmk_node_t *dest = pe_find_node(scheduler->nodes, host_name);
     bool cur_is_dest = false;
 
     if (dest == NULL) {
         return pcmk_rc_node_unknown;
     }
 
     if (promoted_role_only
         && !pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
 
         const pcmk_resource_t *p = pe__const_top_resource(rsc, false);
 
         if (pcmk_is_set(p->flags, pcmk_rsc_promotable)) {
             out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id);
             rsc_id = p->id;
             rsc = p;
 
         } else {
             out->info(out, "Ignoring --promoted option: %s is not promotable",
                       rsc_id);
             promoted_role_only = FALSE;
         }
     }
 
     current = pe__find_active_requires(rsc, &count);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         unsigned int promoted_count = 0;
         pcmk_node_t *promoted_node = NULL;
 
         for (const GList *iter = rsc->children; iter; iter = iter->next) {
             const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
             enum rsc_role_e child_role = child->fns->state(child, TRUE);
 
             if (child_role == pcmk_role_promoted) {
                 rsc = child;
                 promoted_node = pcmk__current_node(child);
                 promoted_count++;
             }
         }
         if (promoted_role_only || (promoted_count != 0)) {
             count = promoted_count;
             current = promoted_node;
         }
 
     }
 
     if (count > 1) {
         if (pe_rsc_is_clone(rsc)) {
             current = NULL;
         } else {
             return pcmk_rc_multiple;
         }
     }
 
     if (pcmk__same_node(current, dest)) {
         cur_is_dest = true;
         if (force) {
             crm_info("%s is already %s on %s, reinforcing placement with location constraint.",
                      rsc_id, promoted_role_only?"promoted":"active",
                      pcmk__node_name(dest));
         } else {
             return pcmk_rc_already;
         }
     }
 
     /* Clear any previous prefer constraints across all nodes. */
     cli_resource_clear(rsc_id, NULL, scheduler->nodes, cib, cib_options, false,
                        force);
 
     /* Clear any previous ban constraints on 'dest'. */
     cli_resource_clear(rsc_id, dest->details->uname, scheduler->nodes, cib,
                        cib_options, TRUE, force);
 
     /* Record an explicit preference for 'dest' */
     rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime,
                              cib, cib_options, promoted_role_only,
                              PCMK__ROLE_PROMOTED);
 
     crm_trace("%s%s now prefers %s%s",
               rsc->id, (promoted_role_only? " (promoted)" : ""),
               pcmk__node_name(dest), force?"(forced)":"");
 
     /* only ban the previous location if current location != destination location.
      * it is possible to use -M to enforce a location without regard of where the
      * resource is currently located */
     if (force && !cur_is_dest) {
         /* Ban the original location if possible */
         if(current) {
             (void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime,
                                    cib, cib_options, promoted_role_only,
                                    PCMK__ROLE_PROMOTED);
         } else if(count > 1) {
             out->info(out, "Resource '%s' is currently %s in %d locations. "
                       "One may now move to %s",
                       rsc_id, (promoted_role_only? "promoted" : "active"),
                       count, pcmk__node_name(dest));
             out->info(out, "To prevent '%s' from being %s at a specific location, "
                       "specify a node.",
                       rsc_id, (promoted_role_only? "promoted" : "active"));
 
         } else {
             crm_trace("Not banning %s from its current location: not active", rsc_id);
         }
     }
 
     return rc;
 }