diff --git a/include/pcmki/pcmki_scheduler.h b/include/pcmki/pcmki_scheduler.h
index 079d6431df..9c3cc43469 100644
--- a/include/pcmki/pcmki_scheduler.h
+++ b/include/pcmki/pcmki_scheduler.h
@@ -1,112 +1,112 @@
 /*
  * Copyright 2014-2021 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 PENGINE__H
 #  define PENGINE__H
 
 typedef struct rsc_ticket_s rsc_ticket_t;
 typedef struct lrm_agent_s lrm_agent_t;
 
 #  include <glib.h>
 #  include <crm/crm.h>
 #  include <crm/common/iso8601.h>
 #  include <crm/pengine/rules.h>
 #  include <crm/pengine/common.h>
 #  include <crm/pengine/status.h>
 
 #  include <crm/pengine/complex.h>
 
 enum pe_stop_fail {
     pesf_block,
     pesf_stonith,
     pesf_ignore
 };
 
 enum pe_weights {
     pe_weights_none = 0x0,
     pe_weights_init = 0x1,
     pe_weights_forward = 0x4,
     pe_weights_positive = 0x8,
     pe_weights_rollback = 0x10,
 };
 
 typedef struct {
     const char *id;
     const char *node_attribute;
     pe_resource_t *rsc_lh;
     pe_resource_t *rsc_rh;
 
     int role_lh;
     int role_rh;
 
     int score;
     bool influence; // Whether rsc_lh should influence active rsc_rh placement
 } pcmk__colocation_t;
 
 enum loss_ticket_policy_e {
     loss_ticket_stop,
     loss_ticket_demote,
     loss_ticket_fence,
     loss_ticket_freeze
 };
 
 struct rsc_ticket_s {
     const char *id;
     pe_resource_t *rsc_lh;
     pe_ticket_t *ticket;
     enum loss_ticket_policy_e loss_policy;
 
     int role_lh;
 };
 
 extern gboolean stage0(pe_working_set_t * data_set);
 extern gboolean probe_resources(pe_working_set_t * data_set);
 extern gboolean stage2(pe_working_set_t * data_set);
 extern gboolean stage3(pe_working_set_t * data_set);
 extern gboolean stage4(pe_working_set_t * data_set);
 extern gboolean stage5(pe_working_set_t * data_set);
 extern gboolean stage6(pe_working_set_t * data_set);
 extern gboolean stage8(pe_working_set_t * data_set);
 
-extern gboolean unpack_constraints(xmlNode * xml_constraints, pe_working_set_t * data_set);
+void pcmk__unpack_constraints(pe_working_set_t *data_set);
 
 extern void graph_element_from_action(pe_action_t * action, pe_working_set_t * data_set);
 extern void add_maintenance_update(pe_working_set_t *data_set);
 xmlNode *pcmk__schedule_actions(pe_working_set_t *data_set, xmlNode *xml_input,
                                 crm_time_t *now);
 
 extern const char *transition_idle_timeout;
 
 /*!
  * \internal
  * \brief Check whether colocation's left-hand preferences should be considered
  *
  * \param[in] colocation  Colocation constraint
  * \param[in] rsc         Right-hand instance (normally this will be
  *                        colocation->rsc_rh, which NULL will be treated as,
  *                        but for clones or bundles with multiple instances
  *                        this can be a particular instance)
  *
  * \return true if colocation influence should be effective, otherwise false
  */
 static inline bool
 pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
                                const pe_resource_t *rsc)
 {
     if (rsc == NULL) {
         rsc = colocation->rsc_rh;
     }
 
     /* The left hand of a colocation influences the right hand's location
      * if the influence option is true, or the right hand is not yet active.
      */
     return colocation->influence || (rsc->running_on == NULL);
 }
 
 #endif
diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c
index 8675d0bc83..14b6ae831a 100644
--- a/lib/pacemaker/pcmk_output.c
+++ b/lib/pacemaker/pcmk_output.c
@@ -1,1844 +1,1838 @@
 /*
  * Copyright 2019-2021 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/common/output.h>
 #include <crm/common/results.h>
 #include <crm/msg_xml.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/pengine/internal.h>
 #include <libxml/tree.h>
 #include <pacemaker-internal.h>
 
 static char *
 colocations_header(pe_resource_t *rsc, pcmk__colocation_t *cons,
                    gboolean dependents) {
     char *score = NULL;
     char *retval = NULL;
 
     score = score2char(cons->score);
     if (cons->role_rh > RSC_ROLE_STARTED) {
             retval = crm_strdup_printf("%s (score=%s, %s role=%s, id=%s)",
                                        rsc->id, score, dependents ? "needs" : "with",
                                        role2text(cons->role_rh), cons->id);
     } else {
         retval = crm_strdup_printf("%s (score=%s, id=%s)",
                                    rsc->id, score, cons->id);
     }
 
     free(score);
     return retval;
 }
 
 static void
 colocations_xml_node(pcmk__output_t *out, pe_resource_t *rsc,
                      pcmk__colocation_t *cons) {
     char *score = NULL;
     xmlNodePtr node = NULL;
 
     score = score2char(cons->score);
     node = pcmk__output_create_xml_node(out, XML_CONS_TAG_RSC_DEPEND,
                                         "id", cons->id,
                                         "rsc", cons->rsc_lh->id,
                                         "with-rsc", cons->rsc_rh->id,
                                         "score", score,
                                         NULL);
 
     if (cons->node_attribute) {
         xmlSetProp(node, (pcmkXmlStr) "node-attribute", (pcmkXmlStr) cons->node_attribute);
     }
 
     if (cons->role_lh != RSC_ROLE_UNKNOWN) {
         xmlSetProp(node, (pcmkXmlStr) "rsc-role", (pcmkXmlStr) role2text(cons->role_lh));
     }
 
     if (cons->role_rh != RSC_ROLE_UNKNOWN) {
         xmlSetProp(node, (pcmkXmlStr) "with-rsc-role", (pcmkXmlStr) role2text(cons->role_rh));
     }
 
     free(score);
 }
 
 static int
 do_locations_list_xml(pcmk__output_t *out, pe_resource_t *rsc, bool add_header)
 {
     GList *lpc = NULL;
     GList *list = rsc->rsc_location;
     int rc = pcmk_rc_no_output;
 
     for (lpc = list; lpc != NULL; lpc = lpc->next) {
         pe__location_t *cons = lpc->data;
 
         GList *lpc2 = NULL;
 
         for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) {
             pe_node_t *node = (pe_node_t *) lpc2->data;
             char *score = score2char(node->weight);
 
             if (add_header) {
                 PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "locations");
             }
 
             pcmk__output_create_xml_node(out, XML_CONS_TAG_RSC_LOCATION,
                                          "node", node->details->uname,
                                          "rsc", rsc->id,
                                          "id", cons->id,
                                          "score", score,
                                          NULL);
             free(score);
         }
     }
 
     if (add_header) {
         PCMK__OUTPUT_LIST_FOOTER(out, rc);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rsc-action-item", "const char *", "pe_resource_t *",
                   "pe_node_t *", "pe_node_t *", "pe_action_t *",
                   "pe_action_t *")
 static int
 rsc_action_item(pcmk__output_t *out, va_list args)
 {
     const char *change = va_arg(args, const char *);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *origin = va_arg(args, pe_node_t *);
     pe_node_t *destination = va_arg(args, pe_node_t *);
     pe_action_t *action = va_arg(args, pe_action_t *);
     pe_action_t *source = va_arg(args, pe_action_t *);
 
     int len = 0;
     char *reason = NULL;
     char *details = NULL;
     bool same_host = FALSE;
     bool same_role = FALSE;
     bool need_role = FALSE;
 
     static int rsc_width = 5;
     static int detail_width = 5;
 
     CRM_ASSERT(action);
     CRM_ASSERT(destination != NULL || origin != NULL);
 
     if(source == NULL) {
         source = action;
     }
 
     len = strlen(rsc->id);
     if(len > rsc_width) {
         rsc_width = len + 2;
     }
 
     if ((rsc->role > RSC_ROLE_STARTED)
         || (rsc->next_role > RSC_ROLE_UNPROMOTED)) {
         need_role = TRUE;
     }
 
     if(origin != NULL && destination != NULL && origin->details == destination->details) {
         same_host = TRUE;
     }
 
     if(rsc->role == rsc->next_role) {
         same_role = TRUE;
     }
 
     if (need_role && (origin == NULL)) {
         /* Starting and promoting a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", role2text(rsc->role), role2text(rsc->next_role), destination->details->uname);
 
     } else if (origin == NULL) {
         /* Starting a resource */
         details = crm_strdup_printf("%s", destination->details->uname);
 
     } else if (need_role && (destination == NULL)) {
         /* Stopping a promotable clone instance */
         details = crm_strdup_printf("%s %s", role2text(rsc->role), origin->details->uname);
 
     } else if (destination == NULL) {
         /* Stopping a resource */
         details = crm_strdup_printf("%s", origin->details->uname);
 
     } else if (need_role && same_role && same_host) {
         /* Recovering, restarting or re-promoting a promotable clone instance */
         details = crm_strdup_printf("%s %s", role2text(rsc->role), origin->details->uname);
 
     } else if (same_role && same_host) {
         /* Recovering or Restarting a normal resource */
         details = crm_strdup_printf("%s", origin->details->uname);
 
     } else if (need_role && same_role) {
         /* Moving a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", origin->details->uname, destination->details->uname, role2text(rsc->role));
 
     } else if (same_role) {
         /* Moving a normal resource */
         details = crm_strdup_printf("%s -> %s", origin->details->uname, destination->details->uname);
 
     } else if (same_host) {
         /* Promoting or demoting a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", role2text(rsc->role), role2text(rsc->next_role), origin->details->uname);
 
     } else {
         /* Moving and promoting/demoting */
         details = crm_strdup_printf("%s %s -> %s %s", role2text(rsc->role), origin->details->uname, role2text(rsc->next_role), destination->details->uname);
     }
 
     len = strlen(details);
     if(len > detail_width) {
         detail_width = len;
     }
 
     if(source->reason && !pcmk_is_set(action->flags, pe_action_runnable)) {
         reason = crm_strdup_printf("due to %s (blocked)", source->reason);
 
     } else if(source->reason) {
         reason = crm_strdup_printf("due to %s", source->reason);
 
     } else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
         reason = strdup("blocked");
 
     }
 
     out->list_item(out, NULL, "%-8s   %-*s   ( %*s )%s%s", change, rsc_width,
                    rsc->id, detail_width, details, reason ? "  " : "", reason ? reason : "");
 
     free(details);
     free(reason);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("rsc-action-item", "const char *", "pe_resource_t *",
                   "pe_node_t *", "pe_node_t *", "pe_action_t *",
                   "pe_action_t *")
 static int
 rsc_action_item_xml(pcmk__output_t *out, va_list args)
 {
     const char *change = va_arg(args, const char *);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *origin = va_arg(args, pe_node_t *);
     pe_node_t *destination = va_arg(args, pe_node_t *);
     pe_action_t *action = va_arg(args, pe_action_t *);
     pe_action_t *source = va_arg(args, pe_action_t *);
 
     char *change_str = NULL;
 
     bool same_host = FALSE;
     bool same_role = FALSE;
     bool need_role = FALSE;
     xmlNode *xml = NULL;
 
     CRM_ASSERT(action);
     CRM_ASSERT(destination != NULL || origin != NULL);
 
     if (source == NULL) {
         source = action;
     }
 
     if ((rsc->role > RSC_ROLE_STARTED)
         || (rsc->next_role > RSC_ROLE_UNPROMOTED)) {
         need_role = TRUE;
     }
 
     if(origin != NULL && destination != NULL && origin->details == destination->details) {
         same_host = TRUE;
     }
 
     if(rsc->role == rsc->next_role) {
         same_role = TRUE;
     }
 
     change_str = g_ascii_strdown(change, -1);
     xml = pcmk__output_create_xml_node(out, "rsc_action",
                                        "action", change_str,
                                        "resource", rsc->id,
                                        NULL);
     g_free(change_str);
 
     if (need_role && (origin == NULL)) {
         /* Starting and promoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "next-role", role2text(rsc->next_role),
                            "dest", destination->details->uname,
                            NULL);
 
     } else if (origin == NULL) {
         /* Starting a resource */
         crm_xml_add(xml, "node", destination->details->uname);
 
     } else if (need_role && (destination == NULL)) {
         /* Stopping a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "node", origin->details->uname,
                            NULL);
 
     } else if (destination == NULL) {
         /* Stopping a resource */
         crm_xml_add(xml, "node", origin->details->uname);
 
     } else if (need_role && same_role && same_host) {
         /* Recovering, restarting or re-promoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "source", origin->details->uname,
                            NULL);
 
     } else if (same_role && same_host) {
         /* Recovering or Restarting a normal resource */
         crm_xml_add(xml, "source", origin->details->uname);
 
     } else if (need_role && same_role) {
         /* Moving a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "source", origin->details->uname,
                            "dest", destination->details->uname,
                            "role", role2text(rsc->role),
                            NULL);
 
     } else if (same_role) {
         /* Moving a normal resource */
         pcmk__xe_set_props(xml,
                            "source", origin->details->uname,
                            "dest", destination->details->uname,
                            NULL);
 
     } else if (same_host) {
         /* Promoting or demoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "next-role", role2text(rsc->next_role),
                            "source", origin->details->uname,
                            NULL);
 
     } else {
         /* Moving and promoting/demoting */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "source", origin->details->uname,
                            "next-role", role2text(rsc->next_role),
                            "dest", destination->details->uname,
                            NULL);
     }
 
     if (source->reason && !pcmk_is_set(action->flags, pe_action_runnable)) {
         pcmk__xe_set_props(xml,
                            "reason", source->reason,
                            "blocked", "true",
                            NULL);
 
     } else if(source->reason) {
         crm_xml_add(xml, "reason", source->reason);
 
     } else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
         crm_xml_add(xml, "blocked", "true");
 
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("rsc-is-colocated-with-list", "pe_resource_t *", "gboolean")
 static int
 rsc_is_colocated_with_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     gboolean recursive = va_arg(args, gboolean);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
         char *hdr = NULL;
 
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Resources %s is colocated with", rsc->id);
 
         if (pcmk_is_set(cons->rsc_rh->flags, pe_rsc_allocating)) {
             out->list_item(out, NULL, "%s (id=%s - loop)", cons->rsc_rh->id, cons->id);
             continue;
         }
 
         hdr = colocations_header(cons->rsc_rh, cons, FALSE);
         out->list_item(out, NULL, "%s", hdr);
         free(hdr);
 
         /* Empty list header just for indentation of information about this resource. */
         out->begin_list(out, NULL, NULL, NULL);
 
         out->message(out, "locations-list", cons->rsc_rh);
         if (recursive) {
             out->message(out, "rsc-is-colocated-with-list", cons->rsc_rh, recursive);
         }
 
         out->end_list(out);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rsc-is-colocated-with-list", "pe_resource_t *", "gboolean")
 static int
 rsc_is_colocated_with_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     gboolean recursive = va_arg(args, gboolean);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
         if (pcmk_is_set(cons->rsc_rh->flags, pe_rsc_allocating)) {
             colocations_xml_node(out, cons->rsc_rh, cons);
             continue;
         }
 
         colocations_xml_node(out, cons->rsc_rh, cons);
         do_locations_list_xml(out, cons->rsc_rh, false);
 
         if (recursive) {
             out->message(out, "rsc-is-colocated-with-list", cons->rsc_rh, recursive);
         }
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rscs-colocated-with-list", "pe_resource_t *", "gboolean")
 static int
 rscs_colocated_with_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     gboolean recursive = va_arg(args, gboolean);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
         char *hdr = NULL;
 
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Resources colocated with %s", rsc->id);
 
         if (pcmk_is_set(cons->rsc_lh->flags, pe_rsc_allocating)) {
             out->list_item(out, NULL, "%s (id=%s - loop)", cons->rsc_lh->id, cons->id);
             continue;
         }
 
         hdr = colocations_header(cons->rsc_lh, cons, TRUE);
         out->list_item(out, NULL, "%s", hdr);
         free(hdr);
 
         /* Empty list header just for indentation of information about this resource. */
         out->begin_list(out, NULL, NULL, NULL);
 
         out->message(out, "locations-list", cons->rsc_lh);
         if (recursive) {
             out->message(out, "rscs-colocated-with-list", cons->rsc_lh, recursive);
         }
 
         out->end_list(out);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rscs-colocated-with-list", "pe_resource_t *", "gboolean")
 static int
 rscs_colocated_with_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     gboolean recursive = va_arg(args, gboolean);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
         if (pcmk_is_set(cons->rsc_lh->flags, pe_rsc_allocating)) {
             colocations_xml_node(out, cons->rsc_lh, cons);
             continue;
         }
 
         colocations_xml_node(out, cons->rsc_lh, cons);
         do_locations_list_xml(out, cons->rsc_lh, false);
 
         if (recursive) {
             out->message(out, "rscs-colocated-with-list", cons->rsc_lh, recursive);
         }
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *")
 static int
 locations_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
 
     GList *lpc = NULL;
     GList *list = rsc->rsc_location;
     int rc = pcmk_rc_no_output;
 
     for (lpc = list; lpc != NULL; lpc = lpc->next) {
         pe__location_t *cons = lpc->data;
 
         GList *lpc2 = NULL;
 
         for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) {
             pe_node_t *node = (pe_node_t *) lpc2->data;
             char *score = score2char(node->weight);
 
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Locations");
             out->list_item(out, NULL, "Node %s (score=%s, id=%s, rsc=%s)",
                            node->details->uname, score, cons->id, rsc->id);
             free(score);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *")
 static int
 locations_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     return do_locations_list_xml(out, rsc, true);
 }
 
 PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean")
 static int
 stacks_and_constraints(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     gboolean recursive = va_arg(args, gboolean);
 
-    xmlNodePtr cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
-                                                 data_set->input);
-
-    unpack_constraints(cib_constraints, data_set);
+    pcmk__unpack_constraints(data_set);
 
     // Constraints apply to group/clone, not member/instance
     rsc = uber_parent(rsc);
 
     out->message(out, "locations-list", rsc);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rscs-colocated-with-list", rsc, recursive);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rsc-is-colocated-with-list", rsc, recursive);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean")
 static int
 stacks_and_constraints_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     gboolean recursive = va_arg(args, gboolean);
 
-    xmlNodePtr cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
-                                                 data_set->input);
-
-    unpack_constraints(cib_constraints, data_set);
+    pcmk__unpack_constraints(data_set);
 
     // Constraints apply to group/clone, not member/instance
     rsc = uber_parent(rsc);
 
     pcmk__output_xml_create_parent(out, "constraints", NULL);
     do_locations_list_xml(out, rsc, false);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rscs-colocated-with-list", rsc, recursive);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rsc-is-colocated-with-list", rsc, recursive);
 
     pcmk__output_xml_pop_parent(out);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
 static int
 health_text(pcmk__output_t *out, va_list args)
 {
     const char *sys_from G_GNUC_UNUSED = va_arg(args, const char *);
     const char *host_from = va_arg(args, const char *);
     const char *fsa_state = va_arg(args, const char *);
     const char *result = va_arg(args, const char *);
 
     if (!out->is_quiet(out)) {
         return out->info(out, "Controller on %s in state %s: %s", crm_str(host_from),
                          crm_str(fsa_state), crm_str(result));
     } else if (fsa_state != NULL) {
         pcmk__formatted_printf(out, "%s\n", fsa_state);
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
 static int
 health_xml(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     const char *host_from = va_arg(args, const char *);
     const char *fsa_state = va_arg(args, const char *);
     const char *result = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, crm_str(sys_from),
                                  "node_name", crm_str(host_from),
                                  "state", crm_str(fsa_state),
                                  "result", crm_str(result),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "const char *", "const char *")
 static int
 pacemakerd_health_text(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     const char *state = va_arg(args, const char *);
     const char *last_updated = va_arg(args, const char *);
 
     if (!out->is_quiet(out)) {
         return out->info(out, "Status of %s: '%s' %s %s", crm_str(sys_from),
                          crm_str(state), (!pcmk__str_empty(last_updated))?
                          "last updated":"", crm_str(last_updated));
     } else {
         pcmk__formatted_printf(out, "%s\n", crm_str(state));
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "const char *", "const char *")
 static int
 pacemakerd_health_xml(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     const char *state = va_arg(args, const char *);
     const char *last_updated = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, crm_str(sys_from),
                                  "state", crm_str(state),
                                  "last_updated", crm_str(last_updated),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_default(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     out->list_item(out, NULL, "Testing %s ... %.2f secs", xml_file,
                    (end - start) / (float) CLOCKS_PER_SEC);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_xml(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     char *duration = pcmk__ftoa((end - start) / (float) CLOCKS_PER_SEC);
 
     pcmk__output_create_xml_node(out, "timing",
                                  "file", xml_file,
                                  "duration", duration,
                                  NULL);
 
     free(duration);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("dc", "const char *")
 static int
 dc_text(pcmk__output_t *out, va_list args)
 {
     const char *dc = va_arg(args, const char *);
 
     if (!out->is_quiet(out)) {
         return out->info(out, "Designated Controller is: %s", crm_str(dc));
     } else if (dc != NULL) {
         pcmk__formatted_printf(out, "%s\n", dc);
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("dc", "const char *")
 static int
 dc_xml(pcmk__output_t *out, va_list args)
 {
     const char *dc = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, "dc",
                                  "node_name", crm_str(dc),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *", "gboolean")
 static int
 crmadmin_node_text(pcmk__output_t *out, va_list args)
 {
     const char *type = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *id = va_arg(args, const char *);
     gboolean BASH_EXPORT = va_arg(args, gboolean);
 
     if (out->is_quiet(out)) {
         pcmk__formatted_printf(out, "%s\n", crm_str(name));
         return pcmk_rc_ok;
     } else if (BASH_EXPORT) {
         return out->info(out, "export %s=%s", crm_str(name), crm_str(id));
     } else {
         return out->info(out, "%s node: %s (%s)", type ? type : "cluster",
                          crm_str(name), crm_str(id));
     }
 }
 
 PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *", "gboolean")
 static int
 crmadmin_node_xml(pcmk__output_t *out, va_list args)
 {
     const char *type = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *id = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, "node",
                                  "type", type ? type : "cluster",
                                  "name", crm_str(name),
                                  "id", crm_str(id),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *",
                   "guint", "op_digest_cache_t *")
 static int
 digests_text(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *task = va_arg(args, const char *);
     guint interval_ms = va_arg(args, guint);
     op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *);
 
     char *action_desc = NULL;
     const char *rsc_desc = "unknown resource";
     const char *node_desc = "unknown node";
 
     if (interval_ms != 0) {
         action_desc = crm_strdup_printf("%ums-interval %s action", interval_ms,
                                         ((task == NULL)? "unknown" : task));
     } else if (pcmk__str_eq(task, "monitor", pcmk__str_none)) {
         action_desc = strdup("probe action");
     } else {
         action_desc = crm_strdup_printf("%s action",
                                         ((task == NULL)? "unknown" : task));
     }
     if ((rsc != NULL) && (rsc->id != NULL)) {
         rsc_desc = rsc->id;
     }
     if ((node != NULL) && (node->details->uname != NULL)) {
         node_desc = node->details->uname;
     }
     out->begin_list(out, NULL, NULL, "Digests for %s %s on %s",
                     rsc_desc, action_desc, node_desc);
     free(action_desc);
 
     if (digests == NULL) {
         out->list_item(out, NULL, "none");
         out->end_list(out);
         return pcmk_rc_ok;
     }
     if (digests->digest_all_calc != NULL) {
         out->list_item(out, NULL, "%s (all parameters)",
                        digests->digest_all_calc);
     }
     if (digests->digest_secure_calc != NULL) {
         out->list_item(out, NULL, "%s (non-private parameters)",
                        digests->digest_secure_calc);
     }
     if (digests->digest_restart_calc != NULL) {
         out->list_item(out, NULL, "%s (non-reloadable parameters)",
                        digests->digest_restart_calc);
     }
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 static void
 add_digest_xml(xmlNode *parent, const char *type, const char *digest,
                xmlNode *digest_source)
 {
     if (digest != NULL) {
         xmlNodePtr digest_xml = create_xml_node(parent, "digest");
 
         crm_xml_add(digest_xml, "type", ((type == NULL)? "unspecified" : type));
         crm_xml_add(digest_xml, "hash", digest);
         if (digest_source != NULL) {
             add_node_copy(digest_xml, digest_source);
         }
     }
 }
 
 PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *",
                   "guint", "op_digest_cache_t *")
 static int
 digests_xml(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *task = va_arg(args, const char *);
     guint interval_ms = va_arg(args, guint);
     op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *);
 
     char *interval_s = crm_strdup_printf("%ums", interval_ms);
     xmlNode *xml = NULL;
 
     xml = pcmk__output_create_xml_node(out, "digests",
                                        "resource", crm_str(rsc->id),
                                        "node", crm_str(node->details->uname),
                                        "task", crm_str(task),
                                        "interval", interval_s,
                                        NULL);
     free(interval_s);
     if (digests != NULL) {
         add_digest_xml(xml, "all", digests->digest_all_calc,
                        digests->params_all);
         add_digest_xml(xml, "nonprivate", digests->digest_secure_calc,
                        digests->params_secure);
         add_digest_xml(xml, "nonreloadable", digests->digest_restart_calc,
                        digests->params_restart);
     }
     return pcmk_rc_ok;
 }
 
 #define STOP_SANITY_ASSERT(lineno) do {                                 \
         if(current && current->details->unclean) {                      \
             /* It will be a pseudo op */                                \
         } else if(stop == NULL) {                                       \
             crm_err("%s:%d: No stop action exists for %s",              \
                     __func__, lineno, rsc->id);                         \
             CRM_ASSERT(stop != NULL);                                   \
         } else if (pcmk_is_set(stop->flags, pe_action_optional)) {      \
             crm_err("%s:%d: Action %s is still optional",               \
                     __func__, lineno, stop->uuid);                      \
             CRM_ASSERT(!pcmk_is_set(stop->flags, pe_action_optional));  \
         }                                                               \
     } while(0)
 
 PCMK__OUTPUT_ARGS("rsc-action", "pe_resource_t *", "pe_node_t *", "pe_node_t *",
                   "gboolean")
 static int
 rsc_action_default(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *current = va_arg(args, pe_node_t *);
     pe_node_t *next = va_arg(args, pe_node_t *);
     gboolean moving = va_arg(args, gboolean);
 
     GList *possible_matches = NULL;
     char *key = NULL;
     int rc = pcmk_rc_no_output;
 
     pe_node_t *start_node = NULL;
     pe_action_t *start = NULL;
     pe_action_t *stop = NULL;
     pe_action_t *promote = NULL;
     pe_action_t *demote = NULL;
 
     if (!pcmk_is_set(rsc->flags, pe_rsc_managed)
         || (current == NULL && next == NULL)) {
         pe_rsc_info(rsc, "Leave   %s\t(%s%s)",
                     rsc->id, role2text(rsc->role),
                     !pcmk_is_set(rsc->flags, pe_rsc_managed)? " unmanaged" : "");
         return rc;
     }
 
     if (current != NULL && next != NULL && !pcmk__str_eq(current->details->id, next->details->id, pcmk__str_casei)) {
         moving = TRUE;
     }
 
     possible_matches = pe__resource_actions(rsc, next, RSC_START, FALSE);
     if (possible_matches) {
         start = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     if ((start == NULL) || !pcmk_is_set(start->flags, pe_action_runnable)) {
         start_node = NULL;
     } else {
         start_node = current;
     }
     possible_matches = pe__resource_actions(rsc, start_node, RSC_STOP, FALSE);
     if (possible_matches) {
         stop = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     possible_matches = pe__resource_actions(rsc, next, RSC_PROMOTE, FALSE);
     if (possible_matches) {
         promote = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     possible_matches = pe__resource_actions(rsc, next, RSC_DEMOTE, FALSE);
     if (possible_matches) {
         demote = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     if (rsc->role == rsc->next_role) {
         pe_action_t *migrate_op = NULL;
 
         possible_matches = pe__resource_actions(rsc, next, RSC_MIGRATED, FALSE);
         if (possible_matches) {
             migrate_op = possible_matches->data;
         }
 
         CRM_CHECK(next != NULL,);
         if (next == NULL) {
         } else if ((migrate_op != NULL) && (current != NULL)
                    && pcmk_is_set(migrate_op->flags, pe_action_runnable)) {
             rc = out->message(out, "rsc-action-item", "Migrate", rsc, current,
                               next, start, NULL);
 
         } else if (pcmk_is_set(rsc->flags, pe_rsc_reload)) {
             rc = out->message(out, "rsc-action-item", "Reload", rsc, current,
                               next, start, NULL);
 
         } else if (start == NULL || pcmk_is_set(start->flags, pe_action_optional)) {
             if ((demote != NULL) && (promote != NULL)
                 && !pcmk_is_set(demote->flags, pe_action_optional)
                 && !pcmk_is_set(promote->flags, pe_action_optional)) {
                 rc = out->message(out, "rsc-action-item", "Re-promote", rsc,
                                   current, next, promote, demote);
             } else {
                 pe_rsc_info(rsc, "Leave   %s\t(%s %s)", rsc->id,
                             role2text(rsc->role), next->details->uname);
             }
 
         } else if (!pcmk_is_set(start->flags, pe_action_runnable)) {
             rc = out->message(out, "rsc-action-item", "Stop", rsc, current,
                               NULL, stop, (stop && stop->reason)? stop : start);
             STOP_SANITY_ASSERT(__LINE__);
 
         } else if (moving && current) {
             rc = out->message(out, "rsc-action-item", pcmk_is_set(rsc->flags, pe_rsc_failed)? "Recover" : "Move",
                               rsc, current, next, stop, NULL);
 
         } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
             rc = out->message(out, "rsc-action-item", "Recover", rsc, current,
                               NULL, stop, NULL);
             STOP_SANITY_ASSERT(__LINE__);
 
         } else {
             rc = out->message(out, "rsc-action-item", "Restart", rsc, current,
                               next, start, NULL);
             /* STOP_SANITY_ASSERT(__LINE__); False positive for migrate-fail-7 */
         }
 
         g_list_free(possible_matches);
         return rc;
     }
 
     if(stop
        && (rsc->next_role == RSC_ROLE_STOPPED
            || (start && !pcmk_is_set(start->flags, pe_action_runnable)))) {
 
         GList *gIter = NULL;
 
         key = stop_key(rsc);
         for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) {
             pe_node_t *node = (pe_node_t *) gIter->data;
             pe_action_t *stop_op = NULL;
 
             possible_matches = find_actions(rsc->actions, key, node);
             if (possible_matches) {
                 stop_op = possible_matches->data;
                 g_list_free(possible_matches);
             }
 
             if (stop_op && (stop_op->flags & pe_action_runnable)) {
                 STOP_SANITY_ASSERT(__LINE__);
             }
 
             if (out->message(out, "rsc-action-item", "Stop", rsc, node, NULL,
                              stop_op, (stop_op && stop_op->reason)? stop_op : start) == pcmk_rc_ok) {
                 rc = pcmk_rc_ok;
             }
         }
 
         free(key);
 
     } else if ((stop != NULL)
                && pcmk_all_flags_set(rsc->flags, pe_rsc_failed|pe_rsc_stop)) {
         /* 'stop' may be NULL if the failure was ignored */
         rc = out->message(out, "rsc-action-item", "Recover", rsc, current,
                           next, stop, start);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (moving) {
         rc = out->message(out, "rsc-action-item", "Move", rsc, current, next,
                           stop, NULL);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (pcmk_is_set(rsc->flags, pe_rsc_reload)) {
         rc = out->message(out, "rsc-action-item", "Reload", rsc, current, next,
                           start, NULL);
 
     } else if (stop != NULL && !pcmk_is_set(stop->flags, pe_action_optional)) {
         rc = out->message(out, "rsc-action-item", "Restart", rsc, current,
                           next, start, NULL);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (rsc->role == RSC_ROLE_PROMOTED) {
         CRM_LOG_ASSERT(current != NULL);
         rc = out->message(out, "rsc-action-item", "Demote", rsc, current,
                           next, demote, NULL);
 
     } else if (rsc->next_role == RSC_ROLE_PROMOTED) {
         CRM_LOG_ASSERT(next);
         rc = out->message(out, "rsc-action-item", "Promote", rsc, current,
                           next, promote, NULL);
 
     } else if (rsc->role == RSC_ROLE_STOPPED && rsc->next_role > RSC_ROLE_STOPPED) {
         rc = out->message(out, "rsc-action-item", "Start", rsc, current, next,
                           start, NULL);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-action", "char *", "char *", "char *")
 static int
 node_action(pcmk__output_t *out, va_list args)
 {
     char *task = va_arg(args, char *);
     char *node_name = va_arg(args, char *);
     char *reason = va_arg(args, char *);
 
     if (task == NULL) {
         return pcmk_rc_no_output;
     } else if (reason) {
         out->list_item(out, NULL, "%s %s '%s'", task, node_name, reason);
     } else {
         crm_notice(" * %s %s\n", task, node_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-action", "char *", "char *", "char *")
 static int
 node_action_xml(pcmk__output_t *out, va_list args)
 {
     char *task = va_arg(args, char *);
     char *node_name = va_arg(args, char *);
     char *reason = va_arg(args, char *);
 
     if (task == NULL) {
         return pcmk_rc_no_output;
     } else if (reason) {
         pcmk__output_create_xml_node(out, "node_action",
                                      "task", task,
                                      "node", node_name,
                                      "reason", reason,
                                      NULL);
     } else {
         crm_notice(" * %s %s\n", task, node_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-cluster-action", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_cluster_action(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
     xmlNodePtr rsc = va_arg(args, xmlNodePtr);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if(rsc) {
         out->list_item(out, NULL, "Cluster action:  %s for %s on %s", task, ID(rsc), node);
     } else {
         out->list_item(out, NULL, "Cluster action:  %s on %s", task, node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-cluster-action", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_cluster_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
     xmlNodePtr rsc = va_arg(args, xmlNodePtr);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "cluster_action",
                                             "task", task,
                                             "node", node,
                                             NULL);
 
     if (rsc) {
         crm_xml_add(xml_node, "id", ID(rsc));
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-fencing-action", "char *", "const char *")
 static int
 inject_fencing_action(pcmk__output_t *out, va_list args)
 {
     char *target = va_arg(args, char *);
     const char *op = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Fencing %s (%s)", target, op);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-fencing-action", "char *", "const char *")
 static int
 inject_fencing_action_xml(pcmk__output_t *out, va_list args)
 {
     char *target = va_arg(args, char *);
     const char *op = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "fencing_action",
                                  "target", target,
                                  "op", op,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-attr", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_attr(pcmk__output_t *out, va_list args)
 {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     xmlNodePtr cib_node = va_arg(args, xmlNodePtr);
 
     xmlChar *node_path = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node_path = xmlGetNodePath(cib_node);
 
     out->list_item(out, NULL, "Injecting attribute %s=%s into %s '%s'",
                    name, value, node_path, ID(cib_node));
 
     free(node_path);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-attr", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_attr_xml(pcmk__output_t *out, va_list args)
 {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     xmlNodePtr cib_node = va_arg(args, xmlNodePtr);
 
     xmlChar *node_path = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node_path = xmlGetNodePath(cib_node);
 
     pcmk__output_create_xml_node(out, "inject_attr",
                                  "name", name,
                                  "value", value,
                                  "node_path", node_path,
                                  "cib_node", ID(cib_node),
                                  NULL);
     free(node_path);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-spec", "char *")
 static int
 inject_spec(pcmk__output_t *out, va_list args)
 {
     char *spec = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Injecting %s into the configuration", spec);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-spec", "char *")
 static int
 inject_spec_xml(pcmk__output_t *out, va_list args)
 {
     char *spec = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "inject_spec",
                                  "spec", spec,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-config", "const char *", "const char *")
 static int
 inject_modify_config(pcmk__output_t *out, va_list args)
 {
     const char *quorum = va_arg(args, const char *);
     const char *watchdog = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->begin_list(out, NULL, NULL, "Performing Requested Modifications");
 
     if (quorum) {
         out->list_item(out, NULL, "Setting quorum: %s", quorum);
     }
 
     if (watchdog) {
         out->list_item(out, NULL, "Setting watchdog: %s", watchdog);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-config", "const char *", "const char *")
 static int
 inject_modify_config_xml(pcmk__output_t *out, va_list args)
 {
     const char *quorum = va_arg(args, const char *);
     const char *watchdog = va_arg(args, const char *);
 
     xmlNodePtr node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node = pcmk__output_xml_create_parent(out, "modifications", NULL);
 
     if (quorum) {
         crm_xml_add(node, "quorum", quorum);
     }
 
     if (watchdog) {
         crm_xml_add(node, "watchdog", watchdog);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-node", "const char *", "char *")
 static int
 inject_modify_node(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *node = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (pcmk__str_eq(action, "Online", pcmk__str_none)) {
         out->list_item(out, NULL, "Bringing node %s online", node);
         return pcmk_rc_ok;
     } else if (pcmk__str_eq(action, "Offline", pcmk__str_none)) {
         out->list_item(out, NULL, "Taking node %s offline", node);
         return pcmk_rc_ok;
     } else if (pcmk__str_eq(action, "Failing", pcmk__str_none)) {
         out->list_item(out, NULL, "Failing node %s", node);
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-node", "const char *", "char *")
 static int
 inject_modify_node_xml(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *node = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "modify_node",
                                  "action", action,
                                  "node", node,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-ticket", "const char *", "char *")
 static int
 inject_modify_ticket(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *ticket = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (pcmk__str_eq(action, "Standby", pcmk__str_none)) {
         out->list_item(out, NULL, "Making ticket %s standby", ticket);
     } else {
         out->list_item(out, NULL, "%s ticket %s", action, ticket);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-ticket", "const char *", "char *")
 static int
 inject_modify_ticket_xml(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *ticket = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "modify_ticket",
                                  "action", action,
                                  "ticket", ticket,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-pseudo-action", "const char *", "const char *")
 static int
 inject_pseudo_action(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Pseudo action:   %s%s%s", task, node ? " on " : "",
                    node ? node : "");
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-pseudo-action", "const char *", "const char *")
 static int
 inject_pseudo_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "pseudo_action",
                                             "task", task,
                                             NULL);
     if (node) {
         crm_xml_add(xml_node, "node", node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-rsc-action", "const char *", "const char *", "char *", "guint")
 static int
 inject_rsc_action(pcmk__output_t *out, va_list args)
 {
     const char *rsc = va_arg(args, const char *);
     const char *operation = va_arg(args, const char *);
     char *node = va_arg(args, char *);
     guint interval_ms = va_arg(args, guint);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (interval_ms) {
         out->list_item(out, NULL, "Resource action: %-15s %s=%u on %s",
                        rsc, operation, interval_ms, node);
     } else {
         out->list_item(out, NULL, "Resource action: %-15s %s on %s",
                        rsc, operation, node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-rsc-action", "const char *", "const char *", "char *", "guint")
 static int
 inject_rsc_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *rsc = va_arg(args, const char *);
     const char *operation = va_arg(args, const char *);
     char *node = va_arg(args, char *);
     guint interval_ms = va_arg(args, guint);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "rsc_action",
                                             "resource", rsc,
                                             "op", operation,
                                             "node", node,
                                             NULL);
 
     if (interval_ms) {
         char *interval_s = pcmk__itoa(interval_ms);
 
         crm_xml_add(xml_node, "interval", interval_s);
         free(interval_s);
     }
 
     return pcmk_rc_ok;
 }
 
 #define CHECK_RC(retcode, retval)   \
     if (retval == pcmk_rc_ok) {     \
         retcode = pcmk_rc_ok;       \
     }
 
 PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
                   "gboolean", "unsigned int", "unsigned int", "const char *", "GList *",
                   "GList *")
 int
 pcmk__cluster_status_text(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     gboolean fence_history = va_arg(args, gboolean);
     unsigned int section_opts = va_arg(args, unsigned int);
     unsigned int show_opts = va_arg(args, unsigned int);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
 
     int rc = pcmk_rc_no_output;
     bool already_printed_failure = false;
 
     CHECK_RC(rc, out->message(out, "cluster-summary", data_set,
                               section_opts, show_opts));
 
     if (pcmk_is_set(section_opts, pcmk_section_nodes) && unames) {
         CHECK_RC(rc, out->message(out, "node-list", data_set->nodes, unames,
                                   resources, show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         CHECK_RC(rc, out->message(out, "resource-list", data_set, show_opts,
                                   TRUE, unames, resources, rc == pcmk_rc_ok));
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         CHECK_RC(rc, out->message(out, "node-attribute-list", data_set,
                                   show_opts, rc == pcmk_rc_ok, unames, resources));
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         CHECK_RC(rc, out->message(out, "node-summary", data_set, unames,
                                   resources, section_opts, show_opts, rc == pcmk_rc_ok));
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         CHECK_RC(rc, out->message(out, "failed-action-list", data_set, unames,
                                   resources, show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print failed stonith actions */
     if (pcmk_is_set(section_opts, pcmk_section_fence_failed) && fence_history) {
         if (history_rc == 0) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "failed-fencing-list", stonith_history, unames,
                                           section_opts, rc == pcmk_rc_ok));
             }
         } else {
             PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
             out->list_item(out, NULL, "Failed to get fencing history: %s",
                            crm_exit_str(history_rc));
             out->end_list(out);
 
             already_printed_failure = true;
         }
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         CHECK_RC(rc, out->message(out, "ticket-list", data_set, rc == pcmk_rc_ok));
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         CHECK_RC(rc, out->message(out, "ban-list", data_set, prefix, resources,
                                   show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print stonith history */
     if (fence_history && pcmk_any_flags_set(section_opts, pcmk_section_fencing_all)) {
         if (history_rc != 0) {
             if (!already_printed_failure) {
                 PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
                 out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
                 out->list_item(out, NULL, "Failed to get fencing history: %s",
                                crm_exit_str(history_rc));
                 out->end_list(out);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_worked)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "fencing-list", hp, unames,
                                           section_opts, rc == pcmk_rc_ok));
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_pending)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL);
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "pending-fencing-list", hp, unames,
                                           section_opts, rc == pcmk_rc_ok));
             }
         }
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
                   "gboolean", "unsigned int", "unsigned int", "const char *", "GList *",
                   "GList *")
 static int
 cluster_status_xml(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     gboolean fence_history = va_arg(args, gboolean);
     unsigned int section_opts = va_arg(args, unsigned int);
     unsigned int show_opts = va_arg(args, unsigned int);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
 
     out->message(out, "cluster-summary", data_set, section_opts, show_opts);
 
     /*** NODES ***/
     if (pcmk_is_set(section_opts, pcmk_section_nodes)) {
         out->message(out, "node-list", data_set->nodes, unames, resources,
                      show_opts, FALSE);
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         /* XML output always displays full details. */
         unsigned int full_show_opts = show_opts & ~pcmk_show_brief;
 
         out->message(out, "resource-list", data_set, full_show_opts,
                      FALSE, unames, resources, FALSE);
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         out->message(out, "node-attribute-list", data_set, show_opts, FALSE,
                      unames, resources);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         out->message(out, "node-summary", data_set, unames,
                      resources, section_opts, show_opts, FALSE);
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         out->message(out, "failed-action-list", data_set, unames, resources,
                      show_opts, FALSE);
     }
 
     /* Print stonith history */
     if (pcmk_is_set(section_opts, pcmk_section_fencing_all) && fence_history) {
         out->message(out, "full-fencing-list", history_rc, stonith_history,
                      unames, section_opts, FALSE);
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         out->message(out, "ticket-list", data_set, FALSE);
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         out->message(out, "ban-list", data_set, prefix, resources, show_opts,
                      FALSE);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
                   "gboolean", "unsigned int", "unsigned int", "const char *", "GList *",
                   "GList *")
 static int
 cluster_status_html(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     gboolean fence_history = va_arg(args, gboolean);
     unsigned int section_opts = va_arg(args, unsigned int);
     unsigned int show_opts = va_arg(args, unsigned int);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
     bool already_printed_failure = false;
 
     out->message(out, "cluster-summary", data_set, section_opts, show_opts);
 
     /*** NODE LIST ***/
     if (pcmk_is_set(section_opts, pcmk_section_nodes) && unames) {
         out->message(out, "node-list", data_set->nodes, unames, resources,
                      show_opts, FALSE);
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         out->message(out, "resource-list", data_set, show_opts, TRUE, unames,
                      resources, FALSE);
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         out->message(out, "node-attribute-list", data_set, show_opts, FALSE,
                      unames, resources);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         out->message(out, "node-summary", data_set, unames,
                      resources, section_opts, show_opts, FALSE);
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         out->message(out, "failed-action-list", data_set, unames, resources,
                      show_opts, FALSE);
     }
 
     /* Print failed stonith actions */
     if (pcmk_is_set(section_opts, pcmk_section_fence_failed) && fence_history) {
         if (history_rc == 0) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 out->message(out, "failed-fencing-list", stonith_history, unames,
                              section_opts, FALSE);
             }
         } else {
             out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
             out->list_item(out, NULL, "Failed to get fencing history: %s",
                            crm_exit_str(history_rc));
             out->end_list(out);
         }
     }
 
     /* Print stonith history */
     if (fence_history && pcmk_any_flags_set(section_opts, pcmk_section_fencing_all)) {
         if (history_rc != 0) {
             if (!already_printed_failure) {
                 out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
                 out->list_item(out, NULL, "Failed to get fencing history: %s",
                                crm_exit_str(history_rc));
                 out->end_list(out);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_worked)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 out->message(out, "fencing-list", hp, unames, section_opts, FALSE);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_pending)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL);
 
             if (hp) {
                 out->message(out, "pending-fencing-list", hp, unames,
                              section_opts, FALSE);
             }
         }
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         out->message(out, "ticket-list", data_set, FALSE);
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         out->message(out, "ban-list", data_set, prefix, resources, show_opts,
                      FALSE);
     }
 
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "cluster-status", "default", pcmk__cluster_status_text },
     { "cluster-status", "html", cluster_status_html },
     { "cluster-status", "xml", cluster_status_xml },
     { "crmadmin-node", "default", crmadmin_node_text },
     { "crmadmin-node", "xml", crmadmin_node_xml },
     { "dc", "default", dc_text },
     { "dc", "xml", dc_xml },
     { "digests", "default", digests_text },
     { "digests", "xml", digests_xml },
     { "health", "default", health_text },
     { "health", "xml", health_xml },
     { "inject-attr", "default", inject_attr },
     { "inject-attr", "xml", inject_attr_xml },
     { "inject-cluster-action", "default", inject_cluster_action },
     { "inject-cluster-action", "xml", inject_cluster_action_xml },
     { "inject-fencing-action", "default", inject_fencing_action },
     { "inject-fencing-action", "xml", inject_fencing_action_xml },
     { "inject-modify-config", "default", inject_modify_config },
     { "inject-modify-config", "xml", inject_modify_config_xml },
     { "inject-modify-node", "default", inject_modify_node },
     { "inject-modify-node", "xml", inject_modify_node_xml },
     { "inject-modify-ticket", "default", inject_modify_ticket },
     { "inject-modify-ticket", "xml", inject_modify_ticket_xml },
     { "inject-pseudo-action", "default", inject_pseudo_action },
     { "inject-pseudo-action", "xml", inject_pseudo_action_xml },
     { "inject-rsc-action", "default", inject_rsc_action },
     { "inject-rsc-action", "xml", inject_rsc_action_xml },
     { "inject-spec", "default", inject_spec },
     { "inject-spec", "xml", inject_spec_xml },
     { "locations-list", "default", locations_list },
     { "locations-list", "xml", locations_list_xml },
     { "node-action", "default", node_action },
     { "node-action", "xml", node_action_xml },
     { "pacemakerd-health", "default", pacemakerd_health_text },
     { "pacemakerd-health", "xml", pacemakerd_health_xml },
     { "profile", "default", profile_default, },
     { "profile", "xml", profile_xml },
     { "rsc-action", "default", rsc_action_default },
     { "rsc-action-item", "default", rsc_action_item },
     { "rsc-action-item", "xml", rsc_action_item_xml },
     { "rsc-is-colocated-with-list", "default", rsc_is_colocated_with_list },
     { "rsc-is-colocated-with-list", "xml", rsc_is_colocated_with_list_xml },
     { "rscs-colocated-with-list", "default", rscs_colocated_with_list },
     { "rscs-colocated-with-list", "xml", rscs_colocated_with_list_xml },
     { "stacks-constraints", "default", stacks_and_constraints },
     { "stacks-constraints", "xml", stacks_and_constraints_xml },
 
     { NULL, NULL, NULL }
 };
 
 void
 pcmk__register_lib_messages(pcmk__output_t *out) {
     pcmk__register_messages(out, fmt_functions);
 }
diff --git a/lib/pacemaker/pcmk_sched_allocate.c b/lib/pacemaker/pcmk_sched_allocate.c
index e2c81c6dda..f1014cddc7 100644
--- a/lib/pacemaker/pcmk_sched_allocate.c
+++ b/lib/pacemaker/pcmk_sched_allocate.c
@@ -1,2644 +1,2642 @@
 /*
  * Copyright 2004-2021 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/param.h>
 
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <glib.h>
 
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 CRM_TRACE_INIT_DATA(pacemaker);
 
 extern bool pcmk__is_daemon;
 
 void set_alloc_actions(pe_working_set_t * data_set);
 extern void ReloadRsc(pe_resource_t * rsc, pe_node_t *node, pe_working_set_t * data_set);
 extern gboolean DeleteRsc(pe_resource_t * rsc, pe_node_t * node, gboolean optional, pe_working_set_t * data_set);
 static void apply_remote_node_ordering(pe_working_set_t *data_set);
 static enum remote_connection_state get_remote_node_state(pe_node_t *node);
 
 enum remote_connection_state {
     remote_state_unknown = 0,
     remote_state_alive = 1,
     remote_state_resting = 2,
     remote_state_failed = 3,
     remote_state_stopped = 4
 };
 
 static const char *
 state2text(enum remote_connection_state state)
 {
     switch (state) {
         case remote_state_unknown:
             return "unknown";
         case remote_state_alive:
             return "alive";
         case remote_state_resting:
             return "resting";
         case remote_state_failed:
             return "failed";
         case remote_state_stopped:
             return "stopped";
     }
 
     return "impossible";
 }
 
 resource_alloc_functions_t resource_class_alloc_functions[] = {
     {
      pcmk__native_merge_weights,
      pcmk__native_allocate,
      native_create_actions,
      native_create_probe,
      native_internal_constraints,
      native_rsc_colocation_lh,
      native_rsc_colocation_rh,
      native_rsc_location,
      native_action_flags,
      native_update_actions,
      native_expand,
      native_append_meta,
      },
     {
      pcmk__group_merge_weights,
      pcmk__group_allocate,
      group_create_actions,
      native_create_probe,
      group_internal_constraints,
      group_rsc_colocation_lh,
      group_rsc_colocation_rh,
      group_rsc_location,
      group_action_flags,
      group_update_actions,
      group_expand,
      group_append_meta,
      },
     {
      pcmk__native_merge_weights,
      pcmk__clone_allocate,
      clone_create_actions,
      clone_create_probe,
      clone_internal_constraints,
      clone_rsc_colocation_lh,
      clone_rsc_colocation_rh,
      clone_rsc_location,
      clone_action_flags,
      pcmk__multi_update_actions,
      clone_expand,
      clone_append_meta,
      },
     {
      pcmk__native_merge_weights,
      pcmk__bundle_allocate,
      pcmk__bundle_create_actions,
      pcmk__bundle_create_probe,
      pcmk__bundle_internal_constraints,
      pcmk__bundle_rsc_colocation_lh,
      pcmk__bundle_rsc_colocation_rh,
      pcmk__bundle_rsc_location,
      pcmk__bundle_action_flags,
      pcmk__multi_update_actions,
      pcmk__bundle_expand,
      pcmk__bundle_append_meta,
      }
 };
 
 static gboolean
 check_rsc_parameters(pe_resource_t * rsc, pe_node_t * node, xmlNode * rsc_entry,
                      gboolean active_here, pe_working_set_t * data_set)
 {
     int attr_lpc = 0;
     gboolean force_restart = FALSE;
     gboolean delete_resource = FALSE;
     gboolean changed = FALSE;
 
     const char *value = NULL;
     const char *old_value = NULL;
 
     const char *attr_list[] = {
         XML_ATTR_TYPE,
         XML_AGENT_ATTR_CLASS,
         XML_AGENT_ATTR_PROVIDER
     };
 
     for (; attr_lpc < PCMK__NELEM(attr_list); attr_lpc++) {
         value = crm_element_value(rsc->xml, attr_list[attr_lpc]);
         old_value = crm_element_value(rsc_entry, attr_list[attr_lpc]);
         if (value == old_value  /* i.e. NULL */
             || pcmk__str_eq(value, old_value, pcmk__str_none)) {
             continue;
         }
 
         changed = TRUE;
         trigger_unfencing(rsc, node, "Device definition changed", NULL, data_set);
         if (active_here) {
             force_restart = TRUE;
             crm_notice("Forcing restart of %s on %s, %s changed: %s -> %s",
                        rsc->id, node->details->uname, attr_list[attr_lpc],
                        crm_str(old_value), crm_str(value));
         }
     }
     if (force_restart) {
         /* make sure the restart happens */
         stop_action(rsc, node, FALSE);
         pe__set_resource_flags(rsc, pe_rsc_start_pending);
         delete_resource = TRUE;
 
     } else if (changed) {
         delete_resource = TRUE;
     }
     return delete_resource;
 }
 
 static void
 CancelXmlOp(pe_resource_t * rsc, xmlNode * xml_op, pe_node_t * active_node,
             const char *reason, pe_working_set_t * data_set)
 {
     guint interval_ms = 0;
     pe_action_t *cancel = NULL;
 
     const char *task = NULL;
     const char *call_id = NULL;
 
     CRM_CHECK(xml_op != NULL, return);
     CRM_CHECK(active_node != NULL, return);
 
     task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
     crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
 
     crm_info("Action " PCMK__OP_FMT " on %s will be stopped: %s",
              rsc->id, task, interval_ms,
              active_node->details->uname, (reason? reason : "unknown"));
 
     cancel = pe_cancel_op(rsc, task, interval_ms, active_node, data_set);
     add_hash_param(cancel->meta, XML_LRM_ATTR_CALLID, call_id);
     pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel,
                        pe_order_optional, data_set);
 }
 
 static gboolean
 check_action_definition(pe_resource_t * rsc, pe_node_t * active_node, xmlNode * xml_op,
                         pe_working_set_t * data_set)
 {
     char *key = NULL;
     guint interval_ms = 0;
     const op_digest_cache_t *digest_data = NULL;
     gboolean did_change = FALSE;
 
     const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     const char *digest_secure = NULL;
 
     CRM_CHECK(active_node != NULL, return FALSE);
 
     crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
     if (interval_ms > 0) {
         xmlNode *op_match = NULL;
 
         /* we need to reconstruct the key because of the way we used to construct resource IDs */
         key = pcmk__op_key(rsc->id, task, interval_ms);
 
         pe_rsc_trace(rsc, "Checking parameters for %s", key);
         op_match = find_rsc_op_entry(rsc, key);
 
         if ((op_match == NULL)
             && pcmk_is_set(data_set->flags, pe_flag_stop_action_orphans)) {
             CancelXmlOp(rsc, xml_op, active_node, "orphan", data_set);
             free(key);
             return TRUE;
 
         } else if (op_match == NULL) {
             pe_rsc_debug(rsc, "Orphan action detected: %s on %s", key, active_node->details->uname);
             free(key);
             return TRUE;
         }
         free(key);
         key = NULL;
     }
 
     crm_trace("Testing " PCMK__OP_FMT " on %s",
               rsc->id, task, interval_ms, active_node->details->uname);
     if ((interval_ms == 0) && pcmk__str_eq(task, RSC_STATUS, pcmk__str_casei)) {
         /* Reload based on the start action not a probe */
         task = RSC_START;
 
     } else if ((interval_ms == 0) && pcmk__str_eq(task, RSC_MIGRATED, pcmk__str_casei)) {
         /* Reload based on the start action not a migrate */
         task = RSC_START;
     } else if ((interval_ms == 0) && pcmk__str_eq(task, RSC_PROMOTE, pcmk__str_casei)) {
         /* Reload based on the start action not a promote */
         task = RSC_START;
     }
 
     digest_data = rsc_action_digest_cmp(rsc, xml_op, active_node, data_set);
 
     if (pcmk_is_set(data_set->flags, pe_flag_sanitized)) {
         digest_secure = crm_element_value(xml_op, XML_LRM_ATTR_SECURE_DIGEST);
     }
 
     if(digest_data->rc != RSC_DIGEST_MATCH
        && digest_secure
        && digest_data->digest_secure_calc
        && strcmp(digest_data->digest_secure_calc, digest_secure) == 0) {
         if (!pcmk__is_daemon && data_set->priv != NULL) {
             pcmk__output_t *out = data_set->priv;
             out->info(out, "Only 'private' parameters to "
                       PCMK__OP_FMT " on %s changed: %s", rsc->id, task,
                       interval_ms, active_node->details->uname,
                       crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
         }
 
     } else if (digest_data->rc == RSC_DIGEST_RESTART) {
         /* Changes that force a restart */
         pe_action_t *required = NULL;
 
         did_change = TRUE;
         key = pcmk__op_key(rsc->id, task, interval_ms);
         crm_log_xml_info(digest_data->params_restart, "params:restart");
         required = custom_action(rsc, key, task, NULL, FALSE, TRUE, data_set);
         pe_action_set_reason(required, "resource definition change", true);
         trigger_unfencing(rsc, active_node, "Device parameters changed", NULL, data_set);
 
     } else if ((digest_data->rc == RSC_DIGEST_ALL) || (digest_data->rc == RSC_DIGEST_UNKNOWN)) {
         // Changes that can potentially be handled by an agent reload
         const char *digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST);
 
         did_change = TRUE;
         trigger_unfencing(rsc, active_node, "Device parameters changed (reload)", NULL, data_set);
         crm_log_xml_info(digest_data->params_all, "params:reload");
         key = pcmk__op_key(rsc->id, task, interval_ms);
 
         if (interval_ms > 0) {
             pe_action_t *op = NULL;
 
 #if 0
             /* Always reload/restart the entire resource */
             ReloadRsc(rsc, active_node, data_set);
 #else
             /* Re-sending the recurring op is sufficient - the old one will be cancelled automatically */
             op = custom_action(rsc, key, task, active_node, TRUE, TRUE, data_set);
             pe__set_action_flags(op, pe_action_reschedule);
 #endif
 
         } else if (digest_restart) {
             pe_rsc_trace(rsc, "Reloading '%s' action for resource %s", task, rsc->id);
 
             /* Reload this resource */
             ReloadRsc(rsc, active_node, data_set);
             free(key);
 
         } else {
             pe_action_t *required = NULL;
             pe_rsc_trace(rsc, "Resource %s doesn't support agent reloads",
                          rsc->id);
 
             /* Re-send the start/demote/promote op
              * Recurring ops will be detected independently
              */
             required = custom_action(rsc, key, task, NULL, FALSE, TRUE,
                                      data_set);
             pe_action_set_reason(required, "resource definition change", true);
         }
     }
 
     return did_change;
 }
 
 /*!
  * \internal
  * \brief Do deferred action checks after allocation
  *
  * \param[in] data_set  Working set for cluster
  */
 static void
 check_params(pe_resource_t *rsc, pe_node_t *node, xmlNode *rsc_op,
              enum pe_check_parameters check, pe_working_set_t *data_set)
 {
     const char *reason = NULL;
     op_digest_cache_t *digest_data = NULL;
 
     switch (check) {
         case pe_check_active:
             if (check_action_definition(rsc, node, rsc_op, data_set)
                 && pe_get_failcount(node, rsc, NULL, pe_fc_effective, NULL,
                                     data_set)) {
 
                 reason = "action definition changed";
             }
             break;
 
         case pe_check_last_failure:
             digest_data = rsc_action_digest_cmp(rsc, rsc_op, node, data_set);
             switch (digest_data->rc) {
                 case RSC_DIGEST_UNKNOWN:
                     crm_trace("Resource %s history entry %s on %s has no digest to compare",
                               rsc->id, ID(rsc_op), node->details->id);
                     break;
                 case RSC_DIGEST_MATCH:
                     break;
                 default:
                     reason = "resource parameters have changed";
                     break;
             }
             break;
     }
 
     if (reason) {
         pe__clear_failcount(rsc, node, reason, data_set);
     }
 }
 
 static void
 check_actions_for(xmlNode * rsc_entry, pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     int offset = -1;
     int stop_index = 0;
     int start_index = 0;
 
     const char *task = NULL;
 
     xmlNode *rsc_op = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     CRM_CHECK(node != NULL, return);
 
     if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
         pe_resource_t *parent = uber_parent(rsc);
         if(parent == NULL
            || pe_rsc_is_clone(parent) == FALSE
            || pcmk_is_set(parent->flags, pe_rsc_unique)) {
             pe_rsc_trace(rsc, "Skipping param check for %s and deleting: orphan", rsc->id);
             DeleteRsc(rsc, node, FALSE, data_set);
         } else {
             pe_rsc_trace(rsc, "Skipping param check for %s (orphan clone)", rsc->id);
         }
         return;
 
     } else if (pe_find_node_id(rsc->running_on, node->details->id) == NULL) {
         if (check_rsc_parameters(rsc, node, rsc_entry, FALSE, data_set)) {
             DeleteRsc(rsc, node, FALSE, data_set);
         }
         pe_rsc_trace(rsc, "Skipping param check for %s: no longer active on %s",
                      rsc->id, node->details->uname);
         return;
     }
 
     pe_rsc_trace(rsc, "Processing %s on %s", rsc->id, node->details->uname);
 
     if (check_rsc_parameters(rsc, node, rsc_entry, TRUE, data_set)) {
         DeleteRsc(rsc, node, FALSE, data_set);
     }
 
     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)) {
             op_list = g_list_prepend(op_list, rsc_op);
         }
     }
 
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
     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;
         guint interval_ms = 0;
 
         offset++;
 
         if (start_index < stop_index) {
             /* stopped */
             continue;
         } else if (offset < start_index) {
             /* action occurred prior to a start */
             continue;
         }
 
         task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
         crm_element_value_ms(rsc_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
 
         if ((interval_ms > 0) &&
             (pcmk_is_set(rsc->flags, pe_rsc_maintenance) || node->details->maintenance)) {
             // Maintenance mode cancels recurring operations
             CancelXmlOp(rsc, rsc_op, node, "maintenance mode", data_set);
 
         } else if ((interval_ms > 0) || pcmk__strcase_any_of(task, RSC_STATUS, RSC_START,
                                                              RSC_PROMOTE, RSC_MIGRATED, NULL)) {
             /* If a resource operation failed, and the operation's definition
              * has changed, clear any fail count so they can be retried fresh.
              */
 
             if (pe__bundle_needs_remote_name(rsc, data_set)) {
                 /* We haven't allocated resources to nodes yet, so if the
                  * REMOTE_CONTAINER_HACK is used, we may calculate the digest
                  * based on the literal "#uname" value rather than the properly
                  * substituted value. That would mistakenly make the action
                  * definition appear to have been changed. Defer the check until
                  * later in this case.
                  */
                 pe__add_param_check(rsc_op, rsc, node, pe_check_active,
                                     data_set);
 
             } else if (check_action_definition(rsc, node, rsc_op, data_set)
                 && pe_get_failcount(node, rsc, NULL, pe_fc_effective, NULL,
                                     data_set)) {
                 pe__clear_failcount(rsc, node, "action definition changed",
                                     data_set);
             }
         }
     }
     g_list_free(sorted_op_list);
 }
 
 static GList *
 find_rsc_list(GList *result, pe_resource_t * rsc, const char *id, gboolean renamed_clones,
               gboolean partial, pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     gboolean match = FALSE;
 
     if (id == NULL) {
         return NULL;
     }
 
     if (rsc == NULL) {
         if (data_set == NULL) {
             return NULL;
         }
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child = (pe_resource_t *) gIter->data;
 
             result = find_rsc_list(result, child, id, renamed_clones, partial,
                                    NULL);
         }
         return result;
     }
 
     if (partial) {
         if (strstr(rsc->id, id)) {
             match = TRUE;
 
         } else if (renamed_clones && rsc->clone_name && strstr(rsc->clone_name, id)) {
             match = TRUE;
         }
 
     } else {
         if (strcmp(rsc->id, id) == 0) {
             match = TRUE;
 
         } else if (renamed_clones && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) {
             match = TRUE;
         }
     }
 
     if (match) {
         result = g_list_prepend(result, rsc);
     }
 
     if (rsc->children) {
         gIter = rsc->children;
         for (; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child = (pe_resource_t *) gIter->data;
 
             result = find_rsc_list(result, child, id, renamed_clones, partial, NULL);
         }
     }
 
     return result;
 }
 
 static void
 check_actions(pe_working_set_t * data_set)
 {
     const char *id = NULL;
     pe_node_t *node = NULL;
     xmlNode *lrm_rscs = NULL;
     xmlNode *status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
 
     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)) {
             id = crm_element_value(node_state, XML_ATTR_ID);
             lrm_rscs = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
             lrm_rscs = find_xml_node(lrm_rscs, XML_LRM_TAG_RESOURCES, FALSE);
 
             node = pe_find_node_id(data_set->nodes, id);
 
             if (node == NULL) {
                 continue;
 
             /* Still need to check actions for a maintenance node to cancel existing monitor operations */
             } else if (can_run_resources(node) == FALSE && node->details->maintenance == FALSE) {
                 crm_trace("Skipping param check for %s: can't run resources",
                           node->details->uname);
                 continue;
             }
 
             crm_trace("Processing node %s", node->details->uname);
             if (node->details->online
                 || pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
                 xmlNode *rsc_entry = NULL;
 
                 for (rsc_entry = pcmk__xe_first_child(lrm_rscs);
                      rsc_entry != NULL;
                      rsc_entry = pcmk__xe_next(rsc_entry)) {
 
                     if (pcmk__str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, pcmk__str_none)) {
 
                         if (xml_has_children(rsc_entry)) {
                             GList *gIter = NULL;
                             GList *result = NULL;
                             const char *rsc_id = ID(rsc_entry);
 
                             CRM_CHECK(rsc_id != NULL, return);
 
                             result = find_rsc_list(NULL, NULL, rsc_id, TRUE, FALSE, data_set);
                             for (gIter = result; gIter != NULL; gIter = gIter->next) {
                                 pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
                                 if (rsc->variant != pe_native) {
                                     continue;
                                 }
                                 check_actions_for(rsc_entry, rsc, node, data_set);
                             }
                             g_list_free(result);
                         }
                     }
                 }
             }
         }
     }
 }
 
 static void
 apply_placement_constraints(pe_working_set_t * data_set)
 {
     for (GList *gIter = data_set->placement_constraints;
          gIter != NULL; gIter = gIter->next) {
         pe__location_t *cons = gIter->data;
 
         cons->rsc_lh->cmds->rsc_location(cons->rsc_lh, cons);
     }
 }
 
 static gboolean
 failcount_clear_action_exists(pe_node_t * node, pe_resource_t * rsc)
 {
     gboolean rc = FALSE;
     GList *list = pe__resource_actions(rsc, node, CRM_OP_CLEAR_FAILCOUNT, TRUE);
 
     if (list) {
         rc = TRUE;
     }
     g_list_free(list);
     return rc;
 }
 
 static void
 common_apply_stickiness(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set)
 {
     if (rsc->children) {
         GList *gIter = rsc->children;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
             common_apply_stickiness(child_rsc, node, data_set);
         }
         return;
     }
 
     if (pcmk_is_set(rsc->flags, pe_rsc_managed)
         && rsc->stickiness != 0 && pcmk__list_of_1(rsc->running_on)) {
         pe_node_t *current = pe_find_node_id(rsc->running_on, node->details->id);
         pe_node_t *match = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id);
 
         if (current == NULL) {
 
         } else if ((match != NULL)
                    || pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)) {
             pe_resource_t *sticky_rsc = rsc;
 
             resource_location(sticky_rsc, node, rsc->stickiness, "stickiness", data_set);
             pe_rsc_debug(sticky_rsc, "Resource %s: preferring current location"
                          " (node=%s, weight=%d)", sticky_rsc->id,
                          node->details->uname, rsc->stickiness);
         } else {
             GHashTableIter iter;
             pe_node_t *nIter = NULL;
 
             pe_rsc_debug(rsc, "Ignoring stickiness for %s: the cluster is asymmetric"
                          " and node %s is not explicitly allowed", rsc->id, node->details->uname);
             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
             while (g_hash_table_iter_next(&iter, NULL, (void **)&nIter)) {
                 crm_err("%s[%s] = %d", rsc->id, nIter->details->uname, nIter->weight);
             }
         }
     }
 
     /* Check the migration threshold only if a failcount clear action
      * has not already been placed for this resource on the node.
      * There is no sense in potentially forcing the resource from this
      * node if the failcount is being reset anyway.
      *
      * @TODO A clear_failcount operation can be scheduled in stage4() via
      * check_actions_for(), or in stage5() via check_params(). This runs in
      * stage2(), so it cannot detect those, meaning we might check the migration
      * threshold when we shouldn't -- worst case, we stop or move the resource,
      * then move it back next transition.
      */
     if (failcount_clear_action_exists(node, rsc) == FALSE) {
         pe_resource_t *failed = NULL;
 
         if (pcmk__threshold_reached(rsc, node, data_set, &failed)) {
             resource_location(failed, node, -INFINITY, "__fail_limit__",
                               data_set);
         }
     }
 }
 
 void
 complex_set_cmds(pe_resource_t * rsc)
 {
     GList *gIter = rsc->children;
 
     rsc->cmds = &resource_class_alloc_functions[rsc->variant];
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         complex_set_cmds(child_rsc);
     }
 }
 
 void
 set_alloc_actions(pe_working_set_t * data_set)
 {
 
     GList *gIter = data_set->resources;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         complex_set_cmds(rsc);
     }
 }
 
 static void
 calculate_system_health(gpointer gKey, gpointer gValue, gpointer user_data)
 {
     const char *key = (const char *)gKey;
     const char *value = (const char *)gValue;
     int *system_health = (int *)user_data;
 
     if (!gKey || !gValue || !user_data) {
         return;
     }
 
     if (pcmk__starts_with(key, "#health")) {
         int score;
 
         /* Convert the value into an integer */
         score = char2score(value);
 
         /* Add it to the running total */
         *system_health = pe__add_scores(score, *system_health);
     }
 }
 
 static gboolean
 apply_system_health(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     const char *health_strategy = pe_pref(data_set->config_hash, "node-health-strategy");
     int base_health = 0;
 
     if (pcmk__str_eq(health_strategy, "none", pcmk__str_null_matches | pcmk__str_casei)) {
         /* Prevent any accidental health -> score translation */
         pcmk__score_red = 0;
         pcmk__score_yellow = 0;
         pcmk__score_green = 0;
         return TRUE;
 
     } else if (pcmk__str_eq(health_strategy, "migrate-on-red", pcmk__str_casei)) {
 
         /* Resources on nodes which have health values of red are
          * weighted away from that node.
          */
         pcmk__score_red = -INFINITY;
         pcmk__score_yellow = 0;
         pcmk__score_green = 0;
 
     } else if (pcmk__str_eq(health_strategy, "only-green", pcmk__str_casei)) {
 
         /* Resources on nodes which have health values of red or yellow
          * are forced away from that node.
          */
         pcmk__score_red = -INFINITY;
         pcmk__score_yellow = -INFINITY;
         pcmk__score_green = 0;
 
     } else if (pcmk__str_eq(health_strategy, "progressive", pcmk__str_casei)) {
         /* Same as the above, but use the r/y/g scores provided by the user
          * Defaults are provided by the pe_prefs table
          * Also, custom health "base score" can be used
          */
         base_health = char2score(pe_pref(data_set->config_hash,
                                          "node-health-base"));
 
     } else if (pcmk__str_eq(health_strategy, "custom", pcmk__str_casei)) {
 
         /* Requires the admin to configure the rsc_location constaints for
          * processing the stored health scores
          */
         /* TODO: Check for the existence of appropriate node health constraints */
         return TRUE;
 
     } else {
         crm_err("Unknown node health strategy: %s", health_strategy);
         return FALSE;
     }
 
     crm_info("Applying automated node health strategy: %s", health_strategy);
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         int system_health = base_health;
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         /* Search through the node hash table for system health entries. */
         g_hash_table_foreach(node->details->attrs, calculate_system_health, &system_health);
 
         crm_info(" Node %s has an combined system health of %d",
                  node->details->uname, system_health);
 
         /* If the health is non-zero, then create a new location constraint so
          * that the weight will be added later on.
          */
         if (system_health != 0) {
 
             GList *gIter2 = data_set->resources;
 
             for (; gIter2 != NULL; gIter2 = gIter2->next) {
                 pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
 
                 pcmk__new_location(health_strategy, rsc, system_health, NULL,
                                    node, data_set);
             }
         }
     }
 
     return TRUE;
 }
 
 gboolean
 stage0(pe_working_set_t * data_set)
 {
-    xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
-
     if (data_set->input == NULL) {
         return FALSE;
     }
 
     if (!pcmk_is_set(data_set->flags, pe_flag_have_status)) {
         crm_trace("Calculating status");
         cluster_status(data_set);
     }
 
     set_alloc_actions(data_set);
     apply_system_health(data_set);
-    unpack_constraints(cib_constraints, data_set);
+    pcmk__unpack_constraints(data_set);
 
     return TRUE;
 }
 
 /*
  * Check nodes for resources started outside of the LRM
  */
 gboolean
 probe_resources(pe_working_set_t * data_set)
 {
     pe_action_t *probe_node_complete = NULL;
 
     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
         const char *probed = pe_node_attribute_raw(node, CRM_OP_PROBED);
 
         if (node->details->online == FALSE) {
 
             if (pe__is_remote_node(node) && node->details->remote_rsc
                 && (get_remote_node_state(node) == remote_state_failed)) {
 
                 pe_fence_node(data_set, node, "the connection is unrecoverable", FALSE);
             }
             continue;
 
         } else if (node->details->unclean) {
             continue;
 
         } else if (node->details->rsc_discovery_enabled == FALSE) {
             /* resource discovery is disabled for this node */
             continue;
         }
 
         if (probed != NULL && crm_is_true(probed) == FALSE) {
             pe_action_t *probe_op = custom_action(NULL, crm_strdup_printf("%s-%s", CRM_OP_REPROBE, node->details->uname),
                                                   CRM_OP_REPROBE, node, FALSE, TRUE, data_set);
 
             add_hash_param(probe_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
             continue;
         }
 
         for (GList *gIter2 = data_set->resources; gIter2 != NULL; gIter2 = gIter2->next) {
             pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
 
             rsc->cmds->create_probe(rsc, node, probe_node_complete, FALSE, data_set);
         }
     }
     return TRUE;
 }
 
 static void
 rsc_discover_filter(pe_resource_t *rsc, pe_node_t *node)
 {
     pe_resource_t *top = uber_parent(rsc);
     pe_node_t *match;
 
     if (rsc->exclusive_discover == FALSE && top->exclusive_discover == FALSE) {
         return;
     }
 
     g_list_foreach(rsc->children, (GFunc) rsc_discover_filter, node);
 
     match = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
     if (match && match->rsc_discover_mode != pe_discover_exclusive) {
         match->weight = -INFINITY;
     }
 }
 
 static time_t
 shutdown_time(pe_node_t *node, pe_working_set_t *data_set)
 {
     const char *shutdown = pe_node_attribute_raw(node, XML_CIB_ATTR_SHUTDOWN);
     time_t result = 0;
 
     if (shutdown) {
         long long result_ll;
 
         if (pcmk__scan_ll(shutdown, &result_ll, 0LL) == pcmk_rc_ok) {
             result = (time_t) result_ll;
         }
     }
     return result? result : get_effective_time(data_set);
 }
 
 static void
 apply_shutdown_lock(pe_resource_t *rsc, pe_working_set_t *data_set)
 {
     const char *class;
 
     // Only primitives and (uncloned) groups may be locked
     if (rsc->variant == pe_group) {
         g_list_foreach(rsc->children, (GFunc) apply_shutdown_lock, data_set);
     } else if (rsc->variant != pe_native) {
         return;
     }
 
     // Fence devices and remote connections can't be locked
     class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_null_matches)
         || pe__resource_is_remote_conn(rsc, data_set)) {
         return;
     }
 
     if (rsc->lock_node != NULL) {
         // The lock was obtained from resource history
 
         if (rsc->running_on != NULL) {
             /* The resource was started elsewhere even though it is now
              * considered locked. This shouldn't be possible, but as a
              * failsafe, we don't want to disturb the resource now.
              */
             pe_rsc_info(rsc,
                         "Cancelling shutdown lock because %s is already active",
                         rsc->id);
             pe__clear_resource_history(rsc, rsc->lock_node, data_set);
             rsc->lock_node = NULL;
             rsc->lock_time = 0;
         }
 
     // Only a resource active on exactly one node can be locked
     } else if (pcmk__list_of_1(rsc->running_on)) {
         pe_node_t *node = rsc->running_on->data;
 
         if (node->details->shutdown) {
             if (node->details->unclean) {
                 pe_rsc_debug(rsc, "Not locking %s to unclean %s for shutdown",
                              rsc->id, node->details->uname);
             } else {
                 rsc->lock_node = node;
                 rsc->lock_time = shutdown_time(node, data_set);
             }
         }
     }
 
     if (rsc->lock_node == NULL) {
         // No lock needed
         return;
     }
 
     if (data_set->shutdown_lock > 0) {
         time_t lock_expiration = rsc->lock_time + data_set->shutdown_lock;
 
         pe_rsc_info(rsc, "Locking %s to %s due to shutdown (expires @%lld)",
                     rsc->id, rsc->lock_node->details->uname,
                     (long long) lock_expiration);
         pe__update_recheck_time(++lock_expiration, data_set);
     } else {
         pe_rsc_info(rsc, "Locking %s to %s due to shutdown",
                     rsc->id, rsc->lock_node->details->uname);
     }
 
     // If resource is locked to one node, ban it from all other nodes
     for (GList *item = data_set->nodes; item != NULL; item = item->next) {
         pe_node_t *node = item->data;
 
         if (strcmp(node->details->uname, rsc->lock_node->details->uname)) {
             resource_location(rsc, node, -CRM_SCORE_INFINITY,
                               XML_CONFIG_ATTR_SHUTDOWN_LOCK, data_set);
         }
     }
 }
 
 /*
  * \internal
  * \brief Stage 2 of cluster status: apply node-specific criteria
  *
  * Count known nodes, and apply location constraints, stickiness, and exclusive
  * resource discovery.
  */
 gboolean
 stage2(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     if (pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)) {
         g_list_foreach(data_set->resources, (GFunc) apply_shutdown_lock, data_set);
     }
 
     if (!pcmk_is_set(data_set->flags, pe_flag_no_compat)) {
         // @COMPAT API backward compatibility
         for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
             pe_node_t *node = (pe_node_t *) gIter->data;
 
             if (node && (node->weight >= 0) && node->details->online
                 && (node->details->type != node_ping)) {
                 data_set->max_valid_nodes++;
             }
         }
     }
 
     apply_placement_constraints(data_set);
 
     gIter = data_set->nodes;
     for (; gIter != NULL; gIter = gIter->next) {
         GList *gIter2 = NULL;
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         gIter2 = data_set->resources;
         for (; gIter2 != NULL; gIter2 = gIter2->next) {
             pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
 
             common_apply_stickiness(rsc, node, data_set);
             rsc_discover_filter(rsc, node);
         }
     }
 
     return TRUE;
 }
 
 /*
  * Create internal resource constraints before allocation
  */
 gboolean
 stage3(pe_working_set_t * data_set)
 {
 
     GList *gIter = data_set->resources;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         rsc->cmds->internal_constraints(rsc, data_set);
     }
 
     return TRUE;
 }
 
 /*
  * Check for orphaned or redefined actions
  */
 gboolean
 stage4(pe_working_set_t * data_set)
 {
     check_actions(data_set);
     return TRUE;
 }
 
 static void *
 convert_const_pointer(const void *ptr)
 {
     /* Worst function ever */
     return (void *)ptr;
 }
 
 static gint
 sort_rsc_process_order(gconstpointer a, gconstpointer b, gpointer data)
 {
     int rc = 0;
     int r1_weight = -INFINITY;
     int r2_weight = -INFINITY;
 
     const char *reason = "existence";
 
     GList *nodes = (GList *) data;
     const pe_resource_t *resource1 = a;
     const pe_resource_t *resource2 = b;
 
     pe_node_t *r1_node = NULL;
     pe_node_t *r2_node = NULL;
     GList *gIter = NULL;
     GHashTable *r1_nodes = NULL;
     GHashTable *r2_nodes = NULL;
 
     reason = "priority";
     r1_weight = resource1->priority;
     r2_weight = resource2->priority;
 
     if (r1_weight > r2_weight) {
         rc = -1;
         goto done;
     }
 
     if (r1_weight < r2_weight) {
         rc = 1;
         goto done;
     }
 
     reason = "no node list";
     if (nodes == NULL) {
         goto done;
     }
 
     r1_nodes = pcmk__native_merge_weights(convert_const_pointer(resource1),
                                           resource1->id, NULL, NULL, 1,
                                           pe_weights_forward | pe_weights_init);
     pe__show_node_weights(true, NULL, resource1->id, r1_nodes,
                           resource1->cluster);
 
     r2_nodes = pcmk__native_merge_weights(convert_const_pointer(resource2),
                                           resource2->id, NULL, NULL, 1,
                                           pe_weights_forward | pe_weights_init);
     pe__show_node_weights(true, NULL, resource2->id, r2_nodes,
                           resource2->cluster);
 
     /* Current location score */
     reason = "current location";
     r1_weight = -INFINITY;
     r2_weight = -INFINITY;
 
     if (resource1->running_on) {
         r1_node = pe__current_node(resource1);
         r1_node = g_hash_table_lookup(r1_nodes, r1_node->details->id);
         if (r1_node != NULL) {
             r1_weight = r1_node->weight;
         }
     }
     if (resource2->running_on) {
         r2_node = pe__current_node(resource2);
         r2_node = g_hash_table_lookup(r2_nodes, r2_node->details->id);
         if (r2_node != NULL) {
             r2_weight = r2_node->weight;
         }
     }
 
     if (r1_weight > r2_weight) {
         rc = -1;
         goto done;
     }
 
     if (r1_weight < r2_weight) {
         rc = 1;
         goto done;
     }
 
     reason = "score";
     for (gIter = nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         r1_node = NULL;
         r2_node = NULL;
 
         r1_weight = -INFINITY;
         if (r1_nodes) {
             r1_node = g_hash_table_lookup(r1_nodes, node->details->id);
         }
         if (r1_node) {
             r1_weight = r1_node->weight;
         }
 
         r2_weight = -INFINITY;
         if (r2_nodes) {
             r2_node = g_hash_table_lookup(r2_nodes, node->details->id);
         }
         if (r2_node) {
             r2_weight = r2_node->weight;
         }
 
         if (r1_weight > r2_weight) {
             rc = -1;
             goto done;
         }
 
         if (r1_weight < r2_weight) {
             rc = 1;
             goto done;
         }
     }
 
   done:
     crm_trace("%s (%d) on %s %c %s (%d) on %s: %s",
               resource1->id, r1_weight, r1_node ? r1_node->details->id : "n/a",
               rc < 0 ? '>' : rc > 0 ? '<' : '=',
               resource2->id, r2_weight, r2_node ? r2_node->details->id : "n/a", reason);
 
     if (r1_nodes) {
         g_hash_table_destroy(r1_nodes);
     }
     if (r2_nodes) {
         g_hash_table_destroy(r2_nodes);
     }
 
     return rc;
 }
 
 static void
 allocate_resources(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     if (pcmk_is_set(data_set->flags, pe_flag_have_remote_nodes)) {
         /* Allocate remote connection resources first (which will also allocate
          * any colocation dependencies). If the connection is migrating, always
          * prefer the partial migration target.
          */
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *rsc = (pe_resource_t *) gIter->data;
             if (rsc->is_remote_node == FALSE) {
                 continue;
             }
             pe_rsc_trace(rsc, "Allocating remote connection resource '%s'",
                          rsc->id);
             rsc->cmds->allocate(rsc, rsc->partial_migration_target, data_set);
         }
     }
 
     /* now do the rest of the resources */
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
         if (rsc->is_remote_node == TRUE) {
             continue;
         }
         pe_rsc_trace(rsc, "Allocating %s resource '%s'",
                      crm_element_name(rsc->xml), rsc->id);
         rsc->cmds->allocate(rsc, NULL, data_set);
     }
 }
 
 /* We always use pe_order_preserve with these convenience functions to exempt
  * internally generated constraints from the prohibition of user constraints
  * involving remote connection resources.
  *
  * The start ordering additionally uses pe_order_runnable_left so that the
  * specified action is not runnable if the start is not runnable.
  */
 
 static inline void
 order_start_then_action(pe_resource_t *lh_rsc, pe_action_t *rh_action,
                         enum pe_ordering extra, pe_working_set_t *data_set)
 {
     if (lh_rsc && rh_action && data_set) {
         pcmk__new_ordering(lh_rsc, start_key(lh_rsc), NULL,
                            rh_action->rsc, NULL, rh_action,
                            pe_order_preserve|pe_order_runnable_left|extra,
                            data_set);
     }
 }
 
 static inline void
 order_action_then_stop(pe_action_t *lh_action, pe_resource_t *rh_rsc,
                        enum pe_ordering extra, pe_working_set_t *data_set)
 {
     if (lh_action && rh_rsc && data_set) {
         pcmk__new_ordering(lh_action->rsc, NULL, lh_action,
                            rh_rsc, stop_key(rh_rsc), NULL,
                            pe_order_preserve|extra, data_set);
     }
 }
 
 // Clear fail counts for orphaned rsc on all online nodes
 static void
 cleanup_orphans(pe_resource_t * rsc, pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (node->details->online
             && pe_get_failcount(node, rsc, NULL, pe_fc_effective, NULL,
                                 data_set)) {
 
             pe_action_t *clear_op = NULL;
 
             clear_op = pe__clear_failcount(rsc, node, "it is orphaned",
                                            data_set);
 
             /* We can't use order_action_then_stop() here because its
              * pe_order_preserve breaks things
              */
             pcmk__new_ordering(clear_op->rsc, NULL, clear_op,
                                rsc, stop_key(rsc), NULL,
                                pe_order_optional, data_set);
         }
     }
 }
 
 gboolean
 stage5(pe_working_set_t * data_set)
 {
     pcmk__output_t *out = data_set->priv;
     GList *gIter = NULL;
 
     if (!pcmk__str_eq(data_set->placement_strategy, "default", pcmk__str_casei)) {
         GList *nodes = g_list_copy(data_set->nodes);
 
         nodes = sort_nodes_by_weight(nodes, NULL, data_set);
         data_set->resources =
             g_list_sort_with_data(data_set->resources, sort_rsc_process_order, nodes);
 
         g_list_free(nodes);
     }
 
     gIter = data_set->nodes;
     for (; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
             out->message(out, "node-capacity", node, "Original");
         }
     }
 
     crm_trace("Allocating services");
     /* Take (next) highest resource, assign it and create its actions */
 
     allocate_resources(data_set);
 
     gIter = data_set->nodes;
     for (; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
             out->message(out, "node-capacity", node, "Remaining");
         }
     }
 
     // Process deferred action checks
     pe__foreach_param_check(data_set, check_params);
     pe__free_param_checks(data_set);
 
     if (pcmk_is_set(data_set->flags, pe_flag_startup_probes)) {
         crm_trace("Calculating needed probes");
         /* This code probably needs optimization
          * ptest -x with 100 nodes, 100 clones and clone-max=100:
 
          With probes:
 
          ptest[14781]: 2010/09/27_17:56:46 notice: TRACE: do_calculations: pengine.c:258 Calculate cluster status
          ptest[14781]: 2010/09/27_17:56:46 notice: TRACE: do_calculations: pengine.c:278 Applying placement constraints
          ptest[14781]: 2010/09/27_17:56:47 notice: TRACE: do_calculations: pengine.c:285 Create internal constraints
          ptest[14781]: 2010/09/27_17:56:47 notice: TRACE: do_calculations: pengine.c:292 Check actions
          ptest[14781]: 2010/09/27_17:56:48 notice: TRACE: do_calculations: pengine.c:299 Allocate resources
          ptest[14781]: 2010/09/27_17:56:48 notice: TRACE: stage5: allocate.c:881 Allocating services
          ptest[14781]: 2010/09/27_17:56:49 notice: TRACE: stage5: allocate.c:894 Calculating needed probes
          ptest[14781]: 2010/09/27_17:56:51 notice: TRACE: stage5: allocate.c:899 Creating actions
          ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: stage5: allocate.c:905 Creating done
          ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: do_calculations: pengine.c:306 Processing fencing and shutdown cases
          ptest[14781]: 2010/09/27_17:56:52 notice: TRACE: do_calculations: pengine.c:313 Applying ordering constraints
          36s
          ptest[14781]: 2010/09/27_17:57:28 notice: TRACE: do_calculations: pengine.c:320 Create transition graph
 
          Without probes:
 
          ptest[14637]: 2010/09/27_17:56:21 notice: TRACE: do_calculations: pengine.c:258 Calculate cluster status
          ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:278 Applying placement constraints
          ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:285 Create internal constraints
          ptest[14637]: 2010/09/27_17:56:22 notice: TRACE: do_calculations: pengine.c:292 Check actions
          ptest[14637]: 2010/09/27_17:56:23 notice: TRACE: do_calculations: pengine.c:299 Allocate resources
          ptest[14637]: 2010/09/27_17:56:23 notice: TRACE: stage5: allocate.c:881 Allocating services
          ptest[14637]: 2010/09/27_17:56:24 notice: TRACE: stage5: allocate.c:899 Creating actions
          ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: stage5: allocate.c:905 Creating done
          ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:306 Processing fencing and shutdown cases
          ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:313 Applying ordering constraints
          ptest[14637]: 2010/09/27_17:56:25 notice: TRACE: do_calculations: pengine.c:320 Create transition graph
         */
 
         probe_resources(data_set);
     }
 
     crm_trace("Handle orphans");
     if (pcmk_is_set(data_set->flags, pe_flag_stop_rsc_orphans)) {
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
             /* There's no need to recurse into rsc->children because those
              * should just be unallocated clone instances.
              */
             if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
                 cleanup_orphans(rsc, data_set);
             }
         }
     }
 
     crm_trace("Creating actions");
 
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         rsc->cmds->create_actions(rsc, data_set);
     }
 
     crm_trace("Creating done");
     return TRUE;
 }
 
 static gboolean
 is_managed(const pe_resource_t * rsc)
 {
     GList *gIter = rsc->children;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
         return TRUE;
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         if (is_managed(child_rsc)) {
             return TRUE;
         }
     }
 
     return FALSE;
 }
 
 static gboolean
 any_managed_resources(pe_working_set_t * data_set)
 {
 
     GList *gIter = data_set->resources;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         if (is_managed(rsc)) {
             return TRUE;
         }
     }
     return FALSE;
 }
 
 /*
  * Create dependencies for stonith and shutdown operations
  */
 gboolean
 stage6(pe_working_set_t * data_set)
 {
     pe_action_t *dc_down = NULL;
     pe_action_t *stonith_op = NULL;
     gboolean integrity_lost = FALSE;
     gboolean need_stonith = TRUE;
     GList *gIter;
     GList *stonith_ops = NULL;
     GList *shutdown_ops = NULL;
 
     /* Remote ordering constraints need to happen prior to calculating fencing
      * because it is one more place we will mark the node as dirty.
      *
      * A nice side effect of doing them early is that apply_*_ordering() can be
      * simpler because pe_fence_node() has already done some of the work.
      */
     crm_trace("Creating remote ordering constraints");
     apply_remote_node_ordering(data_set);
 
     crm_trace("Processing fencing and shutdown cases");
     if (any_managed_resources(data_set) == FALSE) {
         crm_notice("Delaying fencing operations until there are resources to manage");
         need_stonith = FALSE;
     }
 
     /* Check each node for stonith/shutdown */
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         /* Guest nodes are "fenced" by recovering their container resource,
          * so handle them separately.
          */
         if (pe__is_guest_node(node)) {
             if (node->details->remote_requires_reset && need_stonith
                 && pe_can_fence(data_set, node)) {
                 pcmk__fence_guest(node, data_set);
             }
             continue;
         }
 
         stonith_op = NULL;
 
         if (node->details->unclean
             && need_stonith && pe_can_fence(data_set, node)) {
 
             stonith_op = pe_fence_op(node, NULL, FALSE, "node is unclean", FALSE, data_set);
             pe_warn("Scheduling Node %s for STONITH", node->details->uname);
 
             pcmk__order_vs_fence(stonith_op, data_set);
 
             if (node->details->is_dc) {
                 // Remember if the DC is being fenced
                 dc_down = stonith_op;
 
             } else {
 
                 if (!pcmk_is_set(data_set->flags, pe_flag_concurrent_fencing)
                     && (stonith_ops != NULL)) {
                     /* Concurrent fencing is disabled, so order each non-DC
                      * fencing in a chain. If there is any DC fencing or
                      * shutdown, it will be ordered after the last action in the
                      * chain later.
                      */
                     order_actions((pe_action_t *) stonith_ops->data,
                                   stonith_op, pe_order_optional);
                 }
 
                 // Remember all non-DC fencing actions in a separate list
                 stonith_ops = g_list_prepend(stonith_ops, stonith_op);
             }
 
         } else if (node->details->online && node->details->shutdown &&
                 /* TODO define what a shutdown op means for a remote node.
                  * For now we do not send shutdown operations for remote nodes, but
                  * if we can come up with a good use for this in the future, we will. */
                     pe__is_guest_or_remote_node(node) == FALSE) {
 
             pe_action_t *down_op = sched_shutdown_op(node, data_set);
 
             if (node->details->is_dc) {
                 // Remember if the DC is being shut down
                 dc_down = down_op;
             } else {
                 // Remember non-DC shutdowns for later ordering
                 shutdown_ops = g_list_prepend(shutdown_ops, down_op);
             }
         }
 
         if (node->details->unclean && stonith_op == NULL) {
             integrity_lost = TRUE;
             pe_warn("Node %s is unclean!", node->details->uname);
         }
     }
 
     if (integrity_lost) {
         if (!pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
             pe_warn("YOUR RESOURCES ARE NOW LIKELY COMPROMISED");
             pe_err("ENABLE STONITH TO KEEP YOUR RESOURCES SAFE");
 
         } else if (!pcmk_is_set(data_set->flags, pe_flag_have_quorum)) {
             crm_notice("Cannot fence unclean nodes until quorum is"
                        " attained (or no-quorum-policy is set to ignore)");
         }
     }
 
     if (dc_down != NULL) {
         /* Order any non-DC shutdowns before any DC shutdown, to avoid repeated
          * DC elections. However, we don't want to order non-DC shutdowns before
          * a DC *fencing*, because even though we don't want a node that's
          * shutting down to become DC, the DC fencing could be ordered before a
          * clone stop that's also ordered before the shutdowns, thus leading to
          * a graph loop.
          */
         if (pcmk__str_eq(dc_down->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
             for (gIter = shutdown_ops; gIter != NULL; gIter = gIter->next) {
                 pe_action_t *node_stop = (pe_action_t *) gIter->data;
 
                 crm_debug("Ordering shutdown on %s before %s on DC %s",
                           node_stop->node->details->uname,
                           dc_down->task, dc_down->node->details->uname);
 
                 order_actions(node_stop, dc_down, pe_order_optional);
             }
         }
 
         // Order any non-DC fencing before any DC fencing or shutdown
 
         if (pcmk_is_set(data_set->flags, pe_flag_concurrent_fencing)) {
             /* With concurrent fencing, order each non-DC fencing action
              * separately before any DC fencing or shutdown.
              */
             for (gIter = stonith_ops; gIter != NULL; gIter = gIter->next) {
                 order_actions((pe_action_t *) gIter->data, dc_down,
                               pe_order_optional);
             }
         } else if (stonith_ops) {
             /* Without concurrent fencing, the non-DC fencing actions are
              * already ordered relative to each other, so we just need to order
              * the DC fencing after the last action in the chain (which is the
              * first item in the list).
              */
             order_actions((pe_action_t *) stonith_ops->data, dc_down,
                           pe_order_optional);
         }
     }
     g_list_free(stonith_ops);
     g_list_free(shutdown_ops);
     return TRUE;
 }
 
 static int
 is_recurring_action(pe_action_t *action)
 {
     guint interval_ms;
 
     if (pcmk__guint_from_hash(action->meta,
                               XML_LRM_ATTR_INTERVAL_MS, 0,
                               &interval_ms) != pcmk_rc_ok) {
         return 0;
     }
     return (interval_ms > 0);
 }
 
 static void
 apply_container_ordering(pe_action_t *action, pe_working_set_t *data_set)
 {
     /* VMs are also classified as containers for these purposes... in
      * that they both involve a 'thing' running on a real or remote
      * cluster node.
      *
      * This allows us to be smarter about the type and extent of
      * recovery actions required in various scenarios
      */
     pe_resource_t *remote_rsc = NULL;
     pe_resource_t *container = NULL;
     enum action_tasks task = text2task(action->task);
 
     CRM_ASSERT(action->rsc);
     CRM_ASSERT(action->node);
     CRM_ASSERT(pe__is_guest_or_remote_node(action->node));
 
     remote_rsc = action->node->details->remote_rsc;
     CRM_ASSERT(remote_rsc);
 
     container = remote_rsc->container;
     CRM_ASSERT(container);
 
     if (pcmk_is_set(container->flags, pe_rsc_failed)) {
         pe_fence_node(data_set, action->node, "container failed", FALSE);
     }
 
     crm_trace("Order %s action %s relative to %s%s for %s%s",
               action->task, action->uuid,
               pcmk_is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
               remote_rsc->id,
               pcmk_is_set(container->flags, pe_rsc_failed)? "failed " : "",
               container->id);
 
     if (pcmk__strcase_any_of(action->task, CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, NULL)) {
         /* Migration ops map to "no_action", but we need to apply the same
          * ordering as for stop or demote (see get_router_node()).
          */
         task = stop_rsc;
     }
 
     switch (task) {
         case start_rsc:
         case action_promote:
             /* Force resource recovery if the container is recovered */
             order_start_then_action(container, action, pe_order_implies_then,
                                     data_set);
 
             /* Wait for the connection resource to be up too */
             order_start_then_action(remote_rsc, action, pe_order_none,
                                     data_set);
             break;
 
         case stop_rsc:
         case action_demote:
             if (pcmk_is_set(container->flags, pe_rsc_failed)) {
                 /* When the container representing a guest node fails, any stop
                  * or demote actions for resources running on the guest node
                  * are implied by the container stopping. This is similar to
                  * how fencing operations work for cluster nodes and remote
                  * nodes.
                  */
             } else {
                 /* Ensure the operation happens before the connection is brought
                  * down.
                  *
                  * If we really wanted to, we could order these after the
                  * connection start, IFF the container's current role was
                  * stopped (otherwise we re-introduce an ordering loop when the
                  * connection is restarting).
                  */
                 order_action_then_stop(action, remote_rsc, pe_order_none,
                                        data_set);
             }
             break;
 
         default:
             /* Wait for the connection resource to be up */
             if (is_recurring_action(action)) {
                 /* In case we ever get the recovery logic wrong, force
                  * recurring monitors to be restarted, even if just
                  * the connection was re-established
                  */
                 if(task != no_action) {
                     order_start_then_action(remote_rsc, action,
                                             pe_order_implies_then, data_set);
                 }
             } else {
                 order_start_then_action(remote_rsc, action, pe_order_none,
                                         data_set);
             }
             break;
     }
 }
 
 static enum remote_connection_state
 get_remote_node_state(pe_node_t *node) 
 {
     pe_resource_t *remote_rsc = NULL;
     pe_node_t *cluster_node = NULL;
 
     CRM_ASSERT(node);
 
     remote_rsc = node->details->remote_rsc;
     CRM_ASSERT(remote_rsc);
 
     cluster_node = pe__current_node(remote_rsc);
 
     /* If the cluster node the remote connection resource resides on
      * is unclean or went offline, we can't process any operations
      * on that remote node until after it starts elsewhere.
      */
     if(remote_rsc->next_role == RSC_ROLE_STOPPED || remote_rsc->allocated_to == NULL) {
         /* The connection resource is not going to run anywhere */
 
         if (cluster_node && cluster_node->details->unclean) {
             /* The remote connection is failed because its resource is on a
              * failed node and can't be recovered elsewhere, so we must fence.
              */
             return remote_state_failed;
         }
 
         if (!pcmk_is_set(remote_rsc->flags, pe_rsc_failed)) {
             /* Connection resource is cleanly stopped */
             return remote_state_stopped;
         }
 
         /* Connection resource is failed */
 
         if ((remote_rsc->next_role == RSC_ROLE_STOPPED)
             && remote_rsc->remote_reconnect_ms
             && node->details->remote_was_fenced
             && !pe__shutdown_requested(node)) {
 
             /* We won't know whether the connection is recoverable until the
              * reconnect interval expires and we reattempt connection.
              */
             return remote_state_unknown;
         }
 
         /* The remote connection is in a failed state. If there are any
          * resources known to be active on it (stop) or in an unknown state
          * (probe), we must assume the worst and fence it.
          */
         return remote_state_failed;
 
     } else if (cluster_node == NULL) {
         /* Connection is recoverable but not currently running anywhere, see if we can recover it first */
         return remote_state_unknown;
 
     } else if(cluster_node->details->unclean == TRUE
               || cluster_node->details->online == FALSE) {
         /* Connection is running on a dead node, see if we can recover it first */
         return remote_state_resting;
 
     } else if (pcmk__list_of_multiple(remote_rsc->running_on)
                && remote_rsc->partial_migration_source
                && remote_rsc->partial_migration_target) {
         /* We're in the middle of migrating a connection resource,
          * wait until after the resource migrates before performing
          * any actions.
          */
         return remote_state_resting;
 
     }
     return remote_state_alive;
 }
 
 /*!
  * \internal
  * \brief Order actions on remote node relative to actions for the connection
  */
 static void
 apply_remote_ordering(pe_action_t *action, pe_working_set_t *data_set)
 {
     pe_resource_t *remote_rsc = NULL;
     enum action_tasks task = text2task(action->task);
     enum remote_connection_state state = get_remote_node_state(action->node);
 
     enum pe_ordering order_opts = pe_order_none;
 
     if (action->rsc == NULL) {
         return;
     }
 
     CRM_ASSERT(action->node);
     CRM_ASSERT(pe__is_guest_or_remote_node(action->node));
 
     remote_rsc = action->node->details->remote_rsc;
     CRM_ASSERT(remote_rsc);
 
     crm_trace("Order %s action %s relative to %s%s (state: %s)",
               action->task, action->uuid,
               pcmk_is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
               remote_rsc->id, state2text(state));
 
     if (pcmk__strcase_any_of(action->task, CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, NULL)) {
         /* Migration ops map to "no_action", but we need to apply the same
          * ordering as for stop or demote (see get_router_node()).
          */
         task = stop_rsc;
     }
 
     switch (task) {
         case start_rsc:
         case action_promote:
             order_opts = pe_order_none;
 
             if (state == remote_state_failed) {
                 /* Force recovery, by making this action required */
                 pe__set_order_flags(order_opts, pe_order_implies_then);
             }
 
             /* Ensure connection is up before running this action */
             order_start_then_action(remote_rsc, action, order_opts, data_set);
             break;
 
         case stop_rsc:
             if(state == remote_state_alive) {
                 order_action_then_stop(action, remote_rsc,
                                        pe_order_implies_first, data_set);
 
             } else if(state == remote_state_failed) {
                 /* The resource is active on the node, but since we don't have a
                  * valid connection, the only way to stop the resource is by
                  * fencing the node. There is no need to order the stop relative
                  * to the remote connection, since the stop will become implied
                  * by the fencing.
                  */
                 pe_fence_node(data_set, action->node, "resources are active and the connection is unrecoverable", FALSE);
 
             } else if(remote_rsc->next_role == RSC_ROLE_STOPPED) {
                 /* State must be remote_state_unknown or remote_state_stopped.
                  * Since the connection is not coming back up in this
                  * transition, stop this resource first.
                  */
                 order_action_then_stop(action, remote_rsc,
                                        pe_order_implies_first, data_set);
 
             } else {
                 /* The connection is going to be started somewhere else, so
                  * stop this resource after that completes.
                  */
                 order_start_then_action(remote_rsc, action, pe_order_none, data_set);
             }
             break;
 
         case action_demote:
             /* Only order this demote relative to the connection start if the
              * connection isn't being torn down. Otherwise, the demote would be
              * blocked because the connection start would not be allowed.
              */
             if(state == remote_state_resting || state == remote_state_unknown) {
                 order_start_then_action(remote_rsc, action, pe_order_none,
                                         data_set);
             } /* Otherwise we can rely on the stop ordering */
             break;
 
         default:
             /* Wait for the connection resource to be up */
             if (is_recurring_action(action)) {
                 /* In case we ever get the recovery logic wrong, force
                  * recurring monitors to be restarted, even if just
                  * the connection was re-established
                  */
                 order_start_then_action(remote_rsc, action,
                                         pe_order_implies_then, data_set);
 
             } else {
                 pe_node_t *cluster_node = pe__current_node(remote_rsc);
 
                 if(task == monitor_rsc && state == remote_state_failed) {
                     /* We would only be here if we do not know the
                      * state of the resource on the remote node.
                      * Since we have no way to find out, it is
                      * necessary to fence the node.
                      */
                     pe_fence_node(data_set, action->node, "resources are in an unknown state and the connection is unrecoverable", FALSE);
                 }
 
                 if(cluster_node && state == remote_state_stopped) {
                     /* The connection is currently up, but is going
                      * down permanently.
                      *
                      * Make sure we check services are actually
                      * stopped _before_ we let the connection get
                      * closed
                      */
                     order_action_then_stop(action, remote_rsc,
                                            pe_order_runnable_left, data_set);
 
                 } else {
                     order_start_then_action(remote_rsc, action, pe_order_none,
                                             data_set);
                 }
             }
             break;
     }
 }
 
 static void
 apply_remote_node_ordering(pe_working_set_t *data_set)
 {
     if (!pcmk_is_set(data_set->flags, pe_flag_have_remote_nodes)) {
         return;
     }
 
     for (GList *gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
         pe_resource_t *remote = NULL;
 
         // We are only interested in resource actions
         if (action->rsc == NULL) {
             continue;
         }
 
         /* Special case: If we are clearing the failcount of an actual
          * remote connection resource, then make sure this happens before
          * any start of the resource in this transition.
          */
         if (action->rsc->is_remote_node &&
             pcmk__str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT, pcmk__str_casei)) {
 
             pcmk__new_ordering(action->rsc, NULL, action, action->rsc,
                                pcmk__op_key(action->rsc->id, RSC_START, 0),
                                NULL, pe_order_optional, data_set);
 
             continue;
         }
 
         // We are only interested in actions allocated to a node
         if (action->node == NULL) {
             continue;
         }
 
         if (!pe__is_guest_or_remote_node(action->node)) {
             continue;
         }
 
         /* We are only interested in real actions.
          *
          * @TODO This is probably wrong; pseudo-actions might be converted to
          * real actions and vice versa later in update_actions() at the end of
          * pcmk__apply_orderings().
          */
         if (pcmk_is_set(action->flags, pe_action_pseudo)) {
             continue;
         }
 
         remote = action->node->details->remote_rsc;
         if (remote == NULL) {
             // Orphaned
             continue;
         }
 
         /* Another special case: if a resource is moving to a Pacemaker Remote
          * node, order the stop on the original node after any start of the
          * remote connection. This ensures that if the connection fails to
          * start, we leave the resource running on the original node.
          */
         if (pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
             for (GList *item = action->rsc->actions; item != NULL;
                  item = item->next) {
                 pe_action_t *rsc_action = item->data;
 
                 if ((rsc_action->node->details != action->node->details)
                     && pcmk__str_eq(rsc_action->task, RSC_STOP, pcmk__str_casei)) {
                     pcmk__new_ordering(remote, start_key(remote), NULL,
                                        action->rsc, NULL, rsc_action,
                                        pe_order_optional, data_set);
                 }
             }
         }
 
         /* The action occurs across a remote connection, so create
          * ordering constraints that guarantee the action occurs while the node
          * is active (after start, before stop ... things like that).
          *
          * This is somewhat brittle in that we need to make sure the results of
          * this ordering are compatible with the result of get_router_node().
          * It would probably be better to add XML_LRM_ATTR_ROUTER_NODE as part
          * of this logic rather than action2xml().
          */
         if (remote->container) {
             crm_trace("Container ordering for %s", action->uuid);
             apply_container_ordering(action, data_set);
 
         } else {
             crm_trace("Remote ordering for %s", action->uuid);
             apply_remote_ordering(action, data_set);
         }
     }
 }
 
 static gboolean
 order_first_probe_unneeded(pe_action_t * probe, pe_action_t * rh_action)
 {
     /* No need to probe the resource on the node that is being
      * unfenced. Otherwise it might introduce transition loop
      * since probe will be performed after the node is
      * unfenced.
      */
     if (pcmk__str_eq(rh_action->task, CRM_OP_FENCE, pcmk__str_casei)
          && probe->node && rh_action->node
          && probe->node->details == rh_action->node->details) {
         const char *op = g_hash_table_lookup(rh_action->meta, "stonith_action");
 
         if (pcmk__str_eq(op, "on", pcmk__str_casei)) {
             return TRUE;
         }
     }
 
     // Shutdown waits for probe to complete only if it's on the same node
     if ((pcmk__str_eq(rh_action->task, CRM_OP_SHUTDOWN, pcmk__str_casei))
         && probe->node && rh_action->node
         && probe->node->details != rh_action->node->details) {
         return TRUE;
     }
     return FALSE;
 }
 
 static void
 order_first_probes_imply_stops(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     for (gIter = data_set->ordering_constraints; gIter != NULL; gIter = gIter->next) {
         pe__ordering_t *order = gIter->data;
         enum pe_ordering order_type = pe_order_optional;
 
         pe_resource_t *lh_rsc = order->lh_rsc;
         pe_resource_t *rh_rsc = order->rh_rsc;
         pe_action_t *lh_action = order->lh_action;
         pe_action_t *rh_action = order->rh_action;
         const char *lh_action_task = order->lh_action_task;
         const char *rh_action_task = order->rh_action_task;
 
         GList *probes = NULL;
         GList *rh_actions = NULL;
 
         GList *pIter = NULL;
 
         if (lh_rsc == NULL) {
             continue;
 
         } else if (rh_rsc && lh_rsc == rh_rsc) {
             continue;
         }
 
         if (lh_action == NULL && lh_action_task == NULL) {
             continue;
         }
 
         if (rh_action == NULL && rh_action_task == NULL) {
             continue;
         }
 
         /* Technically probe is expected to return "not running", which could be
          * the alternative of stop action if the status of the resource is
          * unknown yet.
          */
         if (lh_action && !pcmk__str_eq(lh_action->task, RSC_STOP, pcmk__str_casei)) {
             continue;
 
         } else if (lh_action == NULL
                    && lh_action_task
                    && !pcmk__ends_with(lh_action_task, "_" RSC_STOP "_0")) {
             continue;
         }
 
         /* Do not probe the resource inside of a stopping container. Otherwise
          * it might introduce transition loop since probe will be performed
          * after the container starts again.
          */
         if (rh_rsc && lh_rsc->container == rh_rsc) {
             if (rh_action && pcmk__str_eq(rh_action->task, RSC_STOP, pcmk__str_casei)) {
                 continue;
 
             } else if (rh_action == NULL && rh_action_task
                        && pcmk__ends_with(rh_action_task,"_" RSC_STOP "_0")) {
                 continue;
             }
         }
 
         if (order->type == pe_order_none) {
             continue;
         }
 
         // Preserve the order options for future filtering
         if (pcmk_is_set(order->type, pe_order_apply_first_non_migratable)) {
             pe__set_order_flags(order_type,
                                 pe_order_apply_first_non_migratable);
         }
 
         if (pcmk_is_set(order->type, pe_order_same_node)) {
             pe__set_order_flags(order_type, pe_order_same_node);
         }
 
         // Keep the order types for future filtering
         if (order->type == pe_order_anti_colocation
                    || order->type == pe_order_load) {
             order_type = order->type;
         }
 
         probes = pe__resource_actions(lh_rsc, NULL, RSC_STATUS, FALSE);
         if (probes == NULL) {
             continue;
         }
 
         if (rh_action) {
             rh_actions = g_list_prepend(rh_actions, rh_action);
 
         } else if (rh_rsc && rh_action_task) {
             rh_actions = find_actions(rh_rsc->actions, rh_action_task, NULL);
         }
 
         if (rh_actions == NULL) {
             g_list_free(probes);
             continue;
         }
 
         crm_trace("Processing for LH probe based on ordering constraint %s -> %s"
                   " (id=%d, type=%.6x)",
                   lh_action ? lh_action->uuid : lh_action_task,
                   rh_action ? rh_action->uuid : rh_action_task,
                   order->id, order->type);
 
         for (pIter = probes; pIter != NULL; pIter = pIter->next) {
             pe_action_t *probe = (pe_action_t *) pIter->data;
             GList *rIter = NULL;
 
             for (rIter = rh_actions; rIter != NULL; rIter = rIter->next) {
                 pe_action_t *rh_action_iter = (pe_action_t *) rIter->data;
 
                 if (order_first_probe_unneeded(probe, rh_action_iter)) {
                     continue;
                 }
                 order_actions(probe, rh_action_iter, order_type);
             }
         }
 
         g_list_free(rh_actions);
         g_list_free(probes);
     }
 }
 
 static void
 order_first_probe_then_restart_repromote(pe_action_t * probe,
                                          pe_action_t * after,
                                          pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     bool interleave = FALSE;
     pe_resource_t *compatible_rsc = NULL;
 
     if (probe == NULL
         || probe->rsc == NULL
         || probe->rsc->variant != pe_native) {
         return;
     }
 
     if (after == NULL
         // Avoid running into any possible loop
         || pcmk_is_set(after->flags, pe_action_tracking)) {
         return;
     }
 
     if (!pcmk__str_eq(probe->task, RSC_STATUS, pcmk__str_casei)) {
         return;
     }
 
     pe__set_action_flags(after, pe_action_tracking);
 
     crm_trace("Processing based on %s %s -> %s %s",
               probe->uuid,
               probe->node ? probe->node->details->uname: "",
               after->uuid,
               after->node ? after->node->details->uname : "");
 
     if (after->rsc
         /* Better not build a dependency directly with a clone/group.
          * We are going to proceed through the ordering chain and build
          * dependencies with its children.
          */
         && after->rsc->variant == pe_native
         && probe->rsc != after->rsc) {
 
             GList *then_actions = NULL;
             enum pe_ordering probe_order_type = pe_order_optional;
 
             if (pcmk__str_eq(after->task, RSC_START, pcmk__str_casei)) {
                 then_actions = pe__resource_actions(after->rsc, NULL, RSC_STOP, FALSE);
 
             } else if (pcmk__str_eq(after->task, RSC_PROMOTE, pcmk__str_casei)) {
                 then_actions = pe__resource_actions(after->rsc, NULL, RSC_DEMOTE, FALSE);
             }
 
             for (gIter = then_actions; gIter != NULL; gIter = gIter->next) {
                 pe_action_t *then = (pe_action_t *) gIter->data;
 
                 // Skip any pseudo action which for example is implied by fencing
                 if (pcmk_is_set(then->flags, pe_action_pseudo)) {
                     continue;
                 }
 
                 order_actions(probe, then, probe_order_type);
             }
             g_list_free(then_actions);
     }
 
     if (after->rsc
         && after->rsc->variant > pe_group) {
         const char *interleave_s = g_hash_table_lookup(after->rsc->meta,
                                                        XML_RSC_ATTR_INTERLEAVE);
 
         interleave = crm_is_true(interleave_s);
 
         if (interleave) {
             /* For an interleaved clone, we should build a dependency only
              * with the relevant clone child.
              */
             compatible_rsc = find_compatible_child(probe->rsc,
                                                    after->rsc,
                                                    RSC_ROLE_UNKNOWN,
                                                    FALSE, data_set);
         }
     }
 
     for (gIter = after->actions_after; gIter != NULL; gIter = gIter->next) {
         pe_action_wrapper_t *after_wrapper = (pe_action_wrapper_t *) gIter->data;
         /* pe_order_implies_then is the reason why a required A.start
          * implies/enforces B.start to be required too, which is the cause of
          * B.restart/re-promote.
          *
          * Not sure about pe_order_implies_then_on_node though. It's now only
          * used for unfencing case, which tends to introduce transition
          * loops...
          */
 
         if (!pcmk_is_set(after_wrapper->type, pe_order_implies_then)) {
             /* The order type between a group/clone and its child such as
              * B.start-> B_child.start is:
              * pe_order_implies_first_printed | pe_order_runnable_left
              *
              * Proceed through the ordering chain and build dependencies with
              * its children.
              */
             if (after->rsc == NULL
                 || after->rsc->variant < pe_group
                 || probe->rsc->parent == after->rsc
                 || after_wrapper->action->rsc == NULL
                 || after_wrapper->action->rsc->variant > pe_group
                 || after->rsc != after_wrapper->action->rsc->parent) {
                 continue;
             }
 
             /* Proceed to the children of a group or a non-interleaved clone.
              * For an interleaved clone, proceed only to the relevant child.
              */
             if (after->rsc->variant > pe_group
                 && interleave == TRUE
                 && (compatible_rsc == NULL
                     || compatible_rsc != after_wrapper->action->rsc)) {
                 continue;
             }
         }
 
         crm_trace("Proceeding through %s %s -> %s %s (type=0x%.6x)",
                   after->uuid,
                   after->node ? after->node->details->uname: "",
                   after_wrapper->action->uuid,
                   after_wrapper->action->node ? after_wrapper->action->node->details->uname : "",
                   after_wrapper->type);
 
         order_first_probe_then_restart_repromote(probe, after_wrapper->action, data_set);
     }
 }
 
 static void clear_actions_tracking_flag(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         if (pcmk_is_set(action->flags, pe_action_tracking)) {
             pe__clear_action_flags(action, pe_action_tracking);
         }
     }
 }
 
 static void
 order_first_rsc_probes(pe_resource_t * rsc, pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     GList *probes = NULL;
 
     g_list_foreach(rsc->children, (GFunc) order_first_rsc_probes, data_set);
 
     if (rsc->variant != pe_native) {
         return;
     }
 
     probes = pe__resource_actions(rsc, NULL, RSC_STATUS, FALSE);
 
     for (gIter = probes; gIter != NULL; gIter= gIter->next) {
         pe_action_t *probe = (pe_action_t *) gIter->data;
         GList *aIter = NULL;
 
         for (aIter = probe->actions_after; aIter != NULL; aIter = aIter->next) {
             pe_action_wrapper_t *after_wrapper = (pe_action_wrapper_t *) aIter->data;
 
             order_first_probe_then_restart_repromote(probe, after_wrapper->action, data_set);
             clear_actions_tracking_flag(data_set);
         }
     }
 
     g_list_free(probes);
 }
 
 static void
 order_first_probes(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
 
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         order_first_rsc_probes(rsc, data_set);
     }
 
     order_first_probes_imply_stops(data_set);
 }
 
 static void
 order_then_probes(pe_working_set_t * data_set)
 {
 #if 0
     GList *gIter = NULL;
 
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         /* Given "A then B", we would prefer to wait for A to be
          * started before probing B.
          *
          * If A was a filesystem on which the binaries and data for B
          * lived, it would have been useful if the author of B's agent
          * could assume that A is running before B.monitor will be
          * called.
          *
          * However we can't _only_ probe once A is running, otherwise
          * we'd not detect the state of B if A could not be started
          * for some reason.
          *
          * In practice however, we cannot even do an opportunistic
          * version of this because B may be moving:
          *
          *   B.probe -> B.start
          *   B.probe -> B.stop
          *   B.stop -> B.start
          *   A.stop -> A.start
          *   A.start -> B.probe
          *
          * So far so good, but if we add the result of this code:
          *
          *   B.stop -> A.stop
          *
          * Then we get a loop:
          *
          *   B.probe -> B.stop -> A.stop -> A.start -> B.probe
          *
          * We could kill the 'B.probe -> B.stop' dependency, but that
          * could mean stopping B "too" soon, because B.start must wait
          * for the probes to complete.
          *
          * Another option is to allow it only if A is a non-unique
          * clone with clone-max == node-max (since we'll never be
          * moving it).  However, we could still be stopping one
          * instance at the same time as starting another.
 
          * The complexity of checking for allowed conditions combined
          * with the ever narrowing usecase suggests that this code
          * should remain disabled until someone gets smarter.
          */
         pe_action_t *start = NULL;
         GList *actions = NULL;
         GList *probes = NULL;
 
         actions = pe__resource_actions(rsc, NULL, RSC_START, FALSE);
 
         if (actions) {
             start = actions->data;
             g_list_free(actions);
         }
 
         if(start == NULL) {
             crm_err("No start action for %s", rsc->id);
             continue;
         }
 
         probes = pe__resource_actions(rsc, NULL, RSC_STATUS, FALSE);
 
         for (actions = start->actions_before; actions != NULL; actions = actions->next) {
             pe_action_wrapper_t *before = (pe_action_wrapper_t *) actions->data;
 
             GList *pIter = NULL;
             pe_action_t *first = before->action;
             pe_resource_t *first_rsc = first->rsc;
 
             if(first->required_runnable_before) {
                 GList *clone_actions = NULL;
                 for (clone_actions = first->actions_before; clone_actions != NULL; clone_actions = clone_actions->next) {
                     before = (pe_action_wrapper_t *) clone_actions->data;
 
                     crm_trace("Testing %s -> %s (%p) for %s", first->uuid, before->action->uuid, before->action->rsc, start->uuid);
 
                     CRM_ASSERT(before->action->rsc);
                     first_rsc = before->action->rsc;
                     break;
                 }
 
             } else if(!pcmk__str_eq(first->task, RSC_START, pcmk__str_casei)) {
                 crm_trace("Not a start op %s for %s", first->uuid, start->uuid);
             }
 
             if(first_rsc == NULL) {
                 continue;
 
             } else if(uber_parent(first_rsc) == uber_parent(start->rsc)) {
                 crm_trace("Same parent %s for %s", first_rsc->id, start->uuid);
                 continue;
 
             } else if(FALSE && pe_rsc_is_clone(uber_parent(first_rsc)) == FALSE) {
                 crm_trace("Not a clone %s for %s", first_rsc->id, start->uuid);
                 continue;
             }
 
             crm_err("Applying %s before %s %d", first->uuid, start->uuid, uber_parent(first_rsc)->variant);
 
             for (pIter = probes; pIter != NULL; pIter = pIter->next) {
                 pe_action_t *probe = (pe_action_t *) pIter->data;
 
                 crm_err("Ordering %s before %s", first->uuid, probe->uuid);
                 order_actions(first, probe, pe_order_optional);
             }
         }
     }
 #endif
 }
 
 void
 pcmk__order_probes(pe_working_set_t *data_set)
 {
     order_first_probes(data_set);
     order_then_probes(data_set);
 }
 
 static int transition_id = -1;
 
 /*!
  * \internal
  * \brief Log a message after calculating a transition
  *
  * \param[in] filename  Where transition input is stored
  */
 void
 pcmk__log_transition_summary(const char *filename)
 {
     if (was_processing_error) {
         crm_err("Calculated transition %d (with errors)%s%s",
                 transition_id,
                 (filename == NULL)? "" : ", saving inputs in ",
                 (filename == NULL)? "" : filename);
 
     } else if (was_processing_warning) {
         crm_warn("Calculated transition %d (with warnings)%s%s",
                  transition_id,
                  (filename == NULL)? "" : ", saving inputs in ",
                  (filename == NULL)? "" : filename);
 
     } else {
         crm_notice("Calculated transition %d%s%s",
                    transition_id,
                    (filename == NULL)? "" : ", saving inputs in ",
                    (filename == NULL)? "" : filename);
     }
     if (crm_config_error) {
         crm_notice("Configuration errors found during scheduler processing,"
                    "  please run \"crm_verify -L\" to identify issues");
     }
 }
 
 /*
  * Create a dependency graph to send to the transitioner (via the controller)
  */
 gboolean
 stage8(pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     const char *value = NULL;
     long long limit = 0LL;
 
     transition_id++;
     crm_trace("Creating transition graph %d.", transition_id);
 
     data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH);
 
     value = pe_pref(data_set->config_hash, "cluster-delay");
     crm_xml_add(data_set->graph, "cluster-delay", value);
 
     value = pe_pref(data_set->config_hash, "stonith-timeout");
     crm_xml_add(data_set->graph, "stonith-timeout", value);
 
     crm_xml_add(data_set->graph, "failed-stop-offset", "INFINITY");
 
     if (pcmk_is_set(data_set->flags, pe_flag_start_failure_fatal)) {
         crm_xml_add(data_set->graph, "failed-start-offset", "INFINITY");
     } else {
         crm_xml_add(data_set->graph, "failed-start-offset", "1");
     }
 
     value = pe_pref(data_set->config_hash, "batch-limit");
     crm_xml_add(data_set->graph, "batch-limit", value);
 
     crm_xml_add_int(data_set->graph, "transition_id", transition_id);
 
     value = pe_pref(data_set->config_hash, "migration-limit");
     if ((pcmk__scan_ll(value, &limit, 0LL) == pcmk_rc_ok) && (limit > 0)) {
         crm_xml_add(data_set->graph, "migration-limit", value);
     }
 
     if (data_set->recheck_by > 0) {
         char *recheck_epoch = NULL;
 
         recheck_epoch = crm_strdup_printf("%llu",
                                           (long long) data_set->recheck_by);
         crm_xml_add(data_set->graph, "recheck-by", recheck_epoch);
         free(recheck_epoch);
     }
 
     /* The following code will de-duplicate action inputs, so nothing past this
      * should rely on the action input type flags retaining their original
      * values.
      */
 
     gIter = data_set->resources;
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         pe_rsc_trace(rsc, "processing actions for rsc=%s", rsc->id);
         rsc->cmds->expand(rsc, data_set);
     }
 
     crm_log_xml_trace(data_set->graph, "created resource-driven action list");
 
     /* pseudo action to distribute list of nodes with maintenance state update */
     add_maintenance_update(data_set);
 
     /* catch any non-resource specific actions */
     crm_trace("processing non-resource actions");
 
     gIter = data_set->actions;
     for (; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         if (action->rsc
             && action->node
             && action->node->details->shutdown
             && !pcmk_is_set(action->rsc->flags, pe_rsc_maintenance)
             && !pcmk_any_flags_set(action->flags,
                                    pe_action_optional|pe_action_runnable)
             && pcmk__str_eq(action->task, RSC_STOP, pcmk__str_none)
             ) {
             /* Eventually we should just ignore the 'fence' case
              * But for now it's the best way to detect (in CTS) when
              * CIB resource updates are being lost
              */
             if (pcmk_is_set(data_set->flags, pe_flag_have_quorum)
                 || data_set->no_quorum_policy == no_quorum_ignore) {
                 crm_crit("Cannot %s node '%s' because of %s:%s%s (%s)",
                          action->node->details->unclean ? "fence" : "shut down",
                          action->node->details->uname, action->rsc->id,
                          pcmk_is_set(action->rsc->flags, pe_rsc_managed)? " blocked" : " unmanaged",
                          pcmk_is_set(action->rsc->flags, pe_rsc_failed)? " failed" : "",
                          action->uuid);
             }
         }
 
         graph_element_from_action(action, data_set);
     }
 
     crm_log_xml_trace(data_set->graph, "created generic action list");
     crm_trace("Created transition graph %d.", transition_id);
 
     return TRUE;
 }
 
 void
 LogNodeActions(pe_working_set_t * data_set)
 {
     pcmk__output_t *out = data_set->priv;
     GList *gIter = NULL;
 
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         char *node_name = NULL;
         char *task = NULL;
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         if (action->rsc != NULL) {
             continue;
         } else if (pcmk_is_set(action->flags, pe_action_optional)) {
             continue;
         }
 
         if (pe__is_guest_node(action->node)) {
             node_name = crm_strdup_printf("%s (resource: %s)", action->node->details->uname, action->node->details->remote_rsc->container->id);
         } else if(action->node) {
             node_name = crm_strdup_printf("%s", action->node->details->uname);
         }
 
 
         if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
             task = strdup("Shutdown");
         } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
             const char *op = g_hash_table_lookup(action->meta, "stonith_action");
             task = crm_strdup_printf("Fence (%s)", op);
         }
 
         out->message(out, "node-action", task, node_name, action->reason);
 
         free(node_name);
         free(task);
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_constraints.c b/lib/pacemaker/pcmk_sched_constraints.c
index b99e435fa5..7cfb208c19 100644
--- a/lib/pacemaker/pcmk_sched_constraints.c
+++ b/lib/pacemaker/pcmk_sched_constraints.c
@@ -1,365 +1,374 @@
 /*
  * Copyright 2004-2021 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/param.h>
 #include <sys/types.h>
 #include <stdbool.h>
 #include <regex.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/iso8601.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <crm/pengine/rules.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 static bool
 evaluate_lifetime(xmlNode *lifetime, pe_working_set_t *data_set)
 {
     bool result = FALSE;
     crm_time_t *next_change = crm_time_new_undefined();
 
     result = pe_evaluate_rules(lifetime, NULL, data_set->now, next_change);
     if (crm_time_is_defined(next_change)) {
         time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change);
 
         pe__update_recheck_time(recheck, data_set);
     }
     crm_time_free(next_change);
     return result;
 }
 
-gboolean
-unpack_constraints(xmlNode * xml_constraints, pe_working_set_t * data_set)
+/*!
+ * \internal
+ * \brief Unpack constraints from XML
+ *
+ * Given a cluster working set, unpack all constraints from its input XML into
+ * data structures.
+ *
+ * \param[in,out] data_set  Cluster working set
+ */
+void
+pcmk__unpack_constraints(pe_working_set_t *data_set)
 {
-    xmlNode *xml_obj = NULL;
-    xmlNode *lifetime = NULL;
+    xmlNode *xml_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
+                                               data_set->input);
+
+    for (xmlNode *xml_obj = pcmk__xe_first_child(xml_constraints);
+         xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj)) {
 
-    for (xml_obj = pcmk__xe_first_child(xml_constraints); xml_obj != NULL;
-         xml_obj = pcmk__xe_next(xml_obj)) {
+        xmlNode *lifetime = NULL;
         const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
         const char *tag = crm_element_name(xml_obj);
 
         if (id == NULL) {
             pcmk__config_err("Ignoring <%s> constraint without "
                              XML_ATTR_ID, tag);
             continue;
         }
 
         crm_trace("Unpacking %s constraint '%s'", tag, id);
 
         lifetime = first_named_child(xml_obj, "lifetime");
-        if (lifetime) {
+        if (lifetime != NULL) {
             pcmk__config_warn("Support for 'lifetime' attribute (in %s) is "
                               "deprecated (the rules it contains should "
                               "instead be direct descendents of the "
                               "constraint object)", id);
         }
 
-        if (lifetime && !evaluate_lifetime(lifetime, data_set)) {
+        if ((lifetime != NULL) && !evaluate_lifetime(lifetime, data_set)) {
             crm_info("Constraint %s %s is not active", tag, id);
 
         } else if (pcmk__str_eq(XML_CONS_TAG_RSC_ORDER, tag, pcmk__str_casei)) {
             pcmk__unpack_ordering(xml_obj, data_set);
 
         } else if (pcmk__str_eq(XML_CONS_TAG_RSC_DEPEND, tag, pcmk__str_casei)) {
             pcmk__unpack_colocation(xml_obj, data_set);
 
         } else if (pcmk__str_eq(XML_CONS_TAG_RSC_LOCATION, tag, pcmk__str_casei)) {
             pcmk__unpack_location(xml_obj, data_set);
 
         } else if (pcmk__str_eq(XML_CONS_TAG_RSC_TICKET, tag, pcmk__str_casei)) {
             pcmk__unpack_rsc_ticket(xml_obj, data_set);
 
         } else {
             pe_err("Unsupported constraint type: %s", tag);
         }
     }
-
-    return TRUE;
 }
 
 pe_resource_t *
 pcmk__find_constraint_resource(GList *rsc_list, const char *id)
 {
     GList *rIter = NULL;
 
     for (rIter = rsc_list; id && rIter; rIter = rIter->next) {
         pe_resource_t *parent = rIter->data;
         pe_resource_t *match = parent->fns->find_rsc(parent, id, NULL,
                                                      pe_find_renamed);
 
         if (match != NULL) {
             if(!pcmk__str_eq(match->id, id, pcmk__str_casei)) {
                 /* We found an instance of a clone instead */
                 match = uber_parent(match);
                 crm_debug("Found %s for %s", match->id, id);
             }
             return match;
         }
     }
     crm_trace("No match for %s", id);
     return NULL;
 }
 
 static gboolean
 pe_find_constraint_tag(pe_working_set_t * data_set, const char * id, pe_tag_t ** tag)
 {
     gboolean rc = FALSE;
 
     *tag = NULL;
     rc = g_hash_table_lookup_extended(data_set->template_rsc_sets, id,
                                        NULL, (gpointer*) tag);
 
     if (rc == FALSE) {
         rc = g_hash_table_lookup_extended(data_set->tags, id,
                                           NULL, (gpointer*) tag);
 
         if (rc == FALSE) {
             crm_warn("No template or tag named '%s'", id);
             return FALSE;
 
         } else if (*tag == NULL) {
             crm_warn("No resource is tagged with '%s'", id);
             return FALSE;
         }
 
     } else if (*tag == NULL) {
         crm_warn("No resource is derived from template '%s'", id);
         return FALSE;
     }
 
     return rc;
 }
 
 gboolean
 pcmk__valid_resource_or_tag(pe_working_set_t *data_set, const char *id,
                             pe_resource_t **rsc, pe_tag_t **tag)
 {
     gboolean rc = FALSE;
 
     if (rsc) {
         *rsc = NULL;
         *rsc = pcmk__find_constraint_resource(data_set->resources, id);
         if (*rsc) {
             return TRUE;
         }
     }
 
     if (tag) {
         *tag = NULL;
         rc = pe_find_constraint_tag(data_set, id, tag);
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Replace any resource tags with equivalent resource_ref entries
  *
  * If a given constraint has resource sets, check each set for resource_ref
  * entries that list tags rather than resource IDs, and replace any found with
  * resource_ref entries for the corresponding resource IDs.
  *
  * \param[in]  xml_obj       Constraint XML
  * \param[in]  data_set      Cluster working set
  *
  * \return Equivalent XML with resource tags replaced (or NULL if none)
  * \note It is the caller's responsibility to free the result with free_xml().
  */
 xmlNode *
 pcmk__expand_tags_in_sets(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     xmlNode *new_xml = NULL;
     bool any_refs = false;
 
     // Short-circuit if there are no sets
     if (first_named_child(xml_obj, XML_CONS_TAG_RSC_SET) == NULL) {
         return NULL;
     }
 
     new_xml = copy_xml(xml_obj);
 
     for (xmlNode *set = first_named_child(new_xml, XML_CONS_TAG_RSC_SET);
          set != NULL; set = crm_next_same_xml(set)) {
 
         GList *tag_refs = NULL;
         GList *gIter = NULL;
 
         for (xmlNode *xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             pe_resource_t *rsc = NULL;
             pe_tag_t *tag = NULL;
 
             if (!pcmk__valid_resource_or_tag(data_set, ID(xml_rsc), &rsc,
                                              &tag)) {
                 pcmk__config_err("Ignoring resource sets for constraint '%s' "
                                  "because '%s' is not a valid resource or tag",
                                  ID(xml_obj), ID(xml_rsc));
                 free_xml(new_xml);
                 return NULL;
 
             } else if (rsc) {
                 continue;
 
             } else if (tag) {
                 /* The resource_ref under the resource_set references a template/tag */
                 xmlNode *last_ref = xml_rsc;
 
                 /* A sample:
 
                    Original XML:
 
                    <resource_set id="tag1-colocation-0" sequential="true">
                      <resource_ref id="rsc1"/>
                      <resource_ref id="tag1"/>
                      <resource_ref id="rsc4"/>
                    </resource_set>
 
                    Now we are appending rsc2 and rsc3 which are tagged with tag1 right after it:
 
                    <resource_set id="tag1-colocation-0" sequential="true">
                      <resource_ref id="rsc1"/>
                      <resource_ref id="tag1"/>
                      <resource_ref id="rsc2"/>
                      <resource_ref id="rsc3"/>
                      <resource_ref id="rsc4"/>
                    </resource_set>
 
                  */
 
                 for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) {
                     const char *obj_ref = (const char *) gIter->data;
                     xmlNode *new_rsc_ref = NULL;
 
                     new_rsc_ref = xmlNewDocRawNode(getDocPtr(set), NULL,
                                                    (pcmkXmlStr) XML_TAG_RESOURCE_REF, NULL);
                     crm_xml_add(new_rsc_ref, XML_ATTR_ID, obj_ref);
                     xmlAddNextSibling(last_ref, new_rsc_ref);
 
                     last_ref = new_rsc_ref;
                 }
 
                 any_refs = true;
 
                 /* Freeing the resource_ref now would break the XML child
                  * iteration, so just remember it for freeing later.
                  */
                 tag_refs = g_list_append(tag_refs, xml_rsc);
             }
         }
 
         /* Now free '<resource_ref id="tag1"/>', and finally get:
 
            <resource_set id="tag1-colocation-0" sequential="true">
              <resource_ref id="rsc1"/>
              <resource_ref id="rsc2"/>
              <resource_ref id="rsc3"/>
              <resource_ref id="rsc4"/>
            </resource_set>
 
          */
         for (gIter = tag_refs; gIter != NULL; gIter = gIter->next) {
             xmlNode *tag_ref = gIter->data;
 
             free_xml(tag_ref);
         }
         g_list_free(tag_refs);
     }
 
     if (!any_refs) {
         free_xml(new_xml);
         new_xml = NULL;
     }
     return new_xml;
 }
 
 gboolean
 pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
                  gboolean convert_rsc, pe_working_set_t *data_set)
 {
     const char *cons_id = NULL;
     const char *id = NULL;
 
     pe_resource_t *rsc = NULL;
     pe_tag_t *tag = NULL;
 
     *rsc_set = NULL;
 
     CRM_CHECK((xml_obj != NULL) && (attr != NULL), return FALSE);
 
     cons_id = ID(xml_obj);
     if (cons_id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
         return FALSE;
     }
 
     id = crm_element_value(xml_obj, attr);
     if (id == NULL) {
         return TRUE;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, id, &rsc, &tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", cons_id, id);
         return FALSE;
 
     } else if (tag) {
         GList *gIter = NULL;
 
         /* A template/tag is referenced by the "attr" attribute (first, then, rsc or with-rsc).
            Add the template/tag's corresponding "resource_set" which contains the resources derived
            from it or tagged with it under the constraint. */
         *rsc_set = create_xml_node(xml_obj, XML_CONS_TAG_RSC_SET);
         crm_xml_add(*rsc_set, XML_ATTR_ID, id);
 
         for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) {
             const char *obj_ref = (const char *) gIter->data;
             xmlNode *rsc_ref = NULL;
 
             rsc_ref = create_xml_node(*rsc_set, XML_TAG_RESOURCE_REF);
             crm_xml_add(rsc_ref, XML_ATTR_ID, obj_ref);
         }
 
         /* Set sequential="false" for the resource_set */
         crm_xml_add(*rsc_set, "sequential", XML_BOOLEAN_FALSE);
 
     } else if (rsc && convert_rsc) {
         /* Even a regular resource is referenced by "attr", convert it into a resource_set.
            Because the other side of the constraint could be a template/tag reference. */
         xmlNode *rsc_ref = NULL;
 
         *rsc_set = create_xml_node(xml_obj, XML_CONS_TAG_RSC_SET);
         crm_xml_add(*rsc_set, XML_ATTR_ID, id);
 
         rsc_ref = create_xml_node(*rsc_set, XML_TAG_RESOURCE_REF);
         crm_xml_add(rsc_ref, XML_ATTR_ID, id);
 
     } else {
         return TRUE;
     }
 
     /* Remove the "attr" attribute referencing the template/tag */
     if (*rsc_set) {
         xml_remove_prop(xml_obj, attr);
     }
 
     return TRUE;
 }
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index c86ce29c1d..21557d1f33 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2418 +1,2416 @@
 /*
  * Copyright 2004-2021 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/param.h>
 
 #include <crm/crm.h>
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <signal.h>
 #include <sys/utsname.h>
 
 #include <crm/msg_xml.h>
 #include <crm/services.h>
 #include <crm/lrmd.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/curses_internal.h>
 #include <crm/common/internal.h>  // pcmk__ends_with_ext()
 #include <crm/common/ipc.h>
 #include <crm/common/iso8601_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/output.h>
 #include <crm/common/output_internal.h>
 #include <crm/common/util.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <crm/cib/internal.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <pacemaker-internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 
 #include "crm_mon.h"
 
 #define SUMMARY "Provides a summary of cluster's current state.\n\n" \
                 "Outputs varying levels of detail in a number of different formats."
 
 /*
  * Definitions indicating which items to print
  */
 
 static unsigned int show;
 static unsigned int show_opts = pcmk_show_pending;
 
 /*
  * Definitions indicating how to output
  */
 
 static mon_output_format_t output_format = mon_output_unset;
 
 /* other globals */
 static GIOChannel *io_channel = NULL;
 static GMainLoop *mainloop = NULL;
 static guint reconnect_timer = 0;
 static mainloop_timer_t *refresh_timer = NULL;
 static pe_working_set_t *mon_data_set = NULL;
 
 static cib_t *cib = NULL;
 static stonith_t *st = NULL;
 static xmlNode *current_cib = NULL;
 
 static GError *error = NULL;
 static pcmk__common_args_t *args = NULL;
 static pcmk__output_t *out = NULL;
 static GOptionContext *context = NULL;
 static gchar **processed_args = NULL;
 
 static time_t last_refresh = 0;
 volatile crm_trigger_t *refresh_trigger = NULL;
 
 static gboolean fence_history = FALSE;
 static gboolean has_warnings = FALSE;
 static gboolean on_remote_node = FALSE;
 static gboolean use_cib_native = FALSE;
 
 int interactive_fence_level = 0;
 
 static pcmk__supported_format_t formats[] = {
 #if CURSES_ENABLED
     CRM_MON_SUPPORTED_FORMAT_CURSES,
 #endif
     PCMK__SUPPORTED_FORMAT_HTML,
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 /* Define exit codes for monitoring-compatible output
  * For nagios plugins, the possibilities are
  * OK=0, WARN=1, CRIT=2, and UNKNOWN=3
  */
 #define MON_STATUS_WARN    CRM_EX_ERROR
 #define MON_STATUS_CRIT    CRM_EX_INVALID_PARAM
 #define MON_STATUS_UNKNOWN CRM_EX_UNIMPLEMENT_FEATURE
 
 #define RECONNECT_MSECS 5000
 
 struct {
     guint reconnect_ms;
     gboolean daemonize;
     gboolean fence_connect;
     gboolean one_shot;
     gboolean print_pending;
     gboolean show_bans;
     gboolean watch_fencing;
     char *pid_file;
     char *external_agent;
     char *external_recipient;
     char *neg_location_prefix;
     char *only_node;
     char *only_rsc;
     GSList *user_includes_excludes;
     GSList *includes_excludes;
 } options = {
     .fence_connect = TRUE,
     .reconnect_ms = RECONNECT_MSECS
 };
 
 static void clean_up_cib_connection(void);
 static void clean_up_fencing_connection(void);
 static crm_exit_t clean_up(crm_exit_t exit_code);
 static void crm_diff_update(const char *event, xmlNode * msg);
 static void handle_connection_failures(int rc);
 static int mon_refresh_display(gpointer user_data);
 static int cib_connect(gboolean full);
 static int fencing_connect(void);
 static int pacemakerd_status(void);
 static void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
 static void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
 static void refresh_after_event(gboolean data_updated, gboolean enforce);
 
 static unsigned int
 all_includes(mon_output_format_t fmt) {
     if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
         return ~pcmk_section_options;
     } else {
         return pcmk_section_all;
     }
 }
 
 static unsigned int
 default_includes(mon_output_format_t fmt) {
     switch (fmt) {
         case mon_output_monitor:
         case mon_output_plain:
         case mon_output_console:
             return pcmk_section_stack | pcmk_section_dc | pcmk_section_times | pcmk_section_counts |
                    pcmk_section_nodes | pcmk_section_resources | pcmk_section_failures |
                    pcmk_section_maint_mode;
 
         case mon_output_xml:
         case mon_output_legacy_xml:
             return all_includes(fmt);
 
         case mon_output_html:
         case mon_output_cgi:
             return pcmk_section_summary | pcmk_section_nodes | pcmk_section_resources |
                    pcmk_section_failures;
 
         default:
             return 0;
     }
 }
 
 struct {
     const char *name;
     unsigned int bit;
 } sections[] = {
     { "attributes", pcmk_section_attributes },
     { "bans", pcmk_section_bans },
     { "counts", pcmk_section_counts },
     { "dc", pcmk_section_dc },
     { "failcounts", pcmk_section_failcounts },
     { "failures", pcmk_section_failures },
     { "fencing", pcmk_section_fencing_all },
     { "fencing-failed", pcmk_section_fence_failed },
     { "fencing-pending", pcmk_section_fence_pending },
     { "fencing-succeeded", pcmk_section_fence_worked },
     { "maint-mode", pcmk_section_maint_mode },
     { "nodes", pcmk_section_nodes },
     { "operations", pcmk_section_operations },
     { "options", pcmk_section_options },
     { "resources", pcmk_section_resources },
     { "stack", pcmk_section_stack },
     { "summary", pcmk_section_summary },
     { "tickets", pcmk_section_tickets },
     { "times", pcmk_section_times },
     { NULL }
 };
 
 static unsigned int
 find_section_bit(const char *name) {
     for (int i = 0; sections[i].name != NULL; i++) {
         if (pcmk__str_eq(sections[i].name, name, pcmk__str_casei)) {
             return sections[i].bit;
         }
     }
 
     return 0;
 }
 
 static gboolean
 apply_exclude(const gchar *excludes, GError **error) {
     char **parts = NULL;
     gboolean result = TRUE;
 
     parts = g_strsplit(excludes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         unsigned int bit = find_section_bit(*s);
 
         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
             show = 0;
         } else if (pcmk__str_eq(*s, "none", pcmk__str_none)) {
             show = all_includes(output_format);
         } else if (bit != 0) {
             show &= ~bit;
         } else {
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         "--exclude options: all, attributes, bans, counts, dc, "
                         "failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, maint-mode, nodes, "
                         "none, operations, options, resources, stack, summary, "
                         "tickets, times");
             result = FALSE;
             break;
         }
     }
     g_strfreev(parts);
     return result;
 }
 
 static gboolean
 apply_include(const gchar *includes, GError **error) {
     char **parts = NULL;
     gboolean result = TRUE;
 
     parts = g_strsplit(includes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         unsigned int bit = find_section_bit(*s);
 
         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
             show = all_includes(output_format);
         } else if (pcmk__starts_with(*s, "bans")) {
             show |= pcmk_section_bans;
             if (options.neg_location_prefix != NULL) {
                 free(options.neg_location_prefix);
                 options.neg_location_prefix = NULL;
             }
 
             if (strlen(*s) > 4 && (*s)[4] == ':') {
                 options.neg_location_prefix = strdup(*s+5);
             }
         } else if (pcmk__str_any_of(*s, "default", "defaults", NULL)) {
             show |= default_includes(output_format);
         } else if (pcmk__str_eq(*s, "none", pcmk__str_none)) {
             show = 0;
         } else if (bit != 0) {
             show |= bit;
         } else {
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         "--include options: all, attributes, bans[:PREFIX], counts, dc, "
                         "default, failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, maint-mode, nodes, none, "
                         "operations, options, resources, stack, summary, tickets, times");
             result = FALSE;
             break;
         }
     }
     g_strfreev(parts);
     return result;
 }
 
 static gboolean
 apply_include_exclude(GSList *lst, mon_output_format_t fmt, GError **error) {
     gboolean rc = TRUE;
     GSList *node = lst;
 
     /* Set the default of what to display here.  Note that we OR everything to
      * show instead of set show directly because it could have already had some
      * settings applied to it in main.
      */
     show |= default_includes(fmt);
 
     while (node != NULL) {
         char *s = node->data;
 
         if (pcmk__starts_with(s, "--include=")) {
             rc = apply_include(s+10, error);
         } else if (pcmk__starts_with(s, "-I=")) {
             rc = apply_include(s+3, error);
         } else if (pcmk__starts_with(s, "--exclude=")) {
             rc = apply_exclude(s+10, error);
         } else if (pcmk__starts_with(s, "-U=")) {
             rc = apply_exclude(s+3, error);
         }
 
         if (rc != TRUE) {
             break;
         }
 
         node = node->next;
     }
 
     return rc;
 }
 
 static gboolean
 user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.includes_excludes = g_slist_append(options.includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (args->output_ty != NULL) {
         free(args->output_ty);
     }
 
     args->output_ty = strdup("html");
     output_format = mon_output_cgi;
     options.one_shot = TRUE;
     return TRUE;
 }
 
 static gboolean
 as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (args->output_ty != NULL) {
         free(args->output_ty);
     }
 
     if (args->output_dest != NULL) {
         free(args->output_dest);
         args->output_dest = NULL;
     }
 
     if (optarg != NULL) {
         args->output_dest = strdup(optarg);
     }
 
     args->output_ty = strdup("html");
     output_format = mon_output_html;
     umask(S_IWGRP | S_IWOTH);
     return TRUE;
 }
 
 static gboolean
 as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (args->output_ty != NULL) {
         free(args->output_ty);
     }
 
     args->output_ty = strdup("text");
     output_format = mon_output_monitor;
     options.one_shot = TRUE;
     return TRUE;
 }
 
 static gboolean
 as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (args->output_ty != NULL) {
         free(args->output_ty);
     }
 
     args->output_ty = strdup("xml");
     output_format = mon_output_legacy_xml;
     return TRUE;
 }
 
 static gboolean
 fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (optarg == NULL) {
         interactive_fence_level = 2;
     } else {
         pcmk__scan_min_int(optarg, &interactive_fence_level, 0);
     }
 
     switch (interactive_fence_level) {
         case 3:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             return include_exclude_cb("--include", "fencing", data, err);
 
         case 2:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             return include_exclude_cb("--include", "fencing", data, err);
 
         case 1:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
 
         case 0:
             options.fence_connect = FALSE;
             fence_history = FALSE;
             return include_exclude_cb("--exclude", "fencing", data, err);
 
         default:
             g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
             return FALSE;
     }
 }
 
 static gboolean
 group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_rscs_by_node;
     return TRUE;
 }
 
 static gboolean
 hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--exclude", "summary", data, err);
 }
 
 static gboolean
 inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_inactive_rscs;
     return TRUE;
 }
 
 static gboolean
 no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     output_format = mon_output_plain;
     return TRUE;
 }
 
 static gboolean
 print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_brief;
     return TRUE;
 }
 
 static gboolean
 print_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_details;
     return TRUE;
 }
 
 static gboolean
 print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_timing;
     return include_exclude_cb("--include", "operations", data, err);
 }
 
 static gboolean
 reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     int rc = crm_get_msec(optarg);
 
     if (rc == -1) {
         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
         return FALSE;
     } else {
         options.reconnect_ms = crm_parse_interval_spec(optarg);
     }
 
     return TRUE;
 }
 
 static gboolean
 show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "attributes", data, err);
 }
 
 static gboolean
 show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (optarg != NULL) {
         char *s = crm_strdup_printf("bans:%s", optarg);
         gboolean rc = include_exclude_cb("--include", s, data, err);
         free(s);
         return rc;
     } else {
         return include_exclude_cb("--include", "bans", data, err);
     }
 }
 
 static gboolean
 show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "failcounts", data, err);
 }
 
 static gboolean
 show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "failcounts,operations", data, err);
 }
 
 static gboolean
 show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "tickets", data, err);
 }
 
 static gboolean
 use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     setenv("CIB_file", optarg, 1);
     options.one_shot = TRUE;
     return TRUE;
 }
 
 #define INDENT "                                    "
 
 /* *INDENT-OFF* */
 static GOptionEntry addl_entries[] = {
     { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb,
       "Update frequency (default is 5 seconds)",
       "TIMESPEC" },
 
     { "one-shot", '1', 0, G_OPTION_ARG_NONE, &options.one_shot,
       "Display the cluster status once on the console and exit",
       NULL },
 
     { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
       "Run in the background as a daemon.\n"
       INDENT "Requires at least one of --output-to and --external-agent.",
       NULL },
 
     { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file,
       "(Advanced) Daemon pid file location",
       "FILE" },
 
     { "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent,
       "A program to run when resource operations take place",
       "FILE" },
 
     { "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient,
       "A recipient for your program (assuming you want the program to send something to someone).",
       "RCPT" },
 
     { "watch-fencing", 'W', 0, G_OPTION_ARG_NONE, &options.watch_fencing,
       "Listen for fencing events. For use with --external-agent.",
       NULL },
 
     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
       NULL,
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry display_entries[] = {
     { "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to include in the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to exclude from the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "node", 0, 0, G_OPTION_ARG_STRING, &options.only_node,
       "When displaying information about nodes, show only what's related to the given\n"
       INDENT "node, or to all nodes tagged with the given tag",
       "NODE" },
 
     { "resource", 0, 0, G_OPTION_ARG_STRING, &options.only_rsc,
       "When displaying information about resources, show only what's related to the given\n"
       INDENT "resource, or to all resources tagged with the given tag",
       "RSC" },
 
     { "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb,
       "Group resources by node",
       NULL },
 
     { "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb,
       "Display inactive resources",
       NULL },
 
     { "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb,
       "Display resource fail counts",
       NULL },
 
     { "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb,
       "Display resource operation history",
       NULL },
 
     { "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb,
       "Display resource operation history with timing details",
       NULL },
 
     { "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb,
       "Display cluster tickets",
       NULL },
 
     { "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb,
       "Show fence history:\n"
       INDENT "0=off, 1=failures and pending (default without option),\n"
       INDENT "2=add successes (default without value for option),\n"
       INDENT "3=show full history without reduction to most recent of each flavor",
       "LEVEL" },
 
     { "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb,
       "Display negative location constraints [optionally filtered by id prefix]",
       NULL },
 
     { "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb,
       "Display node attributes",
       NULL },
 
     { "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb,
       "Hide all headers",
       NULL },
 
     { "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_detail_cb,
       "Show more details (node IDs, individual clone instances)",
       NULL },
 
     { "brief", 'b', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_brief_cb,
       "Brief output",
       NULL },
 
     { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending,
       "Display pending state if 'record-pending' is enabled",
       NULL },
 
     { "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_simple_cb,
       "Display the cluster status once as a simple one line output (suitable for nagios)",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry deprecated_entries[] = {
     { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
       "Write cluster status to the named HTML file.\n"
       INDENT "Use --output-as=html --output-to=FILE instead.",
       "FILE" },
 
     { "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb,
       "Write cluster status as XML to stdout. This will enable one-shot mode.\n"
       INDENT "Use --output-as=xml instead.",
       NULL },
 
     { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
       "Disable the use of ncurses.\n"
       INDENT "Use --output-as=text instead.",
       NULL },
 
     { "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb,
       "Web mode with output suitable for CGI (preselected when run as *.cgi).\n"
       INDENT "Use --output-as=html --html-cgi instead.",
       NULL },
 
     { NULL }
 };
 /* *INDENT-ON* */
 
 /* Reconnect to the CIB and fencing agent after reconnect_ms has passed.  This sounds
  * like it would be more broadly useful, but only ever happens after a disconnect via
  * mon_cib_connection_destroy.
  */
 static gboolean
 reconnect_after_timeout(gpointer data)
 {
 #if CURSES_ENABLED
     if (output_format == mon_output_console) {
         clear();
         refresh();
     }
 #endif
 
     out->info(out, "Reconnecting...");
     if (pacemakerd_status() == pcmk_rc_ok) {
         fencing_connect();
         if (cib_connect(TRUE) == pcmk_rc_ok) {
             /* trigger redrawing the screen (needs reconnect_timer == 0) */
             reconnect_timer = 0;
             refresh_after_event(FALSE, TRUE);
             return G_SOURCE_REMOVE;
         }
     }
 
     reconnect_timer = g_timeout_add(options.reconnect_ms,
                                     reconnect_after_timeout, NULL);
     return G_SOURCE_REMOVE;
 }
 
 /* Called from various places when we are disconnected from the CIB or from the
  * fencing agent.  If the CIB connection is still valid, this function will also
  * attempt to sign off and reconnect.
  */
 static void
 mon_cib_connection_destroy(gpointer user_data)
 {
     out->info(out, "Connection to the cluster-daemons terminated");
 
     if (refresh_timer != NULL) {
         /* we'll trigger a refresh after reconnect */
         mainloop_timer_stop(refresh_timer);
     }
     if (reconnect_timer) {
         /* we'll trigger a new reconnect-timeout at the end */
         g_source_remove(reconnect_timer);
         reconnect_timer = 0;
     }
     if (st) {
         /* the client API won't properly reconnect notifications
          * if they are still in the table - so remove them
          */
         clean_up_fencing_connection();
     }
     if (cib) {
         cib->cmds->signoff(cib);
         reconnect_timer = g_timeout_add(options.reconnect_ms,
                                         reconnect_after_timeout, NULL);
     }
     return;
 }
 
 /* Signal handler installed into the mainloop for normal program shutdown */
 static void
 mon_shutdown(int nsig)
 {
     clean_up(CRM_EX_OK);
 }
 
 #if CURSES_ENABLED
 static volatile sighandler_t ncurses_winch_handler;
 
 /* Signal handler installed the regular way (not into the main loop) for when
  * the screen is resized.  Commonly, this happens when running in an xterm and
  * the user changes its size.
  */
 static void
 mon_winresize(int nsig)
 {
     static int not_done;
     int lines = 0, cols = 0;
 
     if (!not_done++) {
         if (ncurses_winch_handler)
             /* the original ncurses WINCH signal handler does the
              * magic of retrieving the new window size;
              * otherwise, we'd have to use ioctl or tgetent */
             (*ncurses_winch_handler) (SIGWINCH);
         getmaxyx(stdscr, lines, cols);
         resizeterm(lines, cols);
         /* Alert the mainloop code we'd like the refresh_trigger to run next
          * time the mainloop gets around to checking.
          */
         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
     }
     not_done--;
 }
 #endif
 
 static int
 fencing_connect(void)
 {
     int rc = pcmk_ok;
 
     if (options.fence_connect && st == NULL) {
         st = stonith_api_new();
     }
 
     if (!options.fence_connect || st == NULL || st->state != stonith_disconnected) {
         return rc;
     }
 
     rc = st->cmds->connect(st, crm_system_name, NULL);
     if (rc == pcmk_ok) {
         crm_trace("Setting up stonith callbacks");
         if (options.watch_fencing) {
             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
                                             mon_st_callback_event);
             st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
         } else {
             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
                                             mon_st_callback_display);
             st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
         }
     } else {
         clean_up_fencing_connection();
     }
 
     return rc;
 }
 
 static int
 cib_connect(gboolean full)
 {
     int rc = pcmk_rc_ok;
 
     CRM_CHECK(cib != NULL, return EINVAL);
 
     if (cib->state == cib_connected_query ||
         cib->state == cib_connected_command) {
         return rc;
     }
 
     crm_trace("Connecting to the CIB");
 
     rc = pcmk_legacy2rc(cib->cmds->signon(cib, crm_system_name, cib_query));
     if (rc != pcmk_rc_ok) {
         out->err(out, "Could not connect to the CIB: %s",
                  pcmk_rc_str(rc));
         return rc;
     }
 
 #if CURSES_ENABLED
     /* just show this if refresh is gonna remove all traces */
     if (output_format == mon_output_console) {
         out->info(out,"Waiting for CIB ...");
     }
 #endif
 
     rc = pcmk_legacy2rc(cib->cmds->query(cib, NULL, &current_cib,
                                          cib_scope_local | cib_sync_call));
 
     if (rc == pcmk_rc_ok && full) {
         rc = pcmk_legacy2rc(cib->cmds->set_connection_dnotify(cib,
             mon_cib_connection_destroy));
         if (rc == EPROTONOSUPPORT) {
             out->err(out,
                      "Notification setup not supported, won't be "
                      "able to reconnect after failure");
             if (output_format == mon_output_console) {
                 sleep(2);
             }
             rc = pcmk_rc_ok;
         }
 
         if (rc == pcmk_rc_ok) {
             cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY,
                                            crm_diff_update);
             rc = pcmk_legacy2rc(cib->cmds->add_notify_callback(cib,
                                     T_CIB_DIFF_NOTIFY, crm_diff_update));
         }
 
         if (rc != pcmk_rc_ok) {
             out->err(out, "Notification setup failed, could not monitor CIB actions");
             clean_up_cib_connection();
             clean_up_fencing_connection();
         }
     }
     return rc;
 }
 
 /* This is used to set up the fencing options after the interactive UI has been stared.
  * fence_history_cb can't be used because it builds up a list of includes/excludes that
  * then have to be processed with apply_include_exclude and that could affect other
  * things.
  */
 static void
 set_fencing_options(int level)
 {
     switch (level) {
         case 3:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             show |= pcmk_section_fencing_all;
             break;
 
         case 2:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             show |= pcmk_section_fencing_all;
             break;
 
         case 1:
             options.fence_connect = TRUE;
             fence_history = TRUE;
             show |= pcmk_section_fence_failed | pcmk_section_fence_pending;
             break;
 
         default:
             interactive_fence_level = 0;
             options.fence_connect = FALSE;
             fence_history = FALSE;
             show &= ~pcmk_section_fencing_all;
             break;
     }
 }
 
 /* Before trying to connect to fencer or cib check for state of
    pacemakerd - just no sense in trying till pacemakerd has
    taken care of starting all the sub-processes
 
    Only noteworthy thing to show here is when pacemakerd is
    waiting for startup-trigger from SBD.
  */
 static void
 pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
                     enum pcmk_ipc_event event_type, crm_exit_t status,
                     void *event_data, void *user_data)
 {
     pcmk_pacemakerd_api_reply_t *reply = event_data;
     enum pcmk_pacemakerd_state *state =
         (enum pcmk_pacemakerd_state *) user_data;
 
     /* we are just interested in the latest reply */
     *state = pcmk_pacemakerd_state_invalid;
 
     switch (event_type) {
         case pcmk_ipc_event_reply:
             break;
 
         default:
             return;
     }
 
     if (status != CRM_EX_OK) {
         out->err(out, "Bad reply from pacemakerd: %s",
                  crm_exit_str(status));
         return;
     }
 
     if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
         out->err(out, "Unknown reply type %d from pacemakerd",
                  reply->reply_type);
     } else {
         if ((reply->data.ping.last_good != (time_t) 0) &&
             (reply->data.ping.status == pcmk_rc_ok)) {
             *state = reply->data.ping.state;
         }
     }
 }
 
 static int
 pacemakerd_status(void)
 {
     int rc = pcmk_rc_ok;
     pcmk_ipc_api_t *pacemakerd_api = NULL;
     enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
 
     if (!use_cib_native) {
         /* we don't need fully functional pacemakerd otherwise */
         return rc;
     }
     if (cib != NULL &&
         (cib->state == cib_connected_query ||
          cib->state == cib_connected_command)) {
         /* As long as we have a cib-connection let's go with
          * that to fetch further cluster-status and avoid
          * unnecessary pings to pacemakerd.
          * If cluster is going down and fencer is down already
          * this will lead to a silently failing fencer reconnect.
          * On cluster startup we shouldn't see this situation
          * as first we do is wait for pacemakerd to report all
          * daemons running.
          */
         return rc;
     }
     rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd);
     if (pacemakerd_api == NULL) {
         out->err(out, "Could not connect to pacemakerd: %s",
                  pcmk_rc_str(rc));
         /* this is unrecoverable so return with rc we have */
         return rc;
     }
     pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, (void *) &state);
     rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_poll);
     switch (rc) {
         case pcmk_rc_ok:
             rc = pcmk_pacemakerd_api_ping(pacemakerd_api, crm_system_name);
             if (rc == pcmk_rc_ok) {
                 rc = pcmk_poll_ipc(pacemakerd_api, options.reconnect_ms/2);
                 if (rc == pcmk_rc_ok) {
                     pcmk_dispatch_ipc(pacemakerd_api);
                     rc = ENOTCONN;
                     if ((output_format == mon_output_console) ||
                         (output_format == mon_output_plain)) {
                         switch (state) {
                             case pcmk_pacemakerd_state_running:
                                 rc = pcmk_rc_ok;
                                 break;
                             case pcmk_pacemakerd_state_starting_daemons:
                                 out->info(out,"Pacemaker daemons starting ...");
                                 break;
                             case pcmk_pacemakerd_state_wait_for_ping:
                                 out->info(out,"Waiting for startup-trigger from SBD ...");
                                 break;
                             case pcmk_pacemakerd_state_shutting_down:
                                 out->info(out,"Pacemaker daemons shutting down ...");
                                 /* try our luck maybe CIB is still accessible */
                                 rc = pcmk_rc_ok;
                                 break;
                             case pcmk_pacemakerd_state_shutdown_complete:
                                 /* assuming pacemakerd doesn't dispatch any pings after entering
                                 * that state unless it is waiting for SBD
                                 */
                                 out->info(out,"Pacemaker daemons shut down - reporting to SBD ...");
                                 break;
                             default:
                                 break;
                         }
                     } else {
                         switch (state) {
                             case pcmk_pacemakerd_state_running:
                                 rc = pcmk_rc_ok;
                                 break;
                             case pcmk_pacemakerd_state_shutting_down:
                                 /* try our luck maybe CIB is still accessible */
                                 rc = pcmk_rc_ok;
                                 break;
                             default:
                                 break;
                         }
                     }
                 }
             }
             break;
         case EREMOTEIO:
             rc = pcmk_rc_ok;
             on_remote_node = TRUE;
 #if CURSES_ENABLED
             /* just show this if refresh is gonna remove all traces */
             if (output_format == mon_output_console) {
                 out->info(out,
                     "Running on remote-node waiting to be connected by cluster ...");
             }
 #endif
             break;
         default:
             break;
     }
     pcmk_free_ipc_api(pacemakerd_api);
     /* returning with ENOTCONN triggers a retry */
     return (rc == pcmk_rc_ok)?rc:ENOTCONN;
 }
 
 #if CURSES_ENABLED
 static const char *
 get_option_desc(char c)
 {
     const char *desc = "No help available";
 
     for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
         if (entry->short_name == c) {
             desc = entry->description;
             break;
         }
     }
     return desc;
 }
 
 #define print_option_help(out, option, condition) \
     curses_formatted_printf(out, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
 
 /* This function is called from the main loop when there is something to be read
  * on stdin, like an interactive user's keystroke.  All it does is read the keystroke,
  * set flags (or show the page showing which keystrokes are valid), and redraw the
  * screen.  It does not do anything with connections to the CIB or fencing agent
  * agent what would happen in mon_refresh_display.
  */
 static gboolean
 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data)
 {
     int c;
     gboolean config_mode = FALSE;
 
     while (1) {
 
         /* Get user input */
         c = getchar();
 
         switch (c) {
             case 'm':
                 interactive_fence_level++;
                 if (interactive_fence_level > 3) {
                     interactive_fence_level = 0;
                 }
 
                 set_fencing_options(interactive_fence_level);
                 break;
             case 'c':
                 show ^= pcmk_section_tickets;
                 break;
             case 'f':
                 show ^= pcmk_section_failcounts;
                 break;
             case 'n':
                 show_opts ^= pcmk_show_rscs_by_node;
                 break;
             case 'o':
                 show ^= pcmk_section_operations;
                 if (!pcmk_is_set(show, pcmk_section_operations)) {
                     show_opts &= ~pcmk_show_timing;
                 }
                 break;
             case 'r':
                 show_opts ^= pcmk_show_inactive_rscs;
                 break;
             case 'R':
                 show_opts ^= pcmk_show_details;
                 break;
             case 't':
                 show_opts ^= pcmk_show_timing;
                 if (pcmk_is_set(show_opts, pcmk_show_timing)) {
                     show |= pcmk_section_operations;
                 }
                 break;
             case 'A':
                 show ^= pcmk_section_attributes;
                 break;
             case 'L':
                 show ^= pcmk_section_bans;
                 break;
             case 'D':
                 /* If any header is shown, clear them all, otherwise set them all */
                 if (pcmk_any_flags_set(show, pcmk_section_summary)) {
                     show &= ~pcmk_section_summary;
                 } else {
                     show |= pcmk_section_summary;
                 }
                 /* Regardless, we don't show options in console mode. */
                 show &= ~pcmk_section_options;
                 break;
             case 'b':
                 show_opts ^= pcmk_show_brief;
                 break;
             case 'j':
                 show_opts ^= pcmk_show_pending;
                 break;
             case '?':
                 config_mode = TRUE;
                 break;
             default:
                 /* All other keys just redraw the screen. */
                 goto refresh;
         }
 
         if (!config_mode)
             goto refresh;
 
         blank_screen();
 
         curses_formatted_printf(out, "%s", "Display option change mode\n");
         print_option_help(out, 'c', pcmk_is_set(show, pcmk_section_tickets));
         print_option_help(out, 'f', pcmk_is_set(show, pcmk_section_failcounts));
         print_option_help(out, 'n', pcmk_is_set(show_opts, pcmk_show_rscs_by_node));
         print_option_help(out, 'o', pcmk_is_set(show, pcmk_section_operations));
         print_option_help(out, 'r', pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
         print_option_help(out, 't', pcmk_is_set(show_opts, pcmk_show_timing));
         print_option_help(out, 'A', pcmk_is_set(show, pcmk_section_attributes));
         print_option_help(out, 'L', pcmk_is_set(show, pcmk_section_bans));
         print_option_help(out, 'D', !pcmk_is_set(show, pcmk_section_summary));
         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details));
         print_option_help(out, 'b', pcmk_is_set(show_opts, pcmk_show_brief));
         print_option_help(out, 'j', pcmk_is_set(show_opts, pcmk_show_pending));
         curses_formatted_printf(out, "%d m: \t%s\n", interactive_fence_level, get_option_desc('m'));
         curses_formatted_printf(out, "%s", "\nToggle fields via field letter, type any other key to return\n");
     }
 
 refresh:
     refresh_after_event(FALSE, TRUE);
 
     return TRUE;
 }
 #endif
 
 // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
 static void
 avoid_zombies(void)
 {
     struct sigaction sa;
 
     memset(&sa, 0, sizeof(struct sigaction));
     if (sigemptyset(&sa.sa_mask) < 0) {
         crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno));
         return;
     }
     sa.sa_handler = SIG_IGN;
     sa.sa_flags = SA_RESTART|SA_NOCLDWAIT;
     if (sigaction(SIGCHLD, &sa, NULL) < 0) {
         crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno));
     }
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     const char *description = "Notes:\n\n"
                               "If this program is called as crm_mon.cgi, --output-as=html --html-cgi will\n"
                               "automatically be added to the command line arguments.\n\n"
                               "Time Specification:\n\n"
                               "The TIMESPEC in any command line option can be specified in many different\n"
                               "formats.  It can be just an integer number of seconds, a number plus units\n"
                               "(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n"
                               "Output Control:\n\n"
                               "By default, a certain list of sections are written to the output destination.\n"
                               "The default varies based on the output format - XML includes everything, while\n"
                               "other output formats will display less.  This list can be modified with the\n"
                               "--include and --exclude command line options.  Each option may be given multiple\n"
                               "times on the command line, and each can give a comma-separated list of sections.\n"
                               "The options are applied to the default set, from left to right as seen on the\n"
                               "command line.  For a list of valid sections, pass --include=list or --exclude=list.\n\n"
                               "Interactive Use:\n\n"
                               "When run interactively, crm_mon can be told to hide and display various sections\n"
                               "of output.  To see a help screen explaining the options, hit '?'.  Any key stroke\n"
                               "aside from those listed will cause the screen to refresh.\n\n"
                               "Examples:\n\n"
                               "Display the cluster status on the console with updates as they occur:\n\n"
                               "\tcrm_mon\n\n"
                               "Display the cluster status on the console just once then exit:\n\n"
                               "\tcrm_mon -1\n\n"
                               "Display your cluster status, group resources by node, and include inactive resources in the list:\n\n"
                               "\tcrm_mon --group-by-node --inactive\n\n"
                               "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:\n\n"
                               "\tcrm_mon --daemonize --output-as html --output-to /path/to/docroot/filename.html\n\n"
                               "Start crm_mon and export the current cluster status as XML to stdout, then exit:\n\n"
                               "\tcrm_mon --output-as xml\n\n";
 
     context = pcmk__build_arg_context(args, "console (default), html, text, xml", group, NULL);
     pcmk__add_main_args(context, extra_prog_entries);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "display", "Display Options:",
                         "Show display options", display_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
                         "Show deprecated options", deprecated_entries);
 
     return context;
 }
 
 /* If certain format options were specified, we want to set some extra
  * options.  We can just process these like they were given on the
  * command line.
  */
 static void
 add_output_args(void) {
     GError *err = NULL;
 
     if (output_format == mon_output_plain) {
         if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_cgi) {
         if (!pcmk__force_args(context, &err, "%s --html-cgi", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_xml) {
         if (!pcmk__force_args(context, &err, "%s --xml-simple-list --xml-substitute", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_legacy_xml) {
         output_format = mon_output_xml;
         if (!pcmk__force_args(context, &err, "%s --xml-legacy --xml-substitute", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     }
 }
 
 /* Which output format to use could come from two places:  The --as-xml
  * style arguments we gave in deprecated_entries above, or the formatted output
  * arguments added by pcmk__register_formats.  If the latter were used,
  * output_format will be mon_output_unset.
  *
  * Call the callbacks as if those older style arguments were provided so
  * the various things they do get done.
  */
 static void
 reconcile_output_format(pcmk__common_args_t *args) {
     gboolean retval = TRUE;
     GError *err = NULL;
 
     if (output_format != mon_output_unset) {
         return;
     }
 
     if (pcmk__str_eq(args->output_ty, "html", pcmk__str_casei)) {
         char *dest = NULL;
 
         if (args->output_dest != NULL) {
             dest = strdup(args->output_dest);
         }
 
         retval = as_html_cb("h", dest, NULL, &err);
         free(dest);
     } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_casei)) {
         retval = no_curses_cb("N", NULL, NULL, &err);
     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_casei)) {
         if (args->output_ty != NULL) {
             free(args->output_ty);
         }
 
         args->output_ty = strdup("xml");
         output_format = mon_output_xml;
     } else if (options.one_shot) {
         if (args->output_ty != NULL) {
             free(args->output_ty);
         }
 
         args->output_ty = strdup("text");
         output_format = mon_output_plain;
     } else {
         /* Neither old nor new arguments were given, so set the default. */
         if (args->output_ty != NULL) {
             free(args->output_ty);
         }
 
         args->output_ty = strdup("console");
         output_format = mon_output_console;
     }
 
     if (!retval) {
         g_propagate_error(&error, err);
         clean_up(CRM_EX_USAGE);
     }
 }
 
 static void
 handle_connection_failures(int rc)
 {
     if (rc == pcmk_rc_ok) {
         return;
     }
 
     if (output_format == mon_output_monitor) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
                     pcmk_rc_str(rc));
         rc = MON_STATUS_CRIT;
     } else if (rc == ENOTCONN) {
         if (on_remote_node) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: remote-node not connected to cluster");
         } else {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: cluster is not available on this node");
         }
         rc = pcmk_rc2exitc(rc);
     } else {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Connection to cluster failed: %s", pcmk_rc_str(rc));
         rc = pcmk_rc2exitc(rc);
     }
 
     clean_up(rc);
 }
 
 static void
 one_shot(void)
 {
     int rc;
 
     rc = pacemakerd_status();
 
     if (rc == pcmk_rc_ok) {
         fencing_connect();
         rc = cib_connect(FALSE);
     }
 
     if (rc == pcmk_rc_ok) {
         mon_refresh_display(NULL);
     } else {
         handle_connection_failures(rc);
     }
 
     clean_up(CRM_EX_OK);
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_ok;
     GOptionGroup *output_group = NULL;
 
     args = pcmk__new_common_args(SUMMARY);
     context = build_arg_context(args, &output_group);
     pcmk__register_formats(output_group, formats);
 
     options.pid_file = strdup("/tmp/ClusterMon.pid");
     pcmk__cli_init_logging("crm_mon", 0);
 
     // Avoid needing to wait for subprocesses forked for -E/--external-agent
     avoid_zombies();
 
     if (pcmk__ends_with_ext(argv[0], ".cgi")) {
         output_format = mon_output_cgi;
         options.one_shot = TRUE;
     }
 
     processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
 
     fence_history_cb("--fence-history", "1", NULL, NULL);
 
     /* Set an HTML title regardless of what format we will eventually use.  This can't
      * be done in add_output_args.  That function is called after command line
      * arguments are processed in the next block, which means it'll override whatever
      * title the user provides.  Doing this here means the user can give their own
      * title on the command line.
      */
     if (!pcmk__force_args(context, &error, "%s --html-title \"Cluster Status\"",
                           g_get_prgname())) {
         return clean_up(CRM_EX_USAGE);
     }
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (!args->version) {
         if (args->quiet) {
             include_exclude_cb("--exclude", "times", NULL, NULL);
         }
 
         if (options.watch_fencing) {
             fence_history_cb("--fence-history", "0", NULL, NULL);
             options.fence_connect = TRUE;
         }
 
         /* create the cib-object early to be able to do further
          * decisions based on the cib-source
          */
         cib = cib_new();
 
         if (cib == NULL) {
             rc = -EINVAL;
         } else {
             switch (cib->variant) {
 
                 case cib_native:
                     /* cib & fencing - everything available */
                     use_cib_native = TRUE;
                     break;
 
                 case cib_file:
                     /* Don't try to connect to fencing as we
                      * either don't have a running cluster or
                      * the fencing-information would possibly
                      * not match the cib data from a file.
                      * As we don't expect cib-updates coming
                      * in enforce one-shot. */
                     fence_history_cb("--fence-history", "0", NULL, NULL);
                     options.one_shot = TRUE;
                     break;
 
                 case cib_remote:
                     /* updates coming in but no fencing */
                     fence_history_cb("--fence-history", "0", NULL, NULL);
                     break;
 
                 case cib_undefined:
                 case cib_database:
                 default:
                     /* something is odd */
                     rc = -EINVAL;
                     break;
             }
         }
 
         if (options.one_shot) {
             if (output_format == mon_output_console) {
                 output_format = mon_output_plain;
             }
 
         } else if (options.daemonize) {
             if ((output_format == mon_output_console) || (args->output_dest == NULL)) {
                 output_format = mon_output_none;
             }
             crm_enable_stderr(FALSE);
 
             if (pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches | pcmk__str_casei) && !options.external_agent) {
                 g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--daemonize requires at least one of --output-to and --external-agent");
                 return clean_up(CRM_EX_USAGE);
             }
 
             if (cib) {
                 /* to be on the safe side don't have cib-object around
                  * when we are forking
                  */
                 cib_delete(cib);
                 cib = NULL;
                 pcmk__daemonize(crm_system_name, options.pid_file);
                 cib = cib_new();
                 if (cib == NULL) {
                     rc = -EINVAL;
                 }
                 /* otherwise assume we've got the same cib-object we've just destroyed
                  * in our parent
                  */
             }
 
 
         } else if (output_format == mon_output_console) {
 #if CURSES_ENABLED
             crm_enable_stderr(FALSE);
 #else
             options.one_shot = TRUE;
             output_format = mon_output_plain;
             printf("Defaulting to one-shot mode\n");
             printf("You need to have curses available at compile time to enable console mode\n");
 #endif
         }
     }
 
     if (rc != pcmk_ok) {
         // Shouldn't really be possible
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Invalid CIB source");
         return clean_up(CRM_EX_ERROR);
     }
 
     reconcile_output_format(args);
     add_output_args();
 
     if (args->version && output_format == mon_output_console) {
         /* Use the text output format here if we are in curses mode but were given
          * --version.  Displaying version information uses printf, and then we
          *  immediately exit.  We don't want to initialize curses for that.
          */
         rc = pcmk__output_new(&out, "text", args->output_dest, argv);
     } else {
         rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     }
 
     if (rc != pcmk_rc_ok) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
                     args->output_ty, pcmk_rc_str(rc));
         return clean_up(CRM_EX_ERROR);
     }
 
     /* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
 
     /* Apply --include/--exclude flags we used internally.  There's no error reporting
      * here because this would be a programming error.
      */
     apply_include_exclude(options.includes_excludes, output_format, &error);
 
     /* And now apply any --include/--exclude flags the user gave on the command line.
      * These are done in a separate pass from the internal ones because we want to
      * make sure whatever the user specifies overrides whatever we do.
      */
     if (!apply_include_exclude(options.user_includes_excludes, output_format, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     /* Sync up the initial value of interactive_fence_level with whatever was set with
      * --include/--exclude= options.
      */
     if (pcmk_all_flags_set(show, pcmk_section_fencing_all)) {
         interactive_fence_level = 3;
     } else if (pcmk_is_set(show, pcmk_section_fence_worked)) {
         interactive_fence_level = 2;
     } else if (pcmk_any_flags_set(show, pcmk_section_fence_failed | pcmk_section_fence_pending)) {
         interactive_fence_level = 1;
     } else {
         interactive_fence_level = 0;
     }
 
     pcmk__register_lib_messages(out);
     crm_mon_register_messages(out);
     pe__register_messages(out);
     stonith__register_messages(out);
 
     if (args->version) {
         out->version(out, false);
         return clean_up(CRM_EX_OK);
     }
 
     /* Extra sanity checks when in CGI mode */
     if (output_format == mon_output_cgi) {
         if (cib->variant == cib_file) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
             return clean_up(CRM_EX_USAGE);
         } else if (options.external_agent != NULL) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
             return clean_up(CRM_EX_USAGE);
         } else if (options.daemonize == TRUE) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
             return clean_up(CRM_EX_USAGE);
         }
     }
 
     if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) {
         show_opts |= pcmk_show_inactive_rscs | pcmk_show_timing;
 
         if (!options.daemonize) {
             options.one_shot = TRUE;
         }
     }
 
     if ((output_format == mon_output_html || output_format == mon_output_cgi) &&
         out->dest != stdout) {
         pcmk__html_add_header("meta", "http-equiv", "refresh", "content",
                               pcmk__itoa(options.reconnect_ms / 1000), NULL);
     }
 
     crm_info("Starting %s", crm_system_name);
 
     cib__set_output(cib, out);
 
     if (options.one_shot) {
         one_shot();
     }
 
     do {
         out->info(out,"Waiting until cluster is available on this node ...");
 
         rc = pacemakerd_status();
         if (rc == pcmk_rc_ok) {
             fencing_connect();
             rc = cib_connect(TRUE);
         }
 
         if (rc != pcmk_rc_ok) {
             pcmk__sleep_ms(options.reconnect_ms);
 #if CURSES_ENABLED
             if (output_format == mon_output_console) {
                 clear();
                 refresh();
             }
 #endif
         } else if (output_format == mon_output_html && out->dest != stdout) {
             printf("Writing html to %s ...\n", args->output_dest);
         }
 
     } while (rc == ENOTCONN);
 
     handle_connection_failures(rc);
     set_fencing_options(interactive_fence_level);
     mon_refresh_display(NULL);
 
     mainloop = g_main_loop_new(NULL, FALSE);
 
     mainloop_add_signal(SIGTERM, mon_shutdown);
     mainloop_add_signal(SIGINT, mon_shutdown);
 #if CURSES_ENABLED
     if (output_format == mon_output_console) {
         ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize);
         if (ncurses_winch_handler == SIG_DFL ||
             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
             ncurses_winch_handler = NULL;
 
         io_channel = g_io_channel_unix_new(STDIN_FILENO);
         g_io_add_watch(io_channel, G_IO_IN, detect_user_input, NULL);
     }
 #endif
 
     /* When refresh_trigger->trigger is set to TRUE, call mon_refresh_display.  In
      * this file, that is anywhere mainloop_set_trigger is called.
      */
     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
 
     g_main_loop_run(mainloop);
     g_main_loop_unref(mainloop);
 
     if (io_channel != NULL) {
         g_io_channel_shutdown(io_channel, TRUE, NULL);
     }
 
     crm_info("Exiting %s", crm_system_name);
 
     return clean_up(CRM_EX_OK);
 }
 
 /*!
  * \internal
  * \brief Print one-line status suitable for use with monitoring software
  *
  * \param[in] data_set  Working set of CIB state
  *
  * \note This function's output (and the return code when the program exits)
  *       should conform to https://www.monitoring-plugins.org/doc/guidelines.html
  */
 static void
 print_simple_status(pcmk__output_t *out, pe_working_set_t * data_set)
 {
     GList *gIter = NULL;
     int nodes_online = 0;
     int nodes_standby = 0;
     int nodes_maintenance = 0;
     char *offline_nodes = NULL;
     size_t offline_nodes_len = 0;
     gboolean no_dc = FALSE;
     gboolean offline = FALSE;
 
     if (data_set->dc_node == NULL) {
         has_warnings = TRUE;
         no_dc = TRUE;
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (node->details->standby && node->details->online) {
             nodes_standby++;
         } else if (node->details->maintenance && node->details->online) {
             nodes_maintenance++;
         } else if (node->details->online) {
             nodes_online++;
         } else {
             char *s = crm_strdup_printf("offline node: %s", node->details->uname);
             /* coverity[leaked_storage] False positive */
             pcmk__add_word(&offline_nodes, &offline_nodes_len, s);
             free(s);
             has_warnings = TRUE;
             offline = TRUE;
         }
     }
 
     if (has_warnings) {
         out->info(out, "CLUSTER WARN: %s%s%s",
                   no_dc ? "No DC" : "",
                   no_dc && offline ? ", " : "",
                   (offline? offline_nodes : ""));
         free(offline_nodes);
     } else {
         char *nodes_standby_s = NULL;
         char *nodes_maint_s = NULL;
 
         if (nodes_standby > 0) {
             nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
                                                 pcmk__plural_s(nodes_standby));
         }
 
         if (nodes_maintenance > 0) {
             nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
                                               nodes_maintenance,
                                               pcmk__plural_s(nodes_maintenance));
         }
 
         out->info(out, "CLUSTER OK: %d node%s online%s%s, "
                        "%d resource instance%s configured",
                   nodes_online, pcmk__plural_s(nodes_online),
                   nodes_standby_s != NULL ? nodes_standby_s : "",
                   nodes_maint_s != NULL ? nodes_maint_s : "",
                   data_set->ninstances, pcmk__plural_s(data_set->ninstances));
 
         free(nodes_standby_s);
         free(nodes_maint_s);
     }
     /* coverity[leaked_storage] False positive */
 }
 
 static int
 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
                  int status, const char *desc)
 {
     pid_t pid;
 
     /*setenv needs chars, these are ints */
     char *rc_s = pcmk__itoa(rc);
     char *status_s = pcmk__itoa(status);
     char *target_rc_s = pcmk__itoa(target_rc);
 
     crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent);
 
     if(rsc) {
         setenv("CRM_notify_rsc", rsc, 1);
     }
     if (options.external_recipient) {
         setenv("CRM_notify_recipient", options.external_recipient, 1);
     }
     setenv("CRM_notify_node", node, 1);
     setenv("CRM_notify_task", task, 1);
     setenv("CRM_notify_desc", desc, 1);
     setenv("CRM_notify_rc", rc_s, 1);
     setenv("CRM_notify_target_rc", target_rc_s, 1);
     setenv("CRM_notify_status", status_s, 1);
 
     pid = fork();
     if (pid == -1) {
         crm_perror(LOG_ERR, "notification fork() failed.");
     }
     if (pid == 0) {
         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
         execl(options.external_agent, options.external_agent, NULL);
         exit(CRM_EX_ERROR);
     }
 
     crm_trace("Finished running custom notification program '%s'.", options.external_agent);
     free(target_rc_s);
     free(status_s);
     free(rc_s);
     return 0;
 }
 
 static void
 handle_rsc_op(xmlNode * xml, const char *node_id)
 {
     int rc = -1;
     int status = -1;
     int target_rc = -1;
     gboolean notify = TRUE;
 
     char *rsc = NULL;
     char *task = NULL;
     const char *desc = NULL;
     const char *magic = NULL;
     const char *id = NULL;
     const char *node = NULL;
 
     xmlNode *n = xml;
     xmlNode * rsc_op = xml;
 
     if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
         xmlNode *cIter;
 
         for(cIter = xml->children; cIter; cIter = cIter->next) {
             handle_rsc_op(cIter, node_id);
         }
 
         return;
     }
 
     id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
     if (id == NULL) {
         /* Compatibility with <= 1.1.5 */
         id = ID(rsc_op);
     }
 
     magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
     if (magic == NULL) {
         /* non-change */
         return;
     }
 
     if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
                                  &target_rc)) {
         crm_err("Invalid event %s detected for %s", magic, id);
         return;
     }
 
     if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
         crm_err("Invalid event detected for %s", id);
         goto bail;
     }
 
     node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
 
     while (n != NULL && !pcmk__str_eq(XML_CIB_TAG_STATE, TYPE(n), pcmk__str_casei)) {
         n = n->parent;
     }
 
     if(node == NULL && n) {
         node = crm_element_value(n, XML_ATTR_UNAME);
     }
 
     if (node == NULL && n) {
         node = ID(n);
     }
 
     if (node == NULL) {
         node = node_id;
     }
 
     if (node == NULL) {
         crm_err("No node detected for event %s (%s)", magic, id);
         goto bail;
     }
 
     /* look up where we expected it to be? */
     desc = pcmk_strerror(pcmk_ok);
     if ((status == PCMK_EXEC_DONE) && (target_rc == rc)) {
         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
         if (rc == PCMK_OCF_NOT_RUNNING) {
             notify = FALSE;
         }
 
     } else if (status == PCMK_EXEC_DONE) {
         desc = services_ocf_exitcode_str(rc);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
 
     } else {
         desc = pcmk_exec_status_str(status);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
     }
 
     if (notify && options.external_agent) {
         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
     }
   bail:
     free(rsc);
     free(task);
 }
 
 /* This function is just a wrapper around mainloop_set_trigger so that it can be
  * called from a mainloop directly.  It's simply another way of ensuring the screen
  * gets redrawn.
  */
 static gboolean
 mon_trigger_refresh(gpointer user_data)
 {
     mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
     return FALSE;
 }
 
 static void
 crm_diff_update_v2(const char *event, xmlNode * msg)
 {
     xmlNode *change = NULL;
     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     for (change = pcmk__xml_first_child(diff); change != NULL;
          change = pcmk__xml_next(change)) {
         const char *name = NULL;
         const char *op = crm_element_value(change, XML_DIFF_OP);
         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
         xmlNode *match = NULL;
         const char *node = NULL;
 
         if(op == NULL) {
             continue;
 
         } else if(strcmp(op, "create") == 0) {
             match = change->children;
 
         } else if(strcmp(op, "move") == 0) {
             continue;
 
         } else if(strcmp(op, "delete") == 0) {
             continue;
 
         } else if(strcmp(op, "modify") == 0) {
             match = first_named_child(change, XML_DIFF_RESULT);
             if(match) {
                 match = match->children;
             }
         }
 
         if(match) {
             name = (const char *)match->name;
         }
 
         crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
         if(xpath == NULL) {
             /* Version field, ignore */
 
         } else if(name == NULL) {
             crm_debug("No result for %s operation to %s", op, xpath);
             CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
 
         } else if(strcmp(name, XML_TAG_CIB) == 0) {
             xmlNode *state = NULL;
             xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
 
             for (state = pcmk__xe_first_child(status); state != NULL;
                  state = pcmk__xe_next(state)) {
 
                 node = crm_element_value(state, XML_ATTR_UNAME);
                 if (node == NULL) {
                     node = ID(state);
                 }
                 handle_rsc_op(state, node);
             }
 
         } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
             xmlNode *state = NULL;
 
             for (state = pcmk__xe_first_child(match); state != NULL;
                  state = pcmk__xe_next(state)) {
 
                 node = crm_element_value(state, XML_ATTR_UNAME);
                 if (node == NULL) {
                     node = ID(state);
                 }
                 handle_rsc_op(state, node);
             }
 
         } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
             node = crm_element_value(match, XML_ATTR_UNAME);
             if (node == NULL) {
                 node = ID(match);
             }
             handle_rsc_op(match, node);
 
         } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
             node = ID(match);
             handle_rsc_op(match, node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             handle_rsc_op(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");
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else {
             crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
         }
     }
 }
 
 static void
 crm_diff_update_v1(const char *event, xmlNode * msg)
 {
     /* Process operation updates */
     xmlXPathObject *xpathObj = xpath_search(msg,
                                             "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
                                             "//" XML_LRM_TAG_RSC_OP);
     int lpc = 0, max = numXpathResults(xpathObj);
 
     for (lpc = 0; lpc < max; lpc++) {
         xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
 
         handle_rsc_op(rsc_op, NULL);
     }
     freeXpathObject(xpathObj);
 }
 
 static void
 crm_diff_update(const char *event, xmlNode * msg)
 {
     int rc = -1;
     static bool stale = FALSE;
     gboolean cib_updated = FALSE;
     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     out->progress(out, false);
 
     if (current_cib != NULL) {
         rc = xml_apply_patchset(current_cib, diff, TRUE);
 
         switch (rc) {
             case -pcmk_err_diff_resync:
             case -pcmk_err_diff_failed:
                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(current_cib); current_cib = NULL;
                 break;
             case pcmk_ok:
                 cib_updated = TRUE;
                 break;
             default:
                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(current_cib); current_cib = NULL;
         }
     }
 
     if (current_cib == NULL) {
         crm_trace("Re-requesting the full cib");
         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
     }
 
     if (options.external_agent) {
         int format = 0;
         crm_element_value_int(diff, "format", &format);
         switch(format) {
             case 1:
                 crm_diff_update_v1(event, msg);
                 break;
             case 2:
                 crm_diff_update_v2(event, msg);
                 break;
             default:
                 crm_err("Unknown patch format: %d", format);
         }
     }
 
     if (current_cib == NULL) {
         if(!stale) {
             out->info(out, "--- Stale data ---");
         }
         stale = TRUE;
         return;
     }
 
     stale = FALSE;
     refresh_after_event(cib_updated, FALSE);
 }
 
 static int
 get_fencing_history(stonith_history_t **stonith_history)
 {
     int rc = 0;
 
     while (fence_history) {
         if (st != NULL) {
             rc = st->cmds->history(st, st_opt_sync_call, NULL, stonith_history, 120);
 
             if (rc == 0) {
                 *stonith_history = stonith__sort_history(*stonith_history);
                 if (!pcmk_all_flags_set(show, pcmk_section_fencing_all)
                     && (output_format != mon_output_xml)) {
 
                     *stonith_history = pcmk__reduce_fence_history(*stonith_history);
                 }
                 break; /* all other cases are errors */
             }
         } else {
             rc = ENOTCONN;
             break;
         }
     }
 
     return rc;
 }
 
 static int
 mon_refresh_display(gpointer user_data)
 {
     xmlNode *cib_copy = copy_xml(current_cib);
     stonith_history_t *stonith_history = NULL;
     int history_rc = 0;
     GList *unames = NULL;
     GList *resources = NULL;
 
     last_refresh = time(NULL);
 
     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
         clean_up_cib_connection();
         out->err(out, "Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation));
         clean_up(CRM_EX_CONFIG);
         return 0;
     }
 
     /* get the stonith-history if there is evidence we need it */
     history_rc = get_fencing_history(&stonith_history);
 
     if (mon_data_set == NULL) {
         mon_data_set = pe_new_working_set();
         CRM_ASSERT(mon_data_set != NULL);
     }
     pe__set_working_set_flags(mon_data_set, pe_flag_no_compat);
 
     mon_data_set->input = cib_copy;
     mon_data_set->priv = out;
     cluster_status(mon_data_set);
 
     /* Unpack constraints if any section will need them
      * (tickets may be referenced in constraints but not granted yet,
      * and bans need negative location constraints) */
     if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) {
-        xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
-                                                   mon_data_set->input);
-        unpack_constraints(cib_constraints, mon_data_set);
+        pcmk__unpack_constraints(mon_data_set);
     }
 
     if (options.daemonize) {
         out->reset(out);
     }
 
     unames = pe__build_node_name_list(mon_data_set, options.only_node);
     resources = pe__build_rsc_list(mon_data_set, options.only_rsc);
 
     /* Always print DC if NULL. */
     if (mon_data_set->dc_node == NULL) {
         show |= pcmk_section_dc;
     }
 
     switch (output_format) {
         case mon_output_html:
         case mon_output_cgi:
             if (out->message(out, "cluster-status", mon_data_set, crm_errno2exit(history_rc),
                              stonith_history, fence_history, show, show_opts,
                              options.neg_location_prefix, unames, resources) != 0) {
                 g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT, "Critical: Unable to output html file");
                 clean_up(CRM_EX_CANTCREAT);
                 return 0;
             }
             break;
 
         case mon_output_monitor:
             print_simple_status(out, mon_data_set);
             if (has_warnings) {
                 clean_up(MON_STATUS_WARN);
                 return FALSE;
             }
             break;
 
         case mon_output_unset:
         case mon_output_none:
             break;
 
         default:
             out->message(out, "cluster-status", mon_data_set, crm_errno2exit(history_rc),
                          stonith_history, fence_history, show, show_opts,
                          options.neg_location_prefix, unames, resources);
             break;
     }
 
     if (options.daemonize) {
         out->finish(out, CRM_EX_OK, true, NULL);
     }
 
     g_list_free_full(unames, free);
     g_list_free_full(resources, free);
 
     stonith_history_free(stonith_history);
     stonith_history = NULL;
     pe_reset_working_set(mon_data_set);
     return 1;
 }
 
 /* This function is called for fencing events (see fencing_connect for which ones) when
  * --watch-fencing is used on the command line.
  */
 static void
 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
 {
     if (st->state == stonith_disconnected) {
         /* disconnect cib as well and have everything reconnect */
         mon_cib_connection_destroy(NULL);
     } else if (options.external_agent) {
         char *desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
                                     e->operation, e->origin, e->target, pcmk_strerror(e->result),
                                     e->id);
         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
         free(desc);
     }
 }
 
 /* Cause the screen to be redrawn (via mainloop_set_trigger) when various conditions are met:
  *
  * - If the last update occurred more than reconnect_ms ago (defaults to 5s, but
  *   can be changed via the -i command line option), or
  * - After every 10 CIB updates, or
  * - If it's been 2s since the last update
  *
  * This function sounds like it would be more broadly useful, but it is only called when a
  * fencing event is received or a CIB diff occurrs.
  */
 static void
 refresh_after_event(gboolean data_updated, gboolean enforce)
 {
     static int updates = 0;
     time_t now = time(NULL);
 
     if (data_updated) {
         updates++;
     }
 
     if(refresh_timer == NULL) {
         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
     }
 
     if (reconnect_timer > 0) {
         /* we will receive a refresh request after successful reconnect */
         mainloop_timer_stop(refresh_timer);
         return;
     }
 
     /* as we're not handling initial failure of fencer-connection as
      * fatal give it a retry here
      * not getting here if cib-reconnection is already on the way
      */
     fencing_connect();
 
     if (enforce ||
         ((now - last_refresh) > (options.reconnect_ms / 1000)) ||
         updates >= 10) {
         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else {
         mainloop_timer_start(refresh_timer);
     }
 }
 
 /* This function is called for fencing events (see fencing_connect for which ones) when
  * --watch-fencing is NOT used on the command line.
  */
 static void
 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
 {
     if (st->state == stonith_disconnected) {
         /* disconnect cib as well and have everything reconnect */
         mon_cib_connection_destroy(NULL);
     } else {
         out->progress(out, false);
         refresh_after_event(TRUE, FALSE);
     }
 }
 
 static void
 clean_up_cib_connection(void)
 {
     if (cib == NULL) {
         return;
     }
 
     cib->cmds->signoff(cib);
     cib_delete(cib);
     cib = NULL;
 }
 
 static void
 clean_up_fencing_connection(void)
 {
     if (st == NULL) {
         return;
     }
 
     if (st->state != stonith_disconnected) {
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
         st->cmds->disconnect(st);
     }
 
     stonith_api_delete(st);
     st = NULL;
 }
 
 /*
  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
  * deallocate memory and show usage-message if requested.
  *
  * We don't actually return, but nominally returning crm_exit_t allows a usage
  * like "return clean_up(exit_code);" which helps static analysis understand the
  * code flow.
  */
 static crm_exit_t
 clean_up(crm_exit_t exit_code)
 {
     /* Quitting crm_mon is much more complicated than it ought to be. */
 
     /* (1) Close connections, free things, etc. */
     clean_up_cib_connection();
     clean_up_fencing_connection();
     free(options.neg_location_prefix);
     free(options.only_node);
     free(options.only_rsc);
     free(options.pid_file);
     g_slist_free_full(options.includes_excludes, free);
 
     pe_free_working_set(mon_data_set);
     mon_data_set = NULL;
 
     g_strfreev(processed_args);
 
     /* (2) If this is abnormal termination and we're in curses mode, shut down
      * curses first.  Any messages displayed to the screen before curses is shut
      * down will be lost because doing the shut down will also restore the
      * screen to whatever it looked like before crm_mon was started.
      */
     if ((error != NULL || exit_code == CRM_EX_USAGE) && output_format == mon_output_console) {
         out->finish(out, exit_code, false, NULL);
         pcmk__output_free(out);
         out = NULL;
     }
 
     /* (3) If this is a command line usage related failure, print the usage
      * message.
      */
     if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
         char *help = g_option_context_get_help(context, TRUE, NULL);
 
         fprintf(stderr, "%s", help);
         g_free(help);
     }
 
     pcmk__free_arg_context(context);
 
     /* (4) If this is any kind of error, print the error out and exit.  Make
      * sure to handle situations both before and after formatted output is
      * set up.  We want errors to appear formatted if at all possible.
      */
     if (error != NULL) {
         if (out != NULL) {
             out->err(out, "%s: %s", g_get_prgname(), error->message);
             out->finish(out, exit_code, true, NULL);
             pcmk__output_free(out);
         } else {
             fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         }
 
         g_clear_error(&error);
         crm_exit(exit_code);
     }
 
     /* (5) Print formatted output to the screen if we made it far enough in
      * crm_mon to be able to do so.
      */
     if (out != NULL) {
         if (!options.daemonize) {
             out->finish(out, exit_code, true, NULL);
         }
 
         pcmk__output_free(out);
         pcmk__unregister_formats();
     }
 
     crm_exit(exit_code);
 }
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 582687c493..c1ccfaf39e 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,1981 +1,1978 @@
 /*
  * Copyright 2004-2021 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_controld.h>
 #include <crm/common/lists_internal.h>
 #include <crm/services_internal.h>
 
 resource_checks_t *
 cli_check_resource(pe_resource_t *rsc, char *role_s, char *managed)
 {
     pe_resource_t *parent = uber_parent(rsc);
     resource_checks_t *rc = calloc(1, sizeof(resource_checks_t));
 
     if (role_s) {
         enum rsc_role_e role = text2role(role_s);
 
         if (role == RSC_ROLE_STOPPED) {
             rc->flags |= rsc_remain_stopped;
         } else if (pcmk_is_set(parent->flags, pe_rsc_promotable) &&
                    (role == RSC_ROLE_UNPROMOTED)) {
             rc->flags |= rsc_unpromotable;
         }
     }
 
     if (managed && !crm_is_true(managed)) {
         rc->flags |= rsc_unmanaged;
     }
 
     if (rsc->lock_node) {
         rc->lock_node = rsc->lock_node->details->uname;
     }
 
     rc->rsc = rsc;
     return rc;
 }
 
 static GList *
 build_node_info_list(pe_resource_t *rsc)
 {
     GList *retval = NULL;
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pe_resource_t *child = (pe_resource_t *) iter->data;
 
         for (GList *iter2 = child->running_on; iter2 != NULL; iter2 = iter2->next) {
             pe_node_t *node = (pe_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, pe_rsc_promotable) &&
                            child->fns->state(child, TRUE) == RSC_ROLE_PROMOTED;
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 GList *
 cli_resource_search(pe_resource_t *rsc, const char *requested_name,
                     pe_working_set_t *data_set)
 {
     GList *retval = NULL;
     pe_resource_t *parent = uber_parent(rsc);
 
     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, pe_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) {
             pe_node_t *node = (pe_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) == RSC_ROLE_PROMOTED);
 
             retval = g_list_prepend(retval, ni);
         }
     }
 
     return retval;
 }
 
 #define XPATH_MAX 1024
 
 // \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 offset = 0;
     int rc = pcmk_rc_ok;
     xmlNode *xml_search = NULL;
     char *xpath_string = NULL;
 
     if(value) {
         *value = NULL;
     }
 
     if(the_cib == NULL) {
         return ENOTCONN;
     }
 
     xpath_string = calloc(1, XPATH_MAX);
     offset +=
         snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", get_object_path("resources"));
 
     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc);
 
     if (attr_set_type) {
         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", attr_set_type);
         if (set_name) {
             offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name);
         }
     }
 
     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair[");
     if (attr_id) {
         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id);
     }
 
     if (attr_name) {
         if (attr_id) {
             offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and ");
         }
         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name);
     }
     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]");
     CRM_LOG_ASSERT(offset > 0);
 
     rc = the_cib->cmds->query(the_cib, xpath_string, &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_has_children(xml_search)) {
         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, XML_NVPAIR_ATTR_VALUE), ID(child));
         }
 
         out->spacer(out);
 
     } else if(value) {
         const char *tmp = crm_element_value(xml_search, attr);
 
         if (tmp) {
             *value = strdup(tmp);
         }
     }
 
   done:
     free(xpath_string);
     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/* <pe_resource_t*> */ ** result,
                                        pe_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, (pe_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(pe_clone == rsc->variant) {
             break;
         }
     }
 
     rc = find_resource_attr(out, cib, XML_ATTR_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/*<pe_resource_t*>*/ *
 find_matching_attr_resources(pcmk__output_t *out, pe_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 && pe_clone == rsc->parent->variant) {
         int rc = pcmk_rc_ok;
         char *local_attr_id = NULL;
         rc = find_resource_attr(out, cib, XML_ATTR_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 && pe_clone == rsc->variant) {
         pe_resource_t *child = rsc->children->data;
 
         if(child->variant == pe_native) {
             lookup_id = clone_strip(child->id); /* Could be a cloned group! */
             rc = find_resource_attr(out, cib, XML_ATTR_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(pe_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,
                               pe_working_set_t *data_set, gboolean force)
 {
     pcmk__output_t *out = data_set->priv;
     int rc = pcmk_rc_ok;
     static bool need_init = TRUE;
 
     char *local_attr_id = NULL;
     char *local_attr_set = NULL;
 
     GList/*<pe_resource_t*>*/ *resources = NULL;
     const char *common_attr_id = attr_id;
 
     if (attr_id == NULL && force == FALSE) {
         find_resource_attr (out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL,
                             NULL, NULL, attr_name, NULL);
     }
 
     if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) {
         if (force == FALSE) {
             rc = find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id,
                                     XML_TAG_META_SETS, attr_set, attr_id,
                                     attr_name, &local_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)",
                          uber_parent(rsc)->id, attr_name, local_attr_id);
                 out->err(out, "         Delete '%s' first or use the force option to override",
                          local_attr_id);
             }
             free(local_attr_id);
             if (rc == pcmk_rc_ok) {
                 return ENOTUNIQ;
             }
         }
         resources = g_list_append(resources, rsc);
 
     } else {
         resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type,
                                                  attr_id, attr_name, cib, "update", force);
     }
 
     /* If either attr_set or attr_id is specified,
      * one clearly intends to modify a single resource.
      * It is the last item on the resource list.*/
     for(GList *gIter = (attr_set||attr_id) ? g_list_last(resources) : resources
             ; gIter; gIter = gIter->next) {
         char *lookup_id = NULL;
 
         xmlNode *xml_top = NULL;
         xmlNode *xml_obj = NULL;
         local_attr_id = NULL;
         local_attr_set = NULL;
 
         rsc = (pe_resource_t*)gIter->data;
         attr_id = common_attr_id;
 
         lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
         rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &local_attr_id);
 
         if (rc == pcmk_rc_ok) {
             crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id);
             attr_id = local_attr_id;
 
         } else if (rc != ENXIO) {
             free(lookup_id);
             free(local_attr_id);
             g_list_free(resources);
             return rc;
 
         } else {
             const char *tag = crm_element_name(rsc->xml);
 
             if (attr_set == NULL) {
                 local_attr_set = crm_strdup_printf("%s-%s", lookup_id,
                                                    attr_set_type);
                 attr_set = local_attr_set;
             }
             if (attr_id == NULL) {
                 local_attr_id = crm_strdup_printf("%s-%s", attr_set, attr_name);
                 attr_id = local_attr_id;
             }
 
             xml_top = create_xml_node(NULL, tag);
             crm_xml_add(xml_top, XML_ATTR_ID, lookup_id);
 
             xml_obj = create_xml_node(xml_top, attr_set_type);
             crm_xml_add(xml_obj, XML_ATTR_ID, attr_set);
         }
 
         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_debug(xml_top, "Update");
 
         rc = cib->cmds->modify(cib, XML_CIB_TAG_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, local_attr_id,
                       attr_set ? " set=" : "", attr_set ? attr_set : "",
                       attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value);
         }
 
         free_xml(xml_top);
 
         free(lookup_id);
         free(local_attr_id);
         free(local_attr_set);
 
         if(recursive && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) {
             GList *lpc = NULL;
 
             if(need_init) {
-                xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
-
                 need_init = FALSE;
-                unpack_constraints(cib_constraints, data_set);
-
+                pcmk__unpack_constraints(data_set);
                 pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
             }
 
             crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs);
             pe__set_resource_flags(rsc, pe_rsc_allocating);
             for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
                 pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
                 pe_resource_t *peer = cons->rsc_lh;
 
                 crm_debug("Checking %s %d", cons->id, cons->score);
                 if (cons->score > 0 && !pcmk_is_set(peer->flags, pe_rsc_allocating)) {
                     /* Don't get into colocation loops */
                     crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, peer->id);
                     cli_resource_update_attribute(peer, peer->id, NULL, attr_set_type,
                                                   NULL, attr_name, attr_value, recursive,
                                                   cib, cib_options, data_set, force);
                 }
             }
         }
     }
     g_list_free(resources);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_delete_attribute(pe_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,
                               pe_working_set_t *data_set, gboolean force)
 {
     pcmk__output_t *out = data_set->priv;
     int rc = pcmk_rc_ok;
     GList/*<pe_resource_t*>*/ *resources = NULL;
 
     if (attr_id == NULL && force == FALSE) {
         find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->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 {
         resources = g_list_append(resources, rsc);
     }
 
     for(GList *gIter = resources; gIter; gIter = gIter->next) {
         char *lookup_id = NULL;
         xmlNode *xml_obj = NULL;
         char *local_attr_id = NULL;
 
         rsc = (pe_resource_t*)gIter->data;
 
         lookup_id = clone_strip(rsc->id);
         rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
                                 attr_set, attr_id, attr_name, &local_attr_id);
 
         if (rc == ENXIO) {
             free(lookup_id);
             rc = pcmk_rc_ok;
             continue;
 
         } else if (rc != pcmk_rc_ok) {
             free(lookup_id);
             g_list_free(resources);
             return rc;
         }
 
         if (attr_id == NULL) {
             attr_id = local_attr_id;
         }
 
         xml_obj = crm_create_nvpair_xml(NULL, 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 = pcmk_legacy2rc(rc);
 
         if (rc == pcmk_rc_ok) {
             out->info(out, "Deleted '%s' option: id=%s%s%s%s%s", lookup_id, local_attr_id,
                       attr_set ? " set=" : "", attr_set ? attr_set : "",
                       attr_name ? " name=" : "", attr_name ? attr_name : "");
         }
 
         free(lookup_id);
         free_xml(xml_obj);
         free(local_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, pe_working_set_t *data_set)
 {
     pcmk__output_t *out = data_set->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;
     pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
 
     if (rsc == NULL) {
         out->err(out, "Resource %s not found", rsc_id);
         return ENXIO;
 
     } else if (rsc->variant != pe_native) {
         out->err(out, "We can only process primitive resources, not %s", rsc_id);
         return EINVAL;
     }
 
     rsc_class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
     rsc_provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER),
     rsc_type = crm_element_value(rsc->xml, XML_ATTR_TYPE);
     if ((rsc_class == NULL) || (rsc_type == NULL)) {
         out->err(out, "Resource %s does not have a class and type", rsc_id);
         return EINVAL;
     }
 
     {
         pe_node_t *node = pe_find_node(data_set->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 = pe__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(pe_resource_t *rsc)
 {
     const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
 
     return pcmk_is_set(rsc->flags, pe_rsc_unique)? strdup(name) : 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, pe_working_set_t *data_set)
 {
     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, data_set);
     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, pe_working_set_t *data_set)
 {
     int rc = pcmk_rc_ok;
     const char *failed_value = NULL;
     const char *failed_id = NULL;
     const 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) {
         interval_ms_s = crm_strdup_printf("%u",
                                           crm_parse_interval_spec(interval_spec));
     }
 
     for (xmlNode *xml_op = pcmk__xml_first_child(data_set->failed);
          xml_op != NULL;
          xml_op = pcmk__xml_next(xml_op)) {
 
         failed_id = crm_element_value(xml_op, XML_LRM_ATTR_RSCID);
         if (failed_id == NULL) {
             // Malformed history entry, should never happen
             continue;
         }
 
         // No resource specified means all resources match
         if (rsc_id) {
             pe_resource_t *fail_rsc = pe_find_resource_with_flags(data_set->resources,
                                                                   failed_id,
                                                                   pe_find_renamed|pe_find_anon);
 
             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, XML_ATTR_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, XML_LRM_ATTR_TASK);
             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, XML_LRM_ATTR_INTERVAL_MS);
             if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) {
                 continue;
             }
         }
 
         g_hash_table_add(rscs, (gpointer) failed_id);
     }
 
     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, data_set);
         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(pe_resource_t *rsc, const char *operation,
                      const char *interval_spec, pe_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__node_attr_request_clear(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,
                     pe_resource_t *rsc, const char *operation,
                     const char *interval_spec, bool just_failures,
                     pe_working_set_t *data_set, gboolean force)
 {
     pcmk__output_t *out = data_set->priv;
     int rc = pcmk_rc_ok;
     pe_node_t *node = NULL;
 
     if (rsc == NULL) {
         return ENXIO;
 
     } else if (rsc->children) {
         GList *lpc = NULL;
 
         for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
             pe_resource_t *child = (pe_resource_t *) lpc->data;
 
             rc = cli_resource_delete(controld_api, host_uname, child, operation,
                                      interval_spec, just_failures, data_set,
                                      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(data_set->nodes, false);
 
         } else if(nodes == NULL && rsc->exclusive_discover) {
             GHashTableIter iter;
             pe_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 = (pe_node_t *) lpc->data;
 
             if (node->details->online) {
                 rc = cli_resource_delete(controld_api, node->details->uname,
                                          rsc, operation, interval_spec,
                                          just_failures, data_set, force);
             }
             if (rc != pcmk_rc_ok) {
                 g_list_free(nodes);
                 return rc;
             }
         }
 
         g_list_free(nodes);
         return pcmk_rc_ok;
     }
 
     node = pe_find_node(data_set->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, data_set);
     } else {
         rc = clear_rsc_history(controld_api, host_uname, rsc->id, data_set);
     }
     if (rc != pcmk_rc_ok) {
         out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s",
                  rsc->id, host_uname, pcmk_strerror(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,
                 pe_working_set_t *data_set)
 {
     pcmk__output_t *out = data_set->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) {
         pe_node_t *node = pe_find_node(data_set->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__node_attr_request_clear(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, data_set);
         if (rc != pcmk_rc_ok) {
             out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s",
                      node_name, pcmk_strerror(rc));
             return rc;
         }
     } else {
         for (GList *iter = data_set->nodes; iter; iter = iter->next) {
             pe_node_t *node = (pe_node_t *) iter->data;
 
             rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL,
                                     operation, interval_spec, data_set);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s",
                          pcmk_strerror(rc));
                 return rc;
             }
         }
     }
 
     out->info(out, "Cleaned up all resources on %s", display_name);
     return rc;
 }
 
 int
 cli_resource_check(pcmk__output_t *out, cib_t * cib_conn, pe_resource_t *rsc)
 {
     char *role_s = NULL;
     char *managed = NULL;
     pe_resource_t *parent = uber_parent(rsc);
     int rc = pcmk_rc_no_output;
     resource_checks_t *checks = NULL;
 
     find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
                        NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed);
 
     find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
                        NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s);
 
     checks = cli_check_resource(rsc, role_s, managed);
 
     if (checks->flags != 0 || checks->lock_node != NULL) {
         rc = out->message(out, "resource-check-list", checks);
     }
 
     free(role_s);
     free(managed);
     free(checks);
     return rc;
 }
 
 // \return Standard Pacemaker return code
 int
 cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
                   const char *rsc_id, pe_working_set_t *data_set)
 {
     crm_notice("Failing %s on %s", rsc_id, host_uname);
     return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, data_set);
 }
 
 static GHashTable *
 generate_resource_params(pe_resource_t *rsc, pe_node_t *node,
                          pe_working_set_t *data_set)
 {
     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, data_set);
     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, data_set);
     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(pe_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) {
         pe_node_t *node = (pe_node_t *) hIter->data;
 
         if(strcmp(host, node->details->uname) == 0) {
             crm_trace("Resource %s is running on %s\n", rsc->id, host);
             goto done;
         } else if(strcmp(host, node->details->id) == 0) {
             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) {
         pe_resource_t *rsc = (pe_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 == pe_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 working set input
  *
  * This also updates the working set timestamp to the current time.
  *
  * \param[in] data_set   Working set instance to update
  * \param[in] xml        XML to use as input
  *
  * \return Standard Pacemaker return code
  * \note On success, caller is responsible for freeing memory allocated for
  *       data_set->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_working_set_xml(pe_working_set_t *data_set, xmlNode **xml)
 {
     if (cli_config_update(xml, NULL, FALSE) == FALSE) {
         return ENOKEY;
     }
     data_set->input = *xml;
     data_set->now = crm_time_new(NULL);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Update a working set's XML input based on a CIB query
  *
  * \param[in] data_set   Data set instance 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
  *       data_set->input and data_set->now.
  */
 static int
 update_working_set_from_cib(pcmk__output_t *out, pe_working_set_t * data_set,
                             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_strerror(rc), rc);
         return rc;
     }
     rc = update_working_set_xml(data_set, &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, pe_working_set_t * data_set, bool simulate)
 {
     char *pid = NULL;
     char *shadow_file = NULL;
     cib_t *shadow_cib = NULL;
     int rc = pcmk_rc_ok;
 
     pcmk__output_t *out = data_set->priv;
 
     pe_reset_working_set(data_set);
     rc = update_working_set_from_cib(out, data_set, 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(data_set->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_strerror(rc), rc);
             goto done;
         }
 
         pcmk__schedule_actions(data_set, data_set->input, NULL);
 
         prev_quiet = out->is_quiet(out);
         out->quiet = true;
         run_simulation(data_set, shadow_cib, NULL);
         out->quiet = prev_quiet;
 
         rc = update_dataset(shadow_cib, data_set, FALSE);
 
     } else {
         cluster_status(data_set);
     }
 
   done:
     /* Do not free data_set->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;
 }
 
 static int
 max_delay_for_resource(pe_working_set_t * data_set, pe_resource_t *rsc) 
 {
     int delay = 0;
     int max_delay = 0;
 
     if(rsc && rsc->children) {
         GList *iter = NULL;
 
         for(iter = rsc->children; iter; iter = iter->next) {
             pe_resource_t *child = (pe_resource_t *)iter->data;
 
             delay = max_delay_for_resource(data_set, child);
             if(delay > max_delay) {
                 double seconds = delay / 1000.0;
                 crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id);
                 max_delay = delay;
             }
         }
 
     } else if(rsc) {
         char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP);
         pe_action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set);
         const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT);
         long long result_ll;
 
         if ((pcmk__scan_ll(value, &result_ll, -1LL) == pcmk_rc_ok)
             && (result_ll >= 0) && (result_ll <= INT_MAX)) {
             max_delay = (int) result_ll;
         } else {
             max_delay = -1;
         }
         pe_free_action(stop);
     }
 
     return max_delay;
 }
 
 static int
 max_delay_in(pe_working_set_t * data_set, GList *resources) 
 {
     int max_delay = 0;
     GList *item = NULL;
 
     for (item = resources; item != NULL; item = item->next) {
         int delay = 0;
         pe_resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data);
 
         delay = max_delay_for_resource(data_set, rsc);
 
         if(delay > max_delay) {
             double seconds = delay / 1000.0;
             crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id);
             max_delay = delay;
         }
     }
 
     return 5 + (max_delay / 1000);
 }
 
 #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] rsc        The resource to restart
  * \param[in] host       The host to restart the resource on (or NULL for all)
  * \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, a timeout will be
  *                       calculated based on the resource timeout)
  * \param[in] cib        Connection to the CIB manager
  *
  * \return Standard Pacemaker return code (exits on certain failures)
  */
 int
 cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const char *host,
                      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 *orig_target_role = NULL;
 
     GList *list_delta = NULL;
     GList *target_active = NULL;
     GList *current_active = NULL;
     GList *restart_target_active = NULL;
 
     pe_working_set_t *data_set = NULL;
 
     if (!resource_is_running_on(rsc, host)) {
         const char *id = rsc->clone_name?rsc->clone_name:rsc->id;
         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;
     }
 
     rsc_id = strdup(rsc->id);
     if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) {
         stop_via_ban = TRUE;
     }
 
     /*
       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)
     */
 
     data_set = pe_new_working_set();
     if (data_set == NULL) {
         crm_perror(LOG_ERR, "Could not allocate working set");
         rc = ENOMEM;
         goto done;
     }
 
     data_set->priv = out;
     pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
     rc = update_dataset(cib, data_set, FALSE);
 
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not get new resource list: %s (%d)", pcmk_strerror(rc), rc);
         goto done;
     }
 
     restart_target_active = get_active_resources(host, data_set->resources);
     current_active = get_active_resources(host, data_set->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, rsc_id, host, move_lifetime, NULL, cib,
                               cib_options, promoted_role_only);
 
     } else {
         /* Stop the resource by setting target-role to Stopped.
          * Remember any existing target-role so we can restore it later
          * (though it only makes any difference if it's Unpromoted).
          */
         char *lookup_id = clone_strip(rsc->id);
 
         find_resource_attr(out, cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL,
                            NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role);
         free(lookup_id);
         rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                            NULL, XML_RSC_ATTR_TARGET_ROLE,
                                            RSC_STOPPED, FALSE, cib, cib_options,
                                            data_set, force);
     }
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not set target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc);
         if (current_active) {
             g_list_free_full(current_active, free);
         }
         if (restart_target_active) {
             g_list_free_full(restart_target_active, free);
         }
         goto done;
     }
 
     rc = update_dataset(cib, data_set, 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, data_set->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 = max_delay_in(data_set, 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, data_set, FALSE);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were stopped");
                 goto failure;
             }
 
             if (current_active) {
                 g_list_free_full(current_active, free);
             }
             current_active = get_active_resources(host, data_set->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(rsc_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, XML_RSC_ATTR_TARGET_ROLE,
                                            orig_target_role, FALSE, cib,
                                            cib_options, data_set, force);
         free(orig_target_role);
         orig_target_role = NULL;
     } else {
         rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
                                            NULL, XML_RSC_ATTR_TARGET_ROLE, cib,
                                            cib_options, data_set, force);
     }
 
     if(rc != pcmk_rc_ok) {
         out->err(out, "Could not unset target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc);
         goto done;
     }
 
     if (target_active) {
         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 = max_delay_in(data_set, 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, data_set, FALSE);
             if(rc != pcmk_rc_ok) {
                 out->err(out, "Could not determine which resources were started");
                 goto failure;
             }
 
             if (current_active) {
                 g_list_free_full(current_active, free);
             }
 
             /* It's OK if dependent resources moved to a different node,
              * so we check active resources on all nodes.
              */
             current_active = get_active_resources(NULL, data_set->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(rsc_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,
                                       XML_RSC_ATTR_TARGET_ROLE, orig_target_role,
                                       FALSE, cib, cib_options, data_set, force);
         free(orig_target_role);
     } else {
         cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL,
                                       XML_RSC_ATTR_TARGET_ROLE, cib, cib_options,
                                       data_set, force);
     }
 
 done:
     if (list_delta) {
         g_list_free(list_delta);
     }
     if (current_active) {
         g_list_free_full(current_active, free);
     }
     if (target_active && (target_active != restart_target_active)) {
         g_list_free_full(target_active, free);
     }
     if (restart_target_active) {
         g_list_free_full(restart_target_active, free);
     }
     free(rsc_id);
     pe_free_working_set(data_set);
     return rc;
 }
 
 static inline bool action_is_pending(pe_action_t *action)
 {
     if (pcmk_any_flags_set(action->flags, pe_action_optional|pe_action_pseudo)
         || !pcmk_is_set(action->flags, pe_action_runnable)
         || pcmk__str_eq("notify", action->task, pcmk__str_casei)) {
         return false;
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Return TRUE if 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, FALSE otherwise
  */
 static bool
 actions_are_pending(GList *actions)
 {
     GList *action;
 
     for (action = actions; action != NULL; action = action->next) {
         pe_action_t *a = (pe_action_t *)action->data;
         if (action_is_pending(a)) {
             crm_notice("Waiting for %s (flags=0x%.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) {
         pe_action_t *a = (pe_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, a->node->details->uname);
         } 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] 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] 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)
 {
     pe_working_set_t *data_set = NULL;
     int rc = pcmk_rc_ok;
     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
 
     data_set = pe_new_working_set();
     if (data_set == NULL) {
         return ENOMEM;
     }
     pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
 
     do {
 
         /* Abort if timeout is reached */
         time_diff = expire_time - time(NULL);
         if (time_diff > 0) {
             crm_info("Waiting up to %ld seconds for cluster actions to complete", time_diff);
         } else {
             print_pending_actions(out, data_set->actions);
             pe_free_working_set(data_set);
             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(data_set);
         rc = update_working_set_from_cib(out, data_set, cib);
         if (rc != pcmk_rc_ok) {
             pe_free_working_set(data_set);
             return rc;
         }
         pcmk__schedule_actions(data_set, data_set->input, NULL);
 
         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(data_set->config_hash,
                                                          "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;
             }
         }
 
     } while (actions_are_pending(data_set->actions));
 
     pe_free_working_set(data_set);
     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 = "validate-all";
 
     } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
         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;
 }
 
 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 = NULL;
     const char *action = NULL;
     crm_exit_t exit_code = CRM_EX_OK;
     svc_action_t *op = NULL;
 
     class = !pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei) ?
                 rsc_class : resources_find_service_class(rsc_type);
 
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         out->err(out, "Sorry, the %s option doesn't support %s resources yet",
                  rsc_action, class);
         crm_exit(CRM_EX_UNIMPLEMENT_FEATURE);
     } else if (pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_SYSTEMD,
                 PCMK_RESOURCE_CLASS_UPSTART, PCMK_RESOURCE_CLASS_NAGIOS, NULL)) {
         out->err(out, "Sorry, the %s option doesn't support %s resources",
                  rsc_action, class);
         crm_exit(CRM_EX_UNIMPLEMENT_FEATURE);
     }
 
     action = get_action(rsc_action);
 
     /* If no timeout was provided, grab the default. */
     if (timeout_ms == 0) {
         timeout_ms = crm_get_msec(CRM_DEFAULT_OP_TIMEOUT_S);
     }
 
     /* add meta_timeout env needed by some resource agents */
     g_hash_table_insert(params, strdup("CRM_meta_timeout"),
                         crm_strdup_printf("%d", timeout_ms));
 
     /* add crm_feature_set env needed by some resource agents */
     g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
 
     if (check_level >= 0) {
         char *level = crm_strdup_printf("%d", check_level);
         setenv("OCF_CHECK_LEVEL", level, 1);
         free(level);
     }
 
     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) || (op->rc != PCMK_OCF_OK)) {
         const char *reason = NULL;
 
         if (op == NULL) {
             reason = strerror(ENOMEM);
         } else {
             reason = crm_exit_str(op->rc);
         }
         out->err(out, "Operation %s for %s (%s%s%s:%s) failed: %s",
                  action, rsc_name, rsc_class, (rsc_prov? ":" : ""),
                  (rsc_prov? rsc_prov : ""), rsc_type, reason);
         crm_exit((op == NULL)? CRM_EX_OSERR : op->rc);
     }
 
     setenv("HA_debug", resource_verbose > 0 ? "1" : "0", 1);
     if(resource_verbose > 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);
 
     if (override_hash) {
         GHashTableIter iter;
         char *name = NULL;
         char *value = NULL;
 
         g_hash_table_iter_init(&iter, override_hash);
         while (g_hash_table_iter_next(&iter, (gpointer *) & name, (gpointer *) & value)) {
             g_hash_table_replace(op->params, strdup(name), strdup(value));
         }
     }
 
     if (services_action_sync(op)) {
         exit_code = op->rc;
 
         /* Lookup exit code based on rc for LSB resources */
         if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei) &&
               pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
             exit_code = services_get_ocf_exitcode(action, exit_code);
         }
 
         out->message(out, "resource-agent-action", resource_verbose, rsc_class,
                      rsc_prov, rsc_type, rsc_name, rsc_action, override_hash,
                      exit_code, op->status, op->stdout_data, op->stderr_data);
     } else {
         exit_code = op->rc == 0 ? CRM_EX_ERROR : op->rc;
     }
 
     services_action_free(op);
     /* See comment above about why we free params here. */
     g_hash_table_destroy(params);
     return exit_code;
 }
 
 crm_exit_t
 cli_resource_execute(pe_resource_t *rsc, const char *requested_name,
                      const char *rsc_action, GHashTable *override_hash,
                      int timeout_ms, cib_t * cib, pe_working_set_t *data_set,
                      int resource_verbose, gboolean force, int check_level)
 {
     pcmk__output_t *out = data_set->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, data_set);
             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 target-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 == pe_group) {
         out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action);
         return CRM_EX_UNIMPLEMENT_FEATURE;
     } else if (rsc->variant == pe_container || 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, XML_AGENT_ATTR_CLASS);
     rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
     rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
 
     params = generate_resource_params(rsc, NULL /* @TODO use local node */,
                                       data_set);
 
     if (timeout_ms == 0) {
         timeout_ms = pe_get_configured_timeout(rsc, get_action(rsc_action), data_set);
     }
 
     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(pe_resource_t *rsc, const char *rsc_id, const char *host_name,
                   const char *move_lifetime, cib_t *cib, int cib_options,
                   pe_working_set_t *data_set, gboolean promoted_role_only,
                   gboolean force)
 {
     pcmk__output_t *out = data_set->priv;
     int rc = pcmk_rc_ok;
     unsigned int count = 0;
     pe_node_t *current = NULL;
     pe_node_t *dest = pe_find_node(data_set->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, pe_rsc_promotable)) {
         pe_resource_t *p = uber_parent(rsc);
 
         if (pcmk_is_set(p->flags, pe_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 master option: %s is not promotable", rsc_id);
             promoted_role_only = FALSE;
         }
     }
 
     current = pe__find_active_requires(rsc, &count);
 
     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
         GList *iter = NULL;
         unsigned int promoted_count = 0;
         pe_node_t *promoted_node = NULL;
 
         for(iter = rsc->children; iter; iter = iter->next) {
             pe_resource_t *child = (pe_resource_t *)iter->data;
             enum rsc_role_e child_role = child->fns->state(child, TRUE);
 
             if (child_role == RSC_ROLE_PROMOTED) {
                 rsc = child;
                 promoted_node = pe__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 (current && (current->details == dest->details)) {
         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", dest->details->uname);
         } else {
             return pcmk_rc_already;
         }
     }
 
     /* Clear any previous prefer constraints across all nodes. */
     cli_resource_clear(rsc_id, NULL, data_set->nodes, cib, cib_options, FALSE, force);
 
     /* Clear any previous ban constraints on 'dest'. */
     cli_resource_clear(rsc_id, dest->details->uname, data_set->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);
 
     crm_trace("%s%s now prefers node %s%s",
               rsc->id, (promoted_role_only? " (promoted)" : ""),
               dest->details->uname, 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 == FALSE)) {
         /* Ban the original location if possible */
         if(current) {
             (void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime,
                                    NULL, cib, cib_options, promoted_role_only);
 
         } 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, dest->details->uname);
             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;
 }
diff --git a/tools/crm_ticket.c b/tools/crm_ticket.c
index 4e2131637f..5ed7201306 100644
--- a/tools/crm_ticket.c
+++ b/tools/crm_ticket.c
@@ -1,1075 +1,1073 @@
 /*
  * Copyright 2012-2021 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/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/ipc.h>
 
 #include <crm/cib.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 
 #include <pacemaker-internal.h>
 
 gboolean do_force = FALSE;
 gboolean BE_QUIET = FALSE;
 const char *ticket_id = NULL;
 const char *get_attr_name = NULL;
 const char *attr_name = NULL;
 const char *attr_value = NULL;
 const char *attr_id = NULL;
 const char *set_name = NULL;
 const char *attr_default = NULL;
 const char *xml_file = NULL;
 char ticket_cmd = 'S';
 int cib_options = cib_sync_call;
 
 static pe_ticket_t *
 find_ticket(const char *ticket_id, pe_working_set_t * data_set)
 {
     pe_ticket_t *ticket = NULL;
 
     ticket = g_hash_table_lookup(data_set->tickets, ticket_id);
 
     return ticket;
 }
 
 static void
 print_date(time_t time)
 {
     int lpc = 0;
     char date_str[26];
 
     asctime_r(localtime(&time), date_str);
     for (; lpc < 26; lpc++) {
         if (date_str[lpc] == '\n') {
             date_str[lpc] = 0;
         }
     }
     fprintf(stdout, "'%s'", date_str);
 }
 
 static int
 print_ticket(pe_ticket_t * ticket, gboolean raw, gboolean details)
 {
     if (raw) {
         fprintf(stdout, "%s\n", ticket->id);
         return pcmk_ok;
     }
 
     fprintf(stdout, "%s\t%s %s",
             ticket->id, ticket->granted ? "granted" : "revoked",
             ticket->standby ? "[standby]" : "         ");
 
     if (details && g_hash_table_size(ticket->state) > 0) {
         GHashTableIter iter;
         const char *name = NULL;
         const char *value = NULL;
         int lpc = 0;
 
         fprintf(stdout, " (");
 
         g_hash_table_iter_init(&iter, ticket->state);
         while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
             if (lpc > 0) {
                 fprintf(stdout, ", ");
             }
             fprintf(stdout, "%s=", name);
             if (pcmk__str_any_of(name, "last-granted", "expires", NULL)) {
                 long long time_ll;
 
                 pcmk__scan_ll(value, &time_ll, 0);
                 print_date((time_t) time_ll);
             } else {
                 fprintf(stdout, "%s", value);
             }
             lpc++;
         }
 
         fprintf(stdout, ")\n");
 
     } else {
         if (ticket->last_granted > -1) {
             fprintf(stdout, " last-granted=");
             print_date(ticket->last_granted);
         }
         fprintf(stdout, "\n");
     }
 
     return pcmk_ok;
 }
 
 static int
 print_ticket_list(pe_working_set_t * data_set, gboolean raw, gboolean details)
 {
     GHashTableIter iter;
     pe_ticket_t *ticket = NULL;
 
     g_hash_table_iter_init(&iter, data_set->tickets);
 
     while (g_hash_table_iter_next(&iter, NULL, (void **)&ticket)) {
         print_ticket(ticket, raw, details);
     }
 
     return pcmk_ok;
 }
 
 #define XPATH_MAX 1024
 
 static int
 find_ticket_state(cib_t * the_cib, const char *ticket_id, xmlNode ** ticket_state_xml)
 {
     int offset = 0;
     int rc = pcmk_ok;
     xmlNode *xml_search = NULL;
 
     char *xpath_string = NULL;
 
     CRM_ASSERT(ticket_state_xml != NULL);
     *ticket_state_xml = NULL;
 
     xpath_string = calloc(1, XPATH_MAX);
     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", "/cib/status/tickets");
 
     if (ticket_id) {
         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s[@id=\"%s\"]",
                            XML_CIB_TAG_TICKET_STATE, ticket_id);
     }
 
     CRM_LOG_ASSERT(offset > 0);
     rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
                               cib_sync_call | cib_scope_local | cib_xpath);
 
     if (rc != pcmk_ok) {
         goto done;
     }
 
     crm_log_xml_debug(xml_search, "Match");
     if (xml_has_children(xml_search)) {
         if (ticket_id) {
             fprintf(stdout, "Multiple ticket_states match ticket_id=%s\n", ticket_id);
         }
         *ticket_state_xml = xml_search;
     } else {
         *ticket_state_xml = xml_search;
     }
 
   done:
     free(xpath_string);
     return rc;
 }
 
 static int
 find_ticket_constraints(cib_t * the_cib, const char *ticket_id, xmlNode ** ticket_cons_xml)
 {
     int offset = 0;
     int rc = pcmk_ok;
     xmlNode *xml_search = NULL;
 
     char *xpath_string = NULL;
 
     CRM_ASSERT(ticket_cons_xml != NULL);
     *ticket_cons_xml = NULL;
 
     xpath_string = calloc(1, XPATH_MAX);
     offset +=
         snprintf(xpath_string + offset, XPATH_MAX - offset, "%s/%s",
                  get_object_path(XML_CIB_TAG_CONSTRAINTS), XML_CONS_TAG_RSC_TICKET);
 
     if (ticket_id) {
         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@ticket=\"%s\"]",
                            ticket_id);
     }
 
     CRM_LOG_ASSERT(offset > 0);
     rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
                               cib_sync_call | cib_scope_local | cib_xpath);
 
     if (rc != pcmk_ok) {
         goto done;
     }
 
     crm_log_xml_debug(xml_search, "Match");
     *ticket_cons_xml = xml_search;
 
   done:
     free(xpath_string);
     return rc;
 }
 
 static int
 dump_ticket_xml(cib_t * the_cib, const char *ticket_id)
 {
     int rc = pcmk_ok;
     xmlNode *state_xml = NULL;
 
     rc = find_ticket_state(the_cib, ticket_id, &state_xml);
 
     if (state_xml == NULL) {
         return rc;
     }
 
     fprintf(stdout, "State XML:\n");
     if (state_xml) {
         char *state_xml_str = NULL;
 
         state_xml_str = dump_xml_formatted(state_xml);
         fprintf(stdout, "\n%s\n", crm_str(state_xml_str));
         free_xml(state_xml);
         free(state_xml_str);
     }
 
     return pcmk_ok;
 }
 
 static int
 dump_constraints(cib_t * the_cib, const char *ticket_id)
 {
     int rc = pcmk_ok;
     xmlNode *cons_xml = NULL;
     char *cons_xml_str = NULL;
 
     rc = find_ticket_constraints(the_cib, ticket_id, &cons_xml);
 
     if (cons_xml == NULL) {
         return rc;
     }
 
     cons_xml_str = dump_xml_formatted(cons_xml);
     fprintf(stdout, "Constraints XML:\n\n%s\n", crm_str(cons_xml_str));
     free_xml(cons_xml);
     free(cons_xml_str);
 
     return pcmk_ok;
 }
 
 static int
 get_ticket_state_attr(const char *ticket_id, const char *attr_name, const char **attr_value,
                       pe_working_set_t * data_set)
 {
     pe_ticket_t *ticket = NULL;
 
     CRM_ASSERT(attr_value != NULL);
     *attr_value = NULL;
 
     ticket = g_hash_table_lookup(data_set->tickets, ticket_id);
     if (ticket == NULL) {
         return -ENXIO;
     }
 
     *attr_value = g_hash_table_lookup(ticket->state, attr_name);
     if (*attr_value == NULL) {
         return -ENXIO;
     }
 
     return pcmk_ok;
 }
 
 static gboolean
 ticket_warning(const char *ticket_id, const char *action)
 {
     gboolean rc = FALSE;
     int offset = 0;
     static int text_max = 1024;
 
     char *warning = NULL;
     const char *word = NULL;
 
     warning = calloc(1, text_max);
     if (pcmk__str_eq(action, "grant", pcmk__str_casei)) {
         offset += snprintf(warning + offset, text_max - offset,
                            "This command cannot help you verify whether '%s' has been already granted elsewhere.\n",
                            ticket_id);
         word = "to";
 
     } else {
         offset += snprintf(warning + offset, text_max - offset,
                            "Revoking '%s' can trigger the specified 'loss-policy'(s) relating to '%s'.\n\n",
                            ticket_id, ticket_id);
 
         offset += snprintf(warning + offset, text_max - offset,
                            "You can check that with:\ncrm_ticket --ticket %s --constraints\n\n",
                            ticket_id);
 
         offset += snprintf(warning + offset, text_max - offset,
                            "Otherwise before revoking '%s', you may want to make '%s' standby with:\ncrm_ticket --ticket %s --standby\n\n",
                            ticket_id, ticket_id, ticket_id);
         word = "from";
     }
 
     offset += snprintf(warning + offset, text_max - offset,
                        "If you really want to %s '%s' %s this site now, and you know what you are doing,\n",
                        action, ticket_id, word);
 
     offset += snprintf(warning + offset, text_max - offset, 
                        "please specify --force.");
 
     CRM_LOG_ASSERT(offset > 0);
     fprintf(stdout, "%s\n", warning);
 
     free(warning);
     return rc;
 }
 
 static gboolean
 allow_modification(const char *ticket_id, GList *attr_delete,
                    GHashTable *attr_set)
 {
     const char *value = NULL;
     GList *list_iter = NULL;
 
     if (do_force) {
         return TRUE;
     }
 
     if (g_hash_table_lookup_extended(attr_set, "granted", NULL, (gpointer *) & value)) {
         if (crm_is_true(value)) {
             ticket_warning(ticket_id, "grant");
             return FALSE;
 
         } else {
             ticket_warning(ticket_id, "revoke");
             return FALSE;
         }
     }
 
     for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) {
         const char *key = (const char *)list_iter->data;
 
         if (pcmk__str_eq(key, "granted", pcmk__str_casei)) {
             ticket_warning(ticket_id, "revoke");
             return FALSE;
         }
     }
 
     return TRUE;
 }
 
 static int
 modify_ticket_state(const char * ticket_id, GList *attr_delete, GHashTable * attr_set,
                     cib_t * cib, pe_working_set_t * data_set)
 {
     int rc = pcmk_ok;
     xmlNode *xml_top = NULL;
     xmlNode *ticket_state_xml = NULL;
     gboolean found = FALSE;
 
     GList *list_iter = NULL;
     GHashTableIter hash_iter;
 
     char *key = NULL;
     char *value = NULL;
 
     pe_ticket_t *ticket = NULL;
 
     rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
     if (rc == pcmk_ok) {
         crm_debug("Found a match state for ticket: id=%s", ticket_id);
         xml_top = ticket_state_xml;
         found = TRUE;
 
     } else if (rc != -ENXIO) {
         return rc;
 
     } else if (g_hash_table_size(attr_set) == 0){
         return pcmk_ok;
 
     } else {
         xmlNode *xml_obj = NULL;
 
         xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS);
         xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
         ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE);
         crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id);
     }
 
     for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) {
         const char *key = (const char *)list_iter->data;
         xml_remove_prop(ticket_state_xml, key);
     }
 
     ticket = find_ticket(ticket_id, data_set);
 
     g_hash_table_iter_init(&hash_iter, attr_set);
     while (g_hash_table_iter_next(&hash_iter, (gpointer *) & key, (gpointer *) & value)) {
         crm_xml_add(ticket_state_xml, key, value);
 
         if (pcmk__str_eq(key, "granted", pcmk__str_casei)
             && (ticket == NULL || ticket->granted == FALSE)
             && crm_is_true(value)) {
 
             char *now = pcmk__ttoa(time(NULL));
 
             crm_xml_add(ticket_state_xml, "last-granted", now);
             free(now);
         }
     }
 
     if (found && (attr_delete != NULL)) {
         crm_log_xml_debug(xml_top, "Replace");
         rc = cib->cmds->replace(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options);
 
     } else {
         crm_log_xml_debug(xml_top, "Update");
         rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options);
     }
 
     free_xml(xml_top);
     return rc;
 }
 
 static int
 delete_ticket_state(const char *ticket_id, cib_t * cib)
 {
     xmlNode *ticket_state_xml = NULL;
 
     int rc = pcmk_ok;
 
     rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
 
     if (rc == -ENXIO) {
         return pcmk_ok;
 
     } else if (rc != pcmk_ok) {
         return rc;
     }
 
     crm_log_xml_debug(ticket_state_xml, "Delete");
 
     rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options);
 
     if (rc == pcmk_ok) {
         fprintf(stdout, "Cleaned up %s\n", ticket_id);
     }
 
     free_xml(ticket_state_xml);
     return rc;
 }
 
 static pcmk__cli_option_t long_options[] = {
     // long option, argument type, storage, short option, description, flags
     {
         "help", no_argument, NULL, '?',
         "\t\tThis text", pcmk__option_default
     },
     {
         "version", no_argument, NULL, '$',
         "\t\tVersion information", pcmk__option_default
     },
     {
         "verbose", no_argument, NULL, 'V',
         "\t\tIncrease debug output", pcmk__option_default
     },
     {
         "quiet", no_argument, NULL, 'Q',
         "\t\tPrint only the value on stdout\n", pcmk__option_default
     },
     {
         "ticket", required_argument, NULL, 't',
         "\tTicket ID", pcmk__option_default
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "\nQueries:", pcmk__option_default
     },
     {
         "info", no_argument, NULL, 'l',
         "\t\tDisplay the information of ticket(s)", pcmk__option_default
     },
     {
         "details", no_argument, NULL, 'L',
         "\t\tDisplay the details of ticket(s)", pcmk__option_default
     },
     {
         "raw", no_argument, NULL, 'w',
         "\t\tDisplay the IDs of ticket(s)", pcmk__option_default
     },
     {
         "query-xml", no_argument, NULL, 'q',
         "\tQuery the XML of ticket(s)", pcmk__option_default
     },
     {
         "constraints", no_argument, NULL, 'c',
         "\tDisplay the rsc_ticket constraints that apply to ticket(s)",
         pcmk__option_default
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "\nCommands:", pcmk__option_default
     },
     {
         "grant", no_argument, NULL, 'g',
         "\t\tGrant a ticket to this cluster site", pcmk__option_default
     },
     {
         "revoke", no_argument, NULL, 'r',
         "\t\tRevoke a ticket from this cluster site", pcmk__option_default
     },
     {
         "standby", no_argument, NULL, 's',
         "\t\tTell this cluster site this ticket is standby",
         pcmk__option_default
     },
     {
         "activate", no_argument, NULL, 'a',
         "\tTell this cluster site this ticket is active", pcmk__option_default
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "\nAdvanced Commands:", pcmk__option_default
     },
     {
         "get-attr", required_argument, NULL, 'G',
         "\tDisplay the named attribute for a ticket", pcmk__option_default
     },
     {
         "set-attr", required_argument, NULL, 'S',
         "\tSet the named attribute for a ticket", pcmk__option_default
     },
     {
         "delete-attr", required_argument, NULL, 'D',
         "\tDelete the named attribute for a ticket", pcmk__option_default
     },
     {
         "cleanup", no_argument, NULL, 'C',
         "\t\tDelete all state of a ticket at this cluster site",
         pcmk__option_default
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "\nAdditional Options:", pcmk__option_default
     },
     {
         "attr-value", required_argument, NULL, 'v',
         "\tAttribute value to use with -S", pcmk__option_default
     },
     {
         "default", required_argument, NULL, 'd',
         "\t(Advanced) Default attribute value to display if none is found "
             "(for use with -G)",
         pcmk__option_default
     },
     {
         "force", no_argument, NULL, 'f',
         "\t\t(Advanced) Force the action to be performed", pcmk__option_default
     },
     {
         "xml-file", required_argument, NULL, 'x',
         NULL, pcmk__option_hidden
     },
 
     /* legacy options */
     {
         "set-name", required_argument, NULL, 'n',
         "\t(Advanced) ID of the instance_attributes object to change",
         pcmk__option_default
     },
     {
         "nvpair", required_argument, NULL, 'i',
         "\t(Advanced) ID of the nvpair object to change/delete",
         pcmk__option_default
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "\nExamples:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Display the info of tickets:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --info", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Display the detailed info of tickets:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --details", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Display the XML of 'ticketA':", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --query-xml", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Display the rsc_ticket constraints that apply to 'ticketA':",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --constraints", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Grant 'ticketA' to this cluster site:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --grant", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Revoke 'ticketA' from this cluster site:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --revoke", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Make 'ticketA' standby (the cluster site will treat a granted "
             "'ticketA' as 'standby', and the dependent resources will be "
             "stopped or demoted gracefully without triggering loss-policies):",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --standby", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Activate 'ticketA' from being standby:", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --activate", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Get the value of the 'granted' attribute for 'ticketA':",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --get-attr granted", pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Set the value of the 'standby' attribute for 'ticketA':",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --set-attr standby --attr-value true",
         pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Delete the 'granted' attribute for 'ticketA':", pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --delete-attr granted",
         pcmk__option_example
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "Erase the operation history of 'ticketA' at this cluster site:",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         "The cluster site will 'forget' the existing ticket state.",
         pcmk__option_paragraph
     },
     {
         "-spacer-", no_argument, NULL, '-',
         " crm_ticket --ticket ticketA --cleanup", pcmk__option_example
     },
     { 0, 0, 0, 0 }
 };
 
 int
 main(int argc, char **argv)
 {
     pe_working_set_t *data_set = NULL;
     xmlNode *cib_xml_copy = NULL;
-    xmlNode *cib_constraints = NULL;
 
     cib_t *cib_conn = NULL;
     crm_exit_t exit_code = CRM_EX_OK;
     int rc = pcmk_ok;
 
     int option_index = 0;
     int argerr = 0;
     int flag;
     guint modified = 0;
 
     GList *attr_delete = NULL;
     GHashTable *attr_set = pcmk__strkey_table(free, free);
 
     crm_log_init(NULL, LOG_CRIT, FALSE, FALSE, argc, argv, FALSE);
     pcmk__set_cli_options(NULL, "<query>|<command> [options]", long_options,
                           "perform tasks related to cluster tickets\n\n"
                           "Allows ticket attributes to be queried, modified "
                           "and deleted.\n");
 
     if (argc < 2) {
         pcmk__cli_help('?', CRM_EX_USAGE);
     }
 
     while (1) {
         flag = pcmk__next_cli_option(argc, argv, &option_index, NULL);
         if (flag == -1)
             break;
 
         switch (flag) {
             case 'V':
                 crm_bump_log_level(argc, argv);
                 break;
             case '$':
             case '?':
                 pcmk__cli_help(flag, CRM_EX_OK);
                 break;
             case 'Q':
                 BE_QUIET = TRUE;
                 break;
             case 't':
                 ticket_id = optarg;
                 break;
             case 'l':
             case 'L':
             case 'w':
             case 'q':
             case 'c':
                 ticket_cmd = flag;
                 break;
             case 'g':
                 g_hash_table_insert(attr_set, strdup("granted"), strdup("true"));
                 modified++;
                 break;
             case 'r':
                 g_hash_table_insert(attr_set, strdup("granted"), strdup("false"));
                 modified++;
                 break;
             case 's':
                 g_hash_table_insert(attr_set, strdup("standby"), strdup("true"));
                 modified++;
                 break;
             case 'a':
                 g_hash_table_insert(attr_set, strdup("standby"), strdup("false"));
                 modified++;
                 break;
             case 'G':
                 get_attr_name = optarg;
                 ticket_cmd = flag;
                 break;
             case 'S':
                 attr_name = optarg;
                 if (attr_name && attr_value) {
                     g_hash_table_insert(attr_set, strdup(attr_name), strdup(attr_value));
                     attr_name = NULL;
                     attr_value = NULL;
                     modified++;
                 }
                 break;
             case 'D':
                 attr_delete = g_list_append(attr_delete, optarg);
                 modified++;
                 break;
             case 'C':
                 ticket_cmd = flag;
                 break;
             case 'v':
                 attr_value = optarg;
                 if (attr_name && attr_value) {
                     g_hash_table_insert(attr_set, strdup(attr_name), strdup(attr_value));
                     attr_name = NULL;
                     attr_value = NULL;
                     modified++;
                 }
                 break;
             case 'd':
                 attr_default = optarg;
                 break;
             case 'f':
                 do_force = TRUE;
                 break;
             case 'x':
                 xml_file = optarg;
                 break;
             case 'n':
                 set_name = optarg;
                 break;
             case 'i':
                 attr_id = optarg;
                 break;
 
             default:
                 CMD_ERR("Argument code 0%o (%c) is not (?yet?) supported", flag, flag);
                 ++argerr;
                 break;
         }
     }
 
     if (optind < argc && argv[optind] != NULL) {
         CMD_ERR("non-option ARGV-elements:");
         while (optind < argc && argv[optind] != NULL) {
             CMD_ERR("%s", argv[optind++]);
             ++argerr;
         }
     }
 
     if (optind > argc) {
         ++argerr;
     }
 
     if (argerr) {
         pcmk__cli_help('?', CRM_EX_USAGE);
     }
 
     data_set = pe_new_working_set();
     if (data_set == NULL) {
         crm_perror(LOG_CRIT, "Could not allocate working set");
         exit_code = CRM_EX_OSERR;
         goto done;
     }
     pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
 
     cib_conn = cib_new();
     if (cib_conn == NULL) {
         CMD_ERR("Could not connect to the CIB manager");
         exit_code = CRM_EX_DISCONNECT;
         goto done;
     }
 
     rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
     if (rc != pcmk_ok) {
         CMD_ERR("Could not connect to CIB: %s", pcmk_strerror(rc));
         exit_code = crm_errno2exit(rc);
         goto done;
     }
 
     if (xml_file != NULL) {
         cib_xml_copy = filename2xml(xml_file);
 
     } else {
         rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
         if (rc != pcmk_ok) {
             CMD_ERR("Could not get local CIB: %s", pcmk_strerror(rc));
             exit_code = crm_errno2exit(rc);
             goto done;
         }
     }
 
     if (cli_config_update(&cib_xml_copy, NULL, FALSE) == FALSE) {
         CMD_ERR("Could not update local CIB to latest schema version");
         exit_code = CRM_EX_CONFIG;
         goto done;
     }
 
     data_set->input = cib_xml_copy;
     data_set->now = crm_time_new(NULL);
 
     cluster_status(data_set);
 
     /* For recording the tickets that are referenced in rsc_ticket constraints
      * but have never been granted yet. */
-    cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
-    unpack_constraints(cib_constraints, data_set);
+    pcmk__unpack_constraints(data_set);
 
     if (ticket_cmd == 'l' || ticket_cmd == 'L' || ticket_cmd == 'w') {
         gboolean raw = FALSE;
         gboolean details = FALSE;
 
         if (ticket_cmd == 'L') {
             details = TRUE;
         } else if (ticket_cmd == 'w') {
             raw = TRUE;
         }
 
         if (ticket_id) {
             pe_ticket_t *ticket = find_ticket(ticket_id, data_set);
 
             if (ticket == NULL) {
                 CMD_ERR("No such ticket '%s'", ticket_id);
                 exit_code = CRM_EX_NOSUCH;
                 goto done;
             }
             rc = print_ticket(ticket, raw, details);
 
         } else {
             rc = print_ticket_list(data_set, raw, details);
         }
         if (rc != pcmk_ok) {
             CMD_ERR("Could not print ticket: %s", pcmk_strerror(rc));
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (ticket_cmd == 'q') {
         rc = dump_ticket_xml(cib_conn, ticket_id);
         if (rc != pcmk_ok) {
             CMD_ERR("Could not query ticket XML: %s", pcmk_strerror(rc));
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (ticket_cmd == 'c') {
         rc = dump_constraints(cib_conn, ticket_id);
         if (rc != pcmk_ok) {
             CMD_ERR("Could not show ticket constraints: %s", pcmk_strerror(rc));
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (ticket_cmd == 'G') {
         const char *value = NULL;
 
         if (ticket_id == NULL) {
             CMD_ERR("Must supply ticket ID with -t");
             exit_code = CRM_EX_NOSUCH;
             goto done;
         }
 
         rc = get_ticket_state_attr(ticket_id, get_attr_name, &value, data_set);
         if (rc == pcmk_ok) {
             fprintf(stdout, "%s\n", value);
         } else if (rc == -ENXIO && attr_default) {
             fprintf(stdout, "%s\n", attr_default);
             rc = pcmk_ok;
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (ticket_cmd == 'C') {
         if (ticket_id == NULL) {
             CMD_ERR("Must supply ticket ID with -t");
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (do_force == FALSE) {
             pe_ticket_t *ticket = NULL;
 
             ticket = find_ticket(ticket_id, data_set);
             if (ticket == NULL) {
                 CMD_ERR("No such ticket '%s'", ticket_id);
                 exit_code = CRM_EX_NOSUCH;
                 goto done;
             }
 
             if (ticket->granted) {
                 ticket_warning(ticket_id, "revoke");
                 exit_code = CRM_EX_INSUFFICIENT_PRIV;
                 goto done;
             }
         }
 
         rc = delete_ticket_state(ticket_id, cib_conn);
         if (rc != pcmk_ok) {
             CMD_ERR("Could not clean up ticket: %s", pcmk_strerror(rc));
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (modified) {
         if (ticket_id == NULL) {
             CMD_ERR("Must supply ticket ID with -t");
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (attr_value
             && (pcmk__str_empty(attr_name))) {
             CMD_ERR("Must supply attribute name with -S for -v %s", attr_value);
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (attr_name
             && (pcmk__str_empty(attr_value))) {
             CMD_ERR("Must supply attribute value with -v for -S %s", attr_name);
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (allow_modification(ticket_id, attr_delete, attr_set) == FALSE) {
             CMD_ERR("Ticket modification not allowed");
             exit_code = CRM_EX_INSUFFICIENT_PRIV;
             goto done;
         }
 
         rc = modify_ticket_state(ticket_id, attr_delete, attr_set, cib_conn, data_set);
         if (rc != pcmk_ok) {
             CMD_ERR("Could not modify ticket: %s", pcmk_strerror(rc));
         }
         exit_code = crm_errno2exit(rc);
 
     } else if (ticket_cmd == 'S') {
         /* Correct usage was handled in the "if (modified)" block above, so
          * this is just for reporting usage errors
          */
 
         if (pcmk__str_empty(attr_name)) {
             // We only get here if ticket_cmd was left as default
             CMD_ERR("Must supply a command");
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (ticket_id == NULL) {
             CMD_ERR("Must supply ticket ID with -t");
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
         if (pcmk__str_empty(attr_value)) {
             CMD_ERR("Must supply value with -v for -S %s", attr_name);
             exit_code = CRM_EX_USAGE;
             goto done;
         }
 
     } else {
         CMD_ERR("Unknown command: %c", ticket_cmd);
         exit_code = CRM_EX_USAGE;
     }
 
  done:
     if (attr_set) {
         g_hash_table_destroy(attr_set);
     }
     attr_set = NULL;
 
     if (attr_delete) {
         g_list_free(attr_delete);
     }
     attr_delete = NULL;
 
     pe_free_working_set(data_set);
     data_set = NULL;
 
     if (cib_conn != NULL) {
         cib_conn->cmds->signoff(cib_conn);
         cib_delete(cib_conn);
     }
 
     if (rc == -pcmk_err_no_quorum) {
         CMD_ERR("Use --force to ignore quorum");
     }
 
     crm_exit(exit_code);
 }