diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index 4f5415f768..d2754d0a31 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,365 +1,366 @@
 /*
  * Copyright 2012-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 #ifndef PCMK__CRM_COMMON_RESULTS__H
 #  define PCMK__CRM_COMMON_RESULTS__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /*!
  * \file
  * \brief Function and executable result codes
  * \ingroup core
  */
 
 // Lifted from config.h
 /* The _Noreturn keyword of C11.  */
 #ifndef _Noreturn
 # if (defined __cplusplus \
       && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
           || (defined _MSC_VER && 1900 <= _MSC_VER)))
 #  define _Noreturn [[noreturn]]
 # elif ((!defined __cplusplus || defined __clang__) \
         && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0)  \
             || 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
    /* _Noreturn works as-is.  */
 # elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
 #  define _Noreturn __attribute__ ((__noreturn__))
 # elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
 #  define _Noreturn __declspec (noreturn)
 # else
 #  define _Noreturn
 # endif
 #endif
 
 #  define CRM_ASSERT(expr) do {                                              \
         if (!(expr)) {                                                       \
             crm_abort(__FILE__, __func__, __LINE__, #expr, TRUE, FALSE);     \
             abort(); /* crm_abort() doesn't always abort! */                 \
         }                                                                    \
     } while(0)
 
 /*
  * Function return codes
  *
  * Most Pacemaker API functions return an integer return code. There are two
  * alternative interpretations. The legacy interpration is that the absolute
  * value of the return code is either a system error number or a custom
  * pcmk_err_* number. This is less than ideal because system error numbers are
  * constrained only to the positive int range, so there's the possibility that
  * system errors and custom errors could collide (which did in fact happen
  * already on one architecture). The new intepretation is that negative values
  * are from the pcmk_rc_e enum, and positive values are system error numbers.
  * Both use 0 for success.
  *
  * For system error codes, see:
  * - /usr/include/asm-generic/errno.h
  * - /usr/include/asm-generic/errno-base.h
  */
 
 // Legacy custom return codes for Pacemaker API functions (deprecated)
 #  define pcmk_ok                       0
 #  define PCMK_ERROR_OFFSET             190    /* Replacements on non-linux systems, see include/portability.h */
 #  define PCMK_CUSTOM_OFFSET            200    /* Purely custom codes */
 #  define pcmk_err_generic              201
 #  define pcmk_err_no_quorum            202
 #  define pcmk_err_schema_validation    203
 #  define pcmk_err_transform_failed     204
 #  define pcmk_err_old_data             205
 #  define pcmk_err_diff_failed          206
 #  define pcmk_err_diff_resync          207
 #  define pcmk_err_cib_modified         208
 #  define pcmk_err_cib_backup           209
 #  define pcmk_err_cib_save             210
 #  define pcmk_err_schema_unchanged     211
 #  define pcmk_err_cib_corrupt          212
 #  define pcmk_err_multiple             213
 #  define pcmk_err_node_unknown         214
 #  define pcmk_err_already              215
 /* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
 #ifdef __hppa__
 #  define pcmk_err_bad_nvpair           250 /* 216 is ENOTSOCK */
 #  define pcmk_err_unknown_format       252 /* 217 is EDESTADDRREQ */
 #else
 #  define pcmk_err_bad_nvpair           216
 #  define pcmk_err_unknown_format       217
 #endif
 
 /*!
  * \enum pcmk_rc_e
  * \brief Return codes for Pacemaker API functions
  *
  * Any Pacemaker API function documented as returning a "standard Pacemaker
  * return code" will return pcmk_rc_ok (0) on success, and one of this
  * enumeration's other (negative) values or a (positive) system error number
  * otherwise. The custom codes are at -1001 and lower, so that the caller may
  * use -1 through -1000 for their own custom values if desired. While generally
  * referred to as "errors", nonzero values simply indicate a result, which might
  * or might not be an error depending on the calling context.
  */
 enum pcmk_rc_e {
     /* When adding new values, use consecutively lower numbers, update the array
      * in lib/common/results.c, and test with crm_error.
      */
+    pcmk_rc_unpack_error        = -1032,
     pcmk_rc_invalid_transition  = -1031,
     pcmk_rc_graph_error         = -1030,
     pcmk_rc_dot_error           = -1029,
     pcmk_rc_underflow           = -1028,
     pcmk_rc_no_input            = -1027,
     pcmk_rc_no_output           = -1026,
     pcmk_rc_after_range         = -1025,
     pcmk_rc_within_range        = -1024,
     pcmk_rc_before_range        = -1023,
     pcmk_rc_undetermined        = -1022,
     pcmk_rc_op_unsatisfied      = -1021,
     pcmk_rc_ipc_pid_only        = -1020,
     pcmk_rc_ipc_unresponsive    = -1019,
     pcmk_rc_ipc_unauthorized    = -1018,
     pcmk_rc_no_quorum           = -1017,
     pcmk_rc_schema_validation   = -1016,
     pcmk_rc_schema_unchanged    = -1015,
     pcmk_rc_transform_failed    = -1014,
     pcmk_rc_old_data            = -1013,
     pcmk_rc_diff_failed         = -1012,
     pcmk_rc_diff_resync         = -1011,
     pcmk_rc_cib_modified        = -1010,
     pcmk_rc_cib_backup          = -1009,
     pcmk_rc_cib_save            = -1008,
     pcmk_rc_cib_corrupt         = -1007,
     pcmk_rc_multiple            = -1006,
     pcmk_rc_node_unknown        = -1005,
     pcmk_rc_already             = -1004,
     pcmk_rc_bad_nvpair          = -1003,
     pcmk_rc_unknown_format      = -1002,
     // Developers: Use a more specific code than pcmk_rc_error whenever possible
     pcmk_rc_error               = -1001,
 
     // Values -1 through -1000 reserved for caller use
 
     pcmk_rc_ok                  =     0
 
     // Positive values reserved for system error numbers
 };
 
 
 /*!
  * \enum ocf_exitcode
  * \brief Exit status codes for resource agents
  *
  * The OCF Resource Agent API standard enumerates the possible exit status codes
  * that agents should return. Besides being used with OCF agents, these values
  * are also used by the executor as a universal status for all agent standards;
  * actual results are mapped to these before returning them to clients.
  */
 enum ocf_exitcode {
     PCMK_OCF_OK                   = 0,   //!< Success
     PCMK_OCF_UNKNOWN_ERROR        = 1,   //!< Unspecified error
     PCMK_OCF_INVALID_PARAM        = 2,   //!< Parameter invalid (in local context)
     PCMK_OCF_UNIMPLEMENT_FEATURE  = 3,   //!< Requested action not implemented
     PCMK_OCF_INSUFFICIENT_PRIV    = 4,   //!< Insufficient privileges
     PCMK_OCF_NOT_INSTALLED        = 5,   //!< Dependencies not available locally
     PCMK_OCF_NOT_CONFIGURED       = 6,   //!< Parameter invalid (inherently)
     PCMK_OCF_NOT_RUNNING          = 7,   //!< Service safely stopped
     PCMK_OCF_RUNNING_PROMOTED     = 8,   //!< Service active and promoted
     PCMK_OCF_FAILED_PROMOTED      = 9,   //!< Service failed and possibly in promoted role
     PCMK_OCF_DEGRADED             = 190, //!< Service active but more likely to fail soon
     PCMK_OCF_DEGRADED_PROMOTED    = 191, //!< Service promoted but more likely to fail soon
 
     /* These two are Pacemaker extensions, not in the OCF standard. The
      * controller records PCMK_OCF_UNKNOWN for pending actions.
      * PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support
      * PCMK_EXEC_NOT_CONNECTED.
      */
     PCMK_OCF_CONNECTION_DIED      = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED
     PCMK_OCF_UNKNOWN              = 193, //!< Action is pending
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     // Former Pacemaker extensions
     PCMK_OCF_EXEC_ERROR           = 192, //!< \deprecated (Unused)
     PCMK_OCF_SIGNAL               = 194, //!< \deprecated (Unused)
     PCMK_OCF_NOT_SUPPORTED        = 195, //!< \deprecated (Unused)
     PCMK_OCF_PENDING              = 196, //!< \deprecated (Unused)
     PCMK_OCF_CANCELLED            = 197, //!< \deprecated (Unused)
     PCMK_OCF_TIMEOUT              = 198, //!< \deprecated (Unused)
     PCMK_OCF_OTHER_ERROR          = 199, //!< \deprecated (Unused)
 
     //! \deprecated Use PCMK_OCF_RUNNING_PROMOTED instead
     PCMK_OCF_RUNNING_MASTER     = PCMK_OCF_RUNNING_PROMOTED,
 
     //! \deprecated Use PCMK_OCF_FAILED_PROMOTED instead
     PCMK_OCF_FAILED_MASTER      = PCMK_OCF_FAILED_PROMOTED,
 
     //! \deprecated Use PCMK_OCF_DEGRADED_PROMOTED instead
     PCMK_OCF_DEGRADED_MASTER    = PCMK_OCF_DEGRADED_PROMOTED,
 #endif
 };
 
 /*!
  * \enum crm_exit_e
  * \brief Exit status codes for tools and daemons
  *
  * We want well-specified (i.e. OS-invariant) exit status codes for our daemons
  * and applications so they can be relied on by callers. (Function return codes
  * and errno's do not make good exit statuses.)
  *
  * The only hard rule is that exit statuses must be between 0 and 255; all else
  * is convention. Universally, 0 is success, and 1 is generic error (excluding
  * OSes we don't support -- for example, OpenVMS considers 1 success!).
  *
  * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
  * application use. OCF adds 8-9 and 190-191.
  *
  * sysexits.h was an attempt to give additional meanings, but never really
  * caught on. It uses 0 and 64-78.
  *
  * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command
  * found but not executable", 127 is "command not found", 128 + n is
  * "interrupted by signal n").
  *
  * tldp.org recommends 64-113 for application use.
  *
  * We try to overlap with the above conventions when practical.
  */
 typedef enum crm_exit_e {
     // Common convention
     CRM_EX_OK                   =   0, //!< Success
     CRM_EX_ERROR                =   1, //!< Unspecified error
 
     // LSB + OCF
     CRM_EX_INVALID_PARAM        =   2, //!< Parameter invalid (in local context)
     CRM_EX_UNIMPLEMENT_FEATURE  =   3, //!< Requested action not implemented
     CRM_EX_INSUFFICIENT_PRIV    =   4, //!< Insufficient privileges
     CRM_EX_NOT_INSTALLED        =   5, //!< Dependencies not available locally
     CRM_EX_NOT_CONFIGURED       =   6, //!< Parameter invalid (inherently)
     CRM_EX_NOT_RUNNING          =   7, //!< Service safely stopped
     CRM_EX_PROMOTED             =   8, //!< Service active and promoted
     CRM_EX_FAILED_PROMOTED      =   9, //!< Service failed and possibly promoted
 
     // sysexits.h
     CRM_EX_USAGE                =  64, //!< Command line usage error
     CRM_EX_DATAERR              =  65, //!< User-supplied data incorrect
     CRM_EX_NOINPUT              =  66, //!< Input file not available
     CRM_EX_NOUSER               =  67, //!< User does not exist
     CRM_EX_NOHOST               =  68, //!< Host unknown
     CRM_EX_UNAVAILABLE          =  69, //!< Needed service unavailable
     CRM_EX_SOFTWARE             =  70, //!< Internal software bug
     CRM_EX_OSERR                =  71, //!< External (OS/environmental) problem
     CRM_EX_OSFILE               =  72, //!< System file not usable
     CRM_EX_CANTCREAT            =  73, //!< File couldn't be created
     CRM_EX_IOERR                =  74, //!< File I/O error
     CRM_EX_TEMPFAIL             =  75, //!< Try again
     CRM_EX_PROTOCOL             =  76, //!< Protocol violated
     CRM_EX_NOPERM               =  77, //!< Non-file permission issue
     CRM_EX_CONFIG               =  78, //!< Misconfiguration
 
     // Custom
     CRM_EX_FATAL                = 100, //!< Do not respawn
     CRM_EX_PANIC                = 101, //!< Panic the local host
     CRM_EX_DISCONNECT           = 102, //!< Lost connection to something
     CRM_EX_OLD                  = 103, //!< Update older than existing config
     CRM_EX_DIGEST               = 104, //!< Digest comparison failed
     CRM_EX_NOSUCH               = 105, //!< Requested item does not exist
     CRM_EX_QUORUM               = 106, //!< Local partition does not have quorum
     CRM_EX_UNSAFE               = 107, //!< Requires --force or new conditions
     CRM_EX_EXISTS               = 108, //!< Requested item already exists
     CRM_EX_MULTIPLE             = 109, //!< Requested item has multiple matches
     CRM_EX_EXPIRED              = 110, //!< Requested item has expired
     CRM_EX_NOT_YET_IN_EFFECT    = 111, //!< Requested item is not in effect
     CRM_EX_INDETERMINATE        = 112, //!< Could not determine status
     CRM_EX_UNSATISFIED          = 113, //!< Requested item does not satisfy constraints
 
     // Other
     CRM_EX_TIMEOUT              = 124, //!< Convention from timeout(1)
 
     /* Anything above 128 overlaps with some shells' use of these values for
      * "interrupted by signal N", and so may be unreliable when detected by
      * shell scripts.
      */
 
     // OCF Resource Agent API 1.1
     CRM_EX_DEGRADED             = 190, //!< Service active but more likely to fail soon
     CRM_EX_DEGRADED_PROMOTED    = 191, //!< Service promoted but more likely to fail soon
 
     /* Custom
      *
      * This can be used to initialize exit status variables or to indicate that
      * a command is pending (which is what the controller uses it for).
      */
     CRM_EX_NONE                 = 193, //!< No exit status available
 
     CRM_EX_MAX                  = 255, //!< Ensure crm_exit_t can hold this
 } crm_exit_t;
 
 /*!
  * \enum pcmk_exec_status
  * \brief Execution status
  *
  * These codes are used to specify the result of the attempt to execute an
  * agent, rather than the agent's result itself.
  */
 enum pcmk_exec_status {
     PCMK_EXEC_UNKNOWN = -2,     //!< Used only to initialize variables
     PCMK_EXEC_PENDING = -1,     //!< Action is in progress
     PCMK_EXEC_DONE,             //!< Action completed, result is known
     PCMK_EXEC_CANCELLED,        //!< Action was cancelled
     PCMK_EXEC_TIMEOUT,          //!< Action did not complete in time
     PCMK_EXEC_NOT_SUPPORTED,    //!< Agent does not implement requested action
     PCMK_EXEC_ERROR,            //!< Execution failed, may be retried
     PCMK_EXEC_ERROR_HARD,       //!< Execution failed, do not retry on node
     PCMK_EXEC_ERROR_FATAL,      //!< Execution failed, do not retry anywhere
     PCMK_EXEC_NOT_INSTALLED,    //!< Agent or dependency not available locally
     PCMK_EXEC_NOT_CONNECTED,    //!< No connection to executor
     PCMK_EXEC_INVALID,          //!< Action cannot be attempted (e.g. shutdown)
     PCMK_EXEC_NO_FENCE_DEVICE,  //!< No fence device is configured for target
     PCMK_EXEC_NO_SECRETS,       //!< Necessary CIB secrets are unavailable
 
     // Add new values above here then update this one below
     PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum
 };
 
 const char *pcmk_rc_name(int rc);
 const char *pcmk_rc_str(int rc);
 crm_exit_t pcmk_rc2exitc(int rc);
 enum ocf_exitcode pcmk_rc2ocf(int rc);
 int pcmk_rc2legacy(int rc);
 int pcmk_legacy2rc(int legacy_rc);
 const char *pcmk_strerror(int rc);
 const char *pcmk_errorname(int rc);
 const char *bz2_strerror(int rc);
 const char *crm_exit_name(crm_exit_t exit_code);
 const char *crm_exit_str(crm_exit_t exit_code);
 _Noreturn crm_exit_t crm_exit(crm_exit_t rc);
 
 static inline const char *
 pcmk_exec_status_str(enum pcmk_exec_status status)
 {
     switch (status) {
         case PCMK_EXEC_PENDING:         return "pending";
         case PCMK_EXEC_DONE:            return "complete";
         case PCMK_EXEC_CANCELLED:       return "Cancelled";
         case PCMK_EXEC_TIMEOUT:         return "Timed Out";
         case PCMK_EXEC_NOT_SUPPORTED:   return "NOT SUPPORTED";
         case PCMK_EXEC_ERROR:           return "Error";
         case PCMK_EXEC_ERROR_HARD:      return "Hard error";
         case PCMK_EXEC_ERROR_FATAL:     return "Fatal error";
         case PCMK_EXEC_NOT_INSTALLED:   return "Not installed";
         case PCMK_EXEC_NOT_CONNECTED:   return "Internal communication failure";
         case PCMK_EXEC_INVALID:         return "Cannot execute now";
         case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device";
         case PCMK_EXEC_NO_SECRETS:      return "CIB secrets unavailable";
         default:                        return "UNKNOWN!";
     }
 }
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h
index 1830ee885a..89d8ef9e0a 100644
--- a/include/crm/pengine/internal.h
+++ b/include/crm/pengine/internal.h
@@ -1,619 +1,617 @@
 /*
  * Copyright 2004-2022 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 PE_INTERNAL__H
 #  define PE_INTERNAL__H
 
 #  include <stdint.h>
 #  include <string.h>
 #  include <crm/pengine/status.h>
 #  include <crm/pengine/remote_internal.h>
 #  include <crm/common/internal.h>
 #  include <crm/common/options_internal.h>
 #  include <crm/common/output_internal.h>
 
 enum pe__clone_flags {
     // Whether instances should be started sequentially
     pe__clone_ordered               = (1 << 0),
 
     // Whether promotion scores have been added
     pe__clone_promotion_added       = (1 << 1),
 
     // Whether promotion constraints have been added
     pe__clone_promotion_constrained = (1 << 2),
 };
 
 bool pe__clone_is_ordered(pe_resource_t *clone);
 int pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag);
 
 
 #  define pe_rsc_info(rsc, fmt, args...)  crm_log_tag(LOG_INFO,  rsc ? rsc->id : "<NULL>", fmt, ##args)
 #  define pe_rsc_debug(rsc, fmt, args...) crm_log_tag(LOG_DEBUG, rsc ? rsc->id : "<NULL>", fmt, ##args)
 #  define pe_rsc_trace(rsc, fmt, args...) crm_log_tag(LOG_TRACE, rsc ? rsc->id : "<NULL>", fmt, ##args)
 
 #  define pe_err(fmt...) do {           \
         was_processing_error = TRUE;    \
         pcmk__config_err(fmt);          \
     } while (0)
 
 #  define pe_warn(fmt...) do {          \
         was_processing_warning = TRUE;  \
         pcmk__config_warn(fmt);         \
     } while (0)
 
 #  define pe_proc_err(fmt...) { was_processing_error = TRUE; crm_err(fmt); }
 #  define pe_proc_warn(fmt...) { was_processing_warning = TRUE; crm_warn(fmt); }
 
 #define pe__set_working_set_flags(working_set, flags_to_set) do {           \
         (working_set)->flags = pcmk__set_flags_as(__func__, __LINE__,       \
             LOG_TRACE, "Working set", crm_system_name,                      \
             (working_set)->flags, (flags_to_set), #flags_to_set);           \
     } while (0)
 
 #define pe__clear_working_set_flags(working_set, flags_to_clear) do {       \
         (working_set)->flags = pcmk__clear_flags_as(__func__, __LINE__,     \
             LOG_TRACE, "Working set", crm_system_name,                      \
             (working_set)->flags, (flags_to_clear), #flags_to_clear);       \
     } while (0)
 
 #define pe__set_resource_flags(resource, flags_to_set) do {                 \
         (resource)->flags = pcmk__set_flags_as(__func__, __LINE__,          \
             LOG_TRACE, "Resource", (resource)->id, (resource)->flags,       \
             (flags_to_set), #flags_to_set);                                 \
     } while (0)
 
 #define pe__clear_resource_flags(resource, flags_to_clear) do {             \
         (resource)->flags = pcmk__clear_flags_as(__func__, __LINE__,        \
             LOG_TRACE, "Resource", (resource)->id, (resource)->flags,       \
             (flags_to_clear), #flags_to_clear);                             \
     } while (0)
 
 #define pe__set_action_flags(action, flags_to_set) do {                     \
         (action)->flags = pcmk__set_flags_as(__func__, __LINE__,            \
                                              LOG_TRACE,                     \
                                              "Action", (action)->uuid,      \
                                              (action)->flags,               \
                                              (flags_to_set),                \
                                              #flags_to_set);                \
     } while (0)
 
 #define pe__clear_action_flags(action, flags_to_clear) do {                 \
         (action)->flags = pcmk__clear_flags_as(__func__, __LINE__,          \
                                                LOG_TRACE,                   \
                                                "Action", (action)->uuid,    \
                                                (action)->flags,             \
                                                (flags_to_clear),            \
                                                #flags_to_clear);            \
     } while (0)
 
 #define pe__set_raw_action_flags(action_flags, action_name, flags_to_set) do { \
         action_flags = pcmk__set_flags_as(__func__, __LINE__,               \
                                           LOG_TRACE, "Action", action_name, \
                                           (action_flags),                   \
                                           (flags_to_set), #flags_to_set);   \
     } while (0)
 
 #define pe__clear_raw_action_flags(action_flags, action_name, flags_to_clear) do { \
         action_flags = pcmk__clear_flags_as(__func__, __LINE__,             \
                                             LOG_TRACE,                      \
                                             "Action", action_name,          \
                                             (action_flags),                 \
                                             (flags_to_clear),               \
                                             #flags_to_clear);               \
     } while (0)
 
 #define pe__set_action_flags_as(function, line, action, flags_to_set) do {  \
         (action)->flags = pcmk__set_flags_as((function), (line),            \
                                              LOG_TRACE,                     \
                                              "Action", (action)->uuid,      \
                                              (action)->flags,               \
                                              (flags_to_set),                \
                                              #flags_to_set);                \
     } while (0)
 
 #define pe__clear_action_flags_as(function, line, action, flags_to_clear) do { \
         (action)->flags = pcmk__clear_flags_as((function), (line),          \
                                                LOG_TRACE,                   \
                                                "Action", (action)->uuid,    \
                                                (action)->flags,             \
                                                (flags_to_clear),            \
                                                #flags_to_clear);            \
     } while (0)
 
 #define pe__set_order_flags(order_flags, flags_to_set) do {                 \
         order_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,     \
                                          "Ordering", "constraint",          \
                                          order_flags, (flags_to_set),       \
                                          #flags_to_set);                    \
     } while (0)
 
 #define pe__clear_order_flags(order_flags, flags_to_clear) do {               \
         order_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,     \
                                            "Ordering", "constraint",          \
                                            order_flags, (flags_to_clear),     \
                                            #flags_to_clear);                  \
     } while (0)
 
 // Some warnings we don't want to print every transition
 
 enum pe_warn_once_e {
     pe_wo_blind         = (1 << 0),
     pe_wo_restart_type  = (1 << 1),
     pe_wo_role_after    = (1 << 2),
     pe_wo_poweroff      = (1 << 3),
     pe_wo_require_all   = (1 << 4),
     pe_wo_order_score   = (1 << 5),
     pe_wo_neg_threshold = (1 << 6),
     pe_wo_remove_after  = (1 << 7),
     pe_wo_ping_node     = (1 << 8),
 };
 
 extern uint32_t pe_wo;
 
 #define pe_warn_once(pe_wo_bit, fmt...) do {    \
         if (!pcmk_is_set(pe_wo, pe_wo_bit)) {  \
             if (pe_wo_bit == pe_wo_blind) {     \
                 crm_warn(fmt);                  \
             } else {                            \
                 pe_warn(fmt);                   \
             }                                   \
             pe_wo = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,       \
                                       "Warn-once", "logging", pe_wo,        \
                                       (pe_wo_bit), #pe_wo_bit);             \
         }                                       \
     } while (0);
 
 
 typedef struct pe__location_constraint_s {
     char *id;                           // Constraint XML ID
     pe_resource_t *rsc_lh;              // Resource being located
     enum rsc_role_e role_filter;        // Role to locate
     enum pe_discover_e discover_mode;   // Resource discovery
     GList *node_list_rh;              // List of pe_node_t*
 } pe__location_t;
 
 typedef struct pe__order_constraint_s {
     int id;
     enum pe_ordering type;
 
     void *lh_opaque;
     pe_resource_t *lh_rsc;
     pe_action_t *lh_action;
     char *lh_action_task;
 
     void *rh_opaque;
     pe_resource_t *rh_rsc;
     pe_action_t *rh_action;
     char *rh_action_task;
 } pe__ordering_t;
 
 typedef struct notify_data_s {
     GSList *keys;               // Environment variable name/value pairs
 
     const char *action;
 
     pe_action_t *pre;
     pe_action_t *post;
     pe_action_t *pre_done;
     pe_action_t *post_done;
 
     GList *active;            /* notify_entry_t*  */
     GList *inactive;          /* notify_entry_t*  */
     GList *start;             /* notify_entry_t*  */
     GList *stop;              /* notify_entry_t*  */
     GList *demote;            /* notify_entry_t*  */
     GList *promote;           /* notify_entry_t*  */
     GList *promoted;          /* notify_entry_t*  */
     GList *unpromoted;        /* notify_entry_t*  */
     GHashTable *allowed_nodes;
 
 } notify_data_t;
 
 int pe__clone_promoted_max(pe_resource_t *clone);
 int pe__clone_promoted_node_max(pe_resource_t *clone);
 
 pe_action_t *pe__new_rsc_pseudo_action(pe_resource_t *rsc, const char *task,
                                        bool optional, bool runnable);
 
 void pe__create_promotable_pseudo_ops(pe_resource_t *clone, bool any_promoting,
                                       bool any_demoting);
 
 bool pe_can_fence(pe_working_set_t *data_set, pe_node_t *node);
 
 void add_hash_param(GHashTable * hash, const char *name, const char *value);
 
 char *native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name,
                        pe_working_set_t * data_set);
 pe_node_t *native_location(const pe_resource_t *rsc, GList **list, int current);
 
 void pe_metadata(pcmk__output_t *out);
 void verify_pe_options(GHashTable * options);
 
 void common_update_score(pe_resource_t * rsc, const char *id, int score);
 void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set, gboolean failed);
 
 gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
 gboolean group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
 gboolean clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
 gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set);
 
 pe_resource_t *native_find_rsc(pe_resource_t *rsc, const char *id, const pe_node_t *node,
                                int flags);
 
 gboolean native_active(pe_resource_t * rsc, gboolean all);
 gboolean group_active(pe_resource_t * rsc, gboolean all);
 gboolean clone_active(pe_resource_t * rsc, gboolean all);
 gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all);
 
 void native_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data);
 void group_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data);
 void clone_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data);
 void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options,
                       void *print_data);
 
 gchar * pcmk__native_output_string(pe_resource_t *rsc, const char *name, pe_node_t *node,
                                    uint32_t show_opts, const char *target_role, bool show_nodes);
 
 int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
                          , size_t pairs_count, ...);
 char *pe__node_display_name(pe_node_t *node, bool print_detail);
 
 
 // Clone notifications (pe_notif.c)
 void pe__create_notifications(pe_resource_t *rsc, notify_data_t *n_data);
 notify_data_t *pe__clone_notif_pseudo_ops(pe_resource_t *rsc, const char *task,
                                           pe_action_t *action,
                                           pe_action_t *complete);
 void pe__free_notification_data(notify_data_t *n_data);
 void pe__order_notifs_after_fencing(pe_action_t *action, pe_resource_t *rsc,
                                     pe_action_t *stonith_op);
 
 
 static inline const char *
 pe__rsc_bool_str(pe_resource_t *rsc, uint64_t rsc_flag)
 {
     return pcmk__btoa(pcmk_is_set(rsc->flags, rsc_flag));
 }
 
 int pe__clone_xml(pcmk__output_t *out, va_list args);
 int pe__clone_default(pcmk__output_t *out, va_list args);
 int pe__group_xml(pcmk__output_t *out, va_list args);
 int pe__group_default(pcmk__output_t *out, va_list args);
 int pe__bundle_xml(pcmk__output_t *out, va_list args);
 int pe__bundle_html(pcmk__output_t *out, va_list args);
 int pe__bundle_text(pcmk__output_t *out, va_list args);
 int pe__node_html(pcmk__output_t *out, va_list args);
 int pe__node_text(pcmk__output_t *out, va_list args);
 int pe__node_xml(pcmk__output_t *out, va_list args);
 int pe__resource_xml(pcmk__output_t *out, va_list args);
 int pe__resource_html(pcmk__output_t *out, va_list args);
 int pe__resource_text(pcmk__output_t *out, va_list args);
 
 void native_free(pe_resource_t * rsc);
 void group_free(pe_resource_t * rsc);
 void clone_free(pe_resource_t * rsc);
 void pe__free_bundle(pe_resource_t *rsc);
 
 enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current);
 enum rsc_role_e group_resource_state(const pe_resource_t * rsc, gboolean current);
 enum rsc_role_e clone_resource_state(const pe_resource_t * rsc, gboolean current);
 enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc,
                                           gboolean current);
 
 void pe__count_common(pe_resource_t *rsc);
 void pe__count_bundle(pe_resource_t *rsc);
 
-gboolean common_unpack(xmlNode * xml_obj, pe_resource_t ** rsc, pe_resource_t * parent,
-                       pe_working_set_t * data_set);
 void common_free(pe_resource_t * rsc);
 
 pe_node_t *pe__copy_node(const pe_node_t *this_node);
 extern time_t get_effective_time(pe_working_set_t * data_set);
 
 /* Failure handling utilities (from failcounts.c) */
 
 // bit flags for fail count handling options
 enum pe_fc_flags_e {
     pe_fc_default   = (1 << 0),
     pe_fc_effective = (1 << 1), // don't count expired failures
     pe_fc_fillers   = (1 << 2), // if container, include filler failures in count
 };
 
 int pe_get_failcount(pe_node_t *node, pe_resource_t *rsc, time_t *last_failure,
                      uint32_t flags, xmlNode *xml_op,
                      pe_working_set_t *data_set);
 
 pe_action_t *pe__clear_failcount(pe_resource_t *rsc, pe_node_t *node,
                                  const char *reason,
                                  pe_working_set_t *data_set);
 
 /* Functions for finding/counting a resource's active nodes */
 
 pe_node_t *pe__find_active_on(const pe_resource_t *rsc,
                               unsigned int *count_all,
                               unsigned int *count_clean);
 pe_node_t *pe__find_active_requires(const pe_resource_t *rsc,
                                     unsigned int *count);
 
 static inline pe_node_t *
 pe__current_node(const pe_resource_t *rsc)
 {
     return pe__find_active_on(rsc, NULL, NULL);
 }
 
 
 /* Binary like operators for lists of nodes */
 extern void node_list_exclude(GHashTable * list, GList *list2, gboolean merge_scores);
 
 GHashTable *pe__node_list2table(GList *list);
 
 static inline gpointer
 pe_hash_table_lookup(GHashTable * hash, gconstpointer key)
 {
     if (hash) {
         return g_hash_table_lookup(hash, key);
     }
     return NULL;
 }
 
 extern pe_action_t *get_pseudo_op(const char *name, pe_working_set_t * data_set);
 extern gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order);
 
 /* Printing functions for debug */
 extern void print_str_str(gpointer key, gpointer value, gpointer user_data);
 extern void pe__output_node(pe_node_t * node, gboolean details, pcmk__output_t *out);
 
 void pe__show_node_weights_as(const char *file, const char *function,
                               int line, bool to_log, pe_resource_t *rsc,
                               const char *comment, GHashTable *nodes,
                               pe_working_set_t *data_set);
 
 #define pe__show_node_weights(level, rsc, text, nodes, data_set)    \
         pe__show_node_weights_as(__FILE__, __func__, __LINE__,      \
                                  (level), (rsc), (text), (nodes), (data_set))
 
 extern xmlNode *find_rsc_op_entry(pe_resource_t * rsc, const char *key);
 
 extern pe_action_t *custom_action(pe_resource_t * rsc, char *key, const char *task, pe_node_t * on_node,
                                   gboolean optional, gboolean foo, pe_working_set_t * data_set);
 
 #  define delete_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DELETE, 0)
 #  define delete_action(rsc, node, optional) custom_action(		\
 		rsc, delete_key(rsc), CRMD_ACTION_DELETE, node,		\
 		optional, TRUE, rsc->cluster);
 
 #  define stopped_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOPPED, 0)
 #  define stopped_action(rsc, node, optional) custom_action(		\
 		rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node,	\
 		optional, TRUE, rsc->cluster);
 
 #  define stop_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOP, 0)
 #  define stop_action(rsc, node, optional) custom_action(			\
 		rsc, stop_key(rsc), CRMD_ACTION_STOP, node,		\
 		optional, TRUE, rsc->cluster);
 
 #  define reload_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_RELOAD_AGENT, 0)
 #  define start_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_START, 0)
 #  define start_action(rsc, node, optional) custom_action(		\
 		rsc, start_key(rsc), CRMD_ACTION_START, node,		\
 		optional, TRUE, rsc->cluster)
 
 #  define started_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STARTED, 0)
 #  define started_action(rsc, node, optional) custom_action(		\
 		rsc, started_key(rsc), CRMD_ACTION_STARTED, node,	\
 		optional, TRUE, rsc->cluster)
 
 #  define promote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTE, 0)
 #  define promote_action(rsc, node, optional) custom_action(		\
 		rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node,	\
 		optional, TRUE, rsc->cluster)
 
 #  define promoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTED, 0)
 #  define promoted_action(rsc, node, optional) custom_action(		\
 		rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node,	\
 		optional, TRUE, rsc->cluster)
 
 #  define demote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTE, 0)
 #  define demote_action(rsc, node, optional) custom_action(		\
 		rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node,		\
 		optional, TRUE, rsc->cluster)
 
 #  define demoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTED, 0)
 #  define demoted_action(rsc, node, optional) custom_action(		\
 		rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node,	\
 		optional, TRUE, rsc->cluster)
 
 extern int pe_get_configured_timeout(pe_resource_t *rsc, const char *action,
                                      pe_working_set_t *data_set);
 
 extern pe_action_t *find_first_action(GList *input, const char *uuid, const char *task,
                                       pe_node_t * on_node);
 extern enum action_tasks get_complex_task(pe_resource_t * rsc, const char *name,
                                           gboolean allow_non_atomic);
 
 extern GList *find_actions(GList *input, const char *key, const pe_node_t *on_node);
 GList *find_actions_exact(GList *input, const char *key,
                           const pe_node_t *on_node);
 GList *pe__resource_actions(const pe_resource_t *rsc, const pe_node_t *node,
                             const char *task, bool require_node);
 
 extern void pe_free_action(pe_action_t * action);
 
 extern void resource_location(pe_resource_t * rsc, pe_node_t * node, int score, const char *tag,
                               pe_working_set_t * data_set);
 
 extern int pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
                            bool same_node_default);
 extern gint sort_op_by_callid(gconstpointer a, gconstpointer b);
 extern gboolean get_target_role(pe_resource_t * rsc, enum rsc_role_e *role);
 void pe__set_next_role(pe_resource_t *rsc, enum rsc_role_e role,
                        const char *why);
 
 extern pe_resource_t *find_clone_instance(pe_resource_t * rsc, const char *sub_id,
                                           pe_working_set_t * data_set);
 
 extern void destroy_ticket(gpointer data);
 extern pe_ticket_t *ticket_new(const char *ticket_id, pe_working_set_t * data_set);
 
 // Resources for manipulating resource names
 const char *pe_base_name_end(const char *id);
 char *clone_strip(const char *last_rsc_id);
 char *clone_zero(const char *last_rsc_id);
 
 static inline bool
 pe_base_name_eq(pe_resource_t *rsc, const char *id)
 {
     if (id && rsc && rsc->id) {
         // Number of characters in rsc->id before any clone suffix
         size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1;
 
         return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len);
     }
     return false;
 }
 
 int pe__target_rc_from_xml(xmlNode *xml_op);
 
 gint pe__cmp_node_name(gconstpointer a, gconstpointer b);
 bool is_set_recursive(pe_resource_t * rsc, long long flag, bool any);
 
 enum rsc_digest_cmp_val {
     /*! Digests are the same */
     RSC_DIGEST_MATCH = 0,
     /*! Params that require a restart changed */
     RSC_DIGEST_RESTART,
     /*! Some parameter changed.  */
     RSC_DIGEST_ALL,
     /*! rsc op didn't have a digest associated with it, so
      *  it is unknown if parameters changed or not. */
     RSC_DIGEST_UNKNOWN,
 };
 
 typedef struct op_digest_cache_s {
     enum rsc_digest_cmp_val rc;
     xmlNode *params_all;
     xmlNode *params_secure;
     xmlNode *params_restart;
     char *digest_all_calc;
     char *digest_secure_calc;
     char *digest_restart_calc;
 } op_digest_cache_t;
 
 op_digest_cache_t *pe__calculate_digests(pe_resource_t *rsc, const char *task,
                                          guint *interval_ms, pe_node_t *node,
                                          xmlNode *xml_op, GHashTable *overrides,
                                          bool calc_secure,
                                          pe_working_set_t *data_set);
 
 void pe__free_digests(gpointer ptr);
 
 op_digest_cache_t *rsc_action_digest_cmp(pe_resource_t * rsc, xmlNode * xml_op, pe_node_t * node,
                                          pe_working_set_t * data_set);
 
 pe_action_t *pe_fence_op(pe_node_t * node, const char *op, bool optional, const char *reason, bool priority_delay, pe_working_set_t * data_set);
 void trigger_unfencing(
     pe_resource_t * rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t * data_set);
 
 char *pe__action2reason(pe_action_t *action, enum pe_action_flags flag);
 void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite);
 void pe__add_action_expected_result(pe_action_t *action, int expected_result);
 
 void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
 void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
 void pe__clear_resource_flags_on_all(pe_working_set_t *data_set, uint64_t flag);
 
 gboolean add_tag_ref(GHashTable * tags, const char * tag_name,  const char * obj_ref);
 
 void print_rscs_brief(GList *rsc_list, const char * pre_text, long options,
                       void * print_data, gboolean print_all);
 int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, unsigned int options);
 void pe_fence_node(pe_working_set_t * data_set, pe_node_t * node, const char *reason, bool priority_delay);
 
 pe_node_t *pe_create_node(const char *id, const char *uname, const char *type,
                           const char *score, pe_working_set_t * data_set);
 void common_print(pe_resource_t * rsc, const char *pre_text, const char *name, pe_node_t *node, long options, void *print_data);
 int pe__common_output_text(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, unsigned int options);
 int pe__common_output_html(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, unsigned int options);
 pe_resource_t *pe__find_bundle_replica(const pe_resource_t *bundle,
                                        const pe_node_t *node);
 bool pe__bundle_needs_remote_name(pe_resource_t *rsc,
                                   pe_working_set_t *data_set);
 const char *pe__add_bundle_remote_name(pe_resource_t *rsc,
                                        pe_working_set_t *data_set,
                                        xmlNode *xml, const char *field);
 const char *pe_node_attribute_calculated(const pe_node_t *node,
                                          const char *name,
                                          const pe_resource_t *rsc);
 const char *pe_node_attribute_raw(pe_node_t *node, const char *name);
 bool pe__is_universal_clone(pe_resource_t *rsc,
                             pe_working_set_t *data_set);
 void pe__add_param_check(xmlNode *rsc_op, pe_resource_t *rsc, pe_node_t *node,
                          enum pe_check_parameters, pe_working_set_t *data_set);
 void pe__foreach_param_check(pe_working_set_t *data_set,
                              void (*cb)(pe_resource_t*, pe_node_t*, xmlNode*,
                                         enum pe_check_parameters,
                                         pe_working_set_t*));
 void pe__free_param_checks(pe_working_set_t *data_set);
 
 bool pe__shutdown_requested(pe_node_t *node);
 void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set);
 
 /*!
  * \internal
  * \brief Register xml formatting message functions.
  */
 void pe__register_messages(pcmk__output_t *out);
 
 void pe__unpack_dataset_nvpairs(xmlNode *xml_obj, const char *set_name,
                                 pe_rule_eval_data_t *rule_data, GHashTable *hash,
                                 const char *always_first, gboolean overwrite,
                                 pe_working_set_t *data_set);
 
 bool pe__resource_is_disabled(pe_resource_t *rsc);
 pe_action_t *pe__clear_resource_history(pe_resource_t *rsc, pe_node_t *node,
                                         pe_working_set_t *data_set);
 
 GList *pe__rscs_with_tag(pe_working_set_t *data_set, const char *tag_name);
 GList *pe__unames_with_tag(pe_working_set_t *data_set, const char *tag_name);
 bool pe__rsc_has_tag(pe_working_set_t *data_set, const char *rsc, const char *tag);
 bool pe__uname_has_tag(pe_working_set_t *data_set, const char *node, const char *tag);
 
 bool pe__rsc_running_on_any(pe_resource_t *rsc, GList *node_list);
 GList *pe__filter_rsc_list(GList *rscs, GList *filter);
 GList * pe__build_node_name_list(pe_working_set_t *data_set, const char *s);
 GList * pe__build_rsc_list(pe_working_set_t *data_set, const char *s);
 
 bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GList *only_node);
 
 gboolean pe__bundle_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent);
 gboolean pe__clone_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent);
 gboolean pe__group_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent);
 gboolean pe__native_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent);
 
 xmlNode *pe__failed_probe_for_rsc(pe_resource_t *rsc, const char *name);
 
 const char *pe__clone_child_id(pe_resource_t *rsc);
 
 int pe__sum_node_health_scores(const pe_node_t *node, int base_health);
 int pe__node_health(pe_node_t *node);
 
 static inline enum pcmk__health_strategy
 pe__health_strategy(pe_working_set_t *data_set)
 {
     return pcmk__parse_health_strategy(pe_pref(data_set->config_hash,
                                                PCMK__OPT_NODE_HEALTH_STRATEGY));
 }
 
 static inline int
 pe__health_score(const char *option, pe_working_set_t *data_set)
 {
     return char2score(pe_pref(data_set->config_hash, option));
 }
 
 #endif
diff --git a/lib/common/results.c b/lib/common/results.c
index 0d4873991f..ffa200ef51 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,918 +1,923 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <bzlib.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <qb/qbdefs.h>
 
 #include <crm/common/mainloop.h>
 #include <crm/common/xml.h>
 
 G_DEFINE_QUARK(pcmk-rc-error-quark, pcmk__rc_error)
 G_DEFINE_QUARK(pcmk-exitc-error-quark, pcmk__exitc_error)
 
 // @COMPAT Legacy function return codes
 
 //! \deprecated Use standard return codes and pcmk_rc_name() instead
 const char *
 pcmk_errorname(int rc)
 {
     rc = abs(rc);
     switch (rc) {
         case pcmk_err_generic: return "pcmk_err_generic";
         case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
         case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
         case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
         case pcmk_err_old_data: return "pcmk_err_old_data";
         case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
         case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
         case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
         case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
         case pcmk_err_cib_save: return "pcmk_err_cib_save";
         case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
         case pcmk_err_multiple: return "pcmk_err_multiple";
         case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
         case pcmk_err_already: return "pcmk_err_already";
         case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
         case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
         default: return pcmk_rc_name(rc); // system errno
     }
 }
 
 //! \deprecated Use standard return codes and pcmk_rc_str() instead
 const char *
 pcmk_strerror(int rc)
 {
     return pcmk_rc_str(pcmk_legacy2rc(rc));
 }
 
 // Standard Pacemaker API return codes
 
 /* This array is used only for nonzero values of pcmk_rc_e. Its values must be
  * kept in the exact reverse order of the enum value numbering (i.e. add new
  * values to the end of the array).
  */
 static struct pcmk__rc_info {
     const char *name;
     const char *desc;
     int legacy_rc;
 } pcmk__rcs[] = {
     { "pcmk_rc_error",
       "Error",
       -pcmk_err_generic,
     },
     { "pcmk_rc_unknown_format",
       "Unknown output format",
       -pcmk_err_unknown_format,
     },
     { "pcmk_rc_bad_nvpair",
       "Bad name/value pair given",
       -pcmk_err_bad_nvpair,
     },
     { "pcmk_rc_already",
       "Already in requested state",
       -pcmk_err_already,
     },
     { "pcmk_rc_node_unknown",
       "Node not found",
       -pcmk_err_node_unknown,
     },
     { "pcmk_rc_multiple",
       "Resource active on multiple nodes",
       -pcmk_err_multiple,
     },
     { "pcmk_rc_cib_corrupt",
       "Could not parse on-disk configuration",
       -pcmk_err_cib_corrupt,
     },
     { "pcmk_rc_cib_save",
       "Could not save new configuration to disk",
       -pcmk_err_cib_save,
     },
     { "pcmk_rc_cib_backup",
       "Could not archive previous configuration",
       -pcmk_err_cib_backup,
     },
     { "pcmk_rc_cib_modified",
       "On-disk configuration was manually modified",
       -pcmk_err_cib_modified,
     },
     { "pcmk_rc_diff_resync",
       "Application of update diff failed, requesting full refresh",
       -pcmk_err_diff_resync,
     },
     { "pcmk_rc_diff_failed",
       "Application of update diff failed",
       -pcmk_err_diff_failed,
     },
     { "pcmk_rc_old_data",
       "Update was older than existing configuration",
       -pcmk_err_old_data,
     },
     { "pcmk_rc_transform_failed",
       "Schema transform failed",
       -pcmk_err_transform_failed,
     },
     { "pcmk_rc_schema_unchanged",
       "Schema is already the latest available",
       -pcmk_err_schema_unchanged,
     },
     { "pcmk_rc_schema_validation",
       "Update does not conform to the configured schema",
       -pcmk_err_schema_validation,
     },
     { "pcmk_rc_no_quorum",
       "Operation requires quorum",
       -pcmk_err_no_quorum,
     },
     { "pcmk_rc_ipc_pid_only",
       "IPC server process is active but not accepting connections",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_unresponsive",
       "IPC server is unresponsive",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_unauthorized",
       "IPC server is blocked by unauthorized process",
       -pcmk_err_generic,
     },
     { "pcmk_rc_op_unsatisifed",
       "Not applicable under current conditions",
       -pcmk_err_generic,
     },
     { "pcmk_rc_undetermined",
       "Result undetermined",
       -pcmk_err_generic,
     },
     { "pcmk_rc_before_range",
       "Result occurs before given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_within_range",
       "Result occurs within given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_after_range",
       "Result occurs after given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_no_output",
       "Output message produced no output",
       -pcmk_err_generic,
     },
     { "pcmk_rc_no_input",
       "Input file not available",
       -pcmk_err_generic,
     },
     { "pcmk_rc_underflow",
       "Value too small to be stored in data type",
       -pcmk_err_generic,
     },
     { "pcmk_rc_dot_error",
       "Error writing dot(1) file",
       -pcmk_err_generic,
     },
     { "pcmk_rc_graph_error",
       "Error writing graph file",
       -pcmk_err_generic,
     },
     { "pcmk_rc_invalid_transition",
       "Cluster simulation produced invalid transition",
       -pcmk_err_generic,
     },
+    { "pcmk_rc_unpack_error",
+      "Unable to parse CIB XML",
+      -pcmk_err_generic,
+    },
 };
 
 #define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info))
 
 /*!
  * \brief Get a return code constant name as a string
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String of constant name corresponding to rc
  */
 const char *
 pcmk_rc_name(int rc)
 {
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].name;
     }
     switch (rc) {
         case pcmk_rc_ok:        return "pcmk_rc_ok";
         case E2BIG:             return "E2BIG";
         case EACCES:            return "EACCES";
         case EADDRINUSE:        return "EADDRINUSE";
         case EADDRNOTAVAIL:     return "EADDRNOTAVAIL";
         case EAFNOSUPPORT:      return "EAFNOSUPPORT";
         case EAGAIN:            return "EAGAIN";
         case EALREADY:          return "EALREADY";
         case EBADF:             return "EBADF";
         case EBADMSG:           return "EBADMSG";
         case EBUSY:             return "EBUSY";
         case ECANCELED:         return "ECANCELED";
         case ECHILD:            return "ECHILD";
         case ECOMM:             return "ECOMM";
         case ECONNABORTED:      return "ECONNABORTED";
         case ECONNREFUSED:      return "ECONNREFUSED";
         case ECONNRESET:        return "ECONNRESET";
         /* case EDEADLK:        return "EDEADLK"; */
         case EDESTADDRREQ:      return "EDESTADDRREQ";
         case EDOM:              return "EDOM";
         case EDQUOT:            return "EDQUOT";
         case EEXIST:            return "EEXIST";
         case EFAULT:            return "EFAULT";
         case EFBIG:             return "EFBIG";
         case EHOSTDOWN:         return "EHOSTDOWN";
         case EHOSTUNREACH:      return "EHOSTUNREACH";
         case EIDRM:             return "EIDRM";
         case EILSEQ:            return "EILSEQ";
         case EINPROGRESS:       return "EINPROGRESS";
         case EINTR:             return "EINTR";
         case EINVAL:            return "EINVAL";
         case EIO:               return "EIO";
         case EISCONN:           return "EISCONN";
         case EISDIR:            return "EISDIR";
         case ELIBACC:           return "ELIBACC";
         case ELOOP:             return "ELOOP";
         case EMFILE:            return "EMFILE";
         case EMLINK:            return "EMLINK";
         case EMSGSIZE:          return "EMSGSIZE";
 #ifdef EMULTIHOP // Not available on OpenBSD
         case EMULTIHOP:         return "EMULTIHOP";
 #endif
         case ENAMETOOLONG:      return "ENAMETOOLONG";
         case ENETDOWN:          return "ENETDOWN";
         case ENETRESET:         return "ENETRESET";
         case ENETUNREACH:       return "ENETUNREACH";
         case ENFILE:            return "ENFILE";
         case ENOBUFS:           return "ENOBUFS";
         case ENODATA:           return "ENODATA";
         case ENODEV:            return "ENODEV";
         case ENOENT:            return "ENOENT";
         case ENOEXEC:           return "ENOEXEC";
         case ENOKEY:            return "ENOKEY";
         case ENOLCK:            return "ENOLCK";
 #ifdef ENOLINK // Not available on OpenBSD
         case ENOLINK:           return "ENOLINK";
 #endif
         case ENOMEM:            return "ENOMEM";
         case ENOMSG:            return "ENOMSG";
         case ENOPROTOOPT:       return "ENOPROTOOPT";
         case ENOSPC:            return "ENOSPC";
 #ifdef ENOSR
         case ENOSR:             return "ENOSR";
 #endif
 #ifdef ENOSTR
         case ENOSTR:            return "ENOSTR";
 #endif
         case ENOSYS:            return "ENOSYS";
         case ENOTBLK:           return "ENOTBLK";
         case ENOTCONN:          return "ENOTCONN";
         case ENOTDIR:           return "ENOTDIR";
         case ENOTEMPTY:         return "ENOTEMPTY";
         case ENOTSOCK:          return "ENOTSOCK";
 #if ENOTSUP != EOPNOTSUPP
         case ENOTSUP:           return "ENOTSUP";
 #endif
         case ENOTTY:            return "ENOTTY";
         case ENOTUNIQ:          return "ENOTUNIQ";
         case ENXIO:             return "ENXIO";
         case EOPNOTSUPP:        return "EOPNOTSUPP";
         case EOVERFLOW:         return "EOVERFLOW";
         case EPERM:             return "EPERM";
         case EPFNOSUPPORT:      return "EPFNOSUPPORT";
         case EPIPE:             return "EPIPE";
         case EPROTO:            return "EPROTO";
         case EPROTONOSUPPORT:   return "EPROTONOSUPPORT";
         case EPROTOTYPE:        return "EPROTOTYPE";
         case ERANGE:            return "ERANGE";
         case EREMOTE:           return "EREMOTE";
         case EREMOTEIO:         return "EREMOTEIO";
         case EROFS:             return "EROFS";
         case ESHUTDOWN:         return "ESHUTDOWN";
         case ESPIPE:            return "ESPIPE";
         case ESOCKTNOSUPPORT:   return "ESOCKTNOSUPPORT";
         case ESRCH:             return "ESRCH";
         case ESTALE:            return "ESTALE";
         case ETIME:             return "ETIME";
         case ETIMEDOUT:         return "ETIMEDOUT";
         case ETXTBSY:           return "ETXTBSY";
 #ifdef EUNATCH
         case EUNATCH:           return "EUNATCH";
 #endif
         case EUSERS:            return "EUSERS";
         /* case EWOULDBLOCK:    return "EWOULDBLOCK"; */
         case EXDEV:             return "EXDEV";
 
 #ifdef EBADE // Not available on OS X
         case EBADE:             return "EBADE";
         case EBADFD:            return "EBADFD";
         case EBADSLT:           return "EBADSLT";
         case EDEADLOCK:         return "EDEADLOCK";
         case EBADR:             return "EBADR";
         case EBADRQC:           return "EBADRQC";
         case ECHRNG:            return "ECHRNG";
 #ifdef EISNAM // Not available on OS X, Illumos, Solaris
         case EISNAM:            return "EISNAM";
         case EKEYEXPIRED:       return "EKEYEXPIRED";
         case EKEYREVOKED:       return "EKEYREVOKED";
 #endif
         case EKEYREJECTED:      return "EKEYREJECTED";
         case EL2HLT:            return "EL2HLT";
         case EL2NSYNC:          return "EL2NSYNC";
         case EL3HLT:            return "EL3HLT";
         case EL3RST:            return "EL3RST";
         case ELIBBAD:           return "ELIBBAD";
         case ELIBMAX:           return "ELIBMAX";
         case ELIBSCN:           return "ELIBSCN";
         case ELIBEXEC:          return "ELIBEXEC";
 #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
         case ENOMEDIUM:         return "ENOMEDIUM";
         case EMEDIUMTYPE:       return "EMEDIUMTYPE";
 #endif
         case ENONET:            return "ENONET";
         case ENOPKG:            return "ENOPKG";
         case EREMCHG:           return "EREMCHG";
         case ERESTART:          return "ERESTART";
         case ESTRPIPE:          return "ESTRPIPE";
 #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
         case EUCLEAN:           return "EUCLEAN";
 #endif
         case EXFULL:            return "EXFULL";
 #endif // EBADE
         default:                return "Unknown";
     }
 }
 
 /*!
  * \brief Get a user-friendly description of a return code
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String description of rc
  */
 const char *
 pcmk_rc_str(int rc)
 {
     if (rc == pcmk_rc_ok) {
         return "OK";
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].desc;
     }
     if (rc < 0) {
         return "Error";
     }
 
     // Handle values that could be defined by system or by portability.h
     switch (rc) {
 #ifdef PCMK__ENOTUNIQ
         case ENOTUNIQ:      return "Name not unique on network";
 #endif
 #ifdef PCMK__ECOMM
         case ECOMM:         return "Communication error on send";
 #endif
 #ifdef PCMK__ELIBACC
         case ELIBACC:       return "Can not access a needed shared library";
 #endif
 #ifdef PCMK__EREMOTEIO
         case EREMOTEIO:     return "Remote I/O error";
 #endif
 #ifdef PCMK__ENOKEY
         case ENOKEY:        return "Required key not available";
 #endif
 #ifdef PCMK__ENODATA
         case ENODATA:       return "No data available";
 #endif
 #ifdef PCMK__ETIME
         case ETIME:         return "Timer expired";
 #endif
 #ifdef PCMK__EKEYREJECTED
         case EKEYREJECTED:  return "Key was rejected by service";
 #endif
         default:            return strerror(rc);
     }
 }
 
 // This returns negative values for errors
 //! \deprecated Use standard return codes instead
 int
 pcmk_rc2legacy(int rc)
 {
     if (rc >= 0) {
         return -rc; // OK or system errno
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
     }
     return -pcmk_err_generic;
 }
 
 //! \deprecated Use standard return codes instead
 int
 pcmk_legacy2rc(int legacy_rc)
 {
     legacy_rc = abs(legacy_rc);
     switch (legacy_rc) {
         case pcmk_err_no_quorum:            return pcmk_rc_no_quorum;
         case pcmk_err_schema_validation:    return pcmk_rc_schema_validation;
         case pcmk_err_schema_unchanged:     return pcmk_rc_schema_unchanged;
         case pcmk_err_transform_failed:     return pcmk_rc_transform_failed;
         case pcmk_err_old_data:             return pcmk_rc_old_data;
         case pcmk_err_diff_failed:          return pcmk_rc_diff_failed;
         case pcmk_err_diff_resync:          return pcmk_rc_diff_resync;
         case pcmk_err_cib_modified:         return pcmk_rc_cib_modified;
         case pcmk_err_cib_backup:           return pcmk_rc_cib_backup;
         case pcmk_err_cib_save:             return pcmk_rc_cib_save;
         case pcmk_err_cib_corrupt:          return pcmk_rc_cib_corrupt;
         case pcmk_err_multiple:             return pcmk_rc_multiple;
         case pcmk_err_node_unknown:         return pcmk_rc_node_unknown;
         case pcmk_err_already:              return pcmk_rc_already;
         case pcmk_err_bad_nvpair:           return pcmk_rc_bad_nvpair;
         case pcmk_err_unknown_format:       return pcmk_rc_unknown_format;
         case pcmk_err_generic:              return pcmk_rc_error;
         case pcmk_ok:                       return pcmk_rc_ok;
         default:                            return legacy_rc; // system errno
     }
 }
 
 // Exit status codes
 
 const char *
 crm_exit_name(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "CRM_EX_OK";
         case CRM_EX_ERROR: return "CRM_EX_ERROR";
         case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
         case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
         case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
         case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
         case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
         case CRM_EX_PROMOTED: return "CRM_EX_PROMOTED";
         case CRM_EX_FAILED_PROMOTED: return "CRM_EX_FAILED_PROMOTED";
         case CRM_EX_USAGE: return "CRM_EX_USAGE";
         case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
         case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
         case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
         case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
         case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
         case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
         case CRM_EX_OSERR: return "CRM_EX_OSERR";
         case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
         case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
         case CRM_EX_IOERR: return "CRM_EX_IOERR";
         case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
         case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
         case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
         case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
         case CRM_EX_FATAL: return "CRM_EX_FATAL";
         case CRM_EX_PANIC: return "CRM_EX_PANIC";
         case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
         case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
         case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
         case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
         case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
         case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
         case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
         case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
         case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
         case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
         case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
         case CRM_EX_OLD: return "CRM_EX_OLD";
         case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
         case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED";
         case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED";
         case CRM_EX_NONE: return "CRM_EX_NONE";
         case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
     }
     return "CRM_EX_UNKNOWN";
 }
 
 const char *
 crm_exit_str(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "OK";
         case CRM_EX_ERROR: return "Error occurred";
         case CRM_EX_INVALID_PARAM: return "Invalid parameter";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
         case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
         case CRM_EX_NOT_INSTALLED: return "Not installed";
         case CRM_EX_NOT_CONFIGURED: return "Not configured";
         case CRM_EX_NOT_RUNNING: return "Not running";
         case CRM_EX_PROMOTED: return "Promoted";
         case CRM_EX_FAILED_PROMOTED: return "Failed in promoted role";
         case CRM_EX_USAGE: return "Incorrect usage";
         case CRM_EX_DATAERR: return "Invalid data given";
         case CRM_EX_NOINPUT: return "Input file not available";
         case CRM_EX_NOUSER: return "User does not exist";
         case CRM_EX_NOHOST: return "Host does not exist";
         case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
         case CRM_EX_SOFTWARE: return "Internal software bug";
         case CRM_EX_OSERR: return "Operating system error occurred";
         case CRM_EX_OSFILE: return "System file not available";
         case CRM_EX_CANTCREAT: return "Cannot create output file";
         case CRM_EX_IOERR: return "I/O error occurred";
         case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
         case CRM_EX_PROTOCOL: return "Protocol violated";
         case CRM_EX_NOPERM: return "Insufficient privileges";
         case CRM_EX_CONFIG: return "Invalid configuration";
         case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
         case CRM_EX_PANIC: return "System panic required";
         case CRM_EX_DISCONNECT: return "Not connected";
         case CRM_EX_DIGEST: return "Digest mismatch";
         case CRM_EX_NOSUCH: return "No such object";
         case CRM_EX_QUORUM: return "Quorum required";
         case CRM_EX_UNSAFE: return "Operation not safe";
         case CRM_EX_EXISTS: return "Requested item already exists";
         case CRM_EX_MULTIPLE: return "Multiple items match request";
         case CRM_EX_EXPIRED: return "Requested item has expired";
         case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
         case CRM_EX_INDETERMINATE: return "Could not determine status";
         case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
         case CRM_EX_OLD: return "Update was older than existing configuration";
         case CRM_EX_TIMEOUT: return "Timeout occurred";
         case CRM_EX_DEGRADED: return "Service is active but might fail soon";
         case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon";
         case CRM_EX_NONE: return "No exit status available";
         case CRM_EX_MAX: return "Error occurred";
     }
     if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
         return "Interrupted by signal";
     }
     return "Unknown exit status";
 }
 
 /*!
  * \brief Map a function return code to the most similar exit code
  *
  * \param[in] rc  Function return code
  *
  * \return Most similar exit code
  */
 crm_exit_t
 pcmk_rc2exitc(int rc)
 {
     switch (rc) {
         case pcmk_rc_ok:
             return CRM_EX_OK;
 
         case pcmk_rc_no_quorum:
             return CRM_EX_QUORUM;
 
         case pcmk_rc_old_data:
             return CRM_EX_OLD;
 
         case pcmk_rc_schema_validation:
         case pcmk_rc_transform_failed:
+        case pcmk_rc_unpack_error:
             return CRM_EX_CONFIG;
 
         case pcmk_rc_bad_nvpair:
             return CRM_EX_INVALID_PARAM;
 
         case EACCES:
             return CRM_EX_INSUFFICIENT_PRIV;
 
         case EBADF:
         case EINVAL:
         case EFAULT:
         case ENOSYS:
         case EOVERFLOW:
         case pcmk_rc_underflow:
             return CRM_EX_SOFTWARE;
 
         case EBADMSG:
         case EMSGSIZE:
         case ENOMSG:
         case ENOPROTOOPT:
         case EPROTO:
         case EPROTONOSUPPORT:
         case EPROTOTYPE:
             return CRM_EX_PROTOCOL;
 
         case ECOMM:
         case ENOMEM:
             return CRM_EX_OSERR;
 
         case ECONNABORTED:
         case ECONNREFUSED:
         case ECONNRESET:
         case ENOTCONN:
             return CRM_EX_DISCONNECT;
 
         case EEXIST:
         case pcmk_rc_already:
             return CRM_EX_EXISTS;
 
         case EIO:
         case pcmk_rc_no_output:
         case pcmk_rc_dot_error:
         case pcmk_rc_graph_error:
             return CRM_EX_IOERR;
 
         case ENOTSUP:
 #if EOPNOTSUPP != ENOTSUP
         case EOPNOTSUPP:
 #endif
             return CRM_EX_UNIMPLEMENT_FEATURE;
 
         case ENOTUNIQ:
         case pcmk_rc_multiple:
             return CRM_EX_MULTIPLE;
 
         case ENXIO:
         case pcmk_rc_node_unknown:
         case pcmk_rc_unknown_format:
             return CRM_EX_NOSUCH;
 
         case ETIME:
         case ETIMEDOUT:
             return CRM_EX_TIMEOUT;
 
         case EAGAIN:
         case EBUSY:
             return CRM_EX_UNSATISFIED;
 
         case pcmk_rc_before_range:
             return CRM_EX_NOT_YET_IN_EFFECT;
 
         case pcmk_rc_after_range:
             return CRM_EX_EXPIRED;
 
         case pcmk_rc_undetermined:
             return CRM_EX_INDETERMINATE;
 
         case pcmk_rc_op_unsatisfied:
             return CRM_EX_UNSATISFIED;
 
         case pcmk_rc_within_range:
             return CRM_EX_OK;
 
         case pcmk_rc_no_input:
             return CRM_EX_NOINPUT;
 
         default:
             return CRM_EX_ERROR;
     }
 }
 
 /*!
  * \brief Map a function return code to the most similar OCF exit code
  *
  * \param[in] rc  Function return code
  *
  * \return Most similar OCF exit code
  */
 enum ocf_exitcode
 pcmk_rc2ocf(int rc)
 {
     switch (rc) {
         case pcmk_rc_ok:
             return PCMK_OCF_OK;
 
         case pcmk_rc_bad_nvpair:
             return PCMK_OCF_INVALID_PARAM;
 
         case EACCES:
             return PCMK_OCF_INSUFFICIENT_PRIV;
 
         case ENOTSUP:
 #if EOPNOTSUPP != ENOTSUP
         case EOPNOTSUPP:
 #endif
             return PCMK_OCF_UNIMPLEMENT_FEATURE;
 
         default:
             return PCMK_OCF_UNKNOWN_ERROR;
     }
 }
 
 
 // Other functions
 
 const char *
 bz2_strerror(int rc)
 {
     // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
     switch (rc) {
         case BZ_OK:
         case BZ_RUN_OK:
         case BZ_FLUSH_OK:
         case BZ_FINISH_OK:
         case BZ_STREAM_END:
             return "Ok";
         case BZ_CONFIG_ERROR:
             return "libbz2 has been improperly compiled on your platform";
         case BZ_SEQUENCE_ERROR:
             return "library functions called in the wrong order";
         case BZ_PARAM_ERROR:
             return "parameter is out of range or otherwise incorrect";
         case BZ_MEM_ERROR:
             return "memory allocation failed";
         case BZ_DATA_ERROR:
             return "data integrity error is detected during decompression";
         case BZ_DATA_ERROR_MAGIC:
             return "the compressed stream does not start with the correct magic bytes";
         case BZ_IO_ERROR:
             return "error reading or writing in the compressed file";
         case BZ_UNEXPECTED_EOF:
             return "compressed file finishes before the logical end of stream is detected";
         case BZ_OUTBUFF_FULL:
             return "output data will not fit into the buffer provided";
     }
     return "Data compression error";
 }
 
 crm_exit_t
 crm_exit(crm_exit_t rc)
 {
     /* A compiler could theoretically use any type for crm_exit_t, but an int
      * should always hold it, so cast to int to keep static analysis happy.
      */
     if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
         rc = CRM_EX_ERROR;
     }
 
     mainloop_cleanup();
     crm_xml_cleanup();
 
     pcmk__cli_option_cleanup();
 
     if (crm_system_name) {
         crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
         free(crm_system_name);
     } else {
         crm_trace("Exiting with status %d", rc);
     }
     qb_log_fini(); // Don't log anything after this point
 
     exit(rc);
 }
 
 /*
  * External action results
  */
 
 /*!
  * \internal
  * \brief Set the result of an action
  *
  * \param[out] result        Where to set action result
  * \param[in]  exit_status   OCF exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  exit_reason   Human-friendly description of event to set
  */
 void
 pcmk__set_result(pcmk__action_result_t *result, int exit_status,
                  enum pcmk_exec_status exec_status, const char *exit_reason)
 {
     if (result == NULL) {
         return;
     }
 
     result->exit_status = exit_status;
     result->execution_status = exec_status;
 
     if (!pcmk__str_eq(result->exit_reason, exit_reason, pcmk__str_none)) {
         free(result->exit_reason);
         result->exit_reason = (exit_reason == NULL)? NULL : strdup(exit_reason);
     }
 }
 
 
 /*!
  * \internal
  * \brief Set the result of an action, with a formatted exit reason
  *
  * \param[out] result        Where to set action result
  * \param[in]  exit_status   OCF exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  format        printf-style format for a human-friendly
  *                           description of reason for result
  * \param[in]  ...           arguments for \p format
  */
 G_GNUC_PRINTF(4, 5)
 void
 pcmk__format_result(pcmk__action_result_t *result, int exit_status,
                     enum pcmk_exec_status exec_status,
                     const char *format, ...)
 {
     va_list ap;
     int len = 0;
     char *reason = NULL;
 
     if (result == NULL) {
         return;
     }
 
     result->exit_status = exit_status;
     result->execution_status = exec_status;
 
     if (format != NULL) {
         va_start(ap, format);
         len = vasprintf(&reason, format, ap);
         CRM_ASSERT(len > 0);
         va_end(ap);
     }
     free(result->exit_reason);
     result->exit_reason = reason;
 }
 
 /*!
  * \internal
  * \brief Set the output of an action
  *
  * \param[out] result         Action result to set output for
  * \param[in]  out            Action output to set (must be dynamically
  *                            allocated)
  * \param[in]  err            Action error output to set (must be dynamically
  *                            allocated)
  *
  * \note \p result will take ownership of \p out and \p err, so the caller
  *       should not free them.
  */
 void
 pcmk__set_result_output(pcmk__action_result_t *result, char *out, char *err)
 {
     if (result == NULL) {
         return;
     }
 
     free(result->action_stdout);
     result->action_stdout = out;
 
     free(result->action_stderr);
     result->action_stderr = err;
 }
 
 /*!
  * \internal
  * \brief Clear a result's exit reason, output, and error output
  *
  * \param[in] result  Result to reset
  */
 void
 pcmk__reset_result(pcmk__action_result_t *result)
 {
     if (result == NULL) {
         return;
     }
 
     free(result->exit_reason);
     result->exit_reason = NULL;
 
     free(result->action_stdout);
     result->action_stdout = NULL;
 
     free(result->action_stderr);
     result->action_stderr = NULL;
 }
 
 /*!
  * \internal
  * \brief Copy the result of an action
  *
  * \param[in]  src  Result to copy
  * \param[out] dst  Where to copy \p src to
  */
 void
 pcmk__copy_result(pcmk__action_result_t *src, pcmk__action_result_t *dst)
 {
     CRM_CHECK((src != NULL) && (dst != NULL), return);
     dst->exit_status = src->exit_status;
     dst->execution_status = src->execution_status;
     pcmk__str_update(&src->exit_reason, dst->exit_reason);
     pcmk__str_update(&src->action_stdout, dst->action_stdout);
     pcmk__str_update(&src->action_stderr, dst->action_stderr);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/results_compat.h>
 
 crm_exit_t
 crm_errno2exit(int rc)
 {
     return pcmk_rc2exitc(pcmk_legacy2rc(rc));
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c
index 52122a2460..3d750c1c1e 100644
--- a/lib/pacemaker/pcmk_sched_colocation.c
+++ b/lib/pacemaker/pcmk_sched_colocation.c
@@ -1,1371 +1,1371 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "crm/common/util.h"
 #include "crm/common/xml_internal.h"
 #include "crm/msg_xml.h"
 #include "libpacemaker_private.h"
 
 #define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do {                      \
         __rsc = pcmk__find_constraint_resource(data_set->resources, __name);    \
         if (__rsc == NULL) {                                                    \
             pcmk__config_err("%s: No resource found for %s", __set, __name);    \
             return;                                                             \
         }                                                                       \
     } while(0)
 
 // Used to temporarily mark a node as unusable
 #define INFINITY_HACK   (INFINITY * -100)
 
 static gint
 cmp_dependent_priority(gconstpointer a, gconstpointer b)
 {
     const pcmk__colocation_t *rsc_constraint1 = (const pcmk__colocation_t *) a;
     const pcmk__colocation_t *rsc_constraint2 = (const pcmk__colocation_t *) b;
 
     if (a == NULL) {
         return 1;
     }
     if (b == NULL) {
         return -1;
     }
 
     CRM_ASSERT(rsc_constraint1->dependent != NULL);
     CRM_ASSERT(rsc_constraint1->primary != NULL);
 
     if (rsc_constraint1->dependent->priority > rsc_constraint2->dependent->priority) {
         return -1;
     }
 
     if (rsc_constraint1->dependent->priority < rsc_constraint2->dependent->priority) {
         return 1;
     }
 
     /* Process clones before primitives and groups */
     if (rsc_constraint1->dependent->variant > rsc_constraint2->dependent->variant) {
         return -1;
     }
     if (rsc_constraint1->dependent->variant < rsc_constraint2->dependent->variant) {
         return 1;
     }
 
     /* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
      * clones (probably unnecessary, but avoids having to update regression
      * tests)
      */
     if (rsc_constraint1->dependent->variant == pe_clone) {
         if (pcmk_is_set(rsc_constraint1->dependent->flags, pe_rsc_promotable)
             && !pcmk_is_set(rsc_constraint2->dependent->flags, pe_rsc_promotable)) {
             return -1;
         } else if (!pcmk_is_set(rsc_constraint1->dependent->flags, pe_rsc_promotable)
             && pcmk_is_set(rsc_constraint2->dependent->flags, pe_rsc_promotable)) {
             return 1;
         }
     }
 
     return strcmp(rsc_constraint1->dependent->id,
                   rsc_constraint2->dependent->id);
 }
 
 static gint
 cmp_primary_priority(gconstpointer a, gconstpointer b)
 {
     const pcmk__colocation_t *rsc_constraint1 = (const pcmk__colocation_t *) a;
     const pcmk__colocation_t *rsc_constraint2 = (const pcmk__colocation_t *) b;
 
     if (a == NULL) {
         return 1;
     }
     if (b == NULL) {
         return -1;
     }
 
     CRM_ASSERT(rsc_constraint1->dependent != NULL);
     CRM_ASSERT(rsc_constraint1->primary != NULL);
 
     if (rsc_constraint1->primary->priority > rsc_constraint2->primary->priority) {
         return -1;
     }
 
     if (rsc_constraint1->primary->priority < rsc_constraint2->primary->priority) {
         return 1;
     }
 
     /* Process clones before primitives and groups */
     if (rsc_constraint1->primary->variant > rsc_constraint2->primary->variant) {
         return -1;
     } else if (rsc_constraint1->primary->variant < rsc_constraint2->primary->variant) {
         return 1;
     }
 
     /* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
      * clones (probably unnecessary, but avoids having to update regression
      * tests)
      */
     if (rsc_constraint1->primary->variant == pe_clone) {
         if (pcmk_is_set(rsc_constraint1->primary->flags, pe_rsc_promotable)
             && !pcmk_is_set(rsc_constraint2->primary->flags, pe_rsc_promotable)) {
             return -1;
         } else if (!pcmk_is_set(rsc_constraint1->primary->flags, pe_rsc_promotable)
             && pcmk_is_set(rsc_constraint2->primary->flags, pe_rsc_promotable)) {
             return 1;
         }
     }
 
     return strcmp(rsc_constraint1->primary->id, rsc_constraint2->primary->id);
 }
 
 /*!
  * \internal
  * \brief Add orderings necessary for an anti-colocation constraint
  */
 static void
 anti_colocation_order(pe_resource_t *first_rsc, int first_role,
                       pe_resource_t *then_rsc, int then_role,
                       pe_working_set_t *data_set)
 {
     const char *first_tasks[] = { NULL, NULL };
     const char *then_tasks[] = { NULL, NULL };
 
     /* Actions to make first_rsc lose first_role */
     if (first_role == RSC_ROLE_PROMOTED) {
         first_tasks[0] = CRMD_ACTION_DEMOTE;
 
     } else {
         first_tasks[0] = CRMD_ACTION_STOP;
 
         if (first_role == RSC_ROLE_UNPROMOTED) {
             first_tasks[1] = CRMD_ACTION_PROMOTE;
         }
     }
 
     /* Actions to make then_rsc gain then_role */
     if (then_role == RSC_ROLE_PROMOTED) {
         then_tasks[0] = CRMD_ACTION_PROMOTE;
 
     } else {
         then_tasks[0] = CRMD_ACTION_START;
 
         if (then_role == RSC_ROLE_UNPROMOTED) {
             then_tasks[1] = CRMD_ACTION_DEMOTE;
         }
     }
 
     for (int first_lpc = 0;
          (first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) {
 
         for (int then_lpc = 0;
              (then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) {
 
             pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc],
                                          then_rsc, then_tasks[then_lpc],
                                          pe_order_anti_colocation);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Add a new colocation constraint to a cluster working set
  *
  * \param[in] id              XML ID for this constraint
  * \param[in] node_attr       Colocate by this attribute (or NULL for #uname)
  * \param[in] score           Constraint score
  * \param[in] dependent       Resource to be colocated
  * \param[in] primary         Resource to colocate \p dependent with
  * \param[in] dependent_role  Current role of \p dependent
  * \param[in] primary_role    Current role of \p primary
  * \param[in] influence       Whether colocation constraint has influence
  * \param[in] data_set        Cluster working set to add constraint to
  */
 void
 pcmk__new_colocation(const char *id, const char *node_attr, int score,
                      pe_resource_t *dependent, pe_resource_t *primary,
                      const char *dependent_role, const char *primary_role,
                      bool influence, pe_working_set_t *data_set)
 {
     pcmk__colocation_t *new_con = NULL;
 
     if (score == 0) {
         crm_trace("Ignoring colocation '%s' because score is 0", id);
         return;
     }
     if ((dependent == NULL) || (primary == NULL)) {
         pcmk__config_err("Ignoring colocation '%s' because resource "
                          "does not exist", id);
         return;
     }
 
     new_con = calloc(1, sizeof(pcmk__colocation_t));
     if (new_con == NULL) {
         return;
     }
 
     if (pcmk__str_eq(dependent_role, RSC_ROLE_STARTED_S,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         dependent_role = RSC_ROLE_UNKNOWN_S;
     }
 
     if (pcmk__str_eq(primary_role, RSC_ROLE_STARTED_S,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         primary_role = RSC_ROLE_UNKNOWN_S;
     }
 
     new_con->id = id;
     new_con->dependent = dependent;
     new_con->primary = primary;
     new_con->score = score;
     new_con->dependent_role = text2role(dependent_role);
     new_con->primary_role = text2role(primary_role);
     new_con->node_attribute = node_attr;
     new_con->influence = influence;
 
     if (node_attr == NULL) {
         node_attr = CRM_ATTR_UNAME;
     }
 
     pe_rsc_trace(dependent, "%s ==> %s (%s %d)",
                  dependent->id, primary->id, node_attr, score);
 
     dependent->rsc_cons = g_list_insert_sorted(dependent->rsc_cons, new_con,
                                                cmp_primary_priority);
 
     primary->rsc_cons_lhs = g_list_insert_sorted(primary->rsc_cons_lhs, new_con,
                                                  cmp_dependent_priority);
 
     data_set->colocation_constraints = g_list_append(data_set->colocation_constraints,
                                                      new_con);
 
     if (score <= -INFINITY) {
         anti_colocation_order(dependent, new_con->dependent_role, primary,
                               new_con->primary_role, data_set);
         anti_colocation_order(primary, new_con->primary_role, dependent,
                               new_con->dependent_role, data_set);
     }
 }
 
 /*!
  * \internal
  * \brief Return the boolean influence corresponding to configuration
  *
  * \param[in] coloc_id     Colocation XML ID (for error logging)
  * \param[in] rsc          Resource involved in constraint (for default)
  * \param[in] influence_s  String value of influence option
  *
  * \return true if string evaluates true, false if string evaluates false,
  *         or value of resource's critical option if string is NULL or invalid
  */
 static bool
 unpack_influence(const char *coloc_id, const pe_resource_t *rsc,
                  const char *influence_s)
 {
     if (influence_s != NULL) {
         int influence_i = 0;
 
         if (crm_str_to_boolean(influence_s, &influence_i) < 0) {
             pcmk__config_err("Constraint '%s' has invalid value for "
                              XML_COLOC_ATTR_INFLUENCE " (using default)",
                              coloc_id);
         } else {
             return (influence_i != 0);
         }
     }
     return pcmk_is_set(rsc->flags, pe_rsc_critical);
 }
 
 static void
 unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
                       const char *influence_s, pe_working_set_t *data_set)
 {
     xmlNode *xml_rsc = NULL;
     pe_resource_t *with = NULL;
     pe_resource_t *resource = NULL;
     const char *set_id = ID(set);
     const char *role = crm_element_value(set, "role");
     const char *ordering = crm_element_value(set, "ordering");
     int local_score = score;
     bool sequential = false;
 
     const char *score_s = crm_element_value(set, XML_RULE_ATTR_SCORE);
 
     if (score_s) {
         local_score = char2score(score_s);
     }
     if (local_score == 0) {
         crm_trace("Ignoring colocation '%s' for set '%s' because score is 0",
                   coloc_id, set_id);
         return;
     }
 
     if (ordering == NULL) {
         ordering = "group";
     }
 
     if (pcmk__xe_get_bool_attr(set, "sequential", &sequential) == pcmk_rc_ok && !sequential) {
         return;
 
     } else if ((local_score > 0)
                && pcmk__str_eq(ordering, "group", pcmk__str_casei)) {
         for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
             if (with != NULL) {
                 pe_rsc_trace(resource, "Colocating %s with %s", resource->id, with->id);
                 pcmk__new_colocation(set_id, NULL, local_score, resource,
                                      with, role, role,
                                      unpack_influence(coloc_id, resource,
                                                       influence_s), data_set);
             }
             with = resource;
         }
 
     } else if (local_score > 0) {
         pe_resource_t *last = NULL;
 
         for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
             if (last != NULL) {
                 pe_rsc_trace(resource, "Colocating %s with %s",
                              last->id, resource->id);
                 pcmk__new_colocation(set_id, NULL, local_score, last,
                                      resource, role, role,
                                      unpack_influence(coloc_id, last,
                                                       influence_s), data_set);
             }
 
             last = resource;
         }
 
     } else {
         /* Anti-colocating with every prior resource is
          * the only way to ensure the intuitive result
          * (i.e. that no one in the set can run with anyone else in the set)
          */
 
         for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xmlNode *xml_rsc_with = NULL;
             bool influence = true;
 
             EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
             influence = unpack_influence(coloc_id, resource, influence_s);
 
             for (xml_rsc_with = first_named_child(set, XML_TAG_RESOURCE_REF);
                  xml_rsc_with != NULL;
                  xml_rsc_with = crm_next_same_xml(xml_rsc_with)) {
 
                 if (pcmk__str_eq(resource->id, ID(xml_rsc_with),
                                  pcmk__str_casei)) {
                     break;
                 }
                 EXPAND_CONSTRAINT_IDREF(set_id, with, ID(xml_rsc_with));
                 pe_rsc_trace(resource, "Anti-Colocating %s with %s", resource->id,
                              with->id);
                 pcmk__new_colocation(set_id, NULL, local_score,
                                      resource, with, role, role,
                                      influence, data_set);
             }
         }
     }
 }
 
 static void
 colocate_rsc_sets(const char *id, xmlNode *set1, xmlNode *set2, int score,
                   const char *influence_s, pe_working_set_t *data_set)
 {
     xmlNode *xml_rsc = NULL;
     pe_resource_t *rsc_1 = NULL;
     pe_resource_t *rsc_2 = NULL;
 
     const char *role_1 = crm_element_value(set1, "role");
     const char *role_2 = crm_element_value(set2, "role");
 
     int rc = pcmk_rc_ok;
     bool sequential = false;
 
     if (score == 0) {
         crm_trace("Ignoring colocation '%s' between sets because score is 0",
                   id);
         return;
     }
 
     rc = pcmk__xe_get_bool_attr(set1, "sequential", &sequential);
     if (rc != pcmk_rc_ok || sequential) {
         // Get the first one
         xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
         if (xml_rsc != NULL) {
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
         }
     }
 
     rc = pcmk__xe_get_bool_attr(set2, "sequential", &sequential);
     if (rc != pcmk_rc_ok || sequential) {
         // Get the last one
         const char *rid = NULL;
 
         for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             rid = ID(xml_rsc);
         }
         EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
     }
 
     if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
         pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
                              unpack_influence(id, rsc_1, influence_s),
                              data_set);
 
     } else if (rsc_1 != NULL) {
         bool influence = unpack_influence(id, rsc_1, influence_s);
 
         for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
                                  role_2, influence, data_set);
         }
 
     } else if (rsc_2 != NULL) {
         for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
                                  role_2,
                                  unpack_influence(id, rsc_1, influence_s),
                                  data_set);
         }
 
     } else {
         for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xmlNode *xml_rsc_2 = NULL;
             bool influence = true;
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
             influence = unpack_influence(id, rsc_1, influence_s);
 
             for (xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
                  xml_rsc_2 != NULL;
                  xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
 
                 EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
                 pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
                                      role_1, role_2, influence,
                                      data_set);
             }
         }
     }
 }
 
 static void
 unpack_simple_colocation(xmlNode *xml_obj, const char *id,
                          const char *influence_s, pe_working_set_t *data_set)
 {
     int score_i = 0;
 
     const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
     const char *dependent_id = crm_element_value(xml_obj,
                                                  XML_COLOC_ATTR_SOURCE);
     const char *primary_id = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET);
     const char *dependent_role = crm_element_value(xml_obj,
                                                    XML_COLOC_ATTR_SOURCE_ROLE);
     const char *primary_role = crm_element_value(xml_obj,
                                                  XML_COLOC_ATTR_TARGET_ROLE);
     const char *attr = crm_element_value(xml_obj, XML_COLOC_ATTR_NODE_ATTR);
 
     // experimental syntax from pacemaker-next (unlikely to be adopted as-is)
     const char *dependent_instance = crm_element_value(xml_obj,
                                                        XML_COLOC_ATTR_SOURCE_INSTANCE);
     const char *primary_instance = crm_element_value(xml_obj,
                                                      XML_COLOC_ATTR_TARGET_INSTANCE);
 
     pe_resource_t *dependent = pcmk__find_constraint_resource(data_set->resources,
                                                               dependent_id);
     pe_resource_t *primary = pcmk__find_constraint_resource(data_set->resources,
                                                             primary_id);
 
     if (dependent == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, dependent_id);
         return;
 
     } else if (primary == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, primary_id);
         return;
 
     } else if ((dependent_instance != NULL) && !pe_rsc_is_clone(dependent)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, dependent_id, dependent_instance);
         return;
 
     } else if ((primary_instance != NULL) && !pe_rsc_is_clone(primary)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, primary_id, primary_instance);
         return;
     }
 
     if (dependent_instance != NULL) {
         dependent = find_clone_instance(dependent, dependent_instance, data_set);
         if (dependent == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               id, dependent_id, dependent_instance);
             return;
         }
     }
 
     if (primary_instance != NULL) {
         primary = find_clone_instance(primary, primary_instance, data_set);
         if (primary == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               "'%s'", id, primary_id, primary_instance);
             return;
         }
     }
 
     if (pcmk__xe_attr_is_true(xml_obj, XML_CONS_ATTR_SYMMETRICAL)) {
         pcmk__config_warn("The colocation constraint '"
                           XML_CONS_ATTR_SYMMETRICAL
                           "' attribute has been removed");
     }
 
     if (score) {
         score_i = char2score(score);
     }
 
     pcmk__new_colocation(id, attr, score_i, dependent, primary,
                          dependent_role, primary_role,
                          unpack_influence(id, dependent, influence_s), data_set);
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                        pe_working_set_t *data_set)
 {
     const char *id = NULL;
     const char *dependent_id = NULL;
     const char *primary_id = NULL;
     const char *dependent_role = NULL;
     const char *primary_role = NULL;
 
     pe_resource_t *dependent = NULL;
     pe_resource_t *primary = NULL;
 
     pe_tag_t *dependent_tag = NULL;
     pe_tag_t *primary_tag = NULL;
 
     xmlNode *dependent_set = NULL;
     xmlNode *primary_set = NULL;
     bool any_sets = false;
 
     *expanded_xml = NULL;
 
-    CRM_CHECK(xml_obj != NULL, return pcmk_rc_schema_validation);
+    CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_colocation");
         return pcmk_rc_ok;
     }
 
     dependent_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
     primary_id = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET);
     if ((dependent_id == NULL) || (primary_id == NULL)) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, dependent_id, &dependent,
                                      &dependent_tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, dependent_id);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, primary_id, &primary,
                                      &primary_tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, primary_id);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if ((dependent != NULL) && (primary != NULL)) {
         /* Neither side references any template/tag. */
         return pcmk_rc_ok;
     }
 
     if ((dependent_tag != NULL) && (primary_tag != NULL)) {
         // A colocation constraint between two templates/tags makes no sense
         pcmk__config_err("Ignoring constraint '%s' because two templates or "
                          "tags cannot be colocated", id);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     dependent_role = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE);
     primary_role = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     // Convert template/tag reference in "rsc" into resource_set under constraint
     if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, XML_COLOC_ATTR_SOURCE,
                           true, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (dependent_set != NULL) {
         if (dependent_role != NULL) {
             // Move "rsc-role" into converted resource_set as "role"
             crm_xml_add(dependent_set, "role", dependent_role);
             xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_SOURCE_ROLE);
         }
         any_sets = true;
     }
 
     // Convert template/tag reference in "with-rsc" into resource_set under constraint
     if (!pcmk__tag_to_set(*expanded_xml, &primary_set, XML_COLOC_ATTR_TARGET,
                           true, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (primary_set != NULL) {
         if (primary_role != NULL) {
             // Move "with-rsc-role" into converted resource_set as "role"
             crm_xml_add(primary_set, "role", primary_role);
             xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_TARGET_ROLE);
         }
         any_sets = true;
     }
 
     if (any_sets) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_colocation");
     } else {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Parse a colocation constraint from XML into a cluster working set
  *
  * \param[in] xml_obj   Colocation constraint XML to unpack
  * \param[in] data_set  Cluster working set to add constraint to
  */
 void
 pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     int score_i = 0;
     xmlNode *set = NULL;
     xmlNode *last = NULL;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
     const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
     const char *influence_s = crm_element_value(xml_obj,
                                                 XML_COLOC_ATTR_INFLUENCE);
 
     if (score) {
         score_i = char2score(score);
     }
 
     if (unpack_colocation_tags(xml_obj, &expanded_xml,
                                data_set) != pcmk_rc_ok) {
         return;
     }
     if (expanded_xml) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         set = expand_idref(set, data_set->input);
         if (set == NULL) { // Configuration error, message already logged
             if (expanded_xml != NULL) {
                 free_xml(expanded_xml);
             }
             return;
         }
 
         unpack_colocation_set(set, score_i, id, influence_s, data_set);
 
         if (last != NULL) {
             colocate_rsc_sets(id, last, set, score_i, influence_s, data_set);
         }
         last = set;
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (last == NULL) {
         unpack_simple_colocation(xml_obj, id, influence_s, data_set);
     }
 }
 
 static void
 mark_start_blocked(pe_resource_t *rsc, pe_resource_t *reason,
                    pe_working_set_t *data_set)
 {
     char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
 
     for (GList *gIter = rsc->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         if (pcmk_is_set(action->flags, pe_action_runnable)
             && pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
 
             pe__clear_action_flags(action, pe_action_runnable);
             pe_action_set_reason(action, reason_text, false);
             pcmk__block_colocated_starts(action, data_set);
             pcmk__update_action_for_orderings(action, data_set);
         }
     }
     free(reason_text);
 }
 
 /*!
  * \internal
  * \brief If a start action is unrunnable, block starts of colocated resources
  *
  * \param[in] action    Action to check
  * \param[in] data_set  Cluster working set
  */
 void
 pcmk__block_colocated_starts(pe_action_t *action, pe_working_set_t *data_set)
 {
     GList *gIter = NULL;
     pe_resource_t *rsc = NULL;
 
     if (!pcmk_is_set(action->flags, pe_action_runnable)
         && pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
 
         rsc = uber_parent(action->rsc);
         if (rsc->parent) {
             /* For bundles, uber_parent() returns the clone, not the bundle, so
              * the existence of rsc->parent implies this is a bundle.
              * In this case, we need the bundle resource, so that we can check
              * if all containers are stopped/stopping.
              */
             rsc = rsc->parent;
         }
     }
 
     if ((rsc == NULL) || (rsc->rsc_cons_lhs == NULL)) {
         return;
     }
 
     // Block colocated starts only if all children (if any) have unrunnable starts
     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child = (pe_resource_t *)gIter->data;
         pe_action_t *start = find_first_action(child->actions, NULL, RSC_START, NULL);
 
         if ((start == NULL) || pcmk_is_set(start->flags, pe_action_runnable)) {
             return;
         }
     }
 
     for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
         pcmk__colocation_t *colocate_with = (pcmk__colocation_t *) gIter->data;
 
         if (colocate_with->score == INFINITY) {
             mark_start_blocked(colocate_with->dependent, action->rsc, data_set);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Determine how a colocation constraint should affect a resource
  *
  * Colocation constraints have different effects at different points in the
  * scheduler sequence. Initially, they affect a resource's location; once that
  * is determined, then for promotable clones they can affect a resource
  * instance's role; after both are determined, the constraints no longer matter.
  * Given a specific colocation constraint, check what has been done so far to
  * determine what should be affected at the current point in the scheduler.
  *
  * \param[in] dependent   Dependent resource in colocation
  * \param[in] primary     Primary resource in colocation
  * \param[in] constraint  Colocation constraint
  * \param[in] preview     If true, pretend resources have already been allocated
  *
  * \return How colocation constraint should be applied at this point
  */
 enum pcmk__coloc_affects
 pcmk__colocation_affects(pe_resource_t *dependent, pe_resource_t *primary,
                          pcmk__colocation_t *constraint, bool preview)
 {
     if (!preview && pcmk_is_set(primary->flags, pe_rsc_provisional)) {
         // Primary resource has not been allocated yet, so we can't do anything
         return pcmk__coloc_affects_nothing;
     }
 
     if ((constraint->dependent_role >= RSC_ROLE_UNPROMOTED)
         && (dependent->parent != NULL)
         && pcmk_is_set(dependent->parent->flags, pe_rsc_promotable)
         && !pcmk_is_set(dependent->flags, pe_rsc_provisional)) {
 
         /* This is a colocation by role, and the dependent is a promotable clone
          * that has already been allocated, so the colocation should now affect
          * the role.
          */
         return pcmk__coloc_affects_role;
     }
 
     if (!preview && !pcmk_is_set(dependent->flags, pe_rsc_provisional)) {
         /* The dependent resource has already been through allocation, so the
          * constraint no longer has any effect. Log an error if a mandatory
          * colocation constraint has been violated.
          */
 
         const pe_node_t *primary_node = primary->allocated_to;
 
         if (dependent->allocated_to == NULL) {
             crm_trace("Skipping colocation '%s': %s will not run anywhere",
                       constraint->id, dependent->id);
 
         } else if (constraint->score >= INFINITY) {
             // Dependent resource must colocate with primary resource
 
             if ((primary_node == NULL) ||
                 (primary_node->details != dependent->allocated_to->details)) {
                 crm_err("%s must be colocated with %s but is not (%s vs. %s)",
                         dependent->id, primary->id,
                         dependent->allocated_to->details->uname,
                         (primary_node == NULL)? "unallocated" : primary_node->details->uname);
             }
 
         } else if (constraint->score <= -CRM_SCORE_INFINITY) {
             // Dependent resource must anti-colocate with primary resource
 
             if ((primary_node != NULL) &&
                 (dependent->allocated_to->details == primary_node->details)) {
                 crm_err("%s and %s must be anti-colocated but are allocated "
                         "to the same node (%s)",
                         dependent->id, primary->id, primary_node->details->uname);
             }
         }
         return pcmk__coloc_affects_nothing;
     }
 
     if ((constraint->score > 0)
         && (constraint->dependent_role != RSC_ROLE_UNKNOWN)
         && (constraint->dependent_role != dependent->next_role)) {
 
         crm_trace("Skipping colocation '%s': dependent limited to %s role "
                   "but %s next role is %s",
                   constraint->id, role2text(constraint->dependent_role),
                   dependent->id, role2text(dependent->next_role));
         return pcmk__coloc_affects_nothing;
     }
 
     if ((constraint->score > 0)
         && (constraint->primary_role != RSC_ROLE_UNKNOWN)
         && (constraint->primary_role != primary->next_role)) {
 
         crm_trace("Skipping colocation '%s': primary limited to %s role "
                   "but %s next role is %s",
                   constraint->id, role2text(constraint->primary_role),
                   primary->id, role2text(primary->next_role));
         return pcmk__coloc_affects_nothing;
     }
 
     if ((constraint->score < 0)
         && (constraint->dependent_role != RSC_ROLE_UNKNOWN)
         && (constraint->dependent_role == dependent->next_role)) {
         crm_trace("Skipping anti-colocation '%s': dependent role %s matches",
                   constraint->id, role2text(constraint->dependent_role));
         return pcmk__coloc_affects_nothing;
     }
 
     if ((constraint->score < 0)
         && (constraint->primary_role != RSC_ROLE_UNKNOWN)
         && (constraint->primary_role == primary->next_role)) {
         crm_trace("Skipping anti-colocation '%s': primary role %s matches",
                   constraint->id, role2text(constraint->primary_role));
         return pcmk__coloc_affects_nothing;
     }
 
     return pcmk__coloc_affects_location;
 }
 
 /*!
  * \internal
  * \brief Apply colocation to dependent for allocation purposes
  *
  * Update the allowed node weights of the dependent resource in a colocation,
  * for the purposes of allocating it to a node
  *
  * \param[in] dependent   Dependent resource in colocation
  * \param[in] primary     Primary resource in colocation
  * \param[in] constraint  Colocation constraint
  */
 void
 pcmk__apply_coloc_to_weights(pe_resource_t *dependent, pe_resource_t *primary,
                              pcmk__colocation_t *constraint)
 {
     const char *attribute = CRM_ATTR_ID;
     const char *value = NULL;
     GHashTable *work = NULL;
     GHashTableIter iter;
     pe_node_t *node = NULL;
 
     if (constraint->node_attribute != NULL) {
         attribute = constraint->node_attribute;
     }
 
     if (primary->allocated_to != NULL) {
         value = pe_node_attribute_raw(primary->allocated_to, attribute);
 
     } else if (constraint->score < 0) {
         // Nothing to do (anti-colocation with something that is not running)
         return;
     }
 
     work = pcmk__copy_node_table(dependent->allowed_nodes);
 
     g_hash_table_iter_init(&iter, work);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
         if (primary->allocated_to == NULL) {
             pe_rsc_trace(dependent, "%s: %s@%s -= %d (%s inactive)",
                          constraint->id, dependent->id, node->details->uname,
                          constraint->score, primary->id);
             node->weight = pcmk__add_scores(-constraint->score, node->weight);
 
         } else if (pcmk__str_eq(pe_node_attribute_raw(node, attribute), value,
                                 pcmk__str_casei)) {
             if (constraint->score < CRM_SCORE_INFINITY) {
                 pe_rsc_trace(dependent, "%s: %s@%s += %d",
                              constraint->id, dependent->id,
                              node->details->uname, constraint->score);
                 node->weight = pcmk__add_scores(constraint->score,
                                                 node->weight);
             }
 
         } else if (constraint->score >= CRM_SCORE_INFINITY) {
             pe_rsc_trace(dependent, "%s: %s@%s -= %d (%s mismatch)",
                          constraint->id, dependent->id, node->details->uname,
                          constraint->score, attribute);
             node->weight = pcmk__add_scores(-constraint->score, node->weight);
         }
     }
 
     if ((constraint->score <= -INFINITY) || (constraint->score >= INFINITY)
         || pcmk__any_node_available(work)) {
 
         g_hash_table_destroy(dependent->allowed_nodes);
         dependent->allowed_nodes = work;
         work = NULL;
 
     } else {
         pe_rsc_info(dependent,
                     "%s: Rolling back scores from %s (no available nodes)",
                     dependent->id, primary->id);
     }
 
     if (work != NULL) {
         g_hash_table_destroy(work);
     }
 }
 
 /*!
  * \internal
  * \brief Apply colocation to dependent for role purposes
  *
  * Update the priority of the dependent resource in a colocation, for the
  * purposes of selecting its role
  *
  * \param[in] dependent   Dependent resource in colocation
  * \param[in] primary     Primary resource in colocation
  * \param[in] constraint  Colocation constraint
  */
 void
 pcmk__apply_coloc_to_priority(pe_resource_t *dependent, pe_resource_t *primary,
                               pcmk__colocation_t *constraint)
 {
     const char *dependent_value = NULL;
     const char *primary_value = NULL;
     const char *attribute = CRM_ATTR_ID;
     int score_multiplier = 1;
 
     if ((primary->allocated_to == NULL) || (dependent->allocated_to == NULL)) {
         return;
     }
 
     if (constraint->node_attribute != NULL) {
         attribute = constraint->node_attribute;
     }
 
     dependent_value = pe_node_attribute_raw(dependent->allocated_to, attribute);
     primary_value = pe_node_attribute_raw(primary->allocated_to, attribute);
 
     if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
         if ((constraint->score == INFINITY)
             && (constraint->dependent_role == RSC_ROLE_PROMOTED)) {
             dependent->priority = -INFINITY;
         }
         return;
     }
 
     if ((constraint->primary_role != RSC_ROLE_UNKNOWN)
         && (constraint->primary_role != primary->next_role)) {
         return;
     }
 
     if (constraint->dependent_role == RSC_ROLE_UNPROMOTED) {
         score_multiplier = -1;
     }
 
     dependent->priority = pcmk__add_scores(score_multiplier * constraint->score,
                                            dependent->priority);
 }
 
 /*!
  * \internal
  * \brief Find score of highest-scored node that matches colocation attribute
  *
  * \param[in] rsc    Resource whose allowed nodes should be searched
  * \param[in] attr   Colocation attribute name (must not be NULL)
  * \param[in] value  Colocation attribute value to require
  */
 static int
 best_node_score_matching_attr(const pe_resource_t *rsc, const char *attr,
                               const char *value)
 {
     GHashTableIter iter;
     pe_node_t *node = NULL;
     int best_score = -INFINITY;
     const char *best_node = NULL;
 
     // Find best allowed node with matching attribute
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
 
         if ((node->weight > best_score) && pcmk__node_available(node, false, false)
             && pcmk__str_eq(value, pe_node_attribute_raw(node, attr), pcmk__str_casei)) {
 
             best_score = node->weight;
             best_node = node->details->uname;
         }
     }
 
     if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_casei)) {
         if (best_node == NULL) {
             crm_info("No allowed node for %s matches node attribute %s=%s",
                      rsc->id, attr, value);
         } else {
             crm_info("Allowed node %s for %s had best score (%d) "
                      "of those matching node attribute %s=%s",
                      best_node, rsc->id, best_score, attr, value);
         }
     }
     return best_score;
 }
 
 /*!
  * \internal
  * \brief Add resource's colocation matches to current node allocation scores
  *
  * For each node in a given table, if any of a given resource's allowed nodes
  * have a matching value for the colocation attribute, add the highest of those
  * nodes' scores to the node's score.
  *
  * \param[in,out] nodes  Hash table of nodes with allocation scores so far
  * \param[in]     rsc    Resource whose allowed nodes should be compared
  * \param[in]     attr   Colocation attribute that must match (NULL for default)
  * \param[in]     factor Factor by which to multiply scores being added
  * \param[in]     only_positive  Whether to add only positive scores
  */
 static void
 add_node_scores_matching_attr(GHashTable *nodes, const pe_resource_t *rsc,
                               const char *attr, float factor,
                               bool only_positive)
 {
     GHashTableIter iter;
     pe_node_t *node = NULL;
 
     if (attr == NULL) {
         attr = CRM_ATTR_UNAME;
     }
 
     // Iterate through each node
     g_hash_table_iter_init(&iter, nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
         float weight_f = 0;
         int weight = 0;
         int score = 0;
         int new_score = 0;
 
         score = best_node_score_matching_attr(rsc, attr,
                                               pe_node_attribute_raw(node, attr));
 
         if ((factor < 0) && (score < 0)) {
             /* Negative preference for a node with a negative score
              * should not become a positive preference.
              *
              * @TODO Consider filtering only if weight is -INFINITY
              */
             crm_trace("%s: Filtering %d + %f * %d (double negative disallowed)",
                       node->details->uname, node->weight, factor, score);
             continue;
         }
 
         if (node->weight == INFINITY_HACK) {
             crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
                       node->details->uname, node->weight, factor, score);
             continue;
         }
 
         weight_f = factor * score;
 
         // Round the number; see http://c-faq.com/fp/round.html
         weight = (int) ((weight_f < 0)? (weight_f - 0.5) : (weight_f + 0.5));
 
         /* Small factors can obliterate the small scores that are often actually
          * used in configurations. If the score and factor are nonzero, ensure
          * that the result is nonzero as well.
          */
         if ((weight == 0) && (score != 0)) {
             if (factor > 0.0) {
                 weight = 1;
             } else if (factor < 0.0) {
                 weight = -1;
             }
         }
 
         new_score = pcmk__add_scores(weight, node->weight);
 
         if (only_positive && (new_score < 0) && (node->weight > 0)) {
             crm_trace("%s: Filtering %d + %f * %d = %d "
                       "(negative disallowed, marking node unusable)",
                       node->details->uname, node->weight, factor, score,
                       new_score);
             node->weight = INFINITY_HACK;
             continue;
         }
 
         if (only_positive && (new_score < 0) && (node->weight == 0)) {
             crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
                       node->details->uname, node->weight, factor, score,
                       new_score);
             continue;
         }
 
         crm_trace("%s: %d + %f * %d = %d", node->details->uname,
                   node->weight, factor, score, new_score);
         node->weight = new_score;
     }
 }
 
 static inline bool
 is_nonempty_group(pe_resource_t *rsc)
 {
     return rsc && (rsc->variant == pe_group) && (rsc->children != NULL);
 }
 
 /*!
  * \internal
  * \brief Update nodes with scores of colocated resources' nodes
  *
  * Given a table of nodes and a resource, update the nodes' scores with the
  * scores of the best nodes matching the attribute used for each of the
  * resource's relevant colocations.
  *
  * \param[in,out] rsc      Resource to check colocations for
  * \param[in]     log_id   Resource ID to use in logs (if NULL, use \p rsc ID)
  * \param[in,out] nodes    Nodes to update
  * \param[in]     attr     Colocation attribute (NULL to use default)
  * \param[in]     factor   Incorporate scores multiplied by this factor
  * \param[in]     flags    Bitmask of enum pcmk__coloc_select values
  *
  * \note The caller remains responsible for freeing \p *nodes.
  */
 void
 pcmk__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id,
                                 GHashTable **nodes, const char *attr,
                                 float factor, uint32_t flags)
 {
     GHashTable *work = NULL;
 
     CRM_CHECK((rsc != NULL) && (nodes != NULL), return);
 
     if (log_id == NULL) {
         log_id = rsc->id;
     }
 
     // Avoid infinite recursion
     if (pcmk_is_set(rsc->flags, pe_rsc_merging)) {
         pe_rsc_info(rsc, "%s: Breaking dependency loop at %s",
                     log_id, rsc->id);
         return;
     }
     pe__set_resource_flags(rsc, pe_rsc_merging);
 
     if (*nodes == NULL) {
         /* Only cmp_resources() passes a NULL nodes table, which indicates we
          * should initialize it with the resource's allowed node scores.
          */
         if (is_nonempty_group(rsc)) {
             GList *last = g_list_last(rsc->children);
             pe_resource_t *last_rsc = last->data;
 
             pe_rsc_trace(rsc, "%s: Merging scores from group %s "
                          "using last member %s (at %.6f)",
                          log_id, rsc->id, last_rsc->id, factor);
             last_rsc->cmds->add_colocated_node_scores(last_rsc, log_id,
                                                       &work, attr, factor,
                                                       flags);
         } else {
             work = pcmk__copy_node_table(rsc->allowed_nodes);
         }
 
     } else if (is_nonempty_group(rsc)) {
         pe_resource_t *member = rsc->children->data;
 
         /* The first member of the group will recursively incorporate any
          * constraints involving other members (including the group internal
          * colocation).
          *
          * @TODO The indirect colocations from the dependent group's other
          *       members will be incorporated at full strength rather than by
          *       factor, so the group's combined stickiness will be treated as
          *       (factor + (#members - 1)) * stickiness. It is questionable what
          *       the right approach should be.
          */
         pe_rsc_trace(rsc, "%s: Merging scores from first member of group %s "
                      "(at %.6f)", log_id, rsc->id, factor);
         work = pcmk__copy_node_table(*nodes);
         member->cmds->add_colocated_node_scores(member, log_id, &work, attr,
                                                 factor, flags);
 
     } else {
         pe_rsc_trace(rsc, "%s: Merging scores from %s (at %.6f)",
                      log_id, rsc->id, factor);
         work = pcmk__copy_node_table(*nodes);
         add_node_scores_matching_attr(work, rsc, attr, factor,
                                       pcmk_is_set(flags,
                                                   pcmk__coloc_select_nonnegative));
     }
 
     if (pcmk__any_node_available(work)) {
         GList *gIter = NULL;
         float multiplier = (factor < 0.0)? -1.0 : 1.0;
 
         if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
             gIter = rsc->rsc_cons;
             pe_rsc_trace(rsc,
                          "Checking additional %d optional '%s with' constraints",
                          g_list_length(gIter), rsc->id);
 
         } else if (is_nonempty_group(rsc)) {
             pe_resource_t *last_rsc = g_list_last(rsc->children)->data;
 
             gIter = last_rsc->rsc_cons_lhs;
             pe_rsc_trace(rsc, "Checking additional %d optional 'with group %s' "
                          "constraints using last member %s",
                          g_list_length(gIter), rsc->id, last_rsc->id);
 
         } else {
             gIter = rsc->rsc_cons_lhs;
             pe_rsc_trace(rsc,
                          "Checking additional %d optional 'with %s' constraints",
                          g_list_length(gIter), rsc->id);
         }
 
         for (; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *other = NULL;
             pcmk__colocation_t *constraint = (pcmk__colocation_t *) gIter->data;
 
             if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
                 other = constraint->primary;
             } else if (!pcmk__colocation_has_influence(constraint, NULL)) {
                 continue;
             } else {
                 other = constraint->dependent;
             }
 
             pe_rsc_trace(rsc, "Optionally merging score of '%s' constraint (%s with %s)",
                          constraint->id, constraint->dependent->id,
                          constraint->primary->id);
             factor = multiplier * constraint->score / (float) INFINITY;
             pcmk__add_colocated_node_scores(other, log_id, &work,
                                             constraint->node_attribute, factor,
                                             flags|pcmk__coloc_select_active);
             pe__show_node_weights(true, NULL, log_id, work, rsc->cluster);
         }
 
     } else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
         pe_rsc_info(rsc, "%s: Rolling back optional scores from %s",
                     log_id, rsc->id);
         g_hash_table_destroy(work);
         pe__clear_resource_flags(rsc, pe_rsc_merging);
         return;
     }
 
 
     if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
         pe_node_t *node = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, work);
         while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
             if (node->weight == INFINITY_HACK) {
                 node->weight = 1;
             }
         }
     }
 
     if (*nodes != NULL) {
        g_hash_table_destroy(*nodes);
     }
     *nodes = work;
 
     pe__clear_resource_flags(rsc, pe_rsc_merging);
 }
diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c
index 3386d47a0f..d9ee67026e 100644
--- a/lib/pacemaker/pcmk_sched_location.c
+++ b/lib/pacemaker/pcmk_sched_location.c
@@ -1,676 +1,676 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 static int
 get_node_score(const char *rule, const char *score, bool raw,
                pe_node_t *node, pe_resource_t *rsc)
 {
     int score_f = 0;
 
     if (score == NULL) {
         pe_err("Rule %s: no score specified.  Assuming 0.", rule);
 
     } else if (raw) {
         score_f = char2score(score);
 
     } else {
         const char *attr_score = pe_node_attribute_calculated(node, score, rsc);
 
         if (attr_score == NULL) {
             crm_debug("Rule %s: node %s did not have a value for %s",
                       rule, node->details->uname, score);
             score_f = -INFINITY;
 
         } else {
             crm_debug("Rule %s: node %s had value %s for %s",
                       rule, node->details->uname, attr_score, score);
             score_f = char2score(attr_score);
         }
     }
     return score_f;
 }
 
 static pe__location_t *
 generate_location_rule(pe_resource_t *rsc, xmlNode *rule_xml,
                        const char *discovery, crm_time_t *next_change,
                        pe_working_set_t *data_set,
                        pe_re_match_data_t *re_match_data)
 {
     const char *rule_id = NULL;
     const char *score = NULL;
     const char *boolean = NULL;
     const char *role = NULL;
 
     GList *gIter = NULL;
     GList *match_L = NULL;
 
     bool do_and = true;
     bool accept = true;
     bool raw_score = true;
     bool score_allocated = false;
 
     pe__location_t *location_rule = NULL;
 
     rule_xml = expand_idref(rule_xml, data_set->input);
     if (rule_xml == NULL) {
         return NULL;
     }
 
     rule_id = crm_element_value(rule_xml, XML_ATTR_ID);
     boolean = crm_element_value(rule_xml, XML_RULE_ATTR_BOOLEAN_OP);
     role = crm_element_value(rule_xml, XML_RULE_ATTR_ROLE);
 
     crm_trace("Processing rule: %s", rule_id);
 
     if ((role != NULL) && (text2role(role) == RSC_ROLE_UNKNOWN)) {
         pe_err("Bad role specified for %s: %s", rule_id, role);
         return NULL;
     }
 
     score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE);
     if (score == NULL) {
         score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE_ATTRIBUTE);
         if (score != NULL) {
             raw_score = false;
         }
     }
     if (pcmk__str_eq(boolean, "or", pcmk__str_casei)) {
         do_and = false;
     }
 
     location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL,
                                        data_set);
 
     if (location_rule == NULL) {
         return NULL;
     }
 
     if ((re_match_data != NULL) && (re_match_data->nregs > 0)
         && (re_match_data->pmatch[0].rm_so != -1) && !raw_score) {
 
         char *result = pe_expand_re_matches(score, re_match_data);
 
         if (result != NULL) {
             score = result;
             score_allocated = true;
         }
     }
 
     if (role != NULL) {
         crm_trace("Setting role filter: %s", role);
         location_rule->role_filter = text2role(role);
         if (location_rule->role_filter == RSC_ROLE_UNPROMOTED) {
             /* Any promotable clone cannot be promoted without being in the
              * unpromoted role first. Ergo, any constraint for the unpromoted
              * role applies to every role.
              */
             location_rule->role_filter = RSC_ROLE_UNKNOWN;
         }
     }
     if (do_and) {
         GList *gIter = NULL;
 
         match_L = pcmk__copy_node_list(data_set->nodes, true);
         for (gIter = match_L; gIter != NULL; gIter = gIter->next) {
             pe_node_t *node = (pe_node_t *) gIter->data;
 
             node->weight = get_node_score(rule_id, score, raw_score, node, rsc);
         }
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         int score_f = 0;
         pe_node_t *node = (pe_node_t *) gIter->data;
         pe_match_data_t match_data = {
             .re = re_match_data,
             .params = pe_rsc_params(rsc, node, data_set),
             .meta = rsc->meta,
         };
 
         accept = pe_test_rule(rule_xml, node->details->attrs, RSC_ROLE_UNKNOWN,
                               data_set->now, next_change, &match_data);
 
         crm_trace("Rule %s %s on %s", ID(rule_xml), accept? "passed" : "failed",
                   node->details->uname);
 
         score_f = get_node_score(rule_id, score, raw_score, node, rsc);
 
         if (accept) {
             pe_node_t *local = pe_find_node_id(match_L, node->details->id);
 
             if ((local == NULL) && do_and) {
                 continue;
 
             } else if (local == NULL) {
                 local = pe__copy_node(node);
                 match_L = g_list_append(match_L, local);
             }
 
             if (!do_and) {
                 local->weight = pcmk__add_scores(local->weight, score_f);
             }
             crm_trace("node %s now has weight %d",
                       node->details->uname, local->weight);
 
         } else if (do_and && !accept) {
             // Remove it
             pe_node_t *delete = pe_find_node_id(match_L, node->details->id);
 
             if (delete != NULL) {
                 match_L = g_list_remove(match_L, delete);
                 crm_trace("node %s did not match", node->details->uname);
             }
             free(delete);
         }
     }
 
     if (score_allocated) {
         free((char *)score);
     }
 
     location_rule->node_list_rh = match_L;
     if (location_rule->node_list_rh == NULL) {
         crm_trace("No matching nodes for rule %s", rule_id);
         return NULL;
     }
 
     crm_trace("%s: %d nodes matched",
               rule_id, g_list_length(location_rule->node_list_rh));
     return location_rule;
 }
 
 static void
 unpack_rsc_location(xmlNode *xml_obj, pe_resource_t *rsc, const char *role,
                     const char *score, pe_working_set_t *data_set,
                     pe_re_match_data_t *re_match_data)
 {
     pe__location_t *location = NULL;
     const char *rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE);
     const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
     const char *node = crm_element_value(xml_obj, XML_CIB_TAG_NODE);
     const char *discovery = crm_element_value(xml_obj, XML_LOCATION_ATTR_DISCOVERY);
 
     if (rsc == NULL) {
         pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                           "does not exist", id, rsc_id);
         return;
     }
 
     if (score == NULL) {
         score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
     }
 
     if ((node != NULL) && (score != NULL)) {
         int score_i = char2score(score);
         pe_node_t *match = pe_find_node(data_set->nodes, node);
 
         if (!match) {
             return;
         }
         location = pcmk__new_location(id, rsc, score_i, discovery, match,
                                       data_set);
 
     } else {
         bool empty = true;
         crm_time_t *next_change = crm_time_new_undefined();
 
         /* This loop is logically parallel to pe_evaluate_rules(), except
          * instead of checking whether any rule is active, we set up location
          * constraints for each active rule.
          */
         for (xmlNode *rule_xml = first_named_child(xml_obj, XML_TAG_RULE);
              rule_xml != NULL; rule_xml = crm_next_same_xml(rule_xml)) {
             empty = false;
             crm_trace("Unpacking %s/%s", id, ID(rule_xml));
             generate_location_rule(rsc, rule_xml, discovery, next_change,
                                    data_set, re_match_data);
         }
 
         if (empty) {
             pcmk__config_err("Ignoring constraint '%s' because it contains "
                              "no rules", id);
         }
 
         /* If there is a point in the future when the evaluation of a rule will
          * change, make sure the scheduler is re-run by that time.
          */
         if (crm_time_is_defined(next_change)) {
             time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change);
 
             pe__update_recheck_time(t, data_set);
         }
         crm_time_free(next_change);
         return;
     }
 
     if (role == NULL) {
         role = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE);
     }
 
     if ((location != NULL) && (role != NULL)) {
         if (text2role(role) == RSC_ROLE_UNKNOWN) {
             pe_err("Invalid constraint %s: Bad role %s", id, role);
             return;
 
         } else {
             enum rsc_role_e r = text2role(role);
             switch(r) {
                 case RSC_ROLE_UNKNOWN:
                 case RSC_ROLE_STARTED:
                 case RSC_ROLE_UNPROMOTED:
                     /* Applies to all */
                     location->role_filter = RSC_ROLE_UNKNOWN;
                     break;
                 default:
                     location->role_filter = r;
                     break;
             }
         }
     }
 }
 
 static void
 unpack_simple_location(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
     const char *value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE);
 
     if (value) {
         pe_resource_t *rsc;
 
         rsc = pcmk__find_constraint_resource(data_set->resources, value);
         unpack_rsc_location(xml_obj, rsc, NULL, NULL, data_set, NULL);
     }
 
     value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE_PATTERN);
     if (value) {
         regex_t *r_patt = calloc(1, sizeof(regex_t));
         bool invert = false;
         GList *rIter = NULL;
 
         if (value[0] == '!') {
             value++;
             invert = true;
         }
 
         if (regcomp(r_patt, value, REG_EXTENDED)) {
             pcmk__config_err("Ignoring constraint '%s' because "
                              XML_LOC_ATTR_SOURCE_PATTERN
                              " has invalid value '%s'", id, value);
             regfree(r_patt);
             free(r_patt);
             return;
         }
 
         for (rIter = data_set->resources; rIter; rIter = rIter->next) {
             pe_resource_t *r = rIter->data;
             int nregs = 0;
             regmatch_t *pmatch = NULL;
             int status;
 
             if(r_patt->re_nsub > 0) {
                 nregs = r_patt->re_nsub + 1;
             } else {
                 nregs = 1;
             }
             pmatch = calloc(nregs, sizeof(regmatch_t));
 
             status = regexec(r_patt, r->id, nregs, pmatch, 0);
 
             if (!invert && (status == 0)) {
                 pe_re_match_data_t re_match_data = {
                                                 .string = r->id,
                                                 .nregs = nregs,
                                                 .pmatch = pmatch
                                                };
 
                 crm_debug("'%s' matched '%s' for %s", r->id, value, id);
                 unpack_rsc_location(xml_obj, r, NULL, NULL, data_set,
                                     &re_match_data);
 
             } else if (invert && (status != 0)) {
                 crm_debug("'%s' is an inverted match of '%s' for %s",
                           r->id, value, id);
                 unpack_rsc_location(xml_obj, r, NULL, NULL, data_set, NULL);
 
             } else {
                 crm_trace("'%s' does not match '%s' for %s", r->id, value, id);
             }
 
             free(pmatch);
         }
 
         regfree(r_patt);
         free(r_patt);
     }
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                      pe_working_set_t *data_set)
 {
     const char *id = NULL;
     const char *rsc_id = NULL;
     const char *state = NULL;
     pe_resource_t *rsc = NULL;
     pe_tag_t *tag = NULL;
     xmlNode *rsc_set = NULL;
 
     *expanded_xml = NULL;
 
-    CRM_CHECK(xml_obj != NULL, return pcmk_rc_schema_validation);
+    CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_location");
         return pcmk_rc_ok;
     }
 
     rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE);
     if (rsc_id == NULL) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, rsc_id, &rsc, &tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, rsc_id);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
 
     } else if (rsc != NULL) {
         // No template is referenced
         return pcmk_rc_ok;
     }
 
     state = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     // Convert template/tag reference in "rsc" into resource_set under constraint
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_LOC_ATTR_SOURCE,
                           false, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (rsc_set != NULL) {
         if (state != NULL) {
             // Move "rsc-role" into converted resource_set as "role" attribute
             crm_xml_add(rsc_set, "role", state);
             xml_remove_prop(*expanded_xml, XML_RULE_ATTR_ROLE);
         }
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_location");
 
     } else {
         // No sets
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_location_set(xmlNode *location, xmlNode *set, pe_working_set_t *data_set)
 {
     xmlNode *xml_rsc = NULL;
     pe_resource_t *resource = NULL;
     const char *set_id;
     const char *role;
     const char *local_score;
 
-    CRM_CHECK(set != NULL, return pcmk_rc_schema_validation);
+    CRM_CHECK(set != NULL, return EINVAL);
 
     set_id = ID(set);
     if (set_id == NULL) {
         pcmk__config_err("Ignoring " XML_CONS_TAG_RSC_SET " without "
                          XML_ATTR_ID " in constraint '%s'",
                          pcmk__s(ID(location), "(missing ID)"));
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     role = crm_element_value(set, "role");
     local_score = crm_element_value(set, XML_RULE_ATTR_SCORE);
 
     for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
          xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
         resource = pcmk__find_constraint_resource(data_set->resources,
                                                   ID(xml_rsc));
         if (resource == NULL) {
             pcmk__config_err("%s: No resource found for %s",
                              set_id, ID(xml_rsc));
-            return pcmk_rc_schema_validation;
+            return pcmk_rc_unpack_error;
         }
 
         unpack_rsc_location(location, resource, role, local_score, data_set,
                             NULL);
     }
 
     return pcmk_rc_ok;
 }
 
 void
 pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     xmlNode *set = NULL;
     bool any_sets = false;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     if (unpack_location_tags(xml_obj, &expanded_xml, data_set) != pcmk_rc_ok) {
         return;
     }
 
     if (expanded_xml) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         any_sets = true;
         set = expand_idref(set, data_set->input);
         if ((set == NULL) // Configuration error, message already logged
             || (unpack_location_set(xml_obj, set, data_set) != pcmk_rc_ok)) {
 
             if (expanded_xml) {
                 free_xml(expanded_xml);
             }
             return;
         }
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (!any_sets) {
         unpack_simple_location(xml_obj, data_set);
     }
 }
 
 /*!
  * \internal
  * \brief Add a new location constraint to a cluster working set
  *
  * \param[in] id             XML ID of location constraint
  * \param[in] rsc            Resource in location constraint
  * \param[in] node_weight    Constraint score
  * \param[in] discover_mode  Resource discovery option for constraint
  * \param[in] node           Node in location constraint (or NULL if rule-based)
  * \param[in] data_set       Cluster working set to add constraint to
  *
  * \return Newly allocated location constraint
  * \note The result will be added to \p data_set and should not be freed
  *       separately.
  */
 pe__location_t *
 pcmk__new_location(const char *id, pe_resource_t *rsc,
                    int node_weight, const char *discover_mode,
                    pe_node_t *node, pe_working_set_t *data_set)
 {
     pe__location_t *new_con = NULL;
 
     if (id == NULL) {
         pe_err("Invalid constraint: no ID specified");
         return NULL;
 
     } else if (rsc == NULL) {
         pe_err("Invalid constraint %s: no resource specified", id);
         return NULL;
 
     } else if (node == NULL) {
         CRM_CHECK(node_weight == 0, return NULL);
     }
 
     new_con = calloc(1, sizeof(pe__location_t));
     if (new_con != NULL) {
         new_con->id = strdup(id);
         new_con->rsc_lh = rsc;
         new_con->node_list_rh = NULL;
         new_con->role_filter = RSC_ROLE_UNKNOWN;
 
         if (pcmk__str_eq(discover_mode, "always",
                          pcmk__str_null_matches|pcmk__str_casei)) {
             new_con->discover_mode = pe_discover_always;
 
         } else if (pcmk__str_eq(discover_mode, "never", pcmk__str_casei)) {
             new_con->discover_mode = pe_discover_never;
 
         } else if (pcmk__str_eq(discover_mode, "exclusive", pcmk__str_casei)) {
             new_con->discover_mode = pe_discover_exclusive;
             rsc->exclusive_discover = TRUE;
 
         } else {
             pe_err("Invalid " XML_LOCATION_ATTR_DISCOVERY " value %s "
                    "in location constraint", discover_mode);
         }
 
         if (node != NULL) {
             pe_node_t *copy = pe__copy_node(node);
 
             copy->weight = node_weight;
             new_con->node_list_rh = g_list_prepend(NULL, copy);
         }
 
         data_set->placement_constraints = g_list_prepend(data_set->placement_constraints,
                                                          new_con);
         rsc->rsc_location = g_list_prepend(rsc->rsc_location, new_con);
     }
 
     return new_con;
 }
 
 /*!
  * \internal
  * \brief Apply all location constraints
  *
  * \param[in] data_set       Cluster working set
  */
 void
 pcmk__apply_locations(pe_working_set_t *data_set)
 {
     for (GList *iter = data_set->placement_constraints;
          iter != NULL; iter = iter->next) {
         pe__location_t *location = iter->data;
 
         location->rsc_lh->cmds->rsc_location(location->rsc_lh, location);
     }
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a resource's allowed node weights
  *
  * \param[in] constraint  Location constraint to apply
  * \param[in] rsc         Resource to apply constraint to
  */
 void
 pcmk__apply_location(pe__location_t *constraint, pe_resource_t *rsc)
 {
     bool need_role = false;
 
     CRM_CHECK((constraint != NULL) && (rsc != NULL), return);
 
     // If a role was specified, ensure constraint is applicable
     need_role = (constraint->role_filter > RSC_ROLE_UNKNOWN);
     if (need_role && (constraint->role_filter != rsc->next_role)) {
         pe_rsc_trace(rsc,
                      "Not applying %s to %s because role will be %s not %s",
                      constraint->id, rsc->id, role2text(rsc->next_role),
                      role2text(constraint->role_filter));
         return;
     }
 
     if (constraint->node_list_rh == NULL) {
         pe_rsc_trace(rsc, "Not applying %s to %s because no nodes match",
                      constraint->id, rsc->id);
         return;
     }
 
     pe_rsc_trace(rsc, "Applying %s%s%s to %s", constraint->id,
                  (need_role? " for role " : ""),
                  (need_role? role2text(constraint->role_filter) : ""), rsc->id);
 
     for (GList *gIter = constraint->node_list_rh; gIter != NULL;
          gIter = gIter->next) {
 
         pe_node_t *node = (pe_node_t *) gIter->data;
         pe_node_t *weighted_node = NULL;
 
         weighted_node = (pe_node_t *) pe_hash_table_lookup(rsc->allowed_nodes,
                                                            node->details->id);
         if (weighted_node == NULL) {
             pe_rsc_trace(rsc, "* = %d on %s",
                          node->weight, node->details->uname);
             weighted_node = pe__copy_node(node);
             g_hash_table_insert(rsc->allowed_nodes,
                                 (gpointer) weighted_node->details->id,
                                 weighted_node);
         } else {
             pe_rsc_trace(rsc, "* + %d on %s",
                          node->weight, node->details->uname);
             weighted_node->weight = pcmk__add_scores(weighted_node->weight,
                                                      node->weight);
         }
 
         if (weighted_node->rsc_discover_mode < constraint->discover_mode) {
             if (constraint->discover_mode == pe_discover_exclusive) {
                 rsc->exclusive_discover = TRUE;
             }
             /* exclusive > never > always... always is default */
             weighted_node->rsc_discover_mode = constraint->discover_mode;
         }
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_ordering.c b/lib/pacemaker/pcmk_sched_ordering.c
index 364b1b4fb3..0f5a16feaf 100644
--- a/lib/pacemaker/pcmk_sched_ordering.c
+++ b/lib/pacemaker/pcmk_sched_ordering.c
@@ -1,1605 +1,1605 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 enum pe_order_kind {
     pe_order_kind_optional,
     pe_order_kind_mandatory,
     pe_order_kind_serialize,
 };
 
 enum ordering_symmetry {
     ordering_asymmetric,        // the only relation in an asymmetric ordering
     ordering_symmetric,         // the normal relation in a symmetric ordering
     ordering_symmetric_inverse, // the inverse relation in a symmetric ordering
 };
 
 #define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do {                      \
         __rsc = pcmk__find_constraint_resource(data_set->resources, __name);    \
         if (__rsc == NULL) {                                                    \
             pcmk__config_err("%s: No resource found for %s", __set, __name);    \
-            return pcmk_rc_schema_validation;                                   \
+            return pcmk_rc_unpack_error;                                        \
         }                                                                       \
     } while (0)
 
 static const char *
 invert_action(const char *action)
 {
     if (pcmk__str_eq(action, RSC_START, pcmk__str_casei)) {
         return RSC_STOP;
 
     } else if (pcmk__str_eq(action, RSC_STOP, pcmk__str_casei)) {
         return RSC_START;
 
     } else if (pcmk__str_eq(action, RSC_PROMOTE, pcmk__str_casei)) {
         return RSC_DEMOTE;
 
     } else if (pcmk__str_eq(action, RSC_DEMOTE, pcmk__str_casei)) {
         return RSC_PROMOTE;
 
     } else if (pcmk__str_eq(action, RSC_PROMOTED, pcmk__str_casei)) {
         return RSC_DEMOTED;
 
     } else if (pcmk__str_eq(action, RSC_DEMOTED, pcmk__str_casei)) {
         return RSC_PROMOTED;
 
     } else if (pcmk__str_eq(action, RSC_STARTED, pcmk__str_casei)) {
         return RSC_STOPPED;
 
     } else if (pcmk__str_eq(action, RSC_STOPPED, pcmk__str_casei)) {
         return RSC_STARTED;
     }
     crm_warn("Unknown action '%s' specified in order constraint", action);
     return NULL;
 }
 
 static enum pe_order_kind
 get_ordering_type(xmlNode *xml_obj)
 {
     enum pe_order_kind kind_e = pe_order_kind_mandatory;
     const char *kind = crm_element_value(xml_obj, XML_ORDER_ATTR_KIND);
 
     if (kind == NULL) {
         const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
 
         kind_e = pe_order_kind_mandatory;
 
         if (score) {
             // @COMPAT deprecated informally since 1.0.7, formally since 2.0.1
             int score_i = char2score(score);
 
             if (score_i == 0) {
                 kind_e = pe_order_kind_optional;
             }
             pe_warn_once(pe_wo_order_score,
                          "Support for 'score' in rsc_order is deprecated "
                          "and will be removed in a future release "
                          "(use 'kind' instead)");
         }
 
     } else if (pcmk__str_eq(kind, "Mandatory", pcmk__str_casei)) {
         kind_e = pe_order_kind_mandatory;
 
     } else if (pcmk__str_eq(kind, "Optional", pcmk__str_casei)) {
         kind_e = pe_order_kind_optional;
 
     } else if (pcmk__str_eq(kind, "Serialize", pcmk__str_casei)) {
         kind_e = pe_order_kind_serialize;
 
     } else {
         pcmk__config_err("Resetting '" XML_ORDER_ATTR_KIND "' for constraint "
                          "%s to 'Mandatory' because '%s' is not valid",
                          pcmk__s(ID(xml_obj), "missing ID"), kind);
     }
     return kind_e;
 }
 
 /*!
  * \internal
  * \brief Get ordering symmetry from XML
  *
  * \param[in] xml_obj               Ordering XML
  * \param[in] parent_kind           Default ordering kind
  * \param[in] parent_symmetrical_s  Parent element's symmetrical setting, if any
  *
  * \retval ordering_symmetric   Ordering is symmetric
  * \retval ordering_asymmetric  Ordering is asymmetric
  */
 static enum ordering_symmetry
 get_ordering_symmetry(xmlNode *xml_obj, enum pe_order_kind parent_kind,
                       const char *parent_symmetrical_s)
 {
     int rc = pcmk_rc_ok;
     bool symmetric = false;
     enum pe_order_kind kind = parent_kind; // Default to parent's kind
 
     // Check ordering XML for explicit kind
     if ((crm_element_value(xml_obj, XML_ORDER_ATTR_KIND) != NULL)
         || (crm_element_value(xml_obj, XML_RULE_ATTR_SCORE) != NULL)) {
         kind = get_ordering_type(xml_obj);
     }
 
     // Check ordering XML (and parent) for explicit symmetrical setting
     rc = pcmk__xe_get_bool_attr(xml_obj, XML_CONS_ATTR_SYMMETRICAL, &symmetric);
 
     if (rc != pcmk_rc_ok && parent_symmetrical_s != NULL) {
         symmetric = crm_is_true(parent_symmetrical_s);
         rc = pcmk_rc_ok;
     }
 
     if (rc == pcmk_rc_ok) {
         if (symmetric) {
             if (kind == pe_order_kind_serialize) {
                 pcmk__config_warn("Ignoring " XML_CONS_ATTR_SYMMETRICAL
                                   " for '%s' because not valid with "
                                   XML_ORDER_ATTR_KIND " of 'Serialize'",
                                   ID(xml_obj));
             } else {
                 return ordering_symmetric;
             }
         }
         return ordering_asymmetric;
     }
 
     // Use default symmetry
     if (kind == pe_order_kind_serialize) {
         return ordering_asymmetric;
     }
     return ordering_symmetric;
 }
 
 /*!
  * \internal
  * \brief Get ordering flags appropriate to ordering kind
  *
  * \param[in] kind      Ordering kind
  * \param[in] first     Action name for 'first' action
  * \param[in] symmetry  This ordering's symmetry role
  *
  * \return Minimal ordering flags appropriate to \p kind
  */
 static enum pe_ordering
 ordering_flags_for_kind(enum pe_order_kind kind, const char *first,
                         enum ordering_symmetry symmetry)
 {
     enum pe_ordering flags = pe_order_none; // so we trace-log all flags set
 
     pe__set_order_flags(flags, pe_order_optional);
 
     switch (kind) {
         case pe_order_kind_optional:
             break;
 
         case pe_order_kind_serialize:
             pe__set_order_flags(flags, pe_order_serialize_only);
             break;
 
         case pe_order_kind_mandatory:
             switch (symmetry) {
                 case ordering_asymmetric:
                     pe__set_order_flags(flags, pe_order_asymmetrical);
                     break;
 
                 case ordering_symmetric:
                     pe__set_order_flags(flags, pe_order_implies_then);
                     if (pcmk__strcase_any_of(first, RSC_START, RSC_PROMOTE,
                                              NULL)) {
                         pe__set_order_flags(flags, pe_order_runnable_left);
                     }
                     break;
 
                 case ordering_symmetric_inverse:
                     pe__set_order_flags(flags, pe_order_implies_first);
                     break;
             }
             break;
     }
     return flags;
 }
 
 /*!
  * \internal
  * \brief Find resource corresponding to ID specified in ordering
  *
  * \param[in] xml            Ordering XML
  * \param[in] resource_attr  XML attribute name for resource ID
  * \param[in] instance_attr  XML attribute name for instance number
  * \param[in] data_set       Cluster working set
  *
  * \return Resource corresponding to \p id, or NULL if none
  */
 static pe_resource_t *
 get_ordering_resource(xmlNode *xml, const char *resource_attr,
                       const char *instance_attr, pe_working_set_t *data_set)
 {
     pe_resource_t *rsc = NULL;
     const char *rsc_id = crm_element_value(xml, resource_attr);
     const char *instance_id = crm_element_value(xml, instance_attr);
 
     if (rsc_id == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without %s",
                          ID(xml), resource_attr);
         return NULL;
     }
 
     rsc = pcmk__find_constraint_resource(data_set->resources, rsc_id);
     if (rsc == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", ID(xml), rsc_id);
         return NULL;
     }
 
     if (instance_id != NULL) {
         if (!pe_rsc_is_clone(rsc)) {
             pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                              "is not a clone but instance '%s' was requested",
                              ID(xml), rsc_id, instance_id);
             return NULL;
         }
         rsc = find_clone_instance(rsc, instance_id, data_set);
         if (rsc == NULL) {
             pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                              "does not have an instance '%s'",
                              "'%s'", ID(xml), rsc_id, instance_id);
             return NULL;
         }
     }
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Determine minimum number of 'first' instances required in ordering
  *
  * \param[in] rsc  'First' resource in ordering
  * \param[in] xml  Ordering XML
  *
  * \return Minimum 'first' instances required (or 0 if not applicable)
  */
 static int
 get_minimum_first_instances(pe_resource_t *rsc, xmlNode *xml)
 {
     const char *clone_min = NULL;
     bool require_all = false;
 
     if (!pe_rsc_is_clone(rsc)) {
         return 0;
     }
 
     clone_min = g_hash_table_lookup(rsc->meta,
                                     XML_RSC_ATTR_INCARNATION_MIN);
     if (clone_min != NULL) {
         int clone_min_int = 0;
 
         pcmk__scan_min_int(clone_min, &clone_min_int, 0);
         return clone_min_int;
     }
 
     /* @COMPAT 1.1.13:
      * require-all=false is deprecated equivalent of clone-min=1
      */
     if (pcmk__xe_get_bool_attr(xml, "require-all", &require_all) != ENODATA) {
         pe_warn_once(pe_wo_require_all,
                      "Support for require-all in ordering constraints "
                      "is deprecated and will be removed in a future release"
                      " (use clone-min clone meta-attribute instead)");
         if (!require_all) {
             return 1;
         }
     }
 
     return 0;
 }
 
 /*!
  * \internal
  * \brief Create orderings for a constraint with clone-min > 0
  *
  * \param[in] id            Ordering ID
  * \param[in] rsc_first     'First' resource in ordering (a clone)
  * \param[in] action_first  'First' action in ordering
  * \param[in] rsc_then      'Then' resource in ordering
  * \param[in] action_then   'Then' action in ordering
  * \param[in] flags         Ordering flags
  * \param[in] clone_min     Minimum required instances of 'first'
  * \param[in] data_set      Cluster working set
  */
 static void
 clone_min_ordering(const char *id,
                    pe_resource_t *rsc_first, const char *action_first,
                    pe_resource_t *rsc_then, const char *action_then,
                    enum pe_ordering flags, int clone_min,
                    pe_working_set_t *data_set)
 {
     // Create a pseudo-action for when the minimum instances are active
     char *task = crm_strdup_printf(CRM_OP_RELAXED_CLONE ":%s", id);
     pe_action_t *clone_min_met = get_pseudo_op(task, data_set);
 
     free(task);
 
     /* Require the pseudo-action to have the required number of actions to be
      * considered runnable before allowing the pseudo-action to be runnable.
      */
     clone_min_met->required_runnable_before = clone_min;
     pe__set_action_flags(clone_min_met, pe_action_requires_any);
 
     // Order the actions for each clone instance before the pseudo-action
     for (GList *rIter = rsc_first->children; rIter != NULL;
          rIter = rIter->next) {
 
         pe_resource_t *child = rIter->data;
 
         pcmk__new_ordering(child, pcmk__op_key(child->id, action_first, 0),
                            NULL, NULL, NULL, clone_min_met,
                            pe_order_one_or_more|pe_order_implies_then_printed,
                            data_set);
     }
 
     // Order "then" action after the pseudo-action (if runnable)
     pcmk__new_ordering(NULL, NULL, clone_min_met, rsc_then,
                        pcmk__op_key(rsc_then->id, action_then, 0),
                        NULL, flags|pe_order_runnable_left, data_set);
 }
 
 /*!
  * \internal
  * \brief Update ordering flags for restart-type=restart
  *
  * \param[in]  rsc    'Then' resource in ordering
  * \param[in]  kind   Ordering kind
  * \param[in]  flag   Ordering flag to set (when applicable)
  * \param[out] flags  Ordering flag set to update
  *
  * \compat The restart-type resource meta-attribute is deprecated. Eventually,
  *         it will be removed, and pe_restart_ignore will be the only behavior,
  *         at which time this can just be removed entirely.
  */
 #define handle_restart_type(rsc, kind, flag, flags) do {        \
         if (((kind) == pe_order_kind_optional)                  \
             && ((rsc)->restart_type == pe_restart_restart)) {   \
             pe__set_order_flags((flags), (flag));               \
         }                                                       \
     } while (0)
 
 /*!
  * \internal
  * \brief Create new ordering for inverse of symmetric constraint
  *
  * \param[in] id            Ordering ID (for logging only)
  * \param[in] kind          Ordering kind
  * \param[in] rsc_first     'First' resource in ordering (a clone)
  * \param[in] action_first  'First' action in ordering
  * \param[in] rsc_then      'Then' resource in ordering
  * \param[in] action_then   'Then' action in ordering
  * \param[in] data_set      Cluster working set
  */
 static void
 inverse_ordering(const char *id, enum pe_order_kind kind,
                  pe_resource_t *rsc_first, const char *action_first,
                  pe_resource_t *rsc_then, const char *action_then,
                  pe_working_set_t *data_set)
 {
     action_then = invert_action(action_then);
     action_first = invert_action(action_first);
     if ((action_then == NULL) || (action_first == NULL)) {
         pcmk__config_warn("Cannot invert constraint '%s' "
                           "(please specify inverse manually)", id);
     } else {
         enum pe_ordering flags = ordering_flags_for_kind(kind, action_first,
                                                          ordering_symmetric_inverse);
 
         handle_restart_type(rsc_then, kind, pe_order_implies_first, flags);
         pcmk__order_resource_actions(rsc_then, action_then, rsc_first,
                                      action_first, flags);
     }
 }
 
 static void
 unpack_simple_rsc_order(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     pe_resource_t *rsc_then = NULL;
     pe_resource_t *rsc_first = NULL;
     int min_required_before = 0;
     enum pe_order_kind kind = pe_order_kind_mandatory;
     enum pe_ordering cons_weight = pe_order_none;
     enum ordering_symmetry symmetry;
 
     const char *action_then = NULL;
     const char *action_first = NULL;
     const char *id = NULL;
 
     CRM_CHECK(xml_obj != NULL, return);
 
     id = crm_element_value(xml_obj, XML_ATTR_ID);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
         return;
     }
 
     rsc_first = get_ordering_resource(xml_obj, XML_ORDER_ATTR_FIRST,
                                       XML_ORDER_ATTR_FIRST_INSTANCE,
                                       data_set);
     if (rsc_first == NULL) {
         return;
     }
 
     rsc_then = get_ordering_resource(xml_obj, XML_ORDER_ATTR_THEN,
                                      XML_ORDER_ATTR_THEN_INSTANCE,
                                      data_set);
     if (rsc_then == NULL) {
         return;
     }
 
     action_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST_ACTION);
     if (action_first == NULL) {
         action_first = RSC_START;
     }
 
     action_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN_ACTION);
     if (action_then == NULL) {
         action_then = action_first;
     }
 
     kind = get_ordering_type(xml_obj);
 
     symmetry = get_ordering_symmetry(xml_obj, kind, NULL);
     cons_weight = ordering_flags_for_kind(kind, action_first, symmetry);
 
     handle_restart_type(rsc_then, kind, pe_order_implies_then, cons_weight);
 
     /* If there is a minimum number of instances that must be runnable before
      * the 'then' action is runnable, we use a pseudo-action for convenience:
      * minimum number of clone instances have runnable actions ->
      * pseudo-action is runnable -> dependency is runnable.
      */
     min_required_before = get_minimum_first_instances(rsc_first, xml_obj);
     if (min_required_before > 0) {
         clone_min_ordering(id, rsc_first, action_first, rsc_then, action_then,
                            cons_weight, min_required_before, data_set);
     } else {
         pcmk__order_resource_actions(rsc_first, action_first, rsc_then,
                                      action_then, cons_weight);
     }
 
     if (symmetry == ordering_symmetric) {
         inverse_ordering(id, kind, rsc_first, action_first,
                          rsc_then, action_then, data_set);
     }
 }
 
 static char *
 task_from_action_or_key(pe_action_t *action, const char *key)
 {
     char *res = NULL;
 
     if (action != NULL) {
         res = strdup(action->task);
     } else if (key != NULL) {
         parse_op_key(key, NULL, &res, NULL);
     }
     return res;
 }
 
 /*!
  * \internal
  * \brief Apply start/stop orderings to migrations
  *
  * Orderings involving start, stop, demote, and promote actions must be honored
  * during a migration as well, so duplicate any such ordering for the
  * corresponding migration actions.
  *
  * \param[in] order     Ordering constraint to check
  * \param[in] data_set  Cluster working set
  */
 static void
 handle_migration_ordering(pe__ordering_t *order, pe_working_set_t *data_set)
 {
     char *first_task = NULL;
     char *then_task = NULL;
     bool then_migratable;
     bool first_migratable;
 
     // Only orderings between two different resources are relevant
     if ((order->lh_rsc == NULL) || (order->rh_rsc == NULL)
         || (order->lh_rsc == order->rh_rsc)) {
         return;
     }
 
     // Constraints between a parent resource and its children are not relevant
     if (is_parent(order->lh_rsc, order->rh_rsc)
         || is_parent(order->rh_rsc, order->lh_rsc)) {
         return;
     }
 
     // Only orderings involving at least one migratable resource are relevant
     first_migratable = pcmk_is_set(order->lh_rsc->flags, pe_rsc_allow_migrate);
     then_migratable = pcmk_is_set(order->rh_rsc->flags, pe_rsc_allow_migrate);
     if (!first_migratable && !then_migratable) {
         return;
     }
 
     // Check which actions are involved
     first_task = task_from_action_or_key(order->lh_action,
                                          order->lh_action_task);
     then_task = task_from_action_or_key(order->rh_action,
                                         order->rh_action_task);
     if ((first_task == NULL) || (then_task == NULL)) {
         goto cleanup_order;
     }
 
     if (pcmk__str_eq(first_task, RSC_START, pcmk__str_casei)
         && pcmk__str_eq(then_task, RSC_START, pcmk__str_casei)) {
 
         int flags = pe_order_optional;
 
         if (first_migratable && then_migratable) {
             /* A start then B start
              * -> A migrate_from then B migrate_to */
             pcmk__new_ordering(order->lh_rsc,
                                pcmk__op_key(order->lh_rsc->id, RSC_MIGRATED, 0),
                                NULL, order->rh_rsc,
                                pcmk__op_key(order->rh_rsc->id, RSC_MIGRATE, 0),
                                NULL, flags, data_set);
         }
 
         if (then_migratable) {
             if (first_migratable) {
                 pe__set_order_flags(flags, pe_order_apply_first_non_migratable);
             }
 
             /* A start then B start
              * -> A start then B migrate_to (if start is not part of a
              *    migration)
              */
             pcmk__new_ordering(order->lh_rsc,
                                pcmk__op_key(order->lh_rsc->id, RSC_START, 0),
                                NULL, order->rh_rsc,
                                pcmk__op_key(order->rh_rsc->id, RSC_MIGRATE, 0),
                                NULL, flags, data_set);
         }
 
     } else if (then_migratable
                && pcmk__str_eq(first_task, RSC_STOP, pcmk__str_casei)
                && pcmk__str_eq(then_task, RSC_STOP, pcmk__str_casei)) {
 
         int flags = pe_order_optional;
 
         if (first_migratable) {
             pe__set_order_flags(flags, pe_order_apply_first_non_migratable);
         }
 
         /* For an ordering "stop A then stop B", if A is moving via restart, and
          * B is migrating, enforce that B's migrate_to occurs after A's stop.
          */
         pcmk__new_ordering(order->lh_rsc,
                            pcmk__op_key(order->lh_rsc->id, RSC_STOP, 0), NULL,
                            order->rh_rsc,
                            pcmk__op_key(order->rh_rsc->id, RSC_MIGRATE, 0),
                            NULL, flags, data_set);
 
         // Also order B's migrate_from after A's stop during partial migrations
         if (order->rh_rsc->partial_migration_target) {
             pcmk__new_ordering(order->lh_rsc,
                                pcmk__op_key(order->lh_rsc->id, RSC_STOP, 0),
                                NULL, order->rh_rsc,
                                pcmk__op_key(order->rh_rsc->id, RSC_MIGRATED, 0),
                                NULL, flags, data_set);
         }
 
     } else if (pcmk__str_eq(first_task, RSC_PROMOTE, pcmk__str_casei)
                && pcmk__str_eq(then_task, RSC_START, pcmk__str_casei)) {
 
         int flags = pe_order_optional;
 
         if (then_migratable) {
             /* A promote then B start
              * -> A promote then B migrate_to */
             pcmk__new_ordering(order->lh_rsc,
                                pcmk__op_key(order->lh_rsc->id, RSC_PROMOTE, 0),
                                NULL, order->rh_rsc,
                                pcmk__op_key(order->rh_rsc->id, RSC_MIGRATE, 0),
                                NULL, flags, data_set);
         }
 
     } else if (pcmk__str_eq(first_task, RSC_DEMOTE, pcmk__str_casei)
                && pcmk__str_eq(then_task, RSC_STOP, pcmk__str_casei)) {
 
         int flags = pe_order_optional;
 
         if (then_migratable) {
             /* A demote then B stop
              * -> A demote then B migrate_to */
             pcmk__new_ordering(order->lh_rsc,
                                pcmk__op_key(order->lh_rsc->id, RSC_DEMOTE, 0),
                                NULL, order->rh_rsc,
                                pcmk__op_key(order->rh_rsc->id, RSC_MIGRATE, 0),
                                NULL, flags, data_set);
 
             // Also order B migrate_from after A demote during partial migrations
             if (order->rh_rsc->partial_migration_target) {
                 pcmk__new_ordering(order->lh_rsc,
                                    pcmk__op_key(order->lh_rsc->id, RSC_DEMOTE, 0),
                                    NULL, order->rh_rsc,
                                    pcmk__op_key(order->rh_rsc->id, RSC_MIGRATED, 0),
                                    NULL, flags, data_set);
             }
         }
     }
 
 cleanup_order:
     free(first_task);
     free(then_task);
 }
 
 /*!
  * \internal
  * \brief Create a new ordering between two actions
  *
  * \param[in] first_rsc          Resource for 'first' action (if NULL and
  *                               \p first_action is a resource action, that
  *                               resource will be used)
  * \param[in] first_action_task  Action key for 'first' action (if NULL and
  *                               \p first_action is not NULL, its UUID will be
  *                               used)
  * \param[in] first_action       'first' action (if NULL, \p first_rsc and
  *                               \p first_action_task must be set)
  *
  * \param[in] then_rsc           Resource for 'then' action (if NULL and
  *                               \p then_action is a resource action, that
  *                               resource will be used)
  * \param[in] then_action_task   Action key for 'then' action (if NULL and
  *                               \p then_action is not NULL, its UUID will be
  *                               used)
  * \param[in] then_action        'then' action (if NULL, \p then_rsc and
  *                               \p then_action_task must be set)
  *
  * \param[in] type               Flag set of enum pe_ordering
  * \param[in] data_set           Cluster working set to add ordering to
  *
  * \note This function takes ownership of first_action_task and
  *       then_action_task, which do not need to be freed by the caller.
  */
 void
 pcmk__new_ordering(pe_resource_t *first_rsc, char *first_action_task,
                    pe_action_t *first_action, pe_resource_t *then_rsc,
                    char *then_action_task, pe_action_t *then_action,
                    enum pe_ordering type, pe_working_set_t *data_set)
 {
     pe__ordering_t *order = NULL;
 
     // One of action or resource must be specified for each side
     CRM_CHECK(((first_action != NULL) || (first_rsc != NULL))
               && ((then_action != NULL) || (then_rsc != NULL)),
               free(first_action_task); free(then_action_task); return);
 
     if ((first_rsc == NULL) && (first_action != NULL)) {
         first_rsc = first_action->rsc;
     }
     if ((then_rsc == NULL) && (then_action != NULL)) {
         then_rsc = then_action->rsc;
     }
 
     order = calloc(1, sizeof(pe__ordering_t));
     CRM_ASSERT(order != NULL);
 
     order->id = data_set->order_id++;
     order->type = type;
     order->lh_rsc = first_rsc;
     order->rh_rsc = then_rsc;
     order->lh_action = first_action;
     order->rh_action = then_action;
     order->lh_action_task = first_action_task;
     order->rh_action_task = then_action_task;
 
     if ((order->lh_action_task == NULL) && (first_action != NULL)) {
         order->lh_action_task = strdup(first_action->uuid);
     }
 
     if ((order->rh_action_task == NULL) && (then_action != NULL)) {
         order->rh_action_task = strdup(then_action->uuid);
     }
 
     if ((order->lh_rsc == NULL) && (first_action != NULL)) {
         order->lh_rsc = first_action->rsc;
     }
 
     if ((order->rh_rsc == NULL) && (then_action != NULL)) {
         order->rh_rsc = then_action->rsc;
     }
 
     pe_rsc_trace(first_rsc, "Created ordering %d for %s then %s",
                  (data_set->order_id - 1),
                  ((first_action_task == NULL)? "?" : first_action_task),
                  ((then_action_task == NULL)? "?" : then_action_task));
 
     data_set->ordering_constraints = g_list_prepend(data_set->ordering_constraints,
                                                     order);
     handle_migration_ordering(order, data_set);
 }
 
 /*!
  * \brief Unpack a set in an ordering constraint
  *
  * \param[in]  set                    Set XML to unpack
  * \param[in]  parent_kind            rsc_order XML "kind" attribute
  * \param[in]  parent_symmetrical_s   rsc_order XML "symmetrical" attribute
  * \param[in]  data_set               Cluster working set
  *
  * \return Standard Pacemaker return code
  */
 static int
 unpack_order_set(xmlNode *set, enum pe_order_kind parent_kind,
                  const char *parent_symmetrical_s, pe_working_set_t *data_set)
 {
     xmlNode *xml_rsc = NULL;
     GList *set_iter = NULL;
     GList *resources = NULL;
 
     pe_resource_t *last = NULL;
     pe_resource_t *resource = NULL;
 
     int local_kind = parent_kind;
     bool sequential = false;
     enum pe_ordering flags = pe_order_optional;
     enum ordering_symmetry symmetry;
 
     char *key = NULL;
     const char *id = ID(set);
     const char *action = crm_element_value(set, "action");
     const char *sequential_s = crm_element_value(set, "sequential");
     const char *kind_s = crm_element_value(set, XML_ORDER_ATTR_KIND);
 
     if (action == NULL) {
         action = RSC_START;
     }
 
     if (kind_s) {
         local_kind = get_ordering_type(set);
     }
     if (sequential_s == NULL) {
         sequential_s = "1";
     }
 
     sequential = crm_is_true(sequential_s);
 
     symmetry = get_ordering_symmetry(set, parent_kind, parent_symmetrical_s);
     flags = ordering_flags_for_kind(local_kind, action, symmetry);
 
     for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
          xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
         EXPAND_CONSTRAINT_IDREF(id, resource, ID(xml_rsc));
         resources = g_list_append(resources, resource);
     }
 
     if (pcmk__list_of_1(resources)) {
         crm_trace("Single set: %s", id);
         goto done;
     }
 
     set_iter = resources;
     while (set_iter != NULL) {
         resource = (pe_resource_t *) set_iter->data;
         set_iter = set_iter->next;
 
         key = pcmk__op_key(resource->id, action, 0);
 
         if (local_kind == pe_order_kind_serialize) {
             /* Serialize before everything that comes after */
 
             for (GList *gIter = set_iter; gIter != NULL; gIter = gIter->next) {
                 pe_resource_t *then_rsc = (pe_resource_t *) gIter->data;
                 char *then_key = pcmk__op_key(then_rsc->id, action, 0);
 
                 pcmk__new_ordering(resource, strdup(key), NULL, then_rsc,
                                    then_key, NULL, flags, data_set);
             }
 
         } else if (sequential) {
             if (last != NULL) {
                 pcmk__order_resource_actions(last, action, resource, action,
                                              flags);
             }
             last = resource;
         }
         free(key);
     }
 
     if (symmetry == ordering_asymmetric) {
         goto done;
     }
 
     last = NULL;
     action = invert_action(action);
 
     flags = ordering_flags_for_kind(local_kind, action,
                                     ordering_symmetric_inverse);
 
     set_iter = resources;
     while (set_iter != NULL) {
         resource = (pe_resource_t *) set_iter->data;
         set_iter = set_iter->next;
 
         if (sequential) {
             if (last != NULL) {
                 pcmk__order_resource_actions(resource, action, last, action,
                                              flags);
             }
             last = resource;
         }
     }
 
   done:
     g_list_free(resources);
     return pcmk_rc_ok;
 }
 
 /*!
  * \brief Order two resource sets relative to each other
  *
  * \param[in] id        Ordering ID (for logging)
  * \param[in] set1      First listed set
  * \param[in] set2      Second listed set
  * \param[in] kind      Ordering kind
  * \param[in] data_set  Cluster working set
  * \param[in] symmetry  Which ordering symmetry applies to this relation
  *
  * \return Standard Pacemaker return code
  */
 static int
 order_rsc_sets(const char *id, xmlNode *set1, xmlNode *set2,
                enum pe_order_kind kind, pe_working_set_t *data_set,
                enum ordering_symmetry symmetry)
 {
 
     xmlNode *xml_rsc = NULL;
     xmlNode *xml_rsc_2 = NULL;
 
     pe_resource_t *rsc_1 = NULL;
     pe_resource_t *rsc_2 = NULL;
 
     const char *action_1 = crm_element_value(set1, "action");
     const char *action_2 = crm_element_value(set2, "action");
 
     enum pe_ordering flags = pe_order_none;
 
     bool require_all = true;
 
     pcmk__xe_get_bool_attr(set1, "require-all", &require_all);
 
     if (action_1 == NULL) {
         action_1 = RSC_START;
     }
 
     if (action_2 == NULL) {
         action_2 = RSC_START;
     }
 
     if (symmetry == ordering_symmetric_inverse) {
         action_1 = invert_action(action_1);
         action_2 = invert_action(action_2);
     }
 
     if (pcmk__str_eq(RSC_STOP, action_1, pcmk__str_casei)
         || pcmk__str_eq(RSC_DEMOTE, action_1, pcmk__str_casei)) {
         /* Assuming: A -> ( B || C) -> D
          * The one-or-more logic only applies during the start/promote phase.
          * During shutdown neither B nor can shutdown until D is down, so simply
          * turn require_all back on.
          */
         require_all = true;
     }
 
     // @TODO is action_2 correct here?
     flags = ordering_flags_for_kind(kind, action_2, symmetry);
 
     /* If we have an unordered set1, whether it is sequential or not is
      * irrelevant in regards to set2.
      */
     if (!require_all) {
         char *task = crm_strdup_printf(CRM_OP_RELAXED_SET ":%s", ID(set1));
         pe_action_t *unordered_action = get_pseudo_op(task, data_set);
 
         free(task);
         pe__set_action_flags(unordered_action, pe_action_requires_any);
 
         for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
 
             /* Add an ordering constraint between every element in set1 and the
              * pseudo action. If any action in set1 is runnable the pseudo
              * action will be runnable.
              */
             pcmk__new_ordering(rsc_1, pcmk__op_key(rsc_1->id, action_1, 0),
                                NULL, NULL, NULL, unordered_action,
                                pe_order_one_or_more|pe_order_implies_then_printed,
                                data_set);
         }
         for (xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
              xml_rsc_2 != NULL; xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
 
             /* Add an ordering constraint between the pseudo-action and every
              * element in set2. If the pseudo-action is runnable, every action
              * in set2 will be runnable.
              */
             pcmk__new_ordering(NULL, NULL, unordered_action,
                                rsc_2, pcmk__op_key(rsc_2->id, action_2, 0),
                                NULL, flags|pe_order_runnable_left, data_set);
         }
 
         return pcmk_rc_ok;
     }
 
     if (pcmk__xe_attr_is_true(set1, "sequential")) {
         if (symmetry == ordering_symmetric_inverse) {
             // Get the first one
             xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
             if (xml_rsc != NULL) {
                 EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
             }
 
         } else {
             // Get the last one
             const char *rid = NULL;
 
             for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
                  xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
                 rid = ID(xml_rsc);
             }
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, rid);
         }
     }
 
     if (pcmk__xe_attr_is_true(set2, "sequential")) {
         if (symmetry == ordering_symmetric_inverse) {
             // Get the last one
             const char *rid = NULL;
 
             for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
                  xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
                 rid = ID(xml_rsc);
             }
             EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
 
         } else {
             // Get the first one
             xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
             if (xml_rsc != NULL) {
                 EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
             }
         }
     }
 
     if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
         pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2, flags);
 
     } else if (rsc_1 != NULL) {
         for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
             pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
                                          flags);
         }
 
     } else if (rsc_2 != NULL) {
         for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
             pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
                                          flags);
         }
 
     } else {
         for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
 
             for (xmlNode *xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
                  xml_rsc_2 != NULL; xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
 
                 EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
                 pcmk__order_resource_actions(rsc_1, action_1, rsc_2,
                                              action_2, flags);
             }
         }
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief If an ordering constraint uses resource tags, expand them
  *
  * \param[in]  xml_obj       Ordering constraint XML
  * \param[out] expanded_xml  Equivalent XML with tags expanded
  * \param[in]  data_set      Cluster working set
  *
  * \return Standard Pacemaker return code (specifically, pcmk_rc_ok on success,
- *         and pcmk_rc_schema_validation on invalid configuration)
+ *         and pcmk_rc_unpack_error on invalid configuration)
  */
 static int
 unpack_order_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                   pe_working_set_t *data_set)
 {
     const char *id_first = NULL;
     const char *id_then = NULL;
     const char *action_first = NULL;
     const char *action_then = NULL;
 
     pe_resource_t *rsc_first = NULL;
     pe_resource_t *rsc_then = NULL;
     pe_tag_t *tag_first = NULL;
     pe_tag_t *tag_then = NULL;
 
     xmlNode *rsc_set_first = NULL;
     xmlNode *rsc_set_then = NULL;
     bool any_sets = false;
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_order");
         return pcmk_rc_ok;
     }
 
     id_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST);
     id_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN);
     if ((id_first == NULL) || (id_then == NULL)) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, id_first, &rsc_first,
                                      &tag_first)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", ID(xml_obj), id_first);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, id_then, &rsc_then, &tag_then)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", ID(xml_obj), id_then);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if ((rsc_first != NULL) && (rsc_then != NULL)) {
         // Neither side references a template or tag
         return pcmk_rc_ok;
     }
 
     action_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST_ACTION);
     action_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN_ACTION);
 
     *expanded_xml = copy_xml(xml_obj);
 
     // Convert template/tag reference in "first" into resource_set under constraint
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_first, XML_ORDER_ATTR_FIRST,
                           true, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (rsc_set_first != NULL) {
         if (action_first != NULL) {
             // Move "first-action" into converted resource_set as "action"
             crm_xml_add(rsc_set_first, "action", action_first);
             xml_remove_prop(*expanded_xml, XML_ORDER_ATTR_FIRST_ACTION);
         }
         any_sets = true;
     }
 
     // Convert template/tag reference in "then" into resource_set under constraint
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_then, XML_ORDER_ATTR_THEN,
                           true, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (rsc_set_then != NULL) {
         if (action_then != NULL) {
             // Move "then-action" into converted resource_set as "action"
             crm_xml_add(rsc_set_then, "action", action_then);
             xml_remove_prop(*expanded_xml, XML_ORDER_ATTR_THEN_ACTION);
         }
         any_sets = true;
     }
 
     if (any_sets) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_order");
     } else {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Unpack ordering constraint XML
  *
  * \param[in]     xml_obj   Ordering constraint XML to unpack
  * \param[in,out] data_set  Cluster working set
  */
 void
 pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     xmlNode *set = NULL;
     xmlNode *last = NULL;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
     const char *invert = crm_element_value(xml_obj, XML_CONS_ATTR_SYMMETRICAL);
     enum pe_order_kind kind = get_ordering_type(xml_obj);
 
     enum ordering_symmetry symmetry = get_ordering_symmetry(xml_obj, kind,
                                                             NULL);
 
     // Expand any resource tags in the constraint XML
     if (unpack_order_tags(xml_obj, &expanded_xml, data_set) != pcmk_rc_ok) {
         return;
     }
     if (expanded_xml != NULL) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     // If the constraint has resource sets, unpack them
     for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET);
          set != NULL; set = crm_next_same_xml(set)) {
 
         set = expand_idref(set, data_set->input);
         if ((set == NULL) // Configuration error, message already logged
             || (unpack_order_set(set, kind, invert, data_set) != pcmk_rc_ok)) {
 
             if (expanded_xml != NULL) {
                 free_xml(expanded_xml);
             }
             return;
         }
 
         if (last != NULL) {
 
             if (order_rsc_sets(id, last, set, kind, data_set,
                                symmetry) != pcmk_rc_ok) {
                 if (expanded_xml != NULL) {
                     free_xml(expanded_xml);
                 }
                 return;
             }
 
             if ((symmetry == ordering_symmetric)
                 && (order_rsc_sets(id, set, last, kind, data_set,
                                    ordering_symmetric_inverse) != pcmk_rc_ok)) {
                 if (expanded_xml != NULL) {
                     free_xml(expanded_xml);
                 }
                 return;
             }
 
         }
         last = set;
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     // If the constraint has no resource sets, unpack it as a simple ordering
     if (last == NULL) {
         return unpack_simple_rsc_order(xml_obj, data_set);
     }
 }
 
 static bool
 ordering_is_invalid(pe_action_t *action, pe_action_wrapper_t *input)
 {
     /* Prevent user-defined ordering constraints between resources
      * running in a guest node and the resource that defines that node.
      */
     if (!pcmk_is_set(input->type, pe_order_preserve)
         && (input->action->rsc != NULL)
         && pcmk__rsc_corresponds_to_guest(action->rsc, input->action->node)) {
 
         crm_warn("Invalid ordering constraint between %s and %s",
                  input->action->rsc->id, action->rsc->id);
         return true;
     }
 
     /* If there's an order like
      * "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
      *
      * then rscA is being migrated from node1 to node2, while rscB is being
      * migrated from node2 to node1. If there would be a graph loop,
      * break the order "load_stopped_node2" -> "rscA_migrate_to node1".
      */
     if ((input->type == pe_order_load) && action->rsc
         && pcmk__str_eq(action->task, RSC_MIGRATE, pcmk__str_casei)
         && pcmk__graph_has_loop(action, action, input)) {
         return true;
     }
 
     return false;
 }
 
 void
 pcmk__disable_invalid_orderings(pe_working_set_t *data_set)
 {
     for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
         pe_action_t *action = (pe_action_t *) iter->data;
         pe_action_wrapper_t *input = NULL;
 
         for (GList *input_iter = action->actions_before;
              input_iter != NULL; input_iter = input_iter->next) {
 
             input = (pe_action_wrapper_t *) input_iter->data;
             if (ordering_is_invalid(action, input)) {
                 input->type = pe_order_none;
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Order stops on a node before the node's shutdown
  *
  * \param[in] node         Node being shut down
  * \param[in] shutdown_op  Shutdown action for node
  */
 void
 pcmk__order_stops_before_shutdown(pe_node_t *node, pe_action_t *shutdown_op)
 {
     for (GList *iter = node->details->data_set->actions;
          iter != NULL; iter = iter->next) {
 
         pe_action_t *action = (pe_action_t *) iter->data;
 
         // Only stops on the node shutting down are relevant
         if ((action->rsc == NULL) || (action->node == NULL)
             || (action->node->details != node->details)
             || !pcmk__str_eq(action->task, RSC_STOP, pcmk__str_casei)) {
             continue;
         }
 
         // Resources and nodes in maintenance mode won't be touched
 
         if (pcmk_is_set(action->rsc->flags, pe_rsc_maintenance)) {
             pe_rsc_trace(action->rsc,
                          "Not ordering %s before %s shutdown because "
                          "resource in maintenance mode",
                          action->uuid, node->details->uname);
             continue;
 
         } else if (node->details->maintenance) {
             pe_rsc_trace(action->rsc,
                          "Not ordering %s before %s shutdown because "
                          "node in maintenance mode",
                          action->uuid, node->details->uname);
             continue;
         }
 
         /* Don't touch a resource that is unmanaged or blocked, to avoid
          * blocking the shutdown (though if another action depends on this one,
          * we may still end up blocking)
          */
         if (!pcmk_any_flags_set(action->rsc->flags,
                                 pe_rsc_managed|pe_rsc_block)) {
             pe_rsc_trace(action->rsc,
                          "Not ordering %s before %s shutdown because "
                          "resource is unmanaged or blocked",
                          action->uuid, node->details->uname);
             continue;
         }
 
         pe_rsc_trace(action->rsc, "Ordering %s before %s shutdown",
                      action->uuid, node->details->uname);
         pe__clear_action_flags(action, pe_action_optional);
         pcmk__new_ordering(action->rsc, NULL, action, NULL,
                            strdup(CRM_OP_SHUTDOWN), shutdown_op,
                            pe_order_optional|pe_order_runnable_left,
                            node->details->data_set);
     }
 }
 
 /*!
  * \brief Find resource actions matching directly or as child
  *
  * \param[in] rsc           Resource to check
  * \param[in] original_key  Action key to search for (possibly referencing
  *                          parent of \rsc)
  *
  * \return Newly allocated list of matching actions
  * \note It is the caller's responsibility to free the result with g_list_free()
  */
 static GList *
 find_actions_by_task(pe_resource_t *rsc, const char *original_key)
 {
     // Search under given task key directly
     GList *list = find_actions(rsc->actions, original_key, NULL);
 
     if (list == NULL) {
         // Search again using this resource's ID
         char *key = NULL;
         char *task = NULL;
         guint interval_ms = 0;
 
         if (parse_op_key(original_key, NULL, &task, &interval_ms)) {
             key = pcmk__op_key(rsc->id, task, interval_ms);
             list = find_actions(rsc->actions, key, NULL);
             free(key);
             free(task);
         } else {
             crm_err("Invalid operation key (bug?): %s", original_key);
         }
     }
     return list;
 }
 
 static void
 rsc_order_then(pe_action_t *first_action, pe_resource_t *rsc,
                pe__ordering_t *order)
 {
     GList *then_actions = NULL;
     pe_action_t *then_action = NULL;
     enum pe_ordering type;
 
     CRM_CHECK(rsc != NULL, return);
     CRM_CHECK(order != NULL, return);
 
     type = order->type;
     then_action = order->rh_action;
     crm_trace("Applying ordering constraint %d (then: %s)", order->id, rsc->id);
 
     if (then_action != NULL) {
         then_actions = g_list_prepend(NULL, then_action);
 
     } else if (rsc != NULL) {
         then_actions = find_actions_by_task(rsc, order->rh_action_task);
     }
 
     if (then_actions == NULL) {
         pe_rsc_trace(rsc,
                      "Ignoring constraint %d: then (%s for %s) not found",
                      order->id, order->rh_action_task, rsc->id);
         return;
     }
 
     if ((first_action != NULL) && (first_action->rsc == rsc)
         && pcmk_is_set(first_action->flags, pe_action_dangle)) {
 
         pe_rsc_trace(rsc, "Detected dangling operation %s -> %s",
                      first_action->uuid, order->rh_action_task);
         pe__clear_order_flags(type, pe_order_implies_then);
     }
 
     for (GList *gIter = then_actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *then_action_iter = (pe_action_t *) gIter->data;
 
         if (first_action != NULL) {
             order_actions(first_action, then_action_iter, type);
 
         } else if (type & pe_order_implies_then) {
             pe__clear_action_flags(then_action_iter, pe_action_runnable);
             crm_warn("Unrunnable %s %#.6x", then_action_iter->uuid, type);
         } else {
             crm_warn("neither %s %#.6x", then_action_iter->uuid, type);
         }
     }
 
     g_list_free(then_actions);
 }
 
 static void
 rsc_order_first(pe_resource_t *first_rsc, pe__ordering_t *order,
                 pe_working_set_t *data_set)
 {
     GList *first_actions = NULL;
     pe_action_t *first_action = order->lh_action;
     pe_resource_t *then_rsc = order->rh_rsc;
 
     CRM_ASSERT(first_rsc != NULL);
     pe_rsc_trace(first_rsc, "Applying ordering constraint %d (first: %s)",
                  order->id, first_rsc->id);
 
     if (first_action != NULL) {
         first_actions = g_list_prepend(NULL, first_action);
 
     } else {
         first_actions = find_actions_by_task(first_rsc, order->lh_action_task);
     }
 
     if ((first_actions == NULL) && (first_rsc == then_rsc)) {
         pe_rsc_trace(first_rsc,
                      "Ignoring constraint %d: first (%s for %s) not found",
                      order->id, order->lh_action_task, first_rsc->id);
 
     } else if (first_actions == NULL) {
         char *key = NULL;
         char *op_type = NULL;
         guint interval_ms = 0;
 
         parse_op_key(order->lh_action_task, NULL, &op_type, &interval_ms);
         key = pcmk__op_key(first_rsc->id, op_type, interval_ms);
 
         if ((first_rsc->fns->state(first_rsc, TRUE) == RSC_ROLE_STOPPED)
             && pcmk__str_eq(op_type, RSC_STOP, pcmk__str_casei)) {
             free(key);
             pe_rsc_trace(first_rsc,
                          "Ignoring constraint %d: first (%s for %s) not found",
                          order->id, order->lh_action_task, first_rsc->id);
 
         } else if ((first_rsc->fns->state(first_rsc, TRUE) == RSC_ROLE_UNPROMOTED)
                    && pcmk__str_eq(op_type, RSC_DEMOTE, pcmk__str_casei)) {
             free(key);
             pe_rsc_trace(first_rsc,
                          "Ignoring constraint %d: first (%s for %s) not found",
                          order->id, order->lh_action_task, first_rsc->id);
 
         } else {
             pe_rsc_trace(first_rsc,
                          "Creating first (%s for %s) for constraint %d ",
                          order->lh_action_task, first_rsc->id, order->id);
             first_action = custom_action(first_rsc, key, op_type, NULL, TRUE,
                                          TRUE, data_set);
             first_actions = g_list_prepend(NULL, first_action);
         }
 
         free(op_type);
     }
 
     if (then_rsc == NULL) {
         if (order->rh_action == NULL) {
             pe_rsc_trace(first_rsc, "Ignoring constraint %d: then not found",
                          order->id);
             return;
         }
         then_rsc = order->rh_action->rsc;
     }
     for (GList *gIter = first_actions; gIter != NULL; gIter = gIter->next) {
         first_action = (pe_action_t *) gIter->data;
 
         if (then_rsc == NULL) {
             order_actions(first_action, order->rh_action, order->type);
 
         } else {
             rsc_order_then(first_action, then_rsc, order);
         }
     }
 
     g_list_free(first_actions);
 }
 
 void
 pcmk__apply_orderings(pe_working_set_t *data_set)
 {
     crm_trace("Applying ordering constraints");
 
     /* Don't ask me why, but apparently they need to be processed in
      * the order they were created in... go figure
      *
      * Also g_list_append() has horrendous performance characteristics
      * So we need to use g_list_prepend() and then reverse the list here
      */
     data_set->ordering_constraints = g_list_reverse(data_set->ordering_constraints);
 
     for (GList *gIter = data_set->ordering_constraints;
          gIter != NULL; gIter = gIter->next) {
 
         pe__ordering_t *order = gIter->data;
         pe_resource_t *rsc = order->lh_rsc;
 
         if (rsc != NULL) {
             rsc_order_first(rsc, order, data_set);
             continue;
         }
 
         rsc = order->rh_rsc;
         if (rsc != NULL) {
             rsc_order_then(order->lh_action, rsc, order);
 
         } else {
             crm_trace("Applying ordering constraint %d (non-resource actions)",
                       order->id);
             order_actions(order->lh_action, order->rh_action, order->type);
         }
     }
 
     g_list_foreach(data_set->actions, (GFunc) pcmk__block_colocated_starts,
                    data_set);
 
     crm_trace("Ordering probes");
     pcmk__order_probes(data_set);
 
     crm_trace("Updating %d actions", g_list_length(data_set->actions));
     g_list_foreach(data_set->actions,
                    (GFunc) pcmk__update_action_for_orderings, data_set);
 
     pcmk__disable_invalid_orderings(data_set);
 }
 
 /*!
  * \internal
  * \brief Order a given action after each action in a given list
  *
  * \param[in] after   "After" action
  * \param[in] list    List of "before" actions
  */
 void
 pcmk__order_after_each(pe_action_t *after, GList *list)
 {
     const char *after_desc = (after->task == NULL)? after->uuid : after->task;
 
     for (GList *iter = list; iter != NULL; iter = iter->next) {
         pe_action_t *before = (pe_action_t *) iter->data;
         const char *before_desc = before->task? before->task : before->uuid;
 
         crm_debug("Ordering %s on %s before %s on %s",
                   before_desc,
                   pcmk__s(before->node->details->uname, "unknown node"),
                   after_desc,
                   pcmk__s(after->node->details->uname, "unknown node"));
         order_actions(before, after, pe_order_optional);
     }
 }
 
 /*!
  * \internal
  * \brief Order promotions and demotions for restarts of a clone or bundle
  *
  * \param[in] rsc  Clone or bundle to order
  */
 void
 pcmk__promotable_restart_ordering(pe_resource_t *rsc)
 {
     // Order start and promote after all instances are stopped
     pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_START,
                                  pe_order_optional);
     pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_PROMOTE,
                                  pe_order_optional);
 
     // Order stop, start, and promote after all instances are demoted
     pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_STOP,
                                  pe_order_optional);
     pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_START,
                                  pe_order_optional);
     pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_PROMOTE,
                                  pe_order_optional);
 
     // Order promote after all instances are started
     pcmk__order_resource_actions(rsc, RSC_STARTED, rsc, RSC_PROMOTE,
                                  pe_order_optional);
 
     // Order demote after all instances are demoted
     pcmk__order_resource_actions(rsc, RSC_DEMOTE, rsc, RSC_DEMOTED,
                                  pe_order_optional);
 }
diff --git a/lib/pacemaker/pcmk_sched_tickets.c b/lib/pacemaker/pcmk_sched_tickets.c
index 48b2b17917..bf8a0e3a2f 100644
--- a/lib/pacemaker/pcmk_sched_tickets.c
+++ b/lib/pacemaker/pcmk_sched_tickets.c
@@ -1,525 +1,525 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 enum loss_ticket_policy {
     loss_ticket_stop,
     loss_ticket_demote,
     loss_ticket_fence,
     loss_ticket_freeze
 };
 
 typedef struct {
     const char *id;
     pe_resource_t *rsc;
     pe_ticket_t *ticket;
     enum loss_ticket_policy loss_policy;
     int role;
 } rsc_ticket_t;
 
 /*!
  * \brief Check whether a ticket constraint matches a resource by role
  *
  * \param[in] rsc_ticket  Ticket constraint
  * \param[in] rsc         Resource to compare with ticket
  *
  * \param[in] true if constraint has no role or resource's role matches
  *            constraint's, otherwise false
  */
 static bool
 ticket_role_matches(pe_resource_t *rsc, rsc_ticket_t *rsc_ticket)
 {
     if ((rsc_ticket->role == RSC_ROLE_UNKNOWN)
         || (rsc_ticket->role == rsc->role)) {
         return true;
     }
     pe_rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
                  role2text(rsc_ticket->role));
     return false;
 }
 
 /*!
  * \brief Create location constraints and fencing as needed for a ticket
  *
  * \param[in] rsc         Resource affected by ticket
  * \param[in] rsc_ticket  Ticket
  * \param[in] data_set    Cluster working set
  */
 static void
 constraints_for_ticket(pe_resource_t *rsc, rsc_ticket_t *rsc_ticket,
                        pe_working_set_t *data_set)
 {
     GList *gIter = NULL;
 
     CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
 
     if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
         return;
     }
 
     if (rsc->children) {
         pe_rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
         for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
             constraints_for_ticket((pe_resource_t *) gIter->data, rsc_ticket,
                                   data_set);
         }
         return;
     }
 
     pe_rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
                  rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
                  role2text(rsc_ticket->role));
 
     if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
 
         switch (rsc_ticket->loss_policy) {
             case loss_ticket_stop:
                 resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
                                   data_set);
                 break;
 
             case loss_ticket_demote:
                 // Promotion score will be set to -INFINITY in promotion_order()
                 if (rsc_ticket->role != RSC_ROLE_PROMOTED) {
                     resource_location(rsc, NULL, -INFINITY,
                                       "__loss_of_ticket__", data_set);
                 }
                 break;
 
             case loss_ticket_fence:
                 if (!ticket_role_matches(rsc, rsc_ticket)) {
                     return;
                 }
 
                 resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
                                   data_set);
 
                 for (gIter = rsc->running_on; gIter != NULL;
                      gIter = gIter->next) {
                     pe_fence_node(data_set, (pe_node_t *) gIter->data,
                                   "deadman ticket was lost", FALSE);
                 }
                 break;
 
             case loss_ticket_freeze:
                 if (!ticket_role_matches(rsc, rsc_ticket)) {
                     return;
                 }
                 if (rsc->running_on != NULL) {
                     pe__clear_resource_flags(rsc, pe_rsc_managed);
                     pe__set_resource_flags(rsc, pe_rsc_block);
                 }
                 break;
         }
 
     } else if (!rsc_ticket->ticket->granted) {
 
         if ((rsc_ticket->role != RSC_ROLE_PROMOTED)
             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
             resource_location(rsc, NULL, -INFINITY, "__no_ticket__",
                               data_set);
         }
 
     } else if (rsc_ticket->ticket->standby) {
 
         if ((rsc_ticket->role != RSC_ROLE_PROMOTED)
             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
             resource_location(rsc, NULL, -INFINITY, "__ticket_standby__",
                               data_set);
         }
     }
 }
 
 static void
 rsc_ticket_new(const char *id, pe_resource_t *rsc, pe_ticket_t *ticket,
                const char *state, const char *loss_policy,
                pe_working_set_t *data_set)
 {
     rsc_ticket_t *new_rsc_ticket = NULL;
 
     if (rsc == NULL) {
         pcmk__config_err("Ignoring ticket '%s' because resource "
                          "does not exist", id);
         return;
     }
 
     new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
     if (new_rsc_ticket == NULL) {
         return;
     }
 
     if (pcmk__str_eq(state, RSC_ROLE_STARTED_S,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         state = RSC_ROLE_UNKNOWN_S;
     }
 
     new_rsc_ticket->id = id;
     new_rsc_ticket->ticket = ticket;
     new_rsc_ticket->rsc = rsc;
     new_rsc_ticket->role = text2role(state);
 
     if (pcmk__str_eq(loss_policy, "fence", pcmk__str_casei)) {
         if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
             new_rsc_ticket->loss_policy = loss_ticket_fence;
         } else {
             pcmk__config_err("Resetting '" XML_TICKET_ATTR_LOSS_POLICY
                              "' for ticket '%s' to 'stop' "
                              "because fencing is not configured", ticket->id);
             loss_policy = "stop";
         }
     }
 
     if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
         crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   role2text(new_rsc_ticket->role));
 
     } else if (pcmk__str_eq(loss_policy, "freeze", pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Freeze %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   role2text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_freeze;
 
     } else if (pcmk__str_eq(loss_policy, "demote", pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Demote %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   role2text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_demote;
 
     } else if (pcmk__str_eq(loss_policy, "stop", pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Stop %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   role2text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_stop;
 
     } else {
         if (new_rsc_ticket->role == RSC_ROLE_PROMOTED) {
             crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
                       new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                       role2text(new_rsc_ticket->role));
             new_rsc_ticket->loss_policy = loss_ticket_demote;
 
         } else {
             crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
                       new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                       role2text(new_rsc_ticket->role));
             new_rsc_ticket->loss_policy = loss_ticket_stop;
         }
     }
 
     pe_rsc_trace(rsc, "%s (%s) ==> %s",
                  rsc->id, role2text(new_rsc_ticket->role), ticket->id);
 
     rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
 
     data_set->ticket_constraints = g_list_append(data_set->ticket_constraints,
                                                  new_rsc_ticket);
 
     if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
         constraints_for_ticket(rsc, new_rsc_ticket, data_set);
     }
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_rsc_ticket_set(xmlNode *set, pe_ticket_t *ticket,
                       const char *loss_policy, pe_working_set_t *data_set)
 {
     const char *set_id = NULL;
     const char *role = NULL;
 
     CRM_CHECK(set != NULL, return EINVAL);
     CRM_CHECK(ticket != NULL, return EINVAL);
 
     set_id = ID(set);
     if (set_id == NULL) {
         pcmk__config_err("Ignoring <" XML_CONS_TAG_RSC_SET "> without "
                          XML_ATTR_ID);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     role = crm_element_value(set, "role");
 
     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 *resource = NULL;
 
         resource = pcmk__find_constraint_resource(data_set->resources,
                                                   ID(xml_rsc));
         if (resource == NULL) {
             pcmk__config_err("%s: No resource found for %s",
                              set_id, ID(xml_rsc));
-            return pcmk_rc_schema_validation;
+            return pcmk_rc_unpack_error;
         }
         pe_rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
                      resource->id, ticket->id);
         rsc_ticket_new(set_id, resource, ticket, role, loss_policy, data_set);
     }
 
     return pcmk_rc_ok;
 }
 
 static void
 unpack_simple_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     const char *id = NULL;
     const char *ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
     const char *loss_policy = crm_element_value(xml_obj,
                                                 XML_TICKET_ATTR_LOSS_POLICY);
 
     pe_ticket_t *ticket = NULL;
 
     const char *rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
     const char *state = crm_element_value(xml_obj,
                                              XML_COLOC_ATTR_SOURCE_ROLE);
 
     // experimental syntax from pacemaker-next (unlikely to be adopted as-is)
     const char *instance = crm_element_value(xml_obj,
                                              XML_COLOC_ATTR_SOURCE_INSTANCE);
 
     pe_resource_t *rsc = NULL;
 
     CRM_CHECK(xml_obj != NULL, return);
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
         return;
     }
 
     if (ticket_str == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without ticket specified",
                          id);
         return;
     } else {
         ticket = g_hash_table_lookup(data_set->tickets, ticket_str);
     }
 
     if (ticket == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
                          "does not exist", id, ticket_str);
         return;
     }
 
     if (rsc_id == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without resource", id);
         return;
     } else {
         rsc = pcmk__find_constraint_resource(data_set->resources, rsc_id);
     }
 
     if (rsc == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, rsc_id);
         return;
 
     } else if ((instance != NULL) && !pe_rsc_is_clone(rsc)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, rsc_id, instance);
         return;
     }
 
     if (instance != NULL) {
         rsc = find_clone_instance(rsc, instance, data_set);
         if (rsc == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               "'%s'", id, rsc_id, instance);
             return;
         }
     }
 
     rsc_ticket_new(id, rsc, ticket, state, loss_policy, data_set);
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                        pe_working_set_t *data_set)
 {
     const char *id = NULL;
     const char *rsc_id = NULL;
     const char *state = NULL;
 
     pe_resource_t *rsc = NULL;
     pe_tag_t *tag = NULL;
 
     xmlNode *rsc_set = NULL;
 
     *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
         return pcmk_rc_ok;
     }
 
     rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
     if (rsc_id == NULL) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(data_set, rsc_id, &rsc, &tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, rsc_id);
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
 
     } else if (rsc != NULL) {
         // No template or tag is referenced
         return pcmk_rc_ok;
     }
 
     state = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     // Convert template/tag reference in "rsc" into resource_set under rsc_ticket
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_COLOC_ATTR_SOURCE,
                           false, data_set)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
-        return pcmk_rc_schema_validation;
+        return pcmk_rc_unpack_error;
     }
 
     if (rsc_set != NULL) {
         if (state != NULL) {
             // Move "rsc-role" into converted resource_set as a "role" attribute
             crm_xml_add(rsc_set, "role", state);
             xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_SOURCE_ROLE);
         }
 
     } else {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 void
 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set)
 {
     xmlNode *set = NULL;
     bool any_sets = false;
 
     const char *id = NULL;
     const char *ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
     const char *loss_policy = crm_element_value(xml_obj, XML_TICKET_ATTR_LOSS_POLICY);
 
     pe_ticket_t *ticket = NULL;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return);
 
     id = ID(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
                          crm_element_name(xml_obj));
         return;
     }
 
     if (data_set->tickets == NULL) {
         data_set->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     if (ticket_str == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without ticket", id);
         return;
     } else {
         ticket = g_hash_table_lookup(data_set->tickets, ticket_str);
     }
 
     if (ticket == NULL) {
         ticket = ticket_new(ticket_str, data_set);
         if (ticket == NULL) {
             return;
         }
     }
 
     if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
                                data_set) != pcmk_rc_ok) {
         return;
     }
     if (expanded_xml != NULL) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         any_sets = true;
         set = expand_idref(set, data_set->input);
         if ((set == NULL) // Configuration error, message already logged
             || (unpack_rsc_ticket_set(set, ticket, loss_policy,
                                       data_set) != pcmk_rc_ok)) {
             if (expanded_xml != NULL) {
                 free_xml(expanded_xml);
             }
             return;
         }
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (!any_sets) {
         unpack_simple_rsc_ticket(xml_obj, data_set);
     }
 }
 
 /*!
  * \internal
  * \brief Ban resource from a node if it doesn't have a promotion ticket
  *
  * If a resource has tickets for the promoted role, and the ticket is either not
  * granted or set to standby, then ban the resource from all nodes.
  *
  * \param[in] rsc  Resource to check
  */
 void
 pcmk__require_promotion_tickets(pe_resource_t *rsc)
 {
     for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
         rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
 
         if ((rsc_ticket->role == RSC_ROLE_PROMOTED)
             && (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
             resource_location(rsc, NULL, -INFINITY,
                               "__stateful_without_ticket__", rsc->cluster);
         }
     }
 }
diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c
index dda5966cfb..1a324877d3 100644
--- a/lib/pengine/bundle.c
+++ b/lib/pengine/bundle.c
@@ -1,2090 +1,2095 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <ctype.h>
 #include <stdint.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/output.h>
 #include <crm/common/xml_internal.h>
 #include <pe_status_private.h>
 
 #define PE__VARIANT_BUNDLE 1
 #include "./variant.h"
 
 static char *
 next_ip(const char *last_ip)
 {
     unsigned int oct1 = 0;
     unsigned int oct2 = 0;
     unsigned int oct3 = 0;
     unsigned int oct4 = 0;
     int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4);
 
     if (rc != 4) {
         /*@ TODO check for IPv6 */
         return NULL;
 
     } else if (oct3 > 253) {
         return NULL;
 
     } else if (oct4 > 253) {
         ++oct3;
         oct4 = 1;
 
     } else {
         ++oct4;
     }
 
     return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4);
 }
 
 static int
 allocate_ip(pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica,
             char *buffer, int max)
 {
     if(data->ip_range_start == NULL) {
         return 0;
 
     } else if(data->ip_last) {
         replica->ipaddr = next_ip(data->ip_last);
 
     } else {
         replica->ipaddr = strdup(data->ip_range_start);
     }
 
     data->ip_last = replica->ipaddr;
     switch (data->agent_type) {
         case PE__CONTAINER_AGENT_DOCKER:
         case PE__CONTAINER_AGENT_PODMAN:
             if (data->add_host) {
                 return snprintf(buffer, max, " --add-host=%s-%d:%s",
                                 data->prefix, replica->offset,
                                 replica->ipaddr);
             }
             // fall through
         case PE__CONTAINER_AGENT_RKT:
             return snprintf(buffer, max, " --hosts-entry=%s=%s-%d",
                             replica->ipaddr, data->prefix, replica->offset);
         default: // PE__CONTAINER_AGENT_UNKNOWN
             break;
     }
     return 0;
 }
 
 static xmlNode *
 create_resource(const char *name, const char *provider, const char *kind)
 {
     xmlNode *rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE);
 
     crm_xml_add(rsc, XML_ATTR_ID, name);
     crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF);
     crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, provider);
     crm_xml_add(rsc, XML_ATTR_TYPE, kind);
 
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Check whether cluster can manage resource inside container
  *
  * \param[in] data  Container variant data
  *
  * \return TRUE if networking configuration is acceptable, FALSE otherwise
  *
  * \note The resource is manageable if an IP range or control port has been
  *       specified. If a control port is used without an IP range, replicas per
  *       host must be 1.
  */
 static bool
 valid_network(pe__bundle_variant_data_t *data)
 {
     if(data->ip_range_start) {
         return TRUE;
     }
     if(data->control_port) {
         if(data->nreplicas_per_host > 1) {
             pe_err("Specifying the 'control-port' for %s requires 'replicas-per-host=1'", data->prefix);
             data->nreplicas_per_host = 1;
             // @TODO to be sure: pe__clear_resource_flags(rsc, pe_rsc_unique);
         }
         return TRUE;
     }
     return FALSE;
 }
 
 static bool
 create_ip_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                    pe__bundle_replica_t *replica, pe_working_set_t *data_set)
 {
     if(data->ip_range_start) {
         char *id = NULL;
         xmlNode *xml_ip = NULL;
         xmlNode *xml_obj = NULL;
 
         id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr);
         crm_xml_sanitize_id(id);
         xml_ip = create_resource(id, "heartbeat", "IPaddr2");
         free(id);
 
         xml_obj = create_xml_node(xml_ip, XML_TAG_ATTR_SETS);
         crm_xml_set_id(xml_obj, "%s-attributes-%d",
                        data->prefix, replica->offset);
 
         crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr);
         if(data->host_network) {
             crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network);
         }
 
         if(data->host_netmask) {
             crm_create_nvpair_xml(xml_obj, NULL,
                                   "cidr_netmask", data->host_netmask);
 
         } else {
             crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32");
         }
 
         xml_obj = create_xml_node(xml_ip, "operations");
         crm_create_op_xml(xml_obj, ID(xml_ip), "monitor", "60s", NULL);
 
         // TODO: Other ops? Timeouts and intervals from underlying resource?
 
-        if (!common_unpack(xml_ip, &replica->ip, parent, data_set)) {
+        if (pe__unpack_resource(xml_ip, &replica->ip, parent,
+                                data_set) != pcmk_rc_ok) {
             return FALSE;
         }
 
         parent->children = g_list_append(parent->children, replica->ip);
     }
     return TRUE;
 }
 
 static bool
 create_docker_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                        pe__bundle_replica_t *replica,
                        pe_working_set_t *data_set)
 {
         int offset = 0, max = 4096;
         char *buffer = calloc(1, max+1);
 
         int doffset = 0, dmax = 1024;
         char *dbuffer = calloc(1, dmax+1);
 
         char *id = NULL;
         xmlNode *xml_container = NULL;
         xmlNode *xml_obj = NULL;
 
         id = crm_strdup_printf("%s-docker-%d", data->prefix, replica->offset);
         crm_xml_sanitize_id(id);
         xml_container = create_resource(id, "heartbeat",
                                         PE__CONTAINER_AGENT_DOCKER_S);
         free(id);
 
         xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS);
         crm_xml_set_id(xml_obj, "%s-attributes-%d",
                        data->prefix, replica->offset);
 
         crm_create_nvpair_xml(xml_obj, NULL, "image", data->image);
         crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE);
         crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE);
         crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE);
 
         offset += snprintf(buffer+offset, max-offset, " --restart=no");
 
         /* Set a container hostname only if we have an IP to map it to.
          * The user can set -h or --uts=host themselves if they want a nicer
          * name for logs, but this makes applications happy who need their
          * hostname to match the IP they bind to.
          */
         if (data->ip_range_start != NULL) {
             offset += snprintf(buffer+offset, max-offset, " -h %s-%d",
                                data->prefix, replica->offset);
         }
 
         offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1");
 
         if (data->container_network) {
 #if 0
             offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s",
                                replica->ipaddr);
 #endif
             offset += snprintf(buffer+offset, max-offset, " --net=%s",
                                data->container_network);
         }
 
         if(data->control_port) {
             offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port);
         } else {
             offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT);
         }
 
         for(GList *pIter = data->mounts; pIter != NULL; pIter = pIter->next) {
             pe__bundle_mount_t *mount = pIter->data;
 
             if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) {
                 char *source = crm_strdup_printf(
                     "%s/%s-%d", mount->source, data->prefix, replica->offset);
 
                 if(doffset > 0) {
                     doffset += snprintf(dbuffer+doffset, dmax-doffset, ",");
                 }
                 doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source);
                 offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target);
                 free(source);
 
             } else {
                 offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target);
             }
             if(mount->options) {
                 offset += snprintf(buffer+offset, max-offset, ":%s", mount->options);
             }
         }
 
         for(GList *pIter = data->ports; pIter != NULL; pIter = pIter->next) {
             pe__bundle_port_t *port = pIter->data;
 
             if (replica->ipaddr) {
                 offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s",
                                    replica->ipaddr, port->source,
                                    port->target);
             } else if(!pcmk__str_eq(data->container_network, "host", pcmk__str_casei)) {
                 // No need to do port mapping if net=host
                 offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target);
             }
         }
 
         if (data->launcher_options) {
             offset += snprintf(buffer+offset, max-offset, " %s",
                                data->launcher_options);
         }
 
         if (data->container_host_options) {
             offset += snprintf(buffer + offset, max - offset, " %s",
                                data->container_host_options);
         }
 
         crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer);
         free(buffer);
 
         crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer);
         free(dbuffer);
 
         if (replica->child) {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", data->container_command);
             } else {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", SBIN_DIR "/pacemaker-remoted");
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We just want to know if the container is alive, we'll
              * monitor the child independently
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
 #if 0
         /* @TODO Consider supporting the use case where we can start and stop
          * resources, but not proxy local commands (such as setting node
          * attributes), by running the local executor in stand-alone mode.
          * However, this would probably be better done via ACLs as with other
          * Pacemaker Remote nodes.
          */
         } else if ((child != NULL) && data->untrusted) {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   CRM_DAEMON_DIR "/pacemaker-execd");
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd",
                                   CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke");
 #endif
         } else {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", data->container_command);
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We don't know what's in the container, so we just want
              * to know if it is alive
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
         }
 
 
         xml_obj = create_xml_node(xml_container, "operations");
         crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL);
 
         // TODO: Other ops? Timeouts and intervals from underlying resource?
-        if (!common_unpack(xml_container, &replica->container, parent, data_set)) {
+        if (pe__unpack_resource(xml_container, &replica->container, parent,
+                                data_set) != pcmk_rc_ok) {
             return FALSE;
         }
         parent->children = g_list_append(parent->children, replica->container);
         return TRUE;
 }
 
 static bool
 create_podman_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                        pe__bundle_replica_t *replica,
                        pe_working_set_t *data_set)
 {
         int offset = 0, max = 4096;
         char *buffer = calloc(1, max+1);
 
         int doffset = 0, dmax = 1024;
         char *dbuffer = calloc(1, dmax+1);
 
         char *id = NULL;
         xmlNode *xml_container = NULL;
         xmlNode *xml_obj = NULL;
 
         id = crm_strdup_printf("%s-podman-%d", data->prefix, replica->offset);
         crm_xml_sanitize_id(id);
         xml_container = create_resource(id, "heartbeat",
                                         PE__CONTAINER_AGENT_PODMAN_S);
         free(id);
 
         xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS);
         crm_xml_set_id(xml_obj, "%s-attributes-%d",
                        data->prefix, replica->offset);
 
         crm_create_nvpair_xml(xml_obj, NULL, "image", data->image);
         crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE);
         crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE);
         crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE);
 
         // FIXME: (bandini 2018-08) podman has no restart policies
         //offset += snprintf(buffer+offset, max-offset, " --restart=no");
 
         /* Set a container hostname only if we have an IP to map it to.
          * The user can set -h or --uts=host themselves if they want a nicer
          * name for logs, but this makes applications happy who need their
          * hostname to match the IP they bind to.
          */
         if (data->ip_range_start != NULL) {
             offset += snprintf(buffer+offset, max-offset, " -h %s-%d",
                                data->prefix, replica->offset);
         }
 
         offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1");
 
         if (data->container_network) {
 #if 0
             // podman has no support for --link-local-ip
             offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s",
                                replica->ipaddr);
 #endif
             offset += snprintf(buffer+offset, max-offset, " --net=%s",
                                data->container_network);
         }
 
         if(data->control_port) {
             offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port);
         } else {
             offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT);
         }
 
         for(GList *pIter = data->mounts; pIter != NULL; pIter = pIter->next) {
             pe__bundle_mount_t *mount = pIter->data;
 
             if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) {
                 char *source = crm_strdup_printf(
                     "%s/%s-%d", mount->source, data->prefix, replica->offset);
 
                 if(doffset > 0) {
                     doffset += snprintf(dbuffer+doffset, dmax-doffset, ",");
                 }
                 doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source);
                 offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target);
                 free(source);
 
             } else {
                 offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target);
             }
             if(mount->options) {
                 offset += snprintf(buffer+offset, max-offset, ":%s", mount->options);
             }
         }
 
         for(GList *pIter = data->ports; pIter != NULL; pIter = pIter->next) {
             pe__bundle_port_t *port = pIter->data;
 
             if (replica->ipaddr) {
                 offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s",
                                    replica->ipaddr, port->source,
                                    port->target);
             } else if(!pcmk__str_eq(data->container_network, "host", pcmk__str_casei)) {
                 // No need to do port mapping if net=host
                 offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target);
             }
         }
 
         if (data->launcher_options) {
             offset += snprintf(buffer+offset, max-offset, " %s",
                                data->launcher_options);
         }
 
         if (data->container_host_options) {
             offset += snprintf(buffer + offset, max - offset, " %s",
                                data->container_host_options);
         }
 
         crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer);
         free(buffer);
 
         crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer);
         free(dbuffer);
 
         if (replica->child) {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", data->container_command);
             } else {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", SBIN_DIR "/pacemaker-remoted");
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We just want to know if the container is alive, we'll
              * monitor the child independently
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
 #if 0
         /* @TODO Consider supporting the use case where we can start and stop
          * resources, but not proxy local commands (such as setting node
          * attributes), by running the local executor in stand-alone mode.
          * However, this would probably be better done via ACLs as with other
          * Pacemaker Remote nodes.
          */
         } else if ((child != NULL) && data->untrusted) {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   CRM_DAEMON_DIR "/pacemaker-execd");
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd",
                                   CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke");
 #endif
         } else {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL,
                                       "run_cmd", data->container_command);
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We don't know what's in the container, so we just want
              * to know if it is alive
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
         }
 
 
         xml_obj = create_xml_node(xml_container, "operations");
         crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL);
 
         // TODO: Other ops? Timeouts and intervals from underlying resource?
-        if (!common_unpack(xml_container, &replica->container, parent,
-                           data_set)) {
+        if (pe__unpack_resource(xml_container, &replica->container, parent,
+                                data_set) != pcmk_rc_ok) {
             return FALSE;
         }
         parent->children = g_list_append(parent->children, replica->container);
         return TRUE;
 }
 
 static bool
 create_rkt_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                     pe__bundle_replica_t *replica, pe_working_set_t *data_set)
 {
         int offset = 0, max = 4096;
         char *buffer = calloc(1, max+1);
 
         int doffset = 0, dmax = 1024;
         char *dbuffer = calloc(1, dmax+1);
 
         char *id = NULL;
         xmlNode *xml_container = NULL;
         xmlNode *xml_obj = NULL;
 
         int volid = 0;
 
         id = crm_strdup_printf("%s-rkt-%d", data->prefix, replica->offset);
         crm_xml_sanitize_id(id);
         xml_container = create_resource(id, "heartbeat",
                                         PE__CONTAINER_AGENT_RKT_S);
         free(id);
 
         xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS);
         crm_xml_set_id(xml_obj, "%s-attributes-%d",
                        data->prefix, replica->offset);
 
         crm_create_nvpair_xml(xml_obj, NULL, "image", data->image);
         crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", "true");
         crm_create_nvpair_xml(xml_obj, NULL, "force_kill", "false");
         crm_create_nvpair_xml(xml_obj, NULL, "reuse", "false");
 
         /* Set a container hostname only if we have an IP to map it to.
          * The user can set -h or --uts=host themselves if they want a nicer
          * name for logs, but this makes applications happy who need their
          * hostname to match the IP they bind to.
          */
         if (data->ip_range_start != NULL) {
             offset += snprintf(buffer+offset, max-offset, " --hostname=%s-%d",
                                data->prefix, replica->offset);
         }
 
         offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_stderr=1");
 
         if (data->container_network) {
 #if 0
             offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s",
                                replica->ipaddr);
 #endif
             offset += snprintf(buffer+offset, max-offset, " --net=%s",
                                data->container_network);
         }
 
         if(data->control_port) {
             offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%s", data->control_port);
         } else {
             offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%d", DEFAULT_REMOTE_PORT);
         }
 
         for(GList *pIter = data->mounts; pIter != NULL; pIter = pIter->next) {
             pe__bundle_mount_t *mount = pIter->data;
 
             if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) {
                 char *source = crm_strdup_printf(
                     "%s/%s-%d", mount->source, data->prefix, replica->offset);
 
                 if(doffset > 0) {
                     doffset += snprintf(dbuffer+doffset, dmax-doffset, ",");
                 }
                 doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source);
                 offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, source);
                 if(mount->options) {
                     offset += snprintf(buffer+offset, max-offset, ",%s", mount->options);
                 }
                 offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target);
                 free(source);
 
             } else {
                 offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, mount->source);
                 if(mount->options) {
                     offset += snprintf(buffer+offset, max-offset, ",%s", mount->options);
                 }
                 offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target);
             }
             volid++;
         }
 
         for(GList *pIter = data->ports; pIter != NULL; pIter = pIter->next) {
             pe__bundle_port_t *port = pIter->data;
 
             if (replica->ipaddr) {
                 offset += snprintf(buffer+offset, max-offset,
                                    " --port=%s:%s:%s", port->target,
                                    replica->ipaddr, port->source);
             } else {
                 offset += snprintf(buffer+offset, max-offset, " --port=%s:%s", port->target, port->source);
             }
         }
 
         if (data->launcher_options) {
             offset += snprintf(buffer+offset, max-offset, " %s",
                                data->launcher_options);
         }
 
         if (data->container_host_options) {
             offset += snprintf(buffer + offset, max - offset, " %s",
                                data->container_host_options);
         }
 
         crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer);
         free(buffer);
 
         crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer);
         free(dbuffer);
 
         if (replica->child) {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                       data->container_command);
             } else {
                 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                       SBIN_DIR "/pacemaker-remoted");
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We just want to know if the container is alive, we'll
              * monitor the child independently
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
 #if 0
         /* @TODO Consider supporting the use case where we can start and stop
          * resources, but not proxy local commands (such as setting node
          * attributes), by running the local executor in stand-alone mode.
          * However, this would probably be better done via ACLs as with other
          * Pacemaker Remote nodes.
          */
         } else if ((child != NULL) && data->untrusted) {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   CRM_DAEMON_DIR "/pacemaker-execd");
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd",
                                   CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke");
 #endif
         } else {
             if (data->container_command) {
                 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                       data->container_command);
             }
 
             /* TODO: Allow users to specify their own?
              *
              * We don't know what's in the container, so we just want
              * to know if it is alive
              */
             crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
         }
 
 
         xml_obj = create_xml_node(xml_container, "operations");
         crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL);
 
         // TODO: Other ops? Timeouts and intervals from underlying resource?
 
-        if (!common_unpack(xml_container, &replica->container, parent, data_set)) {
+        if (pe__unpack_resource(xml_container, &replica->container, parent,
+                                data_set) != pcmk_rc_ok) {
             return FALSE;
         }
         parent->children = g_list_append(parent->children, replica->container);
         return TRUE;
 }
 
 /*!
  * \brief Ban a node from a resource's (and its children's) allowed nodes list
  *
  * \param[in,out] rsc    Resource to modify
  * \param[in]     uname  Name of node to ban
  */
 static void
 disallow_node(pe_resource_t *rsc, const char *uname)
 {
     gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname);
 
     if (match) {
         ((pe_node_t *) match)->weight = -INFINITY;
         ((pe_node_t *) match)->rsc_discover_mode = pe_discover_never;
     }
     if (rsc->children) {
         g_list_foreach(rsc->children, (GFunc) disallow_node, (gpointer) uname);
     }
 }
 
 static bool
 create_remote_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                        pe__bundle_replica_t *replica,
                        pe_working_set_t *data_set)
 {
     if (replica->child && valid_network(data)) {
         GHashTableIter gIter;
         pe_node_t *node = NULL;
         xmlNode *xml_remote = NULL;
         char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset);
         char *port_s = NULL;
         const char *uname = NULL;
         const char *connect_name = NULL;
 
         if (pe_find_resource(data_set->resources, id) != NULL) {
             free(id);
             // The biggest hammer we have
             id = crm_strdup_printf("pcmk-internal-%s-remote-%d",
                                    replica->child->id, replica->offset);
             //@TODO return false instead of asserting?
             CRM_ASSERT(pe_find_resource(data_set->resources, id) == NULL);
         }
 
         /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the
          * connection does not have its own IP is a magic string that we use to
          * support nested remotes (i.e. a bundle running on a remote node).
          */
         connect_name = (replica->ipaddr? replica->ipaddr : "#uname");
 
         if (data->control_port == NULL) {
             port_s = pcmk__itoa(DEFAULT_REMOTE_PORT);
         }
 
         /* This sets replica->container as replica->remote's container, which is
          * similar to what happens with guest nodes. This is how the scheduler
          * knows that the bundle node is fenced by recovering the container, and
          * that remote should be ordered relative to the container.
          */
         xml_remote = pe_create_remote_xml(NULL, id, replica->container->id,
                                           NULL, NULL, NULL,
                                           connect_name, (data->control_port?
                                           data->control_port : port_s));
         free(port_s);
 
         /* Abandon our created ID, and pull the copy from the XML, because we
          * need something that will get freed during data set cleanup to use as
          * the node ID and uname.
          */
         free(id);
         id = NULL;
         uname = ID(xml_remote);
 
         /* Ensure a node has been created for the guest (it may have already
          * been, if it has a permanent node attribute), and ensure its weight is
          * -INFINITY so no other resources can run on it.
          */
         node = pe_find_node(data_set->nodes, uname);
         if (node == NULL) {
             node = pe_create_node(uname, uname, "remote", "-INFINITY",
                                   data_set);
         } else {
             node->weight = -INFINITY;
         }
         node->rsc_discover_mode = pe_discover_never;
 
         /* unpack_remote_nodes() ensures that each remote node and guest node
          * has a pe_node_t entry. Ideally, it would do the same for bundle nodes.
          * Unfortunately, a bundle has to be mostly unpacked before it's obvious
          * what nodes will be needed, so we do it just above.
          *
          * Worse, that means that the node may have been utilized while
          * unpacking other resources, without our weight correction. The most
-         * likely place for this to happen is when common_unpack() calls
+         * likely place for this to happen is when pe__unpack_resource() calls
          * resource_location() to set a default score in symmetric clusters.
          * This adds a node *copy* to each resource's allowed nodes, and these
          * copies will have the wrong weight.
          *
          * As a hacky workaround, fix those copies here.
          *
          * @TODO Possible alternative: ensure bundles are unpacked before other
          * resources, so the weight is correct before any copies are made.
          */
         g_list_foreach(data_set->resources, (GFunc) disallow_node, (gpointer) uname);
 
         replica->node = pe__copy_node(node);
         replica->node->weight = 500;
         replica->node->rsc_discover_mode = pe_discover_exclusive;
 
         /* Ensure the node shows up as allowed and with the correct discovery set */
         if (replica->child->allowed_nodes != NULL) {
             g_hash_table_destroy(replica->child->allowed_nodes);
         }
         replica->child->allowed_nodes = pcmk__strkey_table(NULL, free);
         g_hash_table_insert(replica->child->allowed_nodes,
                             (gpointer) replica->node->details->id,
                             pe__copy_node(replica->node));
 
         {
             pe_node_t *copy = pe__copy_node(replica->node);
             copy->weight = -INFINITY;
             g_hash_table_insert(replica->child->parent->allowed_nodes,
                                 (gpointer) replica->node->details->id, copy);
         }
-        if (!common_unpack(xml_remote, &replica->remote, parent, data_set)) {
+        if (pe__unpack_resource(xml_remote, &replica->remote, parent,
+                                data_set) != pcmk_rc_ok) {
             return FALSE;
         }
 
         g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes);
         while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) {
             if (pe__is_guest_or_remote_node(node)) {
                 /* Remote resources can only run on 'normal' cluster node */
                 node->weight = -INFINITY;
             }
         }
 
         replica->node->details->remote_rsc = replica->remote;
 
         // Ensure pe__is_guest_node() functions correctly immediately
         replica->remote->container = replica->container;
 
         /* A bundle's #kind is closer to "container" (guest node) than the
          * "remote" set by pe_create_node().
          */
         g_hash_table_insert(replica->node->details->attrs,
                             strdup(CRM_ATTR_KIND), strdup("container"));
 
         /* One effect of this is that setup_container() will add
          * replica->remote to replica->container's fillers, which will make
          * pe__resource_contains_guest_node() true for replica->container.
          *
          * replica->child does NOT get added to replica->container's fillers.
          * The only noticeable effect if it did would be for its fail count to
          * be taken into account when checking replica->container's migration
          * threshold.
          */
         parent->children = g_list_append(parent->children, replica->remote);
     }
     return TRUE;
 }
 
 static bool
 create_container(pe_resource_t *parent, pe__bundle_variant_data_t *data,
                  pe__bundle_replica_t *replica, pe_working_set_t *data_set)
 {
 
     switch (data->agent_type) {
         case PE__CONTAINER_AGENT_DOCKER:
             if (!create_docker_resource(parent, data, replica, data_set)) {
                 return FALSE;
             }
             break;
 
         case PE__CONTAINER_AGENT_PODMAN:
             if (!create_podman_resource(parent, data, replica, data_set)) {
                 return FALSE;
             }
             break;
 
         case PE__CONTAINER_AGENT_RKT:
             if (!create_rkt_resource(parent, data, replica, data_set)) {
                 return FALSE;
             }
             break;
         default: // PE__CONTAINER_AGENT_UNKNOWN
             return FALSE;
     }
 
     if (create_ip_resource(parent, data, replica, data_set) == FALSE) {
         return FALSE;
     }
     if(create_remote_resource(parent, data, replica, data_set) == FALSE) {
         return FALSE;
     }
     if (replica->child && replica->ipaddr) {
         add_hash_param(replica->child->meta, "external-ip", replica->ipaddr);
     }
 
     if (replica->remote) {
         /*
          * Allow the remote connection resource to be allocated to a
          * different node than the one on which the container is active.
          *
          * This makes it possible to have Pacemaker Remote nodes running
          * containers with pacemaker-remoted inside in order to start
          * services inside those containers.
          */
         pe__set_resource_flags(replica->remote, pe_rsc_allow_remote_remotes);
     }
 
     return TRUE;
 }
 
 static void
 mount_add(pe__bundle_variant_data_t *bundle_data, const char *source,
           const char *target, const char *options, uint32_t flags)
 {
     pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t));
 
     CRM_ASSERT(mount != NULL);
     mount->source = strdup(source);
     mount->target = strdup(target);
     pcmk__str_update(&mount->options, options);
     mount->flags = flags;
     bundle_data->mounts = g_list_append(bundle_data->mounts, mount);
 }
 
 static void
 mount_free(pe__bundle_mount_t *mount)
 {
     free(mount->source);
     free(mount->target);
     free(mount->options);
     free(mount);
 }
 
 static void
 port_free(pe__bundle_port_t *port)
 {
     free(port->source);
     free(port->target);
     free(port);
 }
 
 static pe__bundle_replica_t *
 replica_for_remote(pe_resource_t *remote)
 {
     pe_resource_t *top = remote;
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     if (top == NULL) {
         return NULL;
     }
 
     while (top->parent != NULL) {
         top = top->parent;
     }
 
     get_bundle_variant_data(bundle_data, top);
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
 
         if (replica->remote == remote) {
             return replica;
         }
     }
     CRM_LOG_ASSERT(FALSE);
     return NULL;
 }
 
 bool
 pe__bundle_needs_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set)
 {
     const char *value;
     GHashTable *params = NULL;
 
     if (rsc == NULL) {
         return false;
     }
 
     // Use NULL node since pcmk__bundle_expand() uses that to set value
     params = pe_rsc_params(rsc, NULL, data_set);
     value = g_hash_table_lookup(params, XML_RSC_ATTR_REMOTE_RA_ADDR);
 
     return pcmk__str_eq(value, "#uname", pcmk__str_casei)
            && xml_contains_remote_node(rsc->xml);
 }
 
 const char *
 pe__add_bundle_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set,
                            xmlNode *xml, const char *field)
 {
     // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside
 
     pe_node_t *node = NULL;
     pe__bundle_replica_t *replica = NULL;
 
     if (!pe__bundle_needs_remote_name(rsc, data_set)) {
         return NULL;
     }
 
     replica = replica_for_remote(rsc);
     if (replica == NULL) {
         return NULL;
     }
 
     node = replica->container->allocated_to;
     if (node == NULL) {
         /* If it won't be running anywhere after the
          * transition, go with where it's running now.
          */
         node = pe__current_node(replica->container);
     }
 
     if(node == NULL) {
         crm_trace("Cannot determine address for bundle connection %s", rsc->id);
         return NULL;
     }
 
     crm_trace("Setting address for bundle connection %s to bundle host %s",
               rsc->id, node->details->uname);
     if(xml != NULL && field != NULL) {
         crm_xml_add(xml, field, node->details->uname);
     }
 
     return node->details->uname;
 }
 
 #define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do {     \
         flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,           \
                                    "Bundle mount", ID(mount_xml), flags,    \
                                    (flags_to_set), #flags_to_set);          \
     } while (0)
 
 gboolean
 pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set)
 {
     const char *value = NULL;
     xmlNode *xml_obj = NULL;
     xmlNode *xml_resource = NULL;
     pe__bundle_variant_data_t *bundle_data = NULL;
     bool need_log_mount = TRUE;
 
     CRM_ASSERT(rsc != NULL);
     pe_rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t));
     rsc->variant_opaque = bundle_data;
     bundle_data->prefix = strdup(rsc->id);
 
     xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_DOCKER_S);
     if (xml_obj != NULL) {
         bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER;
     } else {
         xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_RKT_S);
         if (xml_obj != NULL) {
             bundle_data->agent_type = PE__CONTAINER_AGENT_RKT;
         } else {
             xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_PODMAN_S);
             if (xml_obj != NULL) {
                 bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN;
             } else {
                 return FALSE;
             }
         }
     }
 
     // Use 0 for default, minimum, and invalid promoted-max
     value = crm_element_value(xml_obj, XML_RSC_ATTR_PROMOTED_MAX);
     if (value == NULL) {
         // @COMPAT deprecated since 2.0.0
         value = crm_element_value(xml_obj, "masters");
     }
     pcmk__scan_min_int(value, &bundle_data->promoted_max, 0);
 
     // Default replicas to promoted-max if it was specified and 1 otherwise
     value = crm_element_value(xml_obj, "replicas");
     if ((value == NULL) && (bundle_data->promoted_max > 0)) {
         bundle_data->nreplicas = bundle_data->promoted_max;
     } else {
         pcmk__scan_min_int(value, &bundle_data->nreplicas, 1);
     }
 
     /*
      * Communication between containers on the same host via the
      * floating IPs only works if the container is started with:
      *   --userland-proxy=false --ip-masq=false
      */
     value = crm_element_value(xml_obj, "replicas-per-host");
     pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1);
     if (bundle_data->nreplicas_per_host == 1) {
         pe__clear_resource_flags(rsc, pe_rsc_unique);
     }
 
     bundle_data->container_command = crm_element_value_copy(xml_obj, "run-command");
     bundle_data->launcher_options = crm_element_value_copy(xml_obj, "options");
     bundle_data->image = crm_element_value_copy(xml_obj, "image");
     bundle_data->container_network = crm_element_value_copy(xml_obj, "network");
 
     xml_obj = first_named_child(rsc->xml, "network");
     if(xml_obj) {
 
         bundle_data->ip_range_start = crm_element_value_copy(xml_obj, "ip-range-start");
         bundle_data->host_netmask = crm_element_value_copy(xml_obj, "host-netmask");
         bundle_data->host_network = crm_element_value_copy(xml_obj, "host-interface");
         bundle_data->control_port = crm_element_value_copy(xml_obj, "control-port");
         value = crm_element_value(xml_obj, "add-host");
         if (crm_str_to_boolean(value, &bundle_data->add_host) != 1) {
             bundle_data->add_host = TRUE;
         }
 
         for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL;
              xml_child = pcmk__xe_next(xml_child)) {
 
             pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t));
             port->source = crm_element_value_copy(xml_child, "port");
 
             if(port->source == NULL) {
                 port->source = crm_element_value_copy(xml_child, "range");
             } else {
                 port->target = crm_element_value_copy(xml_child, "internal-port");
             }
 
             if(port->source != NULL && strlen(port->source) > 0) {
                 if(port->target == NULL) {
                     port->target = strdup(port->source);
                 }
                 bundle_data->ports = g_list_append(bundle_data->ports, port);
 
             } else {
                 pe_err("Invalid port directive %s", ID(xml_child));
                 port_free(port);
             }
         }
     }
 
     xml_obj = first_named_child(rsc->xml, "storage");
     for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL;
          xml_child = pcmk__xe_next(xml_child)) {
 
         const char *source = crm_element_value(xml_child, "source-dir");
         const char *target = crm_element_value(xml_child, "target-dir");
         const char *options = crm_element_value(xml_child, "options");
         int flags = pe__bundle_mount_none;
 
         if (source == NULL) {
             source = crm_element_value(xml_child, "source-dir-root");
             pe__set_bundle_mount_flags(xml_child, flags,
                                        pe__bundle_mount_subdir);
         }
 
         if (source && target) {
             mount_add(bundle_data, source, target, options, flags);
             if (strcmp(target, "/var/log") == 0) {
                 need_log_mount = FALSE;
             }
         } else {
             pe_err("Invalid mount directive %s", ID(xml_child));
         }
     }
 
     xml_obj = first_named_child(rsc->xml, "primitive");
     if (xml_obj && valid_network(bundle_data)) {
         char *value = NULL;
         xmlNode *xml_set = NULL;
 
         xml_resource = create_xml_node(NULL, XML_CIB_TAG_INCARNATION);
 
         /* @COMPAT We no longer use the <master> tag, but we need to keep it as
          * part of the resource name, so that bundles don't restart in a rolling
          * upgrade. (It also avoids needing to change regression tests.)
          */
         crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix,
                       (bundle_data->promoted_max? "master"
                       : (const char *)xml_resource->name));
 
         xml_set = create_xml_node(xml_resource, XML_TAG_META_SETS);
         crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name);
 
         crm_create_nvpair_xml(xml_set, NULL,
                               XML_RSC_ATTR_ORDERED, XML_BOOLEAN_TRUE);
 
         value = pcmk__itoa(bundle_data->nreplicas);
         crm_create_nvpair_xml(xml_set, NULL,
                               XML_RSC_ATTR_INCARNATION_MAX, value);
         free(value);
 
         value = pcmk__itoa(bundle_data->nreplicas_per_host);
         crm_create_nvpair_xml(xml_set, NULL,
                               XML_RSC_ATTR_INCARNATION_NODEMAX, value);
         free(value);
 
         crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_UNIQUE,
                               pcmk__btoa(bundle_data->nreplicas_per_host > 1));
 
         if (bundle_data->promoted_max) {
             crm_create_nvpair_xml(xml_set, NULL,
                                   XML_RSC_ATTR_PROMOTABLE, XML_BOOLEAN_TRUE);
 
             value = pcmk__itoa(bundle_data->promoted_max);
             crm_create_nvpair_xml(xml_set, NULL,
                                   XML_RSC_ATTR_PROMOTED_MAX, value);
             free(value);
         }
 
         //crm_xml_add(xml_obj, XML_ATTR_ID, bundle_data->prefix);
         add_node_copy(xml_resource, xml_obj);
 
     } else if(xml_obj) {
         pe_err("Cannot control %s inside %s without either ip-range-start or control-port",
                rsc->id, ID(xml_obj));
         return FALSE;
     }
 
     if(xml_resource) {
         int lpc = 0;
         GList *childIter = NULL;
         pe_resource_t *new_rsc = NULL;
         pe__bundle_port_t *port = NULL;
 
         int offset = 0, max = 1024;
         char *buffer = NULL;
 
-        if (common_unpack(xml_resource, &new_rsc, rsc, data_set) == FALSE) {
+        if (pe__unpack_resource(xml_resource, &new_rsc, rsc,
+                                data_set) != pcmk_rc_ok) {
             pe_err("Failed unpacking resource %s", ID(rsc->xml));
             if (new_rsc != NULL && new_rsc->fns != NULL) {
                 new_rsc->fns->free(new_rsc);
             }
             return FALSE;
         }
 
         bundle_data->child = new_rsc;
 
         /* Currently, we always map the default authentication key location
          * into the same location inside the container.
          *
          * Ideally, we would respect the host's PCMK_authkey_location, but:
          * - it may be different on different nodes;
          * - the actual connection will do extra checking to make sure the key
          *   file exists and is readable, that we can't do here on the DC
          * - tools such as crm_resource and crm_simulate may not have the same
          *   environment variables as the cluster, causing operation digests to
          *   differ
          *
          * Always using the default location inside the container is fine,
          * because we control the pacemaker_remote environment, and it avoids
          * having to pass another environment variable to the container.
          *
          * @TODO A better solution may be to have only pacemaker_remote use the
          * environment variable, and have the cluster nodes use a new
          * cluster option for key location. This would introduce the limitation
          * of the location being the same on all cluster nodes, but that's
          * reasonable.
          */
         mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION,
                   DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none);
 
         if (need_log_mount) {
             mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL,
                       pe__bundle_mount_subdir);
         }
 
         port = calloc(1, sizeof(pe__bundle_port_t));
         if(bundle_data->control_port) {
             port->source = strdup(bundle_data->control_port);
         } else {
             /* If we wanted to respect PCMK_remote_port, we could use
              * crm_default_remote_port() here and elsewhere in this file instead
              * of DEFAULT_REMOTE_PORT.
              *
              * However, it gains nothing, since we control both the container
              * environment and the connection resource parameters, and the user
              * can use a different port if desired by setting control-port.
              */
             port->source = pcmk__itoa(DEFAULT_REMOTE_PORT);
         }
         port->target = strdup(port->source);
         bundle_data->ports = g_list_append(bundle_data->ports, port);
 
         buffer = calloc(1, max+1);
         for (childIter = bundle_data->child->children; childIter != NULL;
              childIter = childIter->next) {
 
             pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t));
 
             replica->child = childIter->data;
             replica->child->exclusive_discover = TRUE;
             replica->offset = lpc++;
 
             // Ensure the child's notify gets set based on the underlying primitive's value
             if (pcmk_is_set(replica->child->flags, pe_rsc_notify)) {
                 pe__set_resource_flags(bundle_data->child, pe_rsc_notify);
             }
 
             offset += allocate_ip(bundle_data, replica, buffer+offset,
                                   max-offset);
             bundle_data->replicas = g_list_append(bundle_data->replicas,
                                                   replica);
             bundle_data->attribute_target = g_hash_table_lookup(replica->child->meta,
                                                                 XML_RSC_ATTR_TARGET);
         }
         bundle_data->container_host_options = buffer;
         if (bundle_data->attribute_target) {
             g_hash_table_replace(rsc->meta, strdup(XML_RSC_ATTR_TARGET),
                                  strdup(bundle_data->attribute_target));
             g_hash_table_replace(bundle_data->child->meta,
                                  strdup(XML_RSC_ATTR_TARGET),
                                  strdup(bundle_data->attribute_target));
         }
 
     } else {
         // Just a naked container, no pacemaker-remote
         int offset = 0, max = 1024;
         char *buffer = calloc(1, max+1);
 
         for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) {
             pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t));
 
             replica->offset = lpc;
             offset += allocate_ip(bundle_data, replica, buffer+offset,
                                   max-offset);
             bundle_data->replicas = g_list_append(bundle_data->replicas,
                                                   replica);
         }
         bundle_data->container_host_options = buffer;
     }
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
 
         if (!create_container(rsc, bundle_data, replica, data_set)) {
             pe_err("Failed unpacking resource %s", rsc->id);
             rsc->fns->free(rsc);
             return FALSE;
         }
 
         /* Utilization needs special handling for bundles. It makes no sense for
          * the inner primitive to have utilization, because it is tied
          * one-to-one to the guest node created by the container resource -- and
          * there's no way to set capacities for that guest node anyway.
          *
          * What the user really wants is to configure utilization for the
          * container. However, the schema only allows utilization for
          * primitives, and the container resource is implicit anyway, so the
          * user can *only* configure utilization for the inner primitive. If
          * they do, move the primitive's utilization values to the container.
          *
          * @TODO This means that bundles without an inner primitive can't have
          * utilization. An alternative might be to allow utilization values in
          * the top-level bundle XML in the schema, and copy those to each
          * container.
          */
         if (replica->child != NULL) {
             GHashTable *empty = replica->container->utilization;
 
             replica->container->utilization = replica->child->utilization;
             replica->child->utilization = empty;
         }
     }
 
     if (bundle_data->child) {
         rsc->children = g_list_append(rsc->children, bundle_data->child);
     }
     return TRUE;
 }
 
 static int
 replica_resource_active(pe_resource_t *rsc, gboolean all)
 {
     if (rsc) {
         gboolean child_active = rsc->fns->active(rsc, all);
 
         if (child_active && !all) {
             return TRUE;
         } else if (!child_active && all) {
             return FALSE;
         }
     }
     return -1;
 }
 
 gboolean
 pe__bundle_active(pe_resource_t *rsc, gboolean all)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     GList *iter = NULL;
 
     get_bundle_variant_data(bundle_data, rsc);
     for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
         pe__bundle_replica_t *replica = iter->data;
         int rsc_active;
 
         rsc_active = replica_resource_active(replica->ip, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->child, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->container, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->remote, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
     }
 
     /* If "all" is TRUE, we've already checked that no resources were inactive,
      * so return TRUE; if "all" is FALSE, we didn't find any active resources,
      * so return FALSE.
      */
     return all;
 }
 
 /*!
  * \internal
  * \brief Find the bundle replica corresponding to a given node
  *
  * \param[in] bundle  Top-level bundle resource
  * \param[in] node    Node to search for
  *
  * \return Bundle replica if found, NULL otherwise
  */
 pe_resource_t *
 pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     CRM_ASSERT(bundle && node);
 
     get_bundle_variant_data(bundle_data, bundle);
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica && replica->node);
         if (replica->node->details == node->details) {
             return replica->child;
         }
     }
     return NULL;
 }
 
 static void
 print_rsc_in_list(pe_resource_t *rsc, const char *pre_text, long options,
                   void *print_data)
 {
     if (rsc != NULL) {
         if (options & pe_print_html) {
             status_print("<li>");
         }
         rsc->fns->print(rsc, pre_text, options, print_data);
         if (options & pe_print_html) {
             status_print("</li>\n");
         }
     }
 }
 
 static const char*
 container_agent_str(enum pe__container_agent t)
 {
     switch (t) {
         case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S;
         case PE__CONTAINER_AGENT_RKT:    return PE__CONTAINER_AGENT_RKT_S;
         case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S;
         default: // PE__CONTAINER_AGENT_UNKNOWN
             break;
     }
     return PE__CONTAINER_AGENT_UNKNOWN_S;
 }
 
 static void
 bundle_print_xml(pe_resource_t *rsc, const char *pre_text, long options,
                  void *print_data)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     char *child_text = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     if (pre_text == NULL) {
         pre_text = "";
     }
     child_text = crm_strdup_printf("%s        ", pre_text);
 
     get_bundle_variant_data(bundle_data, rsc);
 
     status_print("%s<bundle ", pre_text);
     status_print("id=\"%s\" ", rsc->id);
     status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type));
     status_print("image=\"%s\" ", bundle_data->image);
     status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique));
     status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed));
     status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed));
     status_print(">\n");
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica);
         status_print("%s    <replica id=\"%d\">\n", pre_text, replica->offset);
         print_rsc_in_list(replica->ip, child_text, options, print_data);
         print_rsc_in_list(replica->child, child_text, options, print_data);
         print_rsc_in_list(replica->container, child_text, options, print_data);
         print_rsc_in_list(replica->remote, child_text, options, print_data);
         status_print("%s    </replica>\n", pre_text);
     }
     status_print("%s</bundle>\n", pre_text);
     free(child_text);
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__bundle_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean printed_header = FALSE;
     gboolean print_everything = TRUE;
 
     CRM_ASSERT(rsc != NULL);
 
     get_bundle_variant_data(bundle_data, rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
         char *id = NULL;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) {
             continue;
         }
 
         if (!printed_header) {
             printed_header = TRUE;
 
             rc = pe__name_and_nvpairs_xml(out, true, "bundle", 6,
                      "id", rsc->id,
                      "type", container_agent_str(bundle_data->agent_type),
                      "image", bundle_data->image,
                      "unique", pe__rsc_bool_str(rsc, pe_rsc_unique),
                      "managed", pe__rsc_bool_str(rsc, pe_rsc_managed),
                      "failed", pe__rsc_bool_str(rsc, pe_rsc_failed));
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
 
         id = pcmk__itoa(replica->offset);
         rc = pe__name_and_nvpairs_xml(out, true, "replica", 1, "id", id);
         free(id);
         CRM_ASSERT(rc == pcmk_rc_ok);
 
         if (print_ip) {
             out->message(out, crm_map_element_name(replica->ip->xml), show_opts,
                          replica->ip, only_node, only_rsc);
         }
 
         if (print_child) {
             out->message(out, crm_map_element_name(replica->child->xml), show_opts,
                          replica->child, only_node, only_rsc);
         }
 
         if (print_ctnr) {
             out->message(out, crm_map_element_name(replica->container->xml), show_opts,
                          replica->container, only_node, only_rsc);
         }
 
         if (print_remote) {
             out->message(out, crm_map_element_name(replica->remote->xml), show_opts,
                          replica->remote, only_node, only_rsc);
         }
 
         pcmk__output_xml_pop_parent(out); // replica
     }
 
     if (printed_header) {
         pcmk__output_xml_pop_parent(out); // bundle
     }
 
     return rc;
 }
 
 static void
 pe__bundle_replica_output_html(pcmk__output_t *out, pe__bundle_replica_t *replica,
                                pe_node_t *node, uint32_t show_opts)
 {
     pe_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     pe__common_output_html(out, rsc, buffer, node, show_opts);
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__bundle_html(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     CRM_ASSERT(rsc != NULL);
 
     get_bundle_variant_data(bundle_data, rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) ||
             (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) {
             /* The text output messages used below require pe_print_implicit to
              * be set to do anything.
              */
             uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
 
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
                                      pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset);
             }
 
             if (print_ip) {
                 out->message(out, crm_map_element_name(replica->ip->xml),
                              new_show_opts, replica->ip, only_node, only_rsc);
             }
 
             if (print_child) {
                 out->message(out, crm_map_element_name(replica->child->xml),
                              new_show_opts, replica->child, only_node, only_rsc);
             }
 
             if (print_ctnr) {
                 out->message(out, crm_map_element_name(replica->container->xml),
                              new_show_opts, replica->container, only_node, only_rsc);
             }
 
             if (print_remote) {
                 out->message(out, crm_map_element_name(replica->remote->xml),
                              new_show_opts, replica->remote, only_node, only_rsc);
             }
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->end_list(out);
             }
         } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
             continue;
         } else {
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
                                      pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
 
             pe__bundle_replica_output_html(out, replica, pe__current_node(replica->container),
                                            show_opts);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 static void
 pe__bundle_replica_output_text(pcmk__output_t *out, pe__bundle_replica_t *replica,
                                pe_node_t *node, uint32_t show_opts)
 {
     pe_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     pe__common_output_text(out, rsc, buffer, node, show_opts);
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__bundle_text(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     get_bundle_variant_data(bundle_data, rsc);
 
     CRM_ASSERT(rsc != NULL);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) ||
             (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) {
             /* The text output messages used below require pe_print_implicit to
              * be set to do anything.
              */
             uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
 
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
                                      pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->list_item(out, NULL, "Replica[%d]", replica->offset);
             }
 
             out->begin_list(out, NULL, NULL, NULL);
 
             if (print_ip) {
                 out->message(out, crm_map_element_name(replica->ip->xml),
                              new_show_opts, replica->ip, only_node, only_rsc);
             }
 
             if (print_child) {
                 out->message(out, crm_map_element_name(replica->child->xml),
                              new_show_opts, replica->child, only_node, only_rsc);
             }
 
             if (print_ctnr) {
                 out->message(out, crm_map_element_name(replica->container->xml),
                              new_show_opts, replica->container, only_node, only_rsc);
             }
 
             if (print_remote) {
                 out->message(out, crm_map_element_name(replica->remote->xml),
                              new_show_opts, replica->remote, only_node, only_rsc);
             }
 
             out->end_list(out);
         } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
             continue;
         } else {
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
                                      pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
 
             pe__bundle_replica_output_text(out, replica, pe__current_node(replica->container),
                                            show_opts);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 static void
 print_bundle_replica(pe__bundle_replica_t *replica, const char *pre_text,
                      long options, void *print_data)
 {
     pe_node_t *node = NULL;
     pe_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     node = pe__current_node(replica->container);
     common_print(rsc, pre_text, buffer, node, options, print_data);
 }
 
 void
 pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options,
                  void *print_data)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     char *child_text = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     if (options & pe_print_xml) {
         bundle_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     get_bundle_variant_data(bundle_data, rsc);
 
     if (pre_text == NULL) {
         pre_text = " ";
     }
 
     status_print("%sContainer bundle%s: %s [%s]%s%s\n",
                  pre_text, ((bundle_data->nreplicas > 1)? " set" : ""),
                  rsc->id, bundle_data->image,
                  pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "",
                  pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)");
     if (options & pe_print_html) {
         status_print("<br />\n<ul>\n");
     }
 
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pe__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica);
         if (options & pe_print_html) {
             status_print("<li>");
         }
 
         if (pcmk_is_set(options, pe_print_implicit)) {
             child_text = crm_strdup_printf("     %s", pre_text);
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 status_print("  %sReplica[%d]\n", pre_text, replica->offset);
             }
             if (options & pe_print_html) {
                 status_print("<br />\n<ul>\n");
             }
             print_rsc_in_list(replica->ip, child_text, options, print_data);
             print_rsc_in_list(replica->container, child_text, options, print_data);
             print_rsc_in_list(replica->remote, child_text, options, print_data);
             print_rsc_in_list(replica->child, child_text, options, print_data);
             if (options & pe_print_html) {
                 status_print("</ul>\n");
             }
         } else {
             child_text = crm_strdup_printf("%s  ", pre_text);
             print_bundle_replica(replica, child_text, options, print_data);
         }
         free(child_text);
 
         if (options & pe_print_html) {
             status_print("</li>\n");
         }
     }
     if (options & pe_print_html) {
         status_print("</ul>\n");
     }
 }
 
 static void
 free_bundle_replica(pe__bundle_replica_t *replica)
 {
     if (replica == NULL) {
         return;
     }
 
     if (replica->node) {
         free(replica->node);
         replica->node = NULL;
     }
 
     if (replica->ip) {
         free_xml(replica->ip->xml);
         replica->ip->xml = NULL;
         replica->ip->fns->free(replica->ip);
         replica->ip = NULL;
     }
     if (replica->container) {
         free_xml(replica->container->xml);
         replica->container->xml = NULL;
         replica->container->fns->free(replica->container);
         replica->container = NULL;
     }
     if (replica->remote) {
         free_xml(replica->remote->xml);
         replica->remote->xml = NULL;
         replica->remote->fns->free(replica->remote);
         replica->remote = NULL;
     }
     free(replica->ipaddr);
     free(replica);
 }
 
 void
 pe__free_bundle(pe_resource_t *rsc)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     get_bundle_variant_data(bundle_data, rsc);
     pe_rsc_trace(rsc, "Freeing %s", rsc->id);
 
     free(bundle_data->prefix);
     free(bundle_data->image);
     free(bundle_data->control_port);
     free(bundle_data->host_network);
     free(bundle_data->host_netmask);
     free(bundle_data->ip_range_start);
     free(bundle_data->container_network);
     free(bundle_data->launcher_options);
     free(bundle_data->container_command);
     free(bundle_data->container_host_options);
 
     g_list_free_full(bundle_data->replicas,
                      (GDestroyNotify) free_bundle_replica);
     g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free);
     g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free);
     g_list_free(rsc->children);
 
     if(bundle_data->child) {
         free_xml(bundle_data->child->xml);
         bundle_data->child->xml = NULL;
         bundle_data->child->fns->free(bundle_data->child);
     }
     common_free(rsc);
 }
 
 enum rsc_role_e
 pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current)
 {
     enum rsc_role_e container_role = RSC_ROLE_UNKNOWN;
     return container_role;
 }
 
 /*!
  * \brief Get the number of configured replicas in a bundle
  *
  * \param[in] rsc  Bundle resource
  *
  * \return Number of configured replicas, or 0 on error
  */
 int
 pe_bundle_replicas(const pe_resource_t *rsc)
 {
     if ((rsc == NULL) || (rsc->variant != pe_container)) {
         return 0;
     } else {
         pe__bundle_variant_data_t *bundle_data = NULL;
 
         get_bundle_variant_data(bundle_data, rsc);
         return bundle_data->nreplicas;
     }
 }
 
 void
 pe__count_bundle(pe_resource_t *rsc)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, rsc);
     for (GList *item = bundle_data->replicas; item != NULL; item = item->next) {
         pe__bundle_replica_t *replica = item->data;
 
         if (replica->ip) {
             replica->ip->fns->count(replica->ip);
         }
         if (replica->child) {
             replica->child->fns->count(replica->child);
         }
         if (replica->container) {
             replica->container->fns->count(replica->container);
         }
         if (replica->remote) {
             replica->remote->fns->count(replica->remote);
         }
     }
 }
 
 gboolean
 pe__bundle_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent)
 {
     gboolean passes = FALSE;
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else {
         get_bundle_variant_data(bundle_data, rsc);
 
         for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) {
             pe__bundle_replica_t *replica = gIter->data;
 
             if (replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (!replica->container->fns->is_filtered(replica->container, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             }
         }
     }
 
     return !passes;
 }
diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c
index 32640f3c85..5916175ff0 100644
--- a/lib/pengine/clone.c
+++ b/lib/pengine/clone.c
@@ -1,1294 +1,1295 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdint.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 #include <crm/msg_xml.h>
 #include <crm/common/output.h>
 #include <crm/common/xml_internal.h>
 
 #define VARIANT_CLONE 1
 #include "./variant.h"
 
 #ifdef PCMK__COMPAT_2_0
 #define PROMOTED_INSTANCES   RSC_ROLE_PROMOTED_LEGACY_S "s"
 #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_LEGACY_S "s"
 #else
 #define PROMOTED_INSTANCES   RSC_ROLE_PROMOTED_S
 #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_S
 #endif
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed to be promoted
  *
  * \param[in] clone  Promotable clone or clone instance to check
  *
  * \return Maximum promoted instances for \p clone
  */
 int
 pe__clone_promoted_max(pe_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, uber_parent(clone));
     return clone_data->promoted_max;
 }
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed to be promoted
  *
  * \param[in] clone  Promotable clone or clone instance to check
  *
  * \return Maximum promoted instances for \p clone
  */
 int
 pe__clone_promoted_node_max(pe_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, uber_parent(clone));
     return clone_data->promoted_node_max;
 }
 
 static GList *
 sorted_hash_table_values(GHashTable *table)
 {
     GList *retval = NULL;
     GHashTableIter iter;
     gpointer key, value;
 
     g_hash_table_iter_init(&iter, table);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
             retval = g_list_prepend(retval, (char *) value);
         }
     }
 
     retval = g_list_sort(retval, (GCompareFunc) strcmp);
     return retval;
 }
 
 static GList *
 nodes_with_status(GHashTable *table, const char *status)
 {
     GList *retval = NULL;
     GHashTableIter iter;
     gpointer key, value;
 
     g_hash_table_iter_init(&iter, table);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         if (!strcmp((char *) value, status)) {
             retval = g_list_prepend(retval, key);
         }
     }
 
     retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
     return retval;
 }
 
 static char *
 node_list_to_str(GList *list)
 {
     char *retval = NULL;
     size_t len = 0;
 
     for (GList *iter = list; iter != NULL; iter = iter->next) {
         pcmk__add_word(&retval, &len, (char *) iter->data);
     }
 
     return retval;
 }
 
 static void
 clone_header(pcmk__output_t *out, int *rc, pe_resource_t *rsc, clone_variant_data_t *clone_data)
 {
     char *attrs = NULL;
     size_t len = 0;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
         pcmk__add_separated_word(&attrs, &len, "promotable", ", ");
     }
 
     if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
         pcmk__add_separated_word(&attrs, &len, "unique", ", ");
     }
 
     if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
         pcmk__add_separated_word(&attrs, &len, "unmanaged", ", ");
     }
 
     if (pe__resource_is_disabled(rsc)) {
         pcmk__add_separated_word(&attrs, &len, "disabled", ", ");
     }
 
     if (attrs) {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)",
                              rsc->id, ID(clone_data->xml_obj_child),
                                  attrs);
         free(attrs);
     } else {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]",
                                  rsc->id, ID(clone_data->xml_obj_child))
     }
 }
 
 void
 pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid,
                pe_working_set_t *data_set)
 {
     if (pe_rsc_is_clone(rsc)) {
         clone_variant_data_t *clone_data = NULL;
 
         get_clone_variant_data(clone_data, rsc);
 
         pe_warn("Ignoring " XML_RSC_ATTR_UNIQUE " for %s because %s resources "
                 "such as %s can be used only as anonymous clones",
                 rsc->id, standard, rid);
 
         clone_data->clone_node_max = 1;
         clone_data->clone_max = QB_MIN(clone_data->clone_max,
                                        g_list_length(data_set->nodes));
     }
 }
 
 pe_resource_t *
 find_clone_instance(pe_resource_t * rsc, const char *sub_id, pe_working_set_t * data_set)
 {
     char *child_id = NULL;
     pe_resource_t *child = NULL;
     const char *child_base = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     child_base = ID(clone_data->xml_obj_child);
     child_id = crm_strdup_printf("%s:%s", child_base, sub_id);
     child = pe_find_resource(rsc->children, child_id);
 
     free(child_id);
     return child;
 }
 
 pe_resource_t *
 pe__create_clone_child(pe_resource_t *rsc, pe_working_set_t *data_set)
 {
     gboolean as_orphan = FALSE;
     char *inc_num = NULL;
     char *inc_max = NULL;
     pe_resource_t *child_rsc = NULL;
     xmlNode *child_copy = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
 
     if (clone_data->total_clones >= clone_data->clone_max) {
         // If we've already used all available instances, this is an orphan
         as_orphan = TRUE;
     }
 
     // Allocate instance numbers in numerical order (starting at 0)
     inc_num = pcmk__itoa(clone_data->total_clones);
     inc_max = pcmk__itoa(clone_data->clone_max);
 
     child_copy = copy_xml(clone_data->xml_obj_child);
 
     crm_xml_add(child_copy, XML_RSC_ATTR_INCARNATION, inc_num);
 
-    if (common_unpack(child_copy, &child_rsc, rsc, data_set) == FALSE) {
+    if (pe__unpack_resource(child_copy, &child_rsc, rsc,
+                            data_set) != pcmk_rc_ok) {
         pe_err("Failed unpacking resource %s", crm_element_value(child_copy, XML_ATTR_ID));
         child_rsc = NULL;
         goto bail;
     }
 /*  child_rsc->globally_unique = rsc->globally_unique; */
 
     CRM_ASSERT(child_rsc);
     clone_data->total_clones += 1;
     pe_rsc_trace(child_rsc, "Setting clone attributes for: %s", child_rsc->id);
     rsc->children = g_list_append(rsc->children, child_rsc);
     if (as_orphan) {
         pe__set_resource_flags_recursive(child_rsc, pe_rsc_orphan);
     }
 
     add_hash_param(child_rsc->meta, XML_RSC_ATTR_INCARNATION_MAX, inc_max);
     pe_rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
 
   bail:
     free(inc_num);
     free(inc_max);
 
     return child_rsc;
 }
 
 gboolean
 clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set)
 {
     int lpc = 0;
     xmlNode *a_child = NULL;
     xmlNode *xml_obj = rsc->xml;
     clone_variant_data_t *clone_data = NULL;
 
     const char *max_clones = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_MAX);
     const char *max_clones_node = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_NODEMAX);
 
     pe_rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     clone_data = calloc(1, sizeof(clone_variant_data_t));
     rsc->variant_opaque = clone_data;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
         const char *promoted_max = NULL;
         const char *promoted_node_max = NULL;
 
         promoted_max = g_hash_table_lookup(rsc->meta,
                                            XML_RSC_ATTR_PROMOTED_MAX);
         if (promoted_max == NULL) {
             // @COMPAT deprecated since 2.0.0
             promoted_max = g_hash_table_lookup(rsc->meta,
                                                PCMK_XA_PROMOTED_MAX_LEGACY);
         }
 
         promoted_node_max = g_hash_table_lookup(rsc->meta,
                                                 XML_RSC_ATTR_PROMOTED_NODEMAX);
         if (promoted_node_max == NULL) {
             // @COMPAT deprecated since 2.0.0
             promoted_node_max =
                 g_hash_table_lookup(rsc->meta,
                                     PCMK_XA_PROMOTED_NODE_MAX_LEGACY);
         }
 
         // Use 1 as default but 0 for minimum and invalid
         if (promoted_max == NULL) {
             clone_data->promoted_max = 1;
         } else {
             pcmk__scan_min_int(promoted_max, &(clone_data->promoted_max), 0);
         }
 
         // Use 1 as default but 0 for minimum and invalid
         if (promoted_node_max == NULL) {
             clone_data->promoted_node_max = 1;
         } else {
             pcmk__scan_min_int(promoted_node_max,
                                &(clone_data->promoted_node_max), 0);
         }
     }
 
     // Implied by calloc()
     /* clone_data->xml_obj_child = NULL; */
 
     // Use 1 as default but 0 for minimum and invalid
     if (max_clones_node == NULL) {
         clone_data->clone_node_max = 1;
     } else {
         pcmk__scan_min_int(max_clones_node, &(clone_data->clone_node_max), 0);
     }
 
     /* Use number of nodes (but always at least 1, which is handy for crm_verify
      * for a CIB without nodes) as default, but 0 for minimum and invalid
      */
     if (max_clones == NULL) {
         clone_data->clone_max = QB_MAX(1, g_list_length(data_set->nodes));
     } else {
         pcmk__scan_min_int(max_clones, &(clone_data->clone_max), 0);
     }
 
     if (crm_is_true(g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_ORDERED))) {
         clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
                                                "Clone", rsc->id,
                                                clone_data->flags,
                                                pe__clone_ordered,
                                                "pe__clone_ordered");
     }
 
     if ((rsc->flags & pe_rsc_unique) == 0 && clone_data->clone_node_max > 1) {
         pcmk__config_err("Ignoring " XML_RSC_ATTR_PROMOTED_MAX " for %s "
                          "because anonymous clones support only one instance "
                          "per node", rsc->id);
         clone_data->clone_node_max = 1;
     }
 
     pe_rsc_trace(rsc, "Options for %s", rsc->id);
     pe_rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
     pe_rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
     pe_rsc_trace(rsc, "\tClone is unique: %s",
                  pe__rsc_bool_str(rsc, pe_rsc_unique));
     pe_rsc_trace(rsc, "\tClone is promotable: %s",
                  pe__rsc_bool_str(rsc, pe_rsc_promotable));
 
     // Clones may contain a single group or primitive
     for (a_child = pcmk__xe_first_child(xml_obj); a_child != NULL;
          a_child = pcmk__xe_next(a_child)) {
 
         if (pcmk__str_any_of((const char *)a_child->name, XML_CIB_TAG_RESOURCE, XML_CIB_TAG_GROUP, NULL)) {
             clone_data->xml_obj_child = a_child;
             break;
         }
     }
 
     if (clone_data->xml_obj_child == NULL) {
         pcmk__config_err("%s has nothing to clone", rsc->id);
         return FALSE;
     }
 
     /*
      * Make clones ever so slightly sticky by default
      *
      * This helps ensure clone instances are not shuffled around the cluster
      * for no benefit in situations when pre-allocation is not appropriate
      */
     if (g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_STICKINESS) == NULL) {
         add_hash_param(rsc->meta, XML_RSC_ATTR_STICKINESS, "1");
     }
 
     /* This ensures that the globally-unique value always exists for children to
      * inherit when being unpacked, as well as in resource agents' environment.
      */
     add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE,
                    pe__rsc_bool_str(rsc, pe_rsc_unique));
 
     if (clone_data->clone_max <= 0) {
         /* Create one child instance so that unpack_find_resource() will hook up
          * any orphans up to the parent correctly.
          */
         if (pe__create_clone_child(rsc, data_set) == NULL) {
             return FALSE;
         }
 
     } else {
         // Create a child instance for each available instance number
         for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
             if (pe__create_clone_child(rsc, data_set) == NULL) {
                 return FALSE;
             }
         }
     }
 
     pe_rsc_trace(rsc, "Added %d children to resource %s...", clone_data->clone_max, rsc->id);
     return TRUE;
 }
 
 gboolean
 clone_active(pe_resource_t * rsc, gboolean all)
 {
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
         gboolean child_active = child_rsc->fns->active(child_rsc, all);
 
         if (all == FALSE && child_active) {
             return TRUE;
         } else if (all && child_active == FALSE) {
             return FALSE;
         }
     }
 
     if (all) {
         return TRUE;
     } else {
         return FALSE;
     }
 }
 
 static void
 short_print(char *list, const char *prefix, const char *type, const char *suffix, long options, void *print_data)
 {
     if(suffix == NULL) {
         suffix = "";
     }
 
     if (list) {
         if (options & pe_print_html) {
             status_print("<li>");
         }
         status_print("%s%s: [ %s ]%s", prefix, type, list, suffix);
 
         if (options & pe_print_html) {
             status_print("</li>\n");
 
         } else if (options & pe_print_suppres_nl) {
             /* nothing */
         } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) {
             status_print("\n");
         }
 
     }
 }
 
 static const char *
 configured_role_str(pe_resource_t * rsc)
 {
     const char *target_role = g_hash_table_lookup(rsc->meta,
                                                   XML_RSC_ATTR_TARGET_ROLE);
 
     if ((target_role == NULL) && rsc->children && rsc->children->data) {
         target_role = g_hash_table_lookup(((pe_resource_t*)rsc->children->data)->meta,
                                           XML_RSC_ATTR_TARGET_ROLE);
     }
     return target_role;
 }
 
 static enum rsc_role_e
 configured_role(pe_resource_t * rsc)
 {
     const char *target_role = configured_role_str(rsc);
 
     if (target_role) {
         return text2role(target_role);
     }
     return RSC_ROLE_UNKNOWN;
 }
 
 static void
 clone_print_xml(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
 {
     char *child_text = crm_strdup_printf("%s    ", pre_text);
     const char *target_role = configured_role_str(rsc);
     GList *gIter = rsc->children;
 
     status_print("%s<clone ", pre_text);
     status_print("id=\"%s\" ", rsc->id);
     status_print("multi_state=\"%s\" ",
                  pe__rsc_bool_str(rsc, pe_rsc_promotable));
     status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique));
     status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed));
     status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed));
     status_print("failure_ignored=\"%s\" ",
                  pe__rsc_bool_str(rsc, pe_rsc_failure_ignored));
     if (target_role) {
         status_print("target_role=\"%s\" ", target_role);
     }
     status_print(">\n");
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         child_rsc->fns->print(child_rsc, child_text, options, print_data);
     }
 
     status_print("%s</clone>\n", pre_text);
     free(child_text);
 }
 
 bool is_set_recursive(pe_resource_t * rsc, long long flag, bool any)
 {
     GList *gIter;
     bool all = !any;
 
     if (pcmk_is_set(rsc->flags, flag)) {
         if(any) {
             return TRUE;
         }
     } else if(all) {
         return FALSE;
     }
 
     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         if(is_set_recursive(gIter->data, flag, any)) {
             if(any) {
                 return TRUE;
             }
 
         } else if(all) {
             return FALSE;
         }
     }
 
     if(all) {
         return TRUE;
     }
     return FALSE;
 }
 
 void
 clone_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
 {
     char *list_text = NULL;
     char *child_text = NULL;
     char *stopped_list = NULL;
     size_t list_text_len = 0;
     size_t stopped_list_len = 0;
 
     GList *promoted_list = NULL;
     GList *started_list = NULL;
     GList *gIter = rsc->children;
 
     clone_variant_data_t *clone_data = NULL;
     int active_instances = 0;
 
     if (pre_text == NULL) {
         pre_text = " ";
     }
 
     if (options & pe_print_xml) {
         clone_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     get_clone_variant_data(clone_data, rsc);
 
     child_text = crm_strdup_printf("%s    ", pre_text);
 
     status_print("%sClone Set: %s [%s]%s%s%s",
                  pre_text ? pre_text : "", rsc->id, ID(clone_data->xml_obj_child),
                  pcmk_is_set(rsc->flags, pe_rsc_promotable)? " (promotable)" : "",
                  pcmk_is_set(rsc->flags, pe_rsc_unique)? " (unique)" : "",
                  pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : " (unmanaged)");
 
     if (options & pe_print_html) {
         status_print("\n<ul>\n");
 
     } else if ((options & pe_print_log) == 0) {
         status_print("\n");
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         gboolean print_full = FALSE;
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 
         if (options & pe_print_clone_details) {
             print_full = TRUE;
         }
 
         if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
             // Print individual instance when unique (except stopped orphans)
             if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
                 print_full = TRUE;
             }
 
         // Everything else in this block is for anonymous clones
 
         } else if (pcmk_is_set(options, pe_print_pending)
                    && (child_rsc->pending_task != NULL)
                    && strcmp(child_rsc->pending_task, "probe")) {
             // Print individual instance when non-probe action is pending
             print_full = TRUE;
 
         } else if (partially_active == FALSE) {
             // List stopped instances when requested (except orphans)
             if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan)
                 && !pcmk_is_set(options, pe_print_clone_active)) {
                 pcmk__add_word(&stopped_list, &stopped_list_len, child_rsc->id);
             }
 
         } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE)
                    || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE
                    || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) {
 
             // Print individual instance when active orphaned/unmanaged/failed
             print_full = TRUE;
 
         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
             // Instance of fully active anonymous clone
 
             pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE);
 
             if (location) {
                 // Instance is active on a single node
 
                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 
                 if (location->details->online == FALSE && location->details->unclean) {
                     print_full = TRUE;
 
                 } else if (a_role > RSC_ROLE_UNPROMOTED) {
                     promoted_list = g_list_append(promoted_list, location);
 
                 } else {
                     started_list = g_list_append(started_list, location);
                 }
 
             } else {
                 /* uncolocated group - bleh */
                 print_full = TRUE;
             }
 
         } else {
             // Instance of partially active anonymous clone
             print_full = TRUE;
         }
 
         if (print_full) {
             if (options & pe_print_html) {
                 status_print("<li>\n");
             }
             child_rsc->fns->print(child_rsc, child_text, options, print_data);
             if (options & pe_print_html) {
                 status_print("</li>\n");
             }
         }
     }
 
     /* Promoted */
     promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
     for (gIter = promoted_list; gIter; gIter = gIter->next) {
         pe_node_t *host = gIter->data;
 
         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
 	active_instances++;
     }
 
     short_print(list_text, child_text, PROMOTED_INSTANCES, NULL, options,
                 print_data);
     g_list_free(promoted_list);
     free(list_text);
     list_text = NULL;
     list_text_len = 0;
 
     /* Started/Unpromoted */
     started_list = g_list_sort(started_list, pe__cmp_node_name);
     for (gIter = started_list; gIter; gIter = gIter->next) {
         pe_node_t *host = gIter->data;
 
         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
         active_instances++;
     }
 
     if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
         enum rsc_role_e role = configured_role(rsc);
 
         if (role == RSC_ROLE_UNPROMOTED) {
             short_print(list_text, child_text,
                         UNPROMOTED_INSTANCES " (target-role)", NULL, options,
                         print_data);
         } else {
             short_print(list_text, child_text, UNPROMOTED_INSTANCES, NULL,
                         options, print_data);
         }
 
     } else {
         short_print(list_text, child_text, "Started", NULL, options, print_data);
     }
 
     g_list_free(started_list);
     free(list_text);
     list_text = NULL;
     list_text_len = 0;
 
     if (!pcmk_is_set(options, pe_print_clone_active)) {
         const char *state = "Stopped";
         enum rsc_role_e role = configured_role(rsc);
 
         if (role == RSC_ROLE_STOPPED) {
             state = "Stopped (disabled)";
         }
 
         if (!pcmk_is_set(rsc->flags, pe_rsc_unique)
             && (clone_data->clone_max > active_instances)) {
 
             GList *nIter;
             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 
             /* Custom stopped list for non-unique clones */
             free(stopped_list);
             stopped_list = NULL;
             stopped_list_len = 0;
 
             if (list == NULL) {
                 /* Clusters with symmetrical=false haven't calculated allowed_nodes yet
                  * If we've not probed for them yet, the Stopped list will be empty
                  */
                 list = g_hash_table_get_values(rsc->known_on);
             }
 
             list = g_list_sort(list, pe__cmp_node_name);
             for (nIter = list; nIter != NULL; nIter = nIter->next) {
                 pe_node_t *node = (pe_node_t *)nIter->data;
 
                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL) {
                     pcmk__add_word(&stopped_list, &stopped_list_len,
                                    node->details->uname);
                 }
             }
             g_list_free(list);
         }
 
         short_print(stopped_list, child_text, state, NULL, options, print_data);
         free(stopped_list);
     }
 
     if (options & pe_print_html) {
         status_print("</ul>\n");
     }
 
     free(child_text);
 }
 
 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__clone_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     GList *gIter = rsc->children;
     GList *all = NULL;
     int rc = pcmk_rc_no_output;
     gboolean printed_header = FALSE;
     gboolean print_everything = TRUE;
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     all = g_list_prepend(all, (gpointer) "*");
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
             continue;
         }
 
         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
             continue;
         }
 
         if (!printed_header) {
             printed_header = TRUE;
 
             rc = pe__name_and_nvpairs_xml(out, true, "clone", 8,
                     "id", rsc->id,
                     "multi_state", pe__rsc_bool_str(rsc, pe_rsc_promotable),
                     "unique", pe__rsc_bool_str(rsc, pe_rsc_unique),
                     "managed", pe__rsc_bool_str(rsc, pe_rsc_managed),
                     "disabled", pcmk__btoa(pe__resource_is_disabled(rsc)),
                     "failed", pe__rsc_bool_str(rsc, pe_rsc_failed),
                     "failure_ignored", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored),
                     "target_role", configured_role_str(rsc));
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
 
         out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
                      child_rsc, only_node, all);
     }
 
     if (printed_header) {
         pcmk__output_xml_pop_parent(out);
     }
 
     g_list_free(all);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__clone_default(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     GHashTable *stopped = NULL;
 
     char *list_text = NULL;
     size_t list_text_len = 0;
 
     GList *promoted_list = NULL;
     GList *started_list = NULL;
     GList *gIter = rsc->children;
 
     clone_variant_data_t *clone_data = NULL;
     int active_instances = 0;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     get_clone_variant_data(clone_data, rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     for (; gIter != NULL; gIter = gIter->next) {
         gboolean print_full = FALSE;
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 
         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
             continue;
         }
 
         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
             continue;
         }
 
         if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
             print_full = TRUE;
         }
 
         if (pcmk_is_set(rsc->flags, pe_rsc_unique)) {
             // Print individual instance when unique (except stopped orphans)
             if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
                 print_full = TRUE;
             }
 
         // Everything else in this block is for anonymous clones
 
         } else if (pcmk_is_set(show_opts, pcmk_show_pending)
                    && (child_rsc->pending_task != NULL)
                    && strcmp(child_rsc->pending_task, "probe")) {
             // Print individual instance when non-probe action is pending
             print_full = TRUE;
 
         } else if (partially_active == FALSE) {
             // List stopped instances when requested (except orphans)
             if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan)
                 && !pcmk_is_set(show_opts, pcmk_show_clone_detail)
                 && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
                 if (stopped == NULL) {
                     stopped = pcmk__strkey_table(free, free);
                 }
                 g_hash_table_insert(stopped, strdup(child_rsc->id), strdup("Stopped"));
             }
 
         } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE)
                    || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE
                    || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) {
 
             // Print individual instance when active orphaned/unmanaged/failed
             print_full = TRUE;
 
         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
             // Instance of fully active anonymous clone
 
             pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE);
 
             if (location) {
                 // Instance is active on a single node
 
                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 
                 if (location->details->online == FALSE && location->details->unclean) {
                     print_full = TRUE;
 
                 } else if (a_role > RSC_ROLE_UNPROMOTED) {
                     promoted_list = g_list_append(promoted_list, location);
 
                 } else {
                     started_list = g_list_append(started_list, location);
                 }
 
             } else {
                 /* uncolocated group - bleh */
                 print_full = TRUE;
             }
 
         } else {
             // Instance of partially active anonymous clone
             print_full = TRUE;
         }
 
         if (print_full) {
             GList *all = NULL;
 
             clone_header(out, &rc, rsc, clone_data);
 
             /* Print every resource that's a child of this clone. */
             all = g_list_prepend(all, (gpointer) "*");
             out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
                          child_rsc, only_node, all);
             g_list_free(all);
         }
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
         PCMK__OUTPUT_LIST_FOOTER(out, rc);
         return pcmk_rc_ok;
     }
 
     /* Promoted */
     promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
     for (gIter = promoted_list; gIter; gIter = gIter->next) {
         pe_node_t *host = gIter->data;
 
         if (!pcmk__str_in_list(host->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
         active_instances++;
     }
     g_list_free(promoted_list);
 
     if (list_text != NULL) {
         clone_header(out, &rc, rsc, clone_data);
 
         out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]", list_text);
         free(list_text);
         list_text = NULL;
         list_text_len = 0;
     }
 
     /* Started/Unpromoted */
     started_list = g_list_sort(started_list, pe__cmp_node_name);
     for (gIter = started_list; gIter; gIter = gIter->next) {
         pe_node_t *host = gIter->data;
 
         if (!pcmk__str_in_list(host->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         pcmk__add_word(&list_text, &list_text_len, host->details->uname);
         active_instances++;
     }
     g_list_free(started_list);
 
     if (list_text != NULL) {
         clone_header(out, &rc, rsc, clone_data);
 
         if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
             enum rsc_role_e role = configured_role(rsc);
 
             if (role == RSC_ROLE_UNPROMOTED) {
                 out->list_item(out, NULL,
                                UNPROMOTED_INSTANCES " (target-role): [ %s ]",
                                list_text);
             } else {
                 out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]",
                                list_text);
             }
 
         } else {
             out->list_item(out, NULL, "Started: [ %s ]", list_text);
         }
         free(list_text);
         list_text = NULL;
         list_text_len = 0;
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
         if (!pcmk_is_set(rsc->flags, pe_rsc_unique)
             && (clone_data->clone_max > active_instances)) {
 
             GList *nIter;
             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 
             /* Custom stopped table for non-unique clones */
             if (stopped != NULL) {
                 g_hash_table_destroy(stopped);
                 stopped = NULL;
             }
 
             if (list == NULL) {
                 /* Clusters with symmetrical=false haven't calculated allowed_nodes yet
                  * If we've not probed for them yet, the Stopped list will be empty
                  */
                 list = g_hash_table_get_values(rsc->known_on);
             }
 
             list = g_list_sort(list, pe__cmp_node_name);
             for (nIter = list; nIter != NULL; nIter = nIter->next) {
                 pe_node_t *node = (pe_node_t *)nIter->data;
 
                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL &&
                     pcmk__str_in_list(node->details->uname, only_node,
                                       pcmk__str_star_matches|pcmk__str_casei)) {
                     xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname);
                     const char *state = "Stopped";
 
                     if (configured_role(rsc) == RSC_ROLE_STOPPED) {
                         state = "Stopped (disabled)";
                     }
 
                     if (stopped == NULL) {
                         stopped = pcmk__strkey_table(free, free);
                     }
                     if (probe_op != NULL) {
                         int rc;
 
                         pcmk__scan_min_int(crm_element_value(probe_op, XML_LRM_ATTR_RC), &rc, 0);
                         g_hash_table_insert(stopped, strdup(node->details->uname),
                                             crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc)));
                     } else {
                         g_hash_table_insert(stopped, strdup(node->details->uname),
                                             strdup(state));
                     }
                 }
             }
             g_list_free(list);
         }
 
         if (stopped != NULL) {
             GList *list = sorted_hash_table_values(stopped);
 
             clone_header(out, &rc, rsc, clone_data);
 
             for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
                 const char *status = status_iter->data;
                 GList *nodes = nodes_with_status(stopped, status);
                 char *str = node_list_to_str(nodes);
 
                 if (str != NULL) {
                     out->list_item(out, NULL, "%s: [ %s ]", status, str);
                     free(str);
                 }
 
                 g_list_free(nodes);
             }
 
             g_list_free(list);
             g_hash_table_destroy(stopped);
 
         /* If there are no instances of this clone (perhaps because there are no
          * nodes configured), simply output the clone header by itself.  This can
          * come up in PCS testing.
          */
         } else if (active_instances == 0) {
             clone_header(out, &rc, rsc, clone_data);
             PCMK__OUTPUT_LIST_FOOTER(out, rc);
             return rc;
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 void
 clone_free(pe_resource_t * rsc)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     pe_rsc_trace(rsc, "Freeing %s", rsc->id);
 
     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         CRM_ASSERT(child_rsc);
         pe_rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
         free_xml(child_rsc->xml);
         child_rsc->xml = NULL;
         /* There could be a saved unexpanded xml */
         free_xml(child_rsc->orig_xml);
         child_rsc->orig_xml = NULL;
         child_rsc->fns->free(child_rsc);
     }
 
     g_list_free(rsc->children);
 
     if (clone_data) {
         CRM_ASSERT(clone_data->demote_notify == NULL);
         CRM_ASSERT(clone_data->stop_notify == NULL);
         CRM_ASSERT(clone_data->start_notify == NULL);
         CRM_ASSERT(clone_data->promote_notify == NULL);
     }
 
     common_free(rsc);
 }
 
 enum rsc_role_e
 clone_resource_state(const pe_resource_t * rsc, gboolean current)
 {
     enum rsc_role_e clone_role = RSC_ROLE_UNKNOWN;
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
         enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, current);
 
         if (a_role > clone_role) {
             clone_role = a_role;
         }
     }
 
     pe_rsc_trace(rsc, "%s role: %s", rsc->id, role2text(clone_role));
     return clone_role;
 }
 
 /*!
  * \internal
  * \brief Check whether a clone has an instance for every node
  *
  * \param[in] rsc       Clone to check
  * \param[in] data_set  Cluster state
  */
 bool
 pe__is_universal_clone(pe_resource_t *rsc,
                        pe_working_set_t *data_set)
 {
     if (pe_rsc_is_clone(rsc)) {
         clone_variant_data_t *clone_data = NULL;
 
         get_clone_variant_data(clone_data, rsc);
         if (clone_data->clone_max == g_list_length(data_set->nodes)) {
             return TRUE;
         }
     }
     return FALSE;
 }
 
 gboolean
 pe__clone_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent)
 {
     gboolean passes = FALSE;
     clone_variant_data_t *clone_data = NULL;
 
     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else {
         get_clone_variant_data(clone_data, rsc);
         passes = pcmk__str_in_list(ID(clone_data->xml_obj_child), only_rsc, pcmk__str_star_matches);
 
         if (!passes) {
             for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
                 pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
                 if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) {
                     passes = TRUE;
                     break;
                 }
             }
         }
     }
 
     return !passes;
 }
 
 const char *
 pe__clone_child_id(pe_resource_t *rsc)
 {
     clone_variant_data_t *clone_data = NULL;
     get_clone_variant_data(clone_data, rsc);
     return ID(clone_data->xml_obj_child);
 }
 
 /*!
  * \internal
  * \brief Check whether a clone is ordered
  *
  * \param[in] clone  Clone resource to check
  *
  * \return true if clone is ordered, otherwise false
  */
 bool
 pe__clone_is_ordered(pe_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
     return pcmk_is_set(clone_data->flags, pe__clone_ordered);
 }
 
 /*!
  * \internal
  * \brief Set a clone flag
  *
  * \param[in] clone  Clone resource to set flag for
  * \param[in] flag   Clone flag to set
  *
  * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
  *         already set or pcmk_rc_already if it was)
  */
 int
 pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
     if (pcmk_is_set(clone_data->flags, flag)) {
         return pcmk_rc_already;
     }
     clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
                                            "Clone", clone->id,
                                            clone_data->flags, flag, "flag");
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Create pseudo-actions needed for promotable clones
  *
  * \param[in] clone          Promotable clone to create actions for
  * \param[in] any_promoting  Whether any instances will be promoted
  * \param[in] any_demoting   Whether any instance will be demoted
  */
 void
 pe__create_promotable_pseudo_ops(pe_resource_t *clone, bool any_promoting,
                                  bool any_demoting)
 {
     pe_action_t *action = NULL;
     pe_action_t *action_complete = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
 
     // Create a "promote" action for the clone itself
     action = pe__new_rsc_pseudo_action(clone, RSC_PROMOTE, !any_promoting,
                                        true);
 
     // Create a "promoted" action for when all promotions are done
     action_complete = pe__new_rsc_pseudo_action(clone, RSC_PROMOTED,
                                                 !any_promoting, true);
     action_complete->priority = INFINITY;
 
     // Create notification pseudo-actions for promotion
     if (clone_data->promote_notify == NULL) {
         clone_data->promote_notify = pe__clone_notif_pseudo_ops(clone,
                                                                 RSC_PROMOTE,
                                                                 action,
                                                                 action_complete);
     }
 
     // Create a "demote" action for the clone itself
     action = pe__new_rsc_pseudo_action(clone, RSC_DEMOTE, !any_demoting, true);
 
     // Create a "demoted" action for when all demotions are done
     action_complete = pe__new_rsc_pseudo_action(clone, RSC_DEMOTED,
                                                 !any_demoting, true);
     action_complete->priority = INFINITY;
 
     // Create notification pseudo-actions for demotion
     if (clone_data->demote_notify == NULL) {
         clone_data->demote_notify = pe__clone_notif_pseudo_ops(clone,
                                                                RSC_DEMOTE,
                                                                action,
                                                                action_complete);
 
         if (clone_data->promote_notify != NULL) {
             order_actions(clone_data->stop_notify->post_done,
                           clone_data->promote_notify->pre,
                           pe_order_optional);
             order_actions(clone_data->start_notify->post_done,
                           clone_data->promote_notify->pre,
                           pe_order_optional);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->promote_notify->pre,
                           pe_order_optional);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->start_notify->pre,
                           pe_order_optional);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->stop_notify->pre,
                           pe_order_optional);
         }
     }
 }
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index 0f1a28da40..9cafb6d788 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1141 +1,1154 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml_internal.h>
 
+#include "pe_status_private.h"
+
 void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length);
 
 resource_object_functions_t resource_class_functions[] = {
     {
          native_unpack,
          native_find_rsc,
          native_parameter,
          native_print,
          native_active,
          native_resource_state,
          native_location,
          native_free,
          pe__count_common,
          pe__native_is_filtered,
     },
     {
          group_unpack,
          native_find_rsc,
          native_parameter,
          group_print,
          group_active,
          group_resource_state,
          native_location,
          group_free,
          pe__count_common,
          pe__group_is_filtered,
     },
     {
          clone_unpack,
          native_find_rsc,
          native_parameter,
          clone_print,
          clone_active,
          clone_resource_state,
          native_location,
          clone_free,
          pe__count_common,
          pe__clone_is_filtered,
     },
     {
          pe__unpack_bundle,
          native_find_rsc,
          native_parameter,
          pe__print_bundle,
          pe__bundle_active,
          pe__bundle_resource_state,
          native_location,
          pe__free_bundle,
          pe__count_bundle,
          pe__bundle_is_filtered,
     }
 };
 
 static enum pe_obj_types
 get_resource_type(const char *name)
 {
     if (pcmk__str_eq(name, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) {
         return pe_native;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_GROUP, pcmk__str_casei)) {
         return pe_group;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_INCARNATION, pcmk__str_casei)) {
         return pe_clone;
 
     } else if (pcmk__str_eq(name, PCMK_XE_PROMOTABLE_LEGACY, pcmk__str_casei)) {
         // @COMPAT deprecated since 2.0.0
         return pe_clone;
 
     } else if (pcmk__str_eq(name, XML_CIB_TAG_CONTAINER, pcmk__str_casei)) {
         return pe_container;
     }
 
     return pe_unknown;
 }
 
 static void
 dup_attr(gpointer key, gpointer value, gpointer user_data)
 {
     add_hash_param(user_data, key, value);
 }
 
 static void
 expand_parents_fixed_nvpairs(pe_resource_t * rsc, pe_rule_eval_data_t * rule_data, GHashTable * meta_hash, pe_working_set_t * data_set)
 {
     GHashTable *parent_orig_meta = pcmk__strkey_table(free, free);
     pe_resource_t *p = rsc->parent;
 
     if (p == NULL) {
         return ;
     }
 
     /* Search all parent resources, get the fixed value of "meta_attributes" set only in the original xml, and stack it in the hash table. */
     /* The fixed value of the lower parent resource takes precedence and is not overwritten. */
     while(p != NULL) {
         /* A hash table for comparison is generated, including the id-ref. */
         pe__unpack_dataset_nvpairs(p->xml, XML_TAG_META_SETS,
                                rule_data, parent_orig_meta, NULL, FALSE, data_set);
         p = p->parent; 
     }
 
     /* If there is a fixed value of "meta_attributes" of the parent resource, it will be processed. */
     if (parent_orig_meta != NULL) {
         GHashTableIter iter;
         char *key = NULL;
         char *value = NULL;
 
         g_hash_table_iter_init(&iter, parent_orig_meta);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
             /* Parameters set in the original xml of the parent resource will also try to overwrite the child resource. */
             /* Attributes that already exist in the child lease are not updated. */
             dup_attr(key, value, meta_hash);
         }
     }
 
     if (parent_orig_meta != NULL) {
         g_hash_table_destroy(parent_orig_meta);
     }
     
     return ;
 
 }
 void
 get_meta_attributes(GHashTable * meta_hash, pe_resource_t * rsc,
                     pe_node_t * node, pe_working_set_t * data_set)
 {
     pe_rsc_eval_data_t rsc_rule_data = {
         .standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS),
         .provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER),
         .agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE)
     };
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = &rsc_rule_data,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->xml); a != NULL; a = a->next) {
         const char *prop_name = (const char *) a->name;
         const char *prop_value = crm_element_value(rsc->xml, prop_name);
 
         add_hash_param(meta_hash, prop_name, prop_value);
     }
 
     pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_META_SETS, &rule_data,
                                meta_hash, NULL, FALSE, data_set);
 
     /* Set the "meta_attributes" explicitly set in the parent resource to the hash table of the child resource. */
     /* If it is already explicitly set as a child, it will not be overwritten. */
     if (rsc->parent != NULL) {
         expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, data_set);
     }
 
     /* check the defaults */
     pe__unpack_dataset_nvpairs(data_set->rsc_defaults, XML_TAG_META_SETS,
                                &rule_data, meta_hash, NULL, FALSE, data_set);
 
     /* If there is "meta_attributes" that the parent resource has not explicitly set, set a value that is not set from rsc_default either. */
     /* The values already set up to this point will not be overwritten. */
     if (rsc->parent) {
         g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash);
     }
 }
 
 void
 get_rsc_attributes(GHashTable * meta_hash, pe_resource_t * rsc,
                    pe_node_t * node, pe_working_set_t * data_set)
 {
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_ATTR_SETS, &rule_data,
                                meta_hash, NULL, FALSE, data_set);
 
     /* set anything else based on the parent */
     if (rsc->parent != NULL) {
         get_rsc_attributes(meta_hash, rsc->parent, node, data_set);
 
     } else {
         /* and finally check the defaults */
         pe__unpack_dataset_nvpairs(data_set->rsc_defaults, XML_TAG_ATTR_SETS,
                                    &rule_data, meta_hash, NULL, FALSE, data_set);
     }
 }
 
 #if ENABLE_VERSIONED_ATTRS
 void
 pe_get_versioned_attributes(xmlNode * meta_hash, pe_resource_t * rsc,
                             pe_node_t * node, pe_working_set_t * data_set)
 {
     pe_rule_eval_data_t rule_data = {
         .node_hash = (node == NULL)? NULL : node->details->attrs,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     pe_eval_versioned_attributes(data_set->input, rsc->xml, XML_TAG_ATTR_SETS,
                                  &rule_data, meta_hash, NULL);
 
     /* set anything else based on the parent */
     if (rsc->parent != NULL) {
         pe_get_versioned_attributes(meta_hash, rsc->parent, node, data_set);
 
     } else {
         /* and finally check the defaults */
         pe_eval_versioned_attributes(data_set->input, data_set->rsc_defaults,
                                      XML_TAG_ATTR_SETS, &rule_data, meta_hash,
                                      NULL);
     }
 }
 #endif
 
 static char *
 template_op_key(xmlNode * op)
 {
     const char *name = crm_element_value(op, "name");
     const char *role = crm_element_value(op, "role");
     char *key = NULL;
 
     if ((role == NULL)
         || pcmk__strcase_any_of(role, RSC_ROLE_STARTED_S, RSC_ROLE_UNPROMOTED_S,
                                 RSC_ROLE_UNPROMOTED_LEGACY_S, NULL)) {
         role = RSC_ROLE_UNKNOWN_S;
     }
 
     key = crm_strdup_printf("%s-%s", name, role);
     return key;
 }
 
 static gboolean
 unpack_template(xmlNode * xml_obj, xmlNode ** expanded_xml, pe_working_set_t * data_set)
 {
     xmlNode *cib_resources = NULL;
     xmlNode *template = NULL;
     xmlNode *new_xml = NULL;
     xmlNode *child_xml = NULL;
     xmlNode *rsc_ops = NULL;
     xmlNode *template_ops = NULL;
     const char *template_ref = NULL;
     const char *clone = NULL;
     const char *id = NULL;
 
     if (xml_obj == NULL) {
         pe_err("No resource object for template unpacking");
         return FALSE;
     }
 
     template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
     if (template_ref == NULL) {
         return TRUE;
     }
 
     id = ID(xml_obj);
     if (id == NULL) {
         pe_err("'%s' object must have a id", crm_element_name(xml_obj));
         return FALSE;
     }
 
     if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
         pe_err("The resource object '%s' should not reference itself", id);
         return FALSE;
     }
 
     cib_resources = get_xpath_object("//"XML_CIB_TAG_RESOURCES, data_set->input, LOG_TRACE);
     if (cib_resources == NULL) {
         pe_err("No resources configured");
         return FALSE;
     }
 
     template = pcmk__xe_match(cib_resources, XML_CIB_TAG_RSC_TEMPLATE,
                               XML_ATTR_ID, template_ref);
     if (template == NULL) {
         pe_err("No template named '%s'", template_ref);
         return FALSE;
     }
 
     new_xml = copy_xml(template);
     xmlNodeSetName(new_xml, xml_obj->name);
     crm_xml_replace(new_xml, XML_ATTR_ID, id);
 
     clone = crm_element_value(xml_obj, XML_RSC_ATTR_INCARNATION);
     if(clone) {
         crm_xml_add(new_xml, XML_RSC_ATTR_INCARNATION, clone);
     }
 
     template_ops = find_xml_node(new_xml, "operations", FALSE);
 
     for (child_xml = pcmk__xe_first_child(xml_obj); child_xml != NULL;
          child_xml = pcmk__xe_next(child_xml)) {
         xmlNode *new_child = NULL;
 
         new_child = add_node_copy(new_xml, child_xml);
 
         if (pcmk__str_eq((const char *)new_child->name, "operations", pcmk__str_none)) {
             rsc_ops = new_child;
         }
     }
 
     if (template_ops && rsc_ops) {
         xmlNode *op = NULL;
         GHashTable *rsc_ops_hash = pcmk__strkey_table(free, NULL);
 
         for (op = pcmk__xe_first_child(rsc_ops); op != NULL;
              op = pcmk__xe_next(op)) {
 
             char *key = template_op_key(op);
 
             g_hash_table_insert(rsc_ops_hash, key, op);
         }
 
         for (op = pcmk__xe_first_child(template_ops); op != NULL;
              op = pcmk__xe_next(op)) {
 
             char *key = template_op_key(op);
 
             if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
                 add_node_copy(rsc_ops, op);
             }
 
             free(key);
         }
 
         if (rsc_ops_hash) {
             g_hash_table_destroy(rsc_ops_hash);
         }
 
         free_xml(template_ops);
     }
 
     /*free_xml(*expanded_xml); */
     *expanded_xml = new_xml;
 
     /* Disable multi-level templates for now */
     /*if(unpack_template(new_xml, expanded_xml, data_set) == FALSE) {
        free_xml(*expanded_xml);
        *expanded_xml = NULL;
 
        return FALSE;
        } */
 
     return TRUE;
 }
 
 static gboolean
 add_template_rsc(xmlNode * xml_obj, pe_working_set_t * data_set)
 {
     const char *template_ref = NULL;
     const char *id = NULL;
 
     if (xml_obj == NULL) {
         pe_err("No resource object for processing resource list of template");
         return FALSE;
     }
 
     template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE);
     if (template_ref == NULL) {
         return TRUE;
     }
 
     id = ID(xml_obj);
     if (id == NULL) {
         pe_err("'%s' object must have a id", crm_element_name(xml_obj));
         return FALSE;
     }
 
     if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
         pe_err("The resource object '%s' should not reference itself", id);
         return FALSE;
     }
 
     if (add_tag_ref(data_set->template_rsc_sets, template_ref, id) == FALSE) {
         return FALSE;
     }
 
     return TRUE;
 }
 
 static bool
 detect_promotable(pe_resource_t *rsc)
 {
     const char *promotable = g_hash_table_lookup(rsc->meta,
                                                  XML_RSC_ATTR_PROMOTABLE);
 
     if (crm_is_true(promotable)) {
         return TRUE;
     }
 
     // @COMPAT deprecated since 2.0.0
     if (pcmk__str_eq(crm_element_name(rsc->xml), PCMK_XE_PROMOTABLE_LEGACY,
                      pcmk__str_casei)) {
         /* @TODO in some future version, pe_warn_once() here,
          *       then drop support in even later version
          */
         g_hash_table_insert(rsc->meta, strdup(XML_RSC_ATTR_PROMOTABLE),
                             strdup(XML_BOOLEAN_TRUE));
         return TRUE;
     }
     return FALSE;
 }
 
 static void
 free_params_table(gpointer data)
 {
     g_hash_table_destroy((GHashTable *) data);
 }
 
 /*!
  * \brief Get a table of resource parameters
  *
  * \param[in] rsc       Resource to query
  * \param[in] node      Node for evaluating rules (NULL for defaults)
  * \param[in] data_set  Cluster working set
  *
  * \return Hash table containing resource parameter names and values
  *         (or NULL if \p rsc or \p data_set is NULL)
  * \note The returned table will be destroyed when the resource is freed, so
  *       callers should not destroy it.
  */
 GHashTable *
 pe_rsc_params(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set)
 {
     GHashTable *params_on_node = NULL;
 
     /* A NULL node is used to request the resource's default parameters
      * (not evaluated for node), but we always want something non-NULL
      * as a hash table key.
      */
     const char *node_name = "";
 
     // Sanity check
     if ((rsc == NULL) || (data_set == NULL)) {
         return NULL;
     }
     if ((node != NULL) && (node->details->uname != NULL)) {
         node_name = node->details->uname;
     }
 
     // Find the parameter table for given node
     if (rsc->parameter_cache == NULL) {
         rsc->parameter_cache = pcmk__strikey_table(free, free_params_table);
     } else {
         params_on_node = g_hash_table_lookup(rsc->parameter_cache, node_name);
     }
 
     // If none exists yet, create one with parameters evaluated for node
     if (params_on_node == NULL) {
         params_on_node = pcmk__strkey_table(free, free);
         get_rsc_attributes(params_on_node, rsc, node, data_set);
         g_hash_table_insert(rsc->parameter_cache, strdup(node_name),
                             params_on_node);
     }
     return params_on_node;
 }
 
 /*!
  * \internal
  * \brief Unpack a resource's "requires" meta-attribute
  *
  * \param[in] rsc         Resource being unpacked
  * \param[in] value       Value of "requires" meta-attribute
  * \param[in] is_default  Whether \p value was selected by default
  */
 static void
 unpack_requires(pe_resource_t *rsc, const char *value, bool is_default)
 {
     if (pcmk__str_eq(value, PCMK__VALUE_NOTHING, pcmk__str_casei)) {
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_QUORUM, pcmk__str_casei)) {
         pe__set_resource_flags(rsc, pe_rsc_needs_quorum);
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_FENCING, pcmk__str_casei)) {
         pe__set_resource_flags(rsc, pe_rsc_needs_fencing);
         if (!pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_enabled)) {
             pcmk__config_warn("%s requires fencing but fencing is disabled",
                               rsc->id);
         }
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_UNFENCING, pcmk__str_casei)) {
         if (pcmk_is_set(rsc->flags, pe_rsc_fence_device)) {
             pcmk__config_warn("Resetting \"" XML_RSC_ATTR_REQUIRES "\" for %s "
                               "to \"" PCMK__VALUE_QUORUM "\" because fencing "
                               "devices cannot require unfencing", rsc->id);
             unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
             return;
 
         } else if (!pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_enabled)) {
             pcmk__config_warn("Resetting \"" XML_RSC_ATTR_REQUIRES "\" for %s "
                               "to \"" PCMK__VALUE_QUORUM "\" because fencing "
                               "is disabled", rsc->id);
             unpack_requires(rsc, PCMK__VALUE_QUORUM, true);
             return;
 
         } else {
             pe__set_resource_flags(rsc,
                                    pe_rsc_needs_fencing|pe_rsc_needs_unfencing);
         }
 
     } else {
         const char *orig_value = value;
 
         if (pcmk_is_set(rsc->flags, pe_rsc_fence_device)) {
             value = PCMK__VALUE_QUORUM;
 
         } else if ((rsc->variant == pe_native)
                    && xml_contains_remote_node(rsc->xml)) {
             value = PCMK__VALUE_QUORUM;
 
         } else if (pcmk_is_set(rsc->cluster->flags, pe_flag_enable_unfencing)) {
             value = PCMK__VALUE_UNFENCING;
 
         } else if (pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_enabled)) {
             value = PCMK__VALUE_FENCING;
 
         } else if (rsc->cluster->no_quorum_policy == no_quorum_ignore) {
             value = PCMK__VALUE_NOTHING;
 
         } else {
             value = PCMK__VALUE_QUORUM;
         }
 
         if (orig_value != NULL) {
             pcmk__config_err("Resetting '" XML_RSC_ATTR_REQUIRES "' for %s "
                              "to '%s' because '%s' is not valid",
                               rsc->id, value, orig_value);
         }
         unpack_requires(rsc, value, true);
         return;
     }
 
     pe_rsc_trace(rsc, "\tRequired to start: %s%s", value,
                  (is_default? " (default)" : ""));
 }
 
-gboolean
-common_unpack(xmlNode * xml_obj, pe_resource_t ** rsc,
-              pe_resource_t * parent, pe_working_set_t * data_set)
+/*!
+ * \internal
+ * \brief Unpack configuration XML for a given resource
+ *
+ * Unpack the XML object containing a resource's configuration into a new
+ * \c pe_resource_t object.
+ *
+ * \param[in]     xml_obj   XML node containing the resource's configuration
+ * \param[out]    rsc       Where to store the unpacked resource information
+ * \param[in]     parent    Resource's parent, if any
+ * \param[in,out] data_set  Cluster working set
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pe__unpack_resource(xmlNode *xml_obj, pe_resource_t **rsc,
+                    pe_resource_t *parent, pe_working_set_t *data_set)
 {
     xmlNode *expanded_xml = NULL;
     xmlNode *ops = NULL;
     const char *value = NULL;
     const char *rclass = NULL; /* Look for this after any templates have been expanded */
     const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
-    bool guest_node = FALSE;
-    bool remote_node = FALSE;
-    bool has_versioned_params = FALSE;
+    bool guest_node = false;
+    bool remote_node = false;
+    bool has_versioned_params = false;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     crm_log_xml_trace(xml_obj, "Processing resource input...");
 
+    CRM_CHECK(rsc != NULL, return EINVAL);
+
     if (id == NULL) {
         pe_err("Must specify id tag in <resource>");
-        return FALSE;
-
-    } else if (rsc == NULL) {
-        pe_err("Nowhere to unpack resource into");
-        return FALSE;
-
+        return pcmk_rc_unpack_error;
     }
 
     if (unpack_template(xml_obj, &expanded_xml, data_set) == FALSE) {
-        return FALSE;
+        return pcmk_rc_unpack_error;
     }
 
     *rsc = calloc(1, sizeof(pe_resource_t));
     (*rsc)->cluster = data_set;
 
     if (expanded_xml) {
         crm_log_xml_trace(expanded_xml, "Expanded resource...");
         (*rsc)->xml = expanded_xml;
         (*rsc)->orig_xml = xml_obj;
 
     } else {
         (*rsc)->xml = xml_obj;
         (*rsc)->orig_xml = NULL;
     }
 
     /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
     rclass = crm_element_value((*rsc)->xml, XML_AGENT_ATTR_CLASS);
     (*rsc)->parent = parent;
 
     ops = find_xml_node((*rsc)->xml, "operations", FALSE);
     (*rsc)->ops_xml = expand_idref(ops, data_set->input);
 
     (*rsc)->variant = get_resource_type(crm_element_name((*rsc)->xml));
     if ((*rsc)->variant == pe_unknown) {
         pe_err("Unknown resource type: %s", crm_element_name((*rsc)->xml));
         free(*rsc);
-        return FALSE;
+        return pcmk_rc_unpack_error;
     }
 
 #if ENABLE_VERSIONED_ATTRS
     (*rsc)->versioned_parameters = create_xml_node(NULL, XML_TAG_RSC_VER_ATTRS);
 #endif
 
     (*rsc)->meta = pcmk__strkey_table(free, free);
     (*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free);
     (*rsc)->known_on = pcmk__strkey_table(NULL, free);
 
     value = crm_element_value((*rsc)->xml, XML_RSC_ATTR_INCARNATION);
     if (value) {
         (*rsc)->id = crm_strdup_printf("%s:%s", id, value);
         add_hash_param((*rsc)->meta, XML_RSC_ATTR_INCARNATION, value);
 
     } else {
         (*rsc)->id = strdup(id);
     }
 
     (*rsc)->fns = &resource_class_functions[(*rsc)->variant];
     pe_rsc_trace((*rsc), "Unpacking resource...");
 
     get_meta_attributes((*rsc)->meta, *rsc, NULL, data_set);
     (*rsc)->parameters = pe_rsc_params(*rsc, NULL, data_set); // \deprecated
 #if ENABLE_VERSIONED_ATTRS
     pe_get_versioned_attributes((*rsc)->versioned_parameters, *rsc, NULL, data_set);
 #endif
 
     (*rsc)->flags = 0;
     pe__set_resource_flags(*rsc, pe_rsc_runnable|pe_rsc_provisional);
 
     if (!pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
         pe__set_resource_flags(*rsc, pe_rsc_managed);
     }
 
     (*rsc)->rsc_cons = NULL;
     (*rsc)->rsc_tickets = NULL;
     (*rsc)->actions = NULL;
     (*rsc)->role = RSC_ROLE_STOPPED;
     (*rsc)->next_role = RSC_ROLE_UNKNOWN;
 
     (*rsc)->recovery_type = recovery_stop_start;
     (*rsc)->stickiness = 0;
     (*rsc)->migration_threshold = INFINITY;
     (*rsc)->failure_timeout = 0;
 
     value = g_hash_table_lookup((*rsc)->meta, XML_CIB_ATTR_PRIORITY);
     (*rsc)->priority = char2score(value);
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_CRITICAL);
     if ((value == NULL) || crm_is_true(value)) {
         pe__set_resource_flags(*rsc, pe_rsc_critical);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_NOTIFY);
     if (crm_is_true(value)) {
         pe__set_resource_flags(*rsc, pe_rsc_notify);
     }
 
     if (xml_contains_remote_node((*rsc)->xml)) {
         (*rsc)->is_remote_node = TRUE;
         if (g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_CONTAINER)) {
-            guest_node = TRUE;
+            guest_node = true;
         } else {
-            remote_node = TRUE;
+            remote_node = true;
         }
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_OP_ATTR_ALLOW_MIGRATE);
 #if ENABLE_VERSIONED_ATTRS
     has_versioned_params = xml_has_children((*rsc)->versioned_parameters);
 #endif
     if (crm_is_true(value) && has_versioned_params) {
         pe_rsc_trace((*rsc), "Migration is disabled for resources with versioned parameters");
     } else if (crm_is_true(value)) {
         pe__set_resource_flags(*rsc, pe_rsc_allow_migrate);
     } else if ((value == NULL) && remote_node && !has_versioned_params) {
         /* By default, we want remote nodes to be able
          * to float around the cluster without having to stop all the
          * resources within the remote-node before moving. Allowing
          * migration support enables this feature. If this ever causes
          * problems, migration support can be explicitly turned off with
          * allow-migrate=false.
          * We don't support migration for versioned resources, though. */
         pe__set_resource_flags(*rsc, pe_rsc_allow_migrate);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MANAGED);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         if (crm_is_true(value)) {
             pe__set_resource_flags(*rsc, pe_rsc_managed);
         } else {
             pe__clear_resource_flags(*rsc, pe_rsc_managed);
         }
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MAINTENANCE);
     if (crm_is_true(value)) {
         pe__clear_resource_flags(*rsc, pe_rsc_managed);
         pe__set_resource_flags(*rsc, pe_rsc_maintenance);
     }
     if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
         pe__clear_resource_flags(*rsc, pe_rsc_managed);
         pe__set_resource_flags(*rsc, pe_rsc_maintenance);
     }
 
     if (pe_rsc_is_clone(uber_parent(*rsc))) {
         value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_UNIQUE);
         if (crm_is_true(value)) {
             pe__set_resource_flags(*rsc, pe_rsc_unique);
         }
         if (detect_promotable(*rsc)) {
             pe__set_resource_flags(*rsc, pe_rsc_promotable);
         }
     } else {
         pe__set_resource_flags(*rsc, pe_rsc_unique);
     }
 
     pe_rsc_trace((*rsc), "Options for %s", (*rsc)->id);
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_RESTART);
     if (pcmk__str_eq(value, "restart", pcmk__str_casei)) {
         (*rsc)->restart_type = pe_restart_restart;
         pe_rsc_trace((*rsc), "\tDependency restart handling: restart");
         pe_warn_once(pe_wo_restart_type,
                      "Support for restart-type is deprecated and will be removed in a future release");
 
     } else {
         (*rsc)->restart_type = pe_restart_ignore;
         pe_rsc_trace((*rsc), "\tDependency restart handling: ignore");
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MULTIPLE);
     if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) {
         (*rsc)->recovery_type = recovery_stop_only;
         pe_rsc_trace((*rsc), "\tMultiple running resource recovery: stop only");
 
     } else if (pcmk__str_eq(value, "block", pcmk__str_casei)) {
         (*rsc)->recovery_type = recovery_block;
         pe_rsc_trace((*rsc), "\tMultiple running resource recovery: block");
 
     } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) {
         (*rsc)->recovery_type = recovery_stop_unexpected;
         pe_rsc_trace((*rsc), "\tMultiple running resource recovery: "
                              "stop unexpected instances");
 
     } else { // "stop_start"
         if (!pcmk__str_eq(value, "stop_start",
                           pcmk__str_casei|pcmk__str_null_matches)) {
             pe_warn("%s is not a valid value for " XML_RSC_ATTR_MULTIPLE
                     ", using default of \"stop_start\"", value);
         }
         (*rsc)->recovery_type = recovery_stop_start;
         pe_rsc_trace((*rsc), "\tMultiple running resource recovery: stop/start");
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_STICKINESS);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         (*rsc)->stickiness = char2score(value);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_STICKINESS);
     if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) {
         (*rsc)->migration_threshold = char2score(value);
         if ((*rsc)->migration_threshold < 0) {
             /* @TODO We use 1 here to preserve previous behavior, but this
              * should probably use the default (INFINITY) or 0 (to disable)
              * instead.
              */
             pe_warn_once(pe_wo_neg_threshold,
                          XML_RSC_ATTR_FAIL_STICKINESS
                          " must be non-negative, using 1 instead");
             (*rsc)->migration_threshold = 1;
         }
     }
 
     if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         pe__set_working_set_flags(data_set, pe_flag_have_stonith_resource);
         pe__set_resource_flags(*rsc, pe_rsc_fence_device);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_REQUIRES);
     unpack_requires(*rsc, value, false);
 
     value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_TIMEOUT);
     if (value != NULL) {
         // Stored as seconds
         (*rsc)->failure_timeout = (int) (crm_parse_interval_spec(value) / 1000);
     }
 
     if (remote_node) {
         GHashTable *params = pe_rsc_params(*rsc, NULL, data_set);
 
         /* Grabbing the value now means that any rules based on node attributes
          * will evaluate to false, so such rules should not be used with
          * reconnect_interval.
          *
          * @TODO Evaluate per node before using
          */
         value = g_hash_table_lookup(params, XML_REMOTE_ATTR_RECONNECT_INTERVAL);
         if (value) {
             /* reconnect delay works by setting failure_timeout and preventing the
              * connection from starting until the failure is cleared. */
             (*rsc)->remote_reconnect_ms = crm_parse_interval_spec(value);
             /* we want to override any default failure_timeout in use when remote
              * reconnect_interval is in use. */ 
             (*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000;
         }
     }
 
     get_target_role(*rsc, &((*rsc)->next_role));
     pe_rsc_trace((*rsc), "\tDesired next state: %s",
                  (*rsc)->next_role != RSC_ROLE_UNKNOWN ? role2text((*rsc)->next_role) : "default");
 
     if ((*rsc)->fns->unpack(*rsc, data_set) == FALSE) {
-        return FALSE;
+        return pcmk_rc_unpack_error;
     }
 
     if (pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)) {
         // This tag must stay exactly the same because it is tested elsewhere
         resource_location(*rsc, NULL, 0, "symmetric_default", data_set);
     } else if (guest_node) {
         /* remote resources tied to a container resource must always be allowed
          * to opt-in to the cluster. Whether the connection resource is actually
          * allowed to be placed on a node is dependent on the container resource */
         resource_location(*rsc, NULL, 0, "remote_connection_default", data_set);
     }
 
     pe_rsc_trace((*rsc), "\tAction notification: %s",
                  pcmk_is_set((*rsc)->flags, pe_rsc_notify)? "required" : "not required");
 
     (*rsc)->utilization = pcmk__strkey_table(free, free);
 
     pe__unpack_dataset_nvpairs((*rsc)->xml, XML_TAG_UTILIZATION, &rule_data,
                                (*rsc)->utilization, NULL, FALSE, data_set);
 
 /* 	data_set->resources = g_list_append(data_set->resources, (*rsc)); */
 
     if (expanded_xml) {
         if (add_template_rsc(xml_obj, data_set) == FALSE) {
-            return FALSE;
+            return pcmk_rc_unpack_error;
         }
     }
-    return TRUE;
+    return pcmk_rc_ok;
 }
 
 void
 common_update_score(pe_resource_t * rsc, const char *id, int score)
 {
     pe_node_t *node = NULL;
 
     node = pe_hash_table_lookup(rsc->allowed_nodes, id);
     if (node != NULL) {
         pe_rsc_trace(rsc, "Updating score for %s on %s: %d + %d", rsc->id, id, node->weight, score);
         node->weight = pcmk__add_scores(node->weight, score);
     }
 
     if (rsc->children) {
         GList *gIter = rsc->children;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
             common_update_score(child_rsc, id, score);
         }
     }
 }
 
 gboolean
 is_parent(pe_resource_t *child, pe_resource_t *rsc)
 {
     pe_resource_t *parent = child;
 
     if (parent == NULL || rsc == NULL) {
         return FALSE;
     }
     while (parent->parent != NULL) {
         if (parent->parent == rsc) {
             return TRUE;
         }
         parent = parent->parent;
     }
     return FALSE;
 }
 
 pe_resource_t *
 uber_parent(pe_resource_t * rsc)
 {
     pe_resource_t *parent = rsc;
 
     if (parent == NULL) {
         return NULL;
     }
     while (parent->parent != NULL && parent->parent->variant != pe_container) {
         parent = parent->parent;
     }
     return parent;
 }
 
 void
 common_free(pe_resource_t * rsc)
 {
     if (rsc == NULL) {
         return;
     }
 
     pe_rsc_trace(rsc, "Freeing %s %d", rsc->id, rsc->variant);
 
     g_list_free(rsc->rsc_cons);
     g_list_free(rsc->rsc_cons_lhs);
     g_list_free(rsc->rsc_tickets);
     g_list_free(rsc->dangling_migrations);
 
     if (rsc->parameter_cache != NULL) {
         g_hash_table_destroy(rsc->parameter_cache);
     }
 #if ENABLE_VERSIONED_ATTRS
     if (rsc->versioned_parameters != NULL) {
         free_xml(rsc->versioned_parameters);
     }
 #endif
     if (rsc->meta != NULL) {
         g_hash_table_destroy(rsc->meta);
     }
     if (rsc->utilization != NULL) {
         g_hash_table_destroy(rsc->utilization);
     }
 
     if ((rsc->parent == NULL) && pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
         free_xml(rsc->xml);
         rsc->xml = NULL;
         free_xml(rsc->orig_xml);
         rsc->orig_xml = NULL;
 
         /* if rsc->orig_xml, then rsc->xml is an expanded xml from a template */
     } else if (rsc->orig_xml) {
         free_xml(rsc->xml);
         rsc->xml = NULL;
     }
     if (rsc->running_on) {
         g_list_free(rsc->running_on);
         rsc->running_on = NULL;
     }
     if (rsc->known_on) {
         g_hash_table_destroy(rsc->known_on);
         rsc->known_on = NULL;
     }
     if (rsc->actions) {
         g_list_free(rsc->actions);
         rsc->actions = NULL;
     }
     if (rsc->allowed_nodes) {
         g_hash_table_destroy(rsc->allowed_nodes);
         rsc->allowed_nodes = NULL;
     }
     g_list_free(rsc->fillers);
     g_list_free(rsc->rsc_location);
     pe_rsc_trace(rsc, "Resource freed");
     free(rsc->id);
     free(rsc->clone_name);
     free(rsc->allocated_to);
     free(rsc->variant_opaque);
     free(rsc->pending_task);
     free(rsc);
 }
 
 /*!
  * \brief
  * \internal Find a node (and optionally count all) where resource is active
  *
  * \param[in]  rsc          Resource to check
  * \param[out] count_all    If not NULL, will be set to count of active nodes
  * \param[out] count_clean  If not NULL, will be set to count of clean nodes
  *
  * \return An active node (or NULL if resource is not active anywhere)
  *
  * \note The order of preference is: an active node that is the resource's
  *       partial migration source; if the resource's "requires" is "quorum" or
  *       "nothing", the first active node in the list that is clean and online;
  *       the first active node in the list.
  */
 pe_node_t *
 pe__find_active_on(const pe_resource_t *rsc, unsigned int *count_all,
                    unsigned int *count_clean)
 {
     pe_node_t *active = NULL;
     pe_node_t *node = NULL;
     bool keep_looking = FALSE;
     bool is_happy = FALSE;
 
     if (count_all) {
         *count_all = 0;
     }
     if (count_clean) {
         *count_clean = 0;
     }
     if (rsc == NULL) {
         return NULL;
     }
 
     for (GList *node_iter = rsc->running_on; node_iter != NULL;
          node_iter = node_iter->next) {
 
         node = node_iter->data;
         keep_looking = FALSE;
 
         is_happy = node->details->online && !node->details->unclean;
 
         if (count_all) {
             ++*count_all;
         }
         if (count_clean && is_happy) {
             ++*count_clean;
         }
         if (count_all || count_clean) {
             // If we're counting, we need to go through entire list
             keep_looking = TRUE;
         }
 
         if (rsc->partial_migration_source != NULL) {
             if (node->details == rsc->partial_migration_source->details) {
                 // This is the migration source
                 active = node;
             } else {
                 keep_looking = TRUE;
             }
         } else if (!pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) {
             if (is_happy && (!active || !active->details->online
                              || active->details->unclean)) {
                 // This is the first clean node
                 active = node;
             } else {
                 keep_looking = TRUE;
             }
         }
         if (active == NULL) {
             // This is first node in list
             active = node;
         }
 
         if (keep_looking == FALSE) {
             // Don't waste time iterating if we don't have to
             break;
         }
     }
     return active;
 }
 
 /*!
  * \brief
  * \internal Find and count active nodes according to "requires"
  *
  * \param[in]  rsc    Resource to check
  * \param[out] count  If not NULL, will be set to count of active nodes
  *
  * \return An active node (or NULL if resource is not active anywhere)
  *
  * \note This is a convenience wrapper for pe__find_active_on() where the count
  *       of all active nodes or only clean active nodes is desired according to
  *       the "requires" meta-attribute.
  */
 pe_node_t *
 pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count)
 {
     if (rsc && !pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) {
         return pe__find_active_on(rsc, NULL, count);
     }
     return pe__find_active_on(rsc, count, NULL);
 }
 
 void
 pe__count_common(pe_resource_t *rsc)
 {
     if (rsc->children != NULL) {
         for (GList *item = rsc->children; item != NULL; item = item->next) {
             ((pe_resource_t *) item->data)->fns->count(item->data);
         }
 
     } else if (!pcmk_is_set(rsc->flags, pe_rsc_orphan)
                || (rsc->role > RSC_ROLE_STOPPED)) {
         rsc->cluster->ninstances++;
         if (pe__resource_is_disabled(rsc)) {
             rsc->cluster->disabled_resources++;
         }
         if (pcmk_is_set(rsc->flags, pe_rsc_block)) {
             rsc->cluster->blocked_resources++;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Update a resource's next role
  *
  * \param[in,out] rsc   Resource to be updated
  * \param[in]     role  Resource's new next role
  * \param[in]     why   Human-friendly reason why role is changing (for logs)
  */
 void
 pe__set_next_role(pe_resource_t *rsc, enum rsc_role_e role, const char *why)
 {
     CRM_ASSERT((rsc != NULL) && (why != NULL));
     if (rsc->next_role != role) {
         pe_rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
                      rsc->id, role2text(rsc->next_role), role2text(role), why);
         rsc->next_role = role;
     }
 }
diff --git a/lib/pengine/group.c b/lib/pengine/group.c
index 14e26aec4a..3f877a807c 100644
--- a/lib/pengine/group.c
+++ b/lib/pengine/group.c
@@ -1,425 +1,426 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdint.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/output.h>
 #include <crm/common/strings_internal.h>
 #include <crm/common/xml_internal.h>
 #include <pe_status_private.h>
 
 #define VARIANT_GROUP 1
 #include "./variant.h"
 
 static int
 inactive_resources(pe_resource_t *rsc)
 {
     int retval = 0;
 
     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         if (!child_rsc->fns->active(child_rsc, TRUE)) {
             retval++;
         }
     }
 
     return retval;
 }
 
 static void
 group_header(pcmk__output_t *out, int *rc, pe_resource_t *rsc, int n_inactive, bool show_inactive)
 {
     char *attrs = NULL;
     size_t len = 0;
 
     if (n_inactive > 0 && !show_inactive) {
         char *word = crm_strdup_printf("%d member%s inactive", n_inactive, pcmk__plural_s(n_inactive));
         pcmk__add_separated_word(&attrs, &len, word, ", ");
         free(word);
     }
 
     if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
         pcmk__add_separated_word(&attrs, &len, "unmanaged", ", ");
     }
 
     if (pe__resource_is_disabled(rsc)) {
         pcmk__add_separated_word(&attrs, &len, "disabled", ", ");
     }
 
     if (attrs) {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s (%s)",
                                  rsc->id, attrs);
         free(attrs);
     } else {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s", rsc->id);
     }
 }
 
 static bool
 skip_child_rsc(pe_resource_t *rsc, pe_resource_t *child, gboolean parent_passes,
                GList *only_rsc, uint32_t show_opts)
 {
     bool star_list = pcmk__list_of_1(only_rsc) &&
                      pcmk__str_eq("*", g_list_first(only_rsc)->data, pcmk__str_none);
     bool child_filtered = child->fns->is_filtered(child, only_rsc, FALSE);
     bool child_active = child->fns->active(child, FALSE);
     bool show_inactive = pcmk_is_set(show_opts, pcmk_show_inactive_rscs);
 
     /* If the resource is in only_rsc by name (so, ignoring "*") then allow
      * it regardless of if it's active or not.
      */
     if (!star_list && !child_filtered) {
         return false;
 
     } else if (!child_filtered && (child_active || show_inactive)) {
         return false;
 
     } else if (parent_passes && (child_active || show_inactive)) {
         return false;
 
     }
 
     return true;
 }
 
 gboolean
 group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set)
 {
     xmlNode *xml_obj = rsc->xml;
     xmlNode *xml_native_rsc = NULL;
     group_variant_data_t *group_data = NULL;
     const char *group_ordered = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_ORDERED);
     const char *group_colocated = g_hash_table_lookup(rsc->meta, "collocated");
     const char *clone_id = NULL;
 
     pe_rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     group_data = calloc(1, sizeof(group_variant_data_t));
     group_data->num_children = 0;
     group_data->first_child = NULL;
     group_data->last_child = NULL;
     rsc->variant_opaque = group_data;
 
     // We don't actually need the null checks but it speeds up the common case
     if ((group_ordered == NULL)
         || (crm_str_to_boolean(group_ordered, &(group_data->ordered)) < 0)) {
         group_data->ordered = TRUE;
     }
     if ((group_colocated == NULL)
         || (crm_str_to_boolean(group_colocated, &(group_data->colocated)) < 0)) {
         group_data->colocated = TRUE;
     }
 
     clone_id = crm_element_value(rsc->xml, XML_RSC_ATTR_INCARNATION);
 
     for (xml_native_rsc = pcmk__xe_first_child(xml_obj); xml_native_rsc != NULL;
          xml_native_rsc = pcmk__xe_next(xml_native_rsc)) {
 
         if (pcmk__str_eq((const char *)xml_native_rsc->name,
                          XML_CIB_TAG_RESOURCE, pcmk__str_none)) {
             pe_resource_t *new_rsc = NULL;
 
             crm_xml_add(xml_native_rsc, XML_RSC_ATTR_INCARNATION, clone_id);
-            if (common_unpack(xml_native_rsc, &new_rsc, rsc, data_set) == FALSE) {
+            if (pe__unpack_resource(xml_native_rsc, &new_rsc, rsc,
+                                    data_set) != pcmk_rc_ok) {
                 pe_err("Failed unpacking resource %s", crm_element_value(xml_obj, XML_ATTR_ID));
                 if (new_rsc != NULL && new_rsc->fns != NULL) {
                     new_rsc->fns->free(new_rsc);
                 }
                 continue;
             }
 
             group_data->num_children++;
             rsc->children = g_list_append(rsc->children, new_rsc);
 
             if (group_data->first_child == NULL) {
                 group_data->first_child = new_rsc;
             }
             group_data->last_child = new_rsc;
             pe_rsc_trace(rsc, "Added %s member %s", rsc->id, new_rsc->id);
         }
     }
 
     if (group_data->num_children == 0) {
         pcmk__config_warn("Group %s does not have any children", rsc->id);
         return TRUE; // Allow empty groups, children can be added later
     }
 
     pe_rsc_trace(rsc, "Added %d children to resource %s...", group_data->num_children, rsc->id);
 
     return TRUE;
 }
 
 gboolean
 group_active(pe_resource_t * rsc, gboolean all)
 {
     gboolean c_all = TRUE;
     gboolean c_any = FALSE;
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         if (child_rsc->fns->active(child_rsc, all)) {
             c_any = TRUE;
         } else {
             c_all = FALSE;
         }
     }
 
     if (c_any == FALSE) {
         return FALSE;
     } else if (all && c_all == FALSE) {
         return FALSE;
     }
     return TRUE;
 }
 
 static void
 group_print_xml(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
 {
     GList *gIter = rsc->children;
     char *child_text = crm_strdup_printf("%s     ", pre_text);
 
     status_print("%s<group id=\"%s\" ", pre_text, rsc->id);
     status_print("number_resources=\"%d\" ", g_list_length(rsc->children));
     status_print(">\n");
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         child_rsc->fns->print(child_rsc, child_text, options, print_data);
     }
 
     status_print("%s</group>\n", pre_text);
     free(child_text);
 }
 
 void
 group_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data)
 {
     char *child_text = NULL;
     GList *gIter = rsc->children;
 
     if (pre_text == NULL) {
         pre_text = " ";
     }
 
     if (options & pe_print_xml) {
         group_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     child_text = crm_strdup_printf("%s    ", pre_text);
 
     status_print("%sResource Group: %s", pre_text ? pre_text : "", rsc->id);
 
     if (options & pe_print_html) {
         status_print("\n<ul>\n");
 
     } else if ((options & pe_print_log) == 0) {
         status_print("\n");
     }
 
     if (options & pe_print_brief) {
         print_rscs_brief(rsc->children, child_text, options, print_data, TRUE);
 
     } else {
         for (; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
             if (options & pe_print_html) {
                 status_print("<li>\n");
             }
             child_rsc->fns->print(child_rsc, child_text, options, print_data);
             if (options & pe_print_html) {
                 status_print("</li>\n");
             }
         }
     }
 
     if (options & pe_print_html) {
         status_print("</ul>\n");
     }
     free(child_text);
 }
 
 PCMK__OUTPUT_ARGS("group", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__group_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     GList *gIter = rsc->children;
     char *count = pcmk__itoa(g_list_length(gIter));
 
     int rc = pcmk_rc_no_output;
 
     gboolean parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                              (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         free(count);
         return rc;
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
             continue;
         }
 
         if (rc == pcmk_rc_no_output) {
             rc = pe__name_and_nvpairs_xml(out, true, "group", 4
                                           , "id", rsc->id
                                           , "number_resources", count
                                           , "managed", pe__rsc_bool_str(rsc, pe_rsc_managed)
                                           , "disabled", pcmk__btoa(pe__resource_is_disabled(rsc)));
             free(count);
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
 
         out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc,
 					 only_node, only_rsc);
     }
 
     if (rc == pcmk_rc_ok) {
         pcmk__output_xml_pop_parent(out);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("group", "uint32_t", "pe_resource_t *", "GList *", "GList *")
 int
 pe__group_default(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     int rc = pcmk_rc_no_output;
 
     gboolean parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                              (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     gboolean active = rsc->fns->active(rsc, TRUE);
     gboolean partially_active = rsc->fns->active(rsc, FALSE);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_brief)) {
         GList *rscs = pe__filter_rsc_list(rsc->children, only_rsc);
 
         if (rscs != NULL) {
             group_header(out, &rc, rsc, !active && partially_active ? inactive_resources(rsc) : 0,
                          pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
             pe__rscs_brief_output(out, rscs, show_opts | pcmk_show_inactive_rscs);
 
             rc = pcmk_rc_ok;
             g_list_free(rscs);
         }
 
     } else {
         for (GList *gIter = rsc->children; gIter; gIter = gIter->next) {
             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
             if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
                 continue;
             }
 
             group_header(out, &rc, rsc, !active && partially_active ? inactive_resources(rsc) : 0,
                          pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
             out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
                          child_rsc, only_node, only_rsc);
         }
     }
 
 	PCMK__OUTPUT_LIST_FOOTER(out, rc);
 
     return rc;
 }
 
 void
 group_free(pe_resource_t * rsc)
 {
     CRM_CHECK(rsc != NULL, return);
 
     pe_rsc_trace(rsc, "Freeing %s", rsc->id);
 
     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
         CRM_ASSERT(child_rsc);
         pe_rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
         child_rsc->fns->free(child_rsc);
     }
 
     pe_rsc_trace(rsc, "Freeing child list");
     g_list_free(rsc->children);
 
     common_free(rsc);
 }
 
 enum rsc_role_e
 group_resource_state(const pe_resource_t * rsc, gboolean current)
 {
     enum rsc_role_e group_role = RSC_ROLE_UNKNOWN;
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
         enum rsc_role_e role = child_rsc->fns->state(child_rsc, current);
 
         if (role > group_role) {
             group_role = role;
         }
     }
 
     pe_rsc_trace(rsc, "%s role: %s", rsc->id, role2text(group_role));
     return group_role;
 }
 
 gboolean
 pe__group_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent)
 {
     gboolean passes = FALSE;
 
     if (check_parent && pcmk__str_in_list(rsc_printable_id(uber_parent(rsc)), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else if (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else {
         for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
             pe_resource_t *child_rsc = (pe_resource_t *) gIter->data;
 
             if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             }
         }
     }
 
     return !passes;
 }
diff --git a/lib/pengine/pe_status_private.h b/lib/pengine/pe_status_private.h
index 00956cbd0b..4b5ddf63f1 100644
--- a/lib/pengine/pe_status_private.h
+++ b/lib/pengine/pe_status_private.h
@@ -1,75 +1,79 @@
 /*
  * Copyright 2018-2022 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 PE_STATUS_PRIVATE__H
 #  define PE_STATUS_PRIVATE__H
 
 /* This header is for the sole use of libpe_status, so that functions can be
  * declared with G_GNUC_INTERNAL for efficiency.
  */
 
 #if defined(PCMK__UNIT_TESTING)
 #undef G_GNUC_INTERNAL
 #define G_GNUC_INTERNAL
 #endif
 
 #  define status_print(fmt, args...)           \
    if(options & pe_print_html) {           \
        FILE *stream = print_data;      \
        fprintf(stream, fmt, ##args);       \
    } else if(options & pe_print_printf || options & pe_print_ncurses) {      \
        FILE *stream = print_data;      \
        fprintf(stream, fmt, ##args);       \
    } else if(options & pe_print_xml) {     \
        FILE *stream = print_data;      \
        fprintf(stream, fmt, ##args);       \
    } else if(options & pe_print_log) {     \
        int log_level = *(int*)print_data;  \
        do_crm_log(log_level, fmt, ##args); \
    }
 
 G_GNUC_INTERNAL
 pe_resource_t *pe__create_clone_child(pe_resource_t *rsc,
                                       pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 void pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid,
                     pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gint pe__cmp_rsc_priority(gconstpointer a, gconstpointer b);
 
+G_GNUC_INTERNAL
+gboolean pe__unpack_resource(xmlNode *xml_obj, pe_resource_t **rsc,
+                             pe_resource_t *parent, pe_working_set_t *data_set);
+
 G_GNUC_INTERNAL
 gboolean unpack_remote_nodes(xmlNode *xml_resources, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gboolean unpack_resources(xmlNode *xml_resources, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gboolean unpack_config(xmlNode *config, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gboolean unpack_nodes(xmlNode *xml_nodes, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gboolean unpack_tags(xmlNode *xml_tags, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 gboolean unpack_status(xmlNode *status, pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 op_digest_cache_t *pe__compare_fencing_digest(pe_resource_t *rsc,
                                               const char *agent,
                                               pe_node_t *node,
                                               pe_working_set_t *data_set);
 
 G_GNUC_INTERNAL
 void pe__unpack_node_health_scores(pe_working_set_t *data_set);
 
 #endif  // PE_STATUS_PRIVATE__H
diff --git a/lib/pengine/unpack.c b/lib/pengine/unpack.c
index 6c6cf17df1..96b502cddd 100644
--- a/lib/pengine/unpack.c
+++ b/lib/pengine/unpack.c
@@ -1,4326 +1,4329 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <string.h>
 #include <glib.h>
 #include <time.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <crm/common/util.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 
 CRM_TRACE_INIT_DATA(pe_status);
 
 /* This uses pcmk__set_flags_as()/pcmk__clear_flags_as() directly rather than
  * use pe__set_working_set_flags()/pe__clear_working_set_flags() so that the
  * flag is stringified more readably in log messages.
  */
 #define set_config_flag(data_set, option, flag) do {                        \
         const char *scf_value = pe_pref((data_set)->config_hash, (option)); \
         if (scf_value != NULL) {                                            \
             if (crm_is_true(scf_value)) {                                   \
                 (data_set)->flags = pcmk__set_flags_as(__func__, __LINE__,  \
                                     LOG_TRACE, "Working set",               \
                                     crm_system_name, (data_set)->flags,     \
                                     (flag), #flag);                         \
             } else {                                                        \
                 (data_set)->flags = pcmk__clear_flags_as(__func__, __LINE__,\
                                     LOG_TRACE, "Working set",               \
                                     crm_system_name, (data_set)->flags,     \
                                     (flag), #flag);                         \
             }                                                               \
         }                                                                   \
     } while(0)
 
 static void unpack_rsc_op(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op,
                           xmlNode **last_failure,
                           enum action_fail_response *failed,
                           pe_working_set_t *data_set);
 static void determine_remote_online_status(pe_working_set_t *data_set,
                                            pe_node_t *this_node);
 static void add_node_attrs(xmlNode *attrs, pe_node_t *node, bool overwrite,
                            pe_working_set_t *data_set);
 static void determine_online_status(xmlNode *node_state, pe_node_t *this_node,
                                     pe_working_set_t *data_set);
 
 static void unpack_node_lrm(pe_node_t *node, xmlNode *xml,
                             pe_working_set_t *data_set);
 
 
 // Bitmask for warnings we only want to print once
 uint32_t pe_wo = 0;
 
 static gboolean
 is_dangling_guest_node(pe_node_t *node)
 {
     /* we are looking for a remote-node that was supposed to be mapped to a
      * container resource, but all traces of that container have disappeared 
      * from both the config and the status section. */
     if (pe__is_guest_or_remote_node(node) &&
         node->details->remote_rsc &&
         node->details->remote_rsc->container == NULL &&
         pcmk_is_set(node->details->remote_rsc->flags,
                     pe_rsc_orphan_container_filler)) {
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \brief Schedule a fence action for a node
  *
  * \param[in,out] data_set  Current working set of cluster
  * \param[in,out] node      Node to fence
  * \param[in]     reason    Text description of why fencing is needed
  * \param[in]     priority_delay  Whether to consider `priority-fencing-delay`
  */
 void
 pe_fence_node(pe_working_set_t * data_set, pe_node_t * node,
               const char *reason, bool priority_delay)
 {
     CRM_CHECK(node, return);
 
     /* A guest node is fenced by marking its container as failed */
     if (pe__is_guest_node(node)) {
         pe_resource_t *rsc = node->details->remote_rsc->container;
 
         if (!pcmk_is_set(rsc->flags, pe_rsc_failed)) {
             if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
                 crm_notice("Not fencing guest node %s "
                            "(otherwise would because %s): "
                            "its guest resource %s is unmanaged",
                            node->details->uname, reason, rsc->id);
             } else {
                 crm_warn("Guest node %s will be fenced "
                          "(by recovering its guest resource %s): %s",
                          node->details->uname, rsc->id, reason);
 
                 /* We don't mark the node as unclean because that would prevent the
                  * node from running resources. We want to allow it to run resources
                  * in this transition if the recovery succeeds.
                  */
                 node->details->remote_requires_reset = TRUE;
                 pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             }
         }
 
     } else if (is_dangling_guest_node(node)) {
         crm_info("Cleaning up dangling connection for guest node %s: "
                  "fencing was already done because %s, "
                  "and guest resource no longer exists",
                  node->details->uname, reason);
         pe__set_resource_flags(node->details->remote_rsc,
                                pe_rsc_failed|pe_rsc_stop);
 
     } else if (pe__is_remote_node(node)) {
         pe_resource_t *rsc = node->details->remote_rsc;
 
         if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
             crm_notice("Not fencing remote node %s "
                        "(otherwise would because %s): connection is unmanaged",
                        node->details->uname, reason);
         } else if(node->details->remote_requires_reset == FALSE) {
             node->details->remote_requires_reset = TRUE;
             crm_warn("Remote node %s %s: %s",
                      node->details->uname,
                      pe_can_fence(data_set, node)? "will be fenced" : "is unclean",
                      reason);
         }
         node->details->unclean = TRUE;
         // No need to apply `priority-fencing-delay` for remote nodes
         pe_fence_op(node, NULL, TRUE, reason, FALSE, data_set);
 
     } else if (node->details->unclean) {
         crm_trace("Cluster node %s %s because %s",
                   node->details->uname,
                   pe_can_fence(data_set, node)? "would also be fenced" : "also is unclean",
                   reason);
 
     } else {
         crm_warn("Cluster node %s %s: %s",
                  node->details->uname,
                  pe_can_fence(data_set, node)? "will be fenced" : "is unclean",
                  reason);
         node->details->unclean = TRUE;
         pe_fence_op(node, NULL, TRUE, reason, priority_delay, data_set);
     }
 }
 
 // @TODO xpaths can't handle templates, rules, or id-refs
 
 // nvpair with provides or requires set to unfencing
 #define XPATH_UNFENCING_NVPAIR XML_CIB_TAG_NVPAIR                \
     "[(@" XML_NVPAIR_ATTR_NAME "='" PCMK_STONITH_PROVIDES "'"    \
     "or @" XML_NVPAIR_ATTR_NAME "='" XML_RSC_ATTR_REQUIRES "') " \
     "and @" XML_NVPAIR_ATTR_VALUE "='" PCMK__VALUE_UNFENCING "']"
 
 // unfencing in rsc_defaults or any resource
 #define XPATH_ENABLE_UNFENCING \
     "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES   \
     "//" XML_TAG_META_SETS "/" XPATH_UNFENCING_NVPAIR                                               \
     "|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RSCCONFIG  \
     "/" XML_TAG_META_SETS "/" XPATH_UNFENCING_NVPAIR
 
 static void
 set_if_xpath(uint64_t flag, const char *xpath, pe_working_set_t *data_set)
 {
     xmlXPathObjectPtr result = NULL;
 
     if (!pcmk_is_set(data_set->flags, flag)) {
         result = xpath_search(data_set->input, xpath);
         if (result && (numXpathResults(result) > 0)) {
             pe__set_working_set_flags(data_set, flag);
         }
         freeXpathObject(result);
     }
 }
 
 gboolean
 unpack_config(xmlNode * config, pe_working_set_t * data_set)
 {
     const char *value = NULL;
     GHashTable *config_hash = pcmk__strkey_table(free, free);
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     data_set->config_hash = config_hash;
 
     pe__unpack_dataset_nvpairs(config, XML_CIB_TAG_PROPSET, &rule_data, config_hash,
                                CIB_OPTIONS_FIRST, FALSE, data_set);
 
     verify_pe_options(data_set->config_hash);
 
     set_config_flag(data_set, "enable-startup-probes", pe_flag_startup_probes);
     if (!pcmk_is_set(data_set->flags, pe_flag_startup_probes)) {
         crm_info("Startup probes: disabled (dangerous)");
     }
 
     value = pe_pref(data_set->config_hash, XML_ATTR_HAVE_WATCHDOG);
     if (value && crm_is_true(value)) {
         crm_info("Watchdog-based self-fencing will be performed via SBD if "
                  "fencing is required and stonith-watchdog-timeout is nonzero");
         pe__set_working_set_flags(data_set, pe_flag_have_stonith_resource);
     }
 
     /* Set certain flags via xpath here, so they can be used before the relevant
      * configuration sections are unpacked.
      */
     set_if_xpath(pe_flag_enable_unfencing, XPATH_ENABLE_UNFENCING, data_set);
 
     value = pe_pref(data_set->config_hash, "stonith-timeout");
     data_set->stonith_timeout = (int) crm_parse_interval_spec(value);
     crm_debug("STONITH timeout: %d", data_set->stonith_timeout);
 
     set_config_flag(data_set, "stonith-enabled", pe_flag_stonith_enabled);
     crm_debug("STONITH of failed nodes is %s",
               pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)? "enabled" : "disabled");
 
     data_set->stonith_action = pe_pref(data_set->config_hash, "stonith-action");
     if (!strcmp(data_set->stonith_action, "poweroff")) {
         pe_warn_once(pe_wo_poweroff,
                      "Support for stonith-action of 'poweroff' is deprecated "
                      "and will be removed in a future release (use 'off' instead)");
         data_set->stonith_action = "off";
     }
     crm_trace("STONITH will %s nodes", data_set->stonith_action);
 
     set_config_flag(data_set, "concurrent-fencing", pe_flag_concurrent_fencing);
     crm_debug("Concurrent fencing is %s",
               pcmk_is_set(data_set->flags, pe_flag_concurrent_fencing)? "enabled" : "disabled");
 
     value = pe_pref(data_set->config_hash,
                     XML_CONFIG_ATTR_PRIORITY_FENCING_DELAY);
     if (value) {
         data_set->priority_fencing_delay = crm_parse_interval_spec(value) / 1000;
         crm_trace("Priority fencing delay is %ds", data_set->priority_fencing_delay);
     }
 
     set_config_flag(data_set, "stop-all-resources", pe_flag_stop_everything);
     crm_debug("Stop all active resources: %s",
               pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stop_everything)));
 
     set_config_flag(data_set, "symmetric-cluster", pe_flag_symmetric_cluster);
     if (pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)) {
         crm_debug("Cluster is symmetric" " - resources can run anywhere by default");
     }
 
     value = pe_pref(data_set->config_hash, "no-quorum-policy");
 
     if (pcmk__str_eq(value, "ignore", pcmk__str_casei)) {
         data_set->no_quorum_policy = no_quorum_ignore;
 
     } else if (pcmk__str_eq(value, "freeze", pcmk__str_casei)) {
         data_set->no_quorum_policy = no_quorum_freeze;
 
     } else if (pcmk__str_eq(value, "demote", pcmk__str_casei)) {
         data_set->no_quorum_policy = no_quorum_demote;
 
     } else if (pcmk__str_eq(value, "suicide", pcmk__str_casei)) {
         if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
             int do_panic = 0;
 
             crm_element_value_int(data_set->input, XML_ATTR_QUORUM_PANIC,
                                   &do_panic);
             if (do_panic || pcmk_is_set(data_set->flags, pe_flag_have_quorum)) {
                 data_set->no_quorum_policy = no_quorum_suicide;
             } else {
                 crm_notice("Resetting no-quorum-policy to 'stop': cluster has never had quorum");
                 data_set->no_quorum_policy = no_quorum_stop;
             }
         } else {
             pcmk__config_err("Resetting no-quorum-policy to 'stop' because "
                              "fencing is disabled");
             data_set->no_quorum_policy = no_quorum_stop;
         }
 
     } else {
         data_set->no_quorum_policy = no_quorum_stop;
     }
 
     switch (data_set->no_quorum_policy) {
         case no_quorum_freeze:
             crm_debug("On loss of quorum: Freeze resources");
             break;
         case no_quorum_stop:
             crm_debug("On loss of quorum: Stop ALL resources");
             break;
         case no_quorum_demote:
             crm_debug("On loss of quorum: "
                       "Demote promotable resources and stop other resources");
             break;
         case no_quorum_suicide:
             crm_notice("On loss of quorum: Fence all remaining nodes");
             break;
         case no_quorum_ignore:
             crm_notice("On loss of quorum: Ignore");
             break;
     }
 
     set_config_flag(data_set, "stop-orphan-resources", pe_flag_stop_rsc_orphans);
     crm_trace("Orphan resources are %s",
               pcmk_is_set(data_set->flags, pe_flag_stop_rsc_orphans)? "stopped" : "ignored");
 
     set_config_flag(data_set, "stop-orphan-actions", pe_flag_stop_action_orphans);
     crm_trace("Orphan resource actions are %s",
               pcmk_is_set(data_set->flags, pe_flag_stop_action_orphans)? "stopped" : "ignored");
 
     value = pe_pref(data_set->config_hash, "remove-after-stop");
     if (value != NULL) {
         if (crm_is_true(value)) {
             pe__set_working_set_flags(data_set, pe_flag_remove_after_stop);
 #ifndef PCMK__COMPAT_2_0
             pe_warn_once(pe_wo_remove_after,
                          "Support for the remove-after-stop cluster property is"
                          " deprecated and will be removed in a future release");
 #endif
         } else {
             pe__clear_working_set_flags(data_set, pe_flag_remove_after_stop);
         }
     }
 
     set_config_flag(data_set, "maintenance-mode", pe_flag_maintenance_mode);
     crm_trace("Maintenance mode: %s",
               pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)));
 
     set_config_flag(data_set, "start-failure-is-fatal", pe_flag_start_failure_fatal);
     crm_trace("Start failures are %s",
               pcmk_is_set(data_set->flags, pe_flag_start_failure_fatal)? "always fatal" : "handled by failcount");
 
     if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
         set_config_flag(data_set, "startup-fencing", pe_flag_startup_fencing);
     }
     if (pcmk_is_set(data_set->flags, pe_flag_startup_fencing)) {
         crm_trace("Unseen nodes will be fenced");
     } else {
         pe_warn_once(pe_wo_blind, "Blind faith: not fencing unseen nodes");
     }
 
     pe__unpack_node_health_scores(data_set);
 
     data_set->placement_strategy = pe_pref(data_set->config_hash, "placement-strategy");
     crm_trace("Placement strategy: %s", data_set->placement_strategy);
 
     set_config_flag(data_set, "shutdown-lock", pe_flag_shutdown_lock);
     crm_trace("Resources will%s be locked to cleanly shut down nodes",
               (pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)? "" : " not"));
     if (pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)) {
         value = pe_pref(data_set->config_hash,
                         XML_CONFIG_ATTR_SHUTDOWN_LOCK_LIMIT);
         data_set->shutdown_lock = crm_parse_interval_spec(value) / 1000;
         crm_trace("Shutdown locks expire after %us", data_set->shutdown_lock);
     }
 
     return TRUE;
 }
 
 pe_node_t *
 pe_create_node(const char *id, const char *uname, const char *type,
                const char *score, pe_working_set_t * data_set)
 {
     pe_node_t *new_node = NULL;
 
     if (pe_find_node(data_set->nodes, uname) != NULL) {
         pcmk__config_warn("More than one node entry has name '%s'", uname);
     }
 
     new_node = calloc(1, sizeof(pe_node_t));
     if (new_node == NULL) {
         return NULL;
     }
 
     new_node->weight = char2score(score);
     new_node->fixed = FALSE;
     new_node->details = calloc(1, sizeof(struct pe_node_shared_s));
 
     if (new_node->details == NULL) {
         free(new_node);
         return NULL;
     }
 
     crm_trace("Creating node for entry %s/%s", uname, id);
     new_node->details->id = id;
     new_node->details->uname = uname;
     new_node->details->online = FALSE;
     new_node->details->shutdown = FALSE;
     new_node->details->rsc_discovery_enabled = TRUE;
     new_node->details->running_rsc = NULL;
     new_node->details->data_set = data_set;
 
     if (pcmk__str_eq(type, "member", pcmk__str_null_matches | pcmk__str_casei)) {
         new_node->details->type = node_member;
 
     } else if (pcmk__str_eq(type, "remote", pcmk__str_casei)) {
         new_node->details->type = node_remote;
         pe__set_working_set_flags(data_set, pe_flag_have_remote_nodes);
 
     } else {
         /* @COMPAT 'ping' is the default for backward compatibility, but it
          * should be changed to 'member' at a compatibility break
          */
         if (!pcmk__str_eq(type, "ping", pcmk__str_casei)) {
             pcmk__config_warn("Node %s has unrecognized type '%s', "
                               "assuming 'ping'", pcmk__s(uname, "without name"),
                               type);
         }
         pe_warn_once(pe_wo_ping_node,
                      "Support for nodes of type 'ping' (such as %s) is "
                      "deprecated and will be removed in a future release",
                      pcmk__s(uname, "unnamed node"));
         new_node->details->type = node_ping;
     }
 
     new_node->details->attrs = pcmk__strkey_table(free, free);
 
     if (pe__is_guest_or_remote_node(new_node)) {
         g_hash_table_insert(new_node->details->attrs, strdup(CRM_ATTR_KIND),
                             strdup("remote"));
     } else {
         g_hash_table_insert(new_node->details->attrs, strdup(CRM_ATTR_KIND),
                             strdup("cluster"));
     }
 
     new_node->details->utilization = pcmk__strkey_table(free, free);
     new_node->details->digest_cache = pcmk__strkey_table(free,
                                                           pe__free_digests);
 
     data_set->nodes = g_list_insert_sorted(data_set->nodes, new_node,
                                            pe__cmp_node_name);
     return new_node;
 }
 
 static const char *
 expand_remote_rsc_meta(xmlNode *xml_obj, xmlNode *parent, pe_working_set_t *data)
 {
     xmlNode *attr_set = NULL;
     xmlNode *attr = NULL;
 
     const char *container_id = ID(xml_obj);
     const char *remote_name = NULL;
     const char *remote_server = NULL;
     const char *remote_port = NULL;
     const char *connect_timeout = "60s";
     const char *remote_allow_migrate=NULL;
     const char *is_managed = NULL;
 
     for (attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
          attr_set = pcmk__xe_next(attr_set)) {
 
         if (!pcmk__str_eq((const char *)attr_set->name, XML_TAG_META_SETS,
                           pcmk__str_casei)) {
             continue;
         }
 
         for (attr = pcmk__xe_first_child(attr_set); attr != NULL;
              attr = pcmk__xe_next(attr)) {
             const char *value = crm_element_value(attr, XML_NVPAIR_ATTR_VALUE);
             const char *name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
 
             if (pcmk__str_eq(name, XML_RSC_ATTR_REMOTE_NODE, pcmk__str_casei)) {
                 remote_name = value;
             } else if (pcmk__str_eq(name, "remote-addr", pcmk__str_casei)) {
                 remote_server = value;
             } else if (pcmk__str_eq(name, "remote-port", pcmk__str_casei)) {
                 remote_port = value;
             } else if (pcmk__str_eq(name, "remote-connect-timeout", pcmk__str_casei)) {
                 connect_timeout = value;
             } else if (pcmk__str_eq(name, "remote-allow-migrate", pcmk__str_casei)) {
                 remote_allow_migrate=value;
             } else if (pcmk__str_eq(name, XML_RSC_ATTR_MANAGED, pcmk__str_casei)) {
                 is_managed = value;
             }
         }
     }
 
     if (remote_name == NULL) {
         return NULL;
     }
 
     if (pe_find_resource(data->resources, remote_name) != NULL) {
         return NULL;
     }
 
     pe_create_remote_xml(parent, remote_name, container_id,
                          remote_allow_migrate, is_managed,
                          connect_timeout, remote_server, remote_port);
     return remote_name;
 }
 
 static void
 handle_startup_fencing(pe_working_set_t *data_set, pe_node_t *new_node)
 {
     if ((new_node->details->type == node_remote) && (new_node->details->remote_rsc == NULL)) {
         /* Ignore fencing for remote nodes that don't have a connection resource
          * associated with them. This happens when remote node entries get left
          * in the nodes section after the connection resource is removed.
          */
         return;
     }
 
     if (pcmk_is_set(data_set->flags, pe_flag_startup_fencing)) {
         // All nodes are unclean until we've seen their status entry
         new_node->details->unclean = TRUE;
 
     } else {
         // Blind faith ...
         new_node->details->unclean = FALSE;
     }
 
     /* We need to be able to determine if a node's status section
      * exists or not separate from whether the node is unclean. */
     new_node->details->unseen = TRUE;
 }
 
 gboolean
 unpack_nodes(xmlNode * xml_nodes, pe_working_set_t * data_set)
 {
     xmlNode *xml_obj = NULL;
     pe_node_t *new_node = NULL;
     const char *id = NULL;
     const char *uname = NULL;
     const char *type = NULL;
     const char *score = NULL;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     for (xml_obj = pcmk__xe_first_child(xml_nodes); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_NODE, pcmk__str_none)) {
             new_node = NULL;
 
             id = crm_element_value(xml_obj, XML_ATTR_ID);
             uname = crm_element_value(xml_obj, XML_ATTR_UNAME);
             type = crm_element_value(xml_obj, XML_ATTR_TYPE);
             score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
             crm_trace("Processing node %s/%s", uname, id);
 
             if (id == NULL) {
                 pcmk__config_err("Ignoring <" XML_CIB_TAG_NODE
                                  "> entry in configuration without id");
                 continue;
             }
             new_node = pe_create_node(id, uname, type, score, data_set);
 
             if (new_node == NULL) {
                 return FALSE;
             }
 
             handle_startup_fencing(data_set, new_node);
 
             add_node_attrs(xml_obj, new_node, FALSE, data_set);
             pe__unpack_dataset_nvpairs(xml_obj, XML_TAG_UTILIZATION, &rule_data,
                                        new_node->details->utilization, NULL,
                                        FALSE, data_set);
 
             crm_trace("Done with node %s", crm_element_value(xml_obj, XML_ATTR_UNAME));
         }
     }
 
     if (data_set->localhost && pe_find_node(data_set->nodes, data_set->localhost) == NULL) {
         crm_info("Creating a fake local node");
         pe_create_node(data_set->localhost, data_set->localhost, NULL, 0,
                        data_set);
     }
 
     return TRUE;
 }
 
 static void
 setup_container(pe_resource_t * rsc, pe_working_set_t * data_set)
 {
     const char *container_id = NULL;
 
     if (rsc->children) {
         g_list_foreach(rsc->children, (GFunc) setup_container, data_set);
         return;
     }
 
     container_id = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_CONTAINER);
     if (container_id && !pcmk__str_eq(container_id, rsc->id, pcmk__str_casei)) {
         pe_resource_t *container = pe_find_resource(data_set->resources, container_id);
 
         if (container) {
             rsc->container = container;
             pe__set_resource_flags(container, pe_rsc_is_container);
             container->fillers = g_list_append(container->fillers, rsc);
             pe_rsc_trace(rsc, "Resource %s's container is %s", rsc->id, container_id);
         } else {
             pe_err("Resource %s: Unknown resource container (%s)", rsc->id, container_id);
         }
     }
 }
 
 gboolean
 unpack_remote_nodes(xmlNode * xml_resources, pe_working_set_t * data_set)
 {
     xmlNode *xml_obj = NULL;
 
     /* Create remote nodes and guest nodes from the resource configuration
      * before unpacking resources.
      */
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         const char *new_node_id = NULL;
 
         /* Check for remote nodes, which are defined by ocf:pacemaker:remote
          * primitives.
          */
         if (xml_contains_remote_node(xml_obj)) {
             new_node_id = ID(xml_obj);
             /* The "pe_find_node" check is here to make sure we don't iterate over
              * an expanded node that has already been added to the node list. */
             if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                 crm_trace("Found remote node %s defined by resource %s",
                           new_node_id, ID(xml_obj));
                 pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                data_set);
             }
             continue;
         }
 
         /* Check for guest nodes, which are defined by special meta-attributes
          * of a primitive of any type (for example, VirtualDomain or Xen).
          */
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_RESOURCE, pcmk__str_none)) {
             /* This will add an ocf:pacemaker:remote primitive to the
              * configuration for the guest node's connection, to be unpacked
              * later.
              */
             new_node_id = expand_remote_rsc_meta(xml_obj, xml_resources, data_set);
             if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                 crm_trace("Found guest node %s in resource %s",
                           new_node_id, ID(xml_obj));
                 pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                data_set);
             }
             continue;
         }
 
         /* Check for guest nodes inside a group. Clones are currently not
          * supported as guest nodes.
          */
         if (pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_GROUP, pcmk__str_none)) {
             xmlNode *xml_obj2 = NULL;
             for (xml_obj2 = pcmk__xe_first_child(xml_obj); xml_obj2 != NULL;
                  xml_obj2 = pcmk__xe_next(xml_obj2)) {
 
                 new_node_id = expand_remote_rsc_meta(xml_obj2, xml_resources, data_set);
 
                 if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                     crm_trace("Found guest node %s in resource %s inside group %s",
                               new_node_id, ID(xml_obj2), ID(xml_obj));
                     pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                    data_set);
                 }
             }
         }
     }
     return TRUE;
 }
 
 /* Call this after all the nodes and resources have been
  * unpacked, but before the status section is read.
  *
  * A remote node's online status is reflected by the state
  * of the remote node's connection resource. We need to link
  * the remote node to this connection resource so we can have
  * easy access to the connection resource during the scheduler calculations.
  */
 static void
 link_rsc2remotenode(pe_working_set_t *data_set, pe_resource_t *new_rsc)
 {
     pe_node_t *remote_node = NULL;
 
     if (new_rsc->is_remote_node == FALSE) {
         return;
     }
 
     if (pcmk_is_set(data_set->flags, pe_flag_quick_location)) {
         /* remote_nodes and remote_resources are not linked in quick location calculations */
         return;
     }
 
     remote_node = pe_find_node(data_set->nodes, new_rsc->id);
     CRM_CHECK(remote_node != NULL, return);
 
     pe_rsc_trace(new_rsc, "Linking remote connection resource %s to node %s",
                  new_rsc->id, remote_node->details->uname);
     remote_node->details->remote_rsc = new_rsc;
 
     if (new_rsc->container == NULL) {
         /* Handle start-up fencing for remote nodes (as opposed to guest nodes)
          * the same as is done for cluster nodes.
          */
         handle_startup_fencing(data_set, remote_node);
 
     } else {
         /* pe_create_node() marks the new node as "remote" or "cluster"; now
          * that we know the node is a guest node, update it correctly.
          */
         g_hash_table_replace(remote_node->details->attrs, strdup(CRM_ATTR_KIND),
                              strdup("container"));
     }
 }
 
 static void
 destroy_tag(gpointer data)
 {
     pe_tag_t *tag = data;
 
     if (tag) {
         free(tag->id);
         g_list_free_full(tag->refs, free);
         free(tag);
     }
 }
 
 /*!
  * \internal
  * \brief Parse configuration XML for resource information
  *
  * \param[in]     xml_resources  Top of resource configuration XML
  * \param[in,out] data_set       Where to put resource information
  *
  * \return TRUE
  *
  * \note unpack_remote_nodes() MUST be called before this, so that the nodes can
- *       be used when common_unpack() calls resource_location()
+ *       be used when pe__unpack_resource() calls resource_location()
  */
 gboolean
 unpack_resources(xmlNode * xml_resources, pe_working_set_t * data_set)
 {
     xmlNode *xml_obj = NULL;
     GList *gIter = NULL;
 
     data_set->template_rsc_sets = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         pe_resource_t *new_rsc = NULL;
         const char *id = ID(xml_obj);
 
         if (pcmk__str_empty(id)) {
             pcmk__config_err("Ignoring <%s> resource without ID",
                              crm_element_name(xml_obj));
             continue;
         }
 
         if (pcmk__str_eq((const char *) xml_obj->name, XML_CIB_TAG_RSC_TEMPLATE,
                          pcmk__str_none)) {
             if (g_hash_table_lookup_extended(data_set->template_rsc_sets, id,
                                              NULL, NULL) == FALSE) {
                 /* Record the template's ID for the knowledge of its existence anyway. */
                 g_hash_table_insert(data_set->template_rsc_sets, strdup(id), NULL);
             }
             continue;
         }
 
         crm_trace("Unpacking <%s id='%s'>", crm_element_name(xml_obj), id);
-        if (common_unpack(xml_obj, &new_rsc, NULL, data_set) && (new_rsc != NULL)) {
+        if ((pe__unpack_resource(xml_obj, &new_rsc, NULL,
+                                 data_set) == pcmk_rc_ok)
+            && (new_rsc != NULL)) {
+
             data_set->resources = g_list_append(data_set->resources, new_rsc);
             pe_rsc_trace(new_rsc, "Added resource %s", new_rsc->id);
 
         } else {
             pcmk__config_err("Ignoring <%s> resource '%s' "
                              "because configuration is invalid",
                              crm_element_name(xml_obj), id);
             if (new_rsc != NULL && new_rsc->fns != NULL) {
                 new_rsc->fns->free(new_rsc);
             }
         }
     }
 
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
 
         setup_container(rsc, data_set);
         link_rsc2remotenode(data_set, rsc);
     }
 
     data_set->resources = g_list_sort(data_set->resources,
                                       pe__cmp_rsc_priority);
     if (pcmk_is_set(data_set->flags, pe_flag_quick_location)) {
         /* Ignore */
 
     } else if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)
                && !pcmk_is_set(data_set->flags, pe_flag_have_stonith_resource)) {
 
         pcmk__config_err("Resource start-up disabled since no STONITH resources have been defined");
         pcmk__config_err("Either configure some or disable STONITH with the stonith-enabled option");
         pcmk__config_err("NOTE: Clusters with shared data need STONITH to ensure data integrity");
     }
 
     return TRUE;
 }
 
 gboolean
 unpack_tags(xmlNode * xml_tags, pe_working_set_t * data_set)
 {
     xmlNode *xml_tag = NULL;
 
     data_set->tags = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_tag = pcmk__xe_first_child(xml_tags); xml_tag != NULL;
          xml_tag = pcmk__xe_next(xml_tag)) {
 
         xmlNode *xml_obj_ref = NULL;
         const char *tag_id = ID(xml_tag);
 
         if (!pcmk__str_eq((const char *)xml_tag->name, XML_CIB_TAG_TAG, pcmk__str_none)) {
             continue;
         }
 
         if (tag_id == NULL) {
             pcmk__config_err("Ignoring <%s> without " XML_ATTR_ID,
                              crm_element_name(xml_tag));
             continue;
         }
 
         for (xml_obj_ref = pcmk__xe_first_child(xml_tag); xml_obj_ref != NULL;
              xml_obj_ref = pcmk__xe_next(xml_obj_ref)) {
 
             const char *obj_ref = ID(xml_obj_ref);
 
             if (!pcmk__str_eq((const char *)xml_obj_ref->name, XML_CIB_TAG_OBJ_REF, pcmk__str_none)) {
                 continue;
             }
 
             if (obj_ref == NULL) {
                 pcmk__config_err("Ignoring <%s> for tag '%s' without " XML_ATTR_ID,
                                  crm_element_name(xml_obj_ref), tag_id);
                 continue;
             }
 
             if (add_tag_ref(data_set->tags, tag_id, obj_ref) == FALSE) {
                 return FALSE;
             }
         }
     }
 
     return TRUE;
 }
 
 /* The ticket state section:
  * "/cib/status/tickets/ticket_state" */
 static gboolean
 unpack_ticket_state(xmlNode * xml_ticket, pe_working_set_t * data_set)
 {
     const char *ticket_id = NULL;
     const char *granted = NULL;
     const char *last_granted = NULL;
     const char *standby = NULL;
     xmlAttrPtr xIter = NULL;
 
     pe_ticket_t *ticket = NULL;
 
     ticket_id = ID(xml_ticket);
     if (pcmk__str_empty(ticket_id)) {
         return FALSE;
     }
 
     crm_trace("Processing ticket state for %s", ticket_id);
 
     ticket = g_hash_table_lookup(data_set->tickets, ticket_id);
     if (ticket == NULL) {
         ticket = ticket_new(ticket_id, data_set);
         if (ticket == NULL) {
             return FALSE;
         }
     }
 
     for (xIter = xml_ticket->properties; xIter; xIter = xIter->next) {
         const char *prop_name = (const char *)xIter->name;
         const char *prop_value = crm_element_value(xml_ticket, prop_name);
 
         if (pcmk__str_eq(prop_name, XML_ATTR_ID, pcmk__str_none)) {
             continue;
         }
         g_hash_table_replace(ticket->state, strdup(prop_name), strdup(prop_value));
     }
 
     granted = g_hash_table_lookup(ticket->state, "granted");
     if (granted && crm_is_true(granted)) {
         ticket->granted = TRUE;
         crm_info("We have ticket '%s'", ticket->id);
     } else {
         ticket->granted = FALSE;
         crm_info("We do not have ticket '%s'", ticket->id);
     }
 
     last_granted = g_hash_table_lookup(ticket->state, "last-granted");
     if (last_granted) {
         long long last_granted_ll;
 
         pcmk__scan_ll(last_granted, &last_granted_ll, 0LL);
         ticket->last_granted = (time_t) last_granted_ll;
     }
 
     standby = g_hash_table_lookup(ticket->state, "standby");
     if (standby && crm_is_true(standby)) {
         ticket->standby = TRUE;
         if (ticket->granted) {
             crm_info("Granted ticket '%s' is in standby-mode", ticket->id);
         }
     } else {
         ticket->standby = FALSE;
     }
 
     crm_trace("Done with ticket state for %s", ticket_id);
 
     return TRUE;
 }
 
 static gboolean
 unpack_tickets_state(xmlNode * xml_tickets, pe_working_set_t * data_set)
 {
     xmlNode *xml_obj = NULL;
 
     for (xml_obj = pcmk__xe_first_child(xml_tickets); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (!pcmk__str_eq((const char *)xml_obj->name, XML_CIB_TAG_TICKET_STATE, pcmk__str_none)) {
             continue;
         }
         unpack_ticket_state(xml_obj, data_set);
     }
 
     return TRUE;
 }
 
 static void
 unpack_handle_remote_attrs(pe_node_t *this_node, xmlNode *state, pe_working_set_t * data_set) 
 {
     const char *resource_discovery_enabled = NULL;
     xmlNode *attrs = NULL;
     pe_resource_t *rsc = NULL;
 
     if (!pcmk__str_eq((const char *)state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
         return;
     }
 
     if ((this_node == NULL) || !pe__is_guest_or_remote_node(this_node)) {
         return;
     }
     crm_trace("Processing remote node id=%s, uname=%s", this_node->details->id, this_node->details->uname);
 
     pcmk__scan_min_int(crm_element_value(state, XML_NODE_IS_MAINTENANCE),
                        &(this_node->details->remote_maintenance), 0);
 
     rsc = this_node->details->remote_rsc;
     if (this_node->details->remote_requires_reset == FALSE) {
         this_node->details->unclean = FALSE;
         this_node->details->unseen = FALSE;
     }
     attrs = find_xml_node(state, XML_TAG_TRANSIENT_NODEATTRS, FALSE);
     add_node_attrs(attrs, this_node, TRUE, data_set);
 
     if (pe__shutdown_requested(this_node)) {
         crm_info("Node %s is shutting down", this_node->details->uname);
         this_node->details->shutdown = TRUE;
     }
  
     if (crm_is_true(pe_node_attribute_raw(this_node, "standby"))) {
         crm_info("Node %s is in standby-mode", this_node->details->uname);
         this_node->details->standby = TRUE;
     }
 
     if (crm_is_true(pe_node_attribute_raw(this_node, "maintenance")) ||
         ((rsc != NULL) && !pcmk_is_set(rsc->flags, pe_rsc_managed))) {
         crm_info("Node %s is in maintenance-mode", this_node->details->uname);
         this_node->details->maintenance = TRUE;
     }
 
     resource_discovery_enabled = pe_node_attribute_raw(this_node, XML_NODE_ATTR_RSC_DISCOVERY);
     if (resource_discovery_enabled && !crm_is_true(resource_discovery_enabled)) {
         if (pe__is_remote_node(this_node)
             && !pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
             crm_warn("Ignoring %s attribute on remote node %s because stonith is disabled",
                      XML_NODE_ATTR_RSC_DISCOVERY, this_node->details->uname);
         } else {
             /* This is either a remote node with fencing enabled, or a guest
              * node. We don't care whether fencing is enabled when fencing guest
              * nodes, because they are "fenced" by recovering their containing
              * resource.
              */
             crm_info("Node %s has resource discovery disabled", this_node->details->uname);
             this_node->details->rsc_discovery_enabled = FALSE;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a cluster node's transient attributes
  *
  * \param[in] state     CIB node state XML
  * \param[in] node      Cluster node whose attributes are being unpacked
  * \param[in] data_set  Cluster working set
  */
 static void
 unpack_transient_attributes(xmlNode *state, pe_node_t *node,
                             pe_working_set_t *data_set)
 {
     const char *discovery = NULL;
     xmlNode *attrs = find_xml_node(state, XML_TAG_TRANSIENT_NODEATTRS, FALSE);
 
     add_node_attrs(attrs, node, TRUE, data_set);
 
     if (crm_is_true(pe_node_attribute_raw(node, "standby"))) {
         crm_info("Node %s is in standby-mode", node->details->uname);
         node->details->standby = TRUE;
     }
 
     if (crm_is_true(pe_node_attribute_raw(node, "maintenance"))) {
         crm_info("Node %s is in maintenance-mode", node->details->uname);
         node->details->maintenance = TRUE;
     }
 
     discovery = pe_node_attribute_raw(node, XML_NODE_ATTR_RSC_DISCOVERY);
     if ((discovery != NULL) && !crm_is_true(discovery)) {
         crm_warn("Ignoring %s attribute for node %s because disabling "
                  "resource discovery is not allowed for cluster nodes",
                  XML_NODE_ATTR_RSC_DISCOVERY, node->details->uname);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a node state entry (first pass)
  *
  * Unpack one node state entry from status. This unpacks information from the
  * node_state element itself and node attributes inside it, but not the
  * resource history inside it. Multiple passes through the status are needed to
  * fully unpack everything.
  *
  * \param[in] state     CIB node state XML
  * \param[in] data_set  Cluster working set
  */
 static void
 unpack_node_state(xmlNode *state, pe_working_set_t *data_set)
 {
     const char *id = NULL;
     const char *uname = NULL;
     pe_node_t *this_node = NULL;
 
     id = crm_element_value(state, XML_ATTR_ID);
     if (id == NULL) {
         crm_warn("Ignoring malformed " XML_CIB_TAG_STATE " entry without "
                  XML_ATTR_ID);
         return;
     }
 
     uname = crm_element_value(state, XML_ATTR_UNAME);
     if (uname == NULL) {
         crm_warn("Ignoring malformed " XML_CIB_TAG_STATE " entry without "
                  XML_ATTR_UNAME);
         return;
     }
 
     this_node = pe_find_node_any(data_set->nodes, id, uname);
     if (this_node == NULL) {
         pcmk__config_warn("Ignoring recorded node state for '%s' because "
                           "it is no longer in the configuration", uname);
         return;
     }
 
     if (pe__is_guest_or_remote_node(this_node)) {
         /* We can't determine the online status of Pacemaker Remote nodes until
          * after all resource history has been unpacked. In this first pass, we
          * do need to mark whether the node has been fenced, as this plays a
          * role during unpacking cluster node resource state.
          */
         pcmk__scan_min_int(crm_element_value(state, XML_NODE_IS_FENCED),
                            &(this_node->details->remote_was_fenced), 0);
         return;
     }
 
     unpack_transient_attributes(state, this_node, data_set);
 
     /* Provisionally mark this cluster node as clean. We have at least seen it
      * in the current cluster's lifetime.
      */
     this_node->details->unclean = FALSE;
     this_node->details->unseen = FALSE;
 
     crm_trace("Determining online status of cluster node %s (id %s)",
               this_node->details->uname, id);
     determine_online_status(state, this_node, data_set);
 
     if (!pcmk_is_set(data_set->flags, pe_flag_have_quorum)
         && this_node->details->online
         && (data_set->no_quorum_policy == no_quorum_suicide)) {
         /* Everything else should flow from this automatically
          * (at least until the scheduler becomes able to migrate off
          * healthy resources)
          */
         pe_fence_node(data_set, this_node, "cluster does not have quorum",
                       FALSE);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack nodes' resource history as much as possible
  *
  * Unpack as many nodes' resource history as possible in one pass through the
  * status. We need to process Pacemaker Remote nodes' connections/containers
  * before unpacking their history; the connection/container history will be
  * in another node's history, so it might take multiple passes to unpack
  * everything.
  *
  * \param[in] status    CIB XML status section
  * \param[in] fence     If true, treat any not-yet-unpacked nodes as unseen
  * \param[in] data_set  Cluster working set
  *
  * \return Standard Pacemaker return code (specifically pcmk_rc_ok if done,
  *         or EAGAIN if more unpacking remains to be done)
  */
 static int
 unpack_node_history(xmlNode *status, bool fence, pe_working_set_t *data_set)
 {
     int rc = pcmk_rc_ok;
 
     // Loop through all node_state entries in CIB status
     for (xmlNode *state = first_named_child(status, XML_CIB_TAG_STATE);
          state != NULL; state = crm_next_same_xml(state)) {
 
         const char *id = ID(state);
         const char *uname = crm_element_value(state, XML_ATTR_UNAME);
         pe_node_t *this_node = NULL;
 
         if ((id == NULL) || (uname == NULL)) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history from malformed "
                       XML_CIB_TAG_STATE " without id and/or uname");
             continue;
         }
 
         this_node = pe_find_node_any(data_set->nodes, id, uname);
         if (this_node == NULL) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history for node %s because "
                       "no longer in configuration", id);
             continue;
         }
 
         if (this_node->details->unpacked) {
             crm_trace("Not unpacking resource history for node %s because "
                       "already unpacked", id);
             continue;
         }
 
         if (fence) {
             // We're processing all remaining nodes
 
         } else if (pe__is_guest_node(this_node)) {
             /* We can unpack a guest node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection and containing resource are both up.
              */
             pe_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL) || (rsc->role != RSC_ROLE_STARTED)
                 || (rsc->container->role != RSC_ROLE_STARTED)) {
                 crm_trace("Not unpacking resource history for guest node %s "
                           "because container and connection are not known to "
                           "be up", id);
                 continue;
             }
 
         } else if (pe__is_remote_node(this_node)) {
             /* We can unpack a remote node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection is up, with the exception of when shutdown locks are
              * in use.
              */
             pe_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL)
                 || (!pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)
                     && (rsc->role != RSC_ROLE_STARTED))) {
                 crm_trace("Not unpacking resource history for remote node %s "
                           "because connection is not known to be up", id);
                 continue;
             }
 
         /* If fencing and shutdown locks are disabled and we're not processing
          * unseen nodes, then we don't want to unpack offline nodes until online
          * nodes have been unpacked. This allows us to number active clone
          * instances first.
          */
         } else if (!pcmk_any_flags_set(data_set->flags, pe_flag_stonith_enabled
                                                         |pe_flag_shutdown_lock)
                    && !this_node->details->online) {
             crm_trace("Not unpacking resource history for offline "
                       "cluster node %s", id);
             continue;
         }
 
         if (pe__is_guest_or_remote_node(this_node)) {
             determine_remote_online_status(data_set, this_node);
             unpack_handle_remote_attrs(this_node, state, data_set);
         }
 
         crm_trace("Unpacking resource history for %snode %s",
                   (fence? "unseen " : ""), id);
 
         this_node->details->unpacked = TRUE;
         unpack_node_lrm(this_node, state, data_set);
 
         rc = EAGAIN; // Other node histories might depend on this one
     }
     return rc;
 }
 
 /* remove nodes that are down, stopping */
 /* create positive rsc_to_node constraints between resources and the nodes they are running on */
 /* anything else? */
 gboolean
 unpack_status(xmlNode * status, pe_working_set_t * data_set)
 {
     xmlNode *state = NULL;
 
     crm_trace("Beginning unpack");
 
     if (data_set->tickets == NULL) {
         data_set->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     for (state = pcmk__xe_first_child(status); state != NULL;
          state = pcmk__xe_next(state)) {
 
         if (pcmk__str_eq((const char *)state->name, XML_CIB_TAG_TICKETS, pcmk__str_none)) {
             unpack_tickets_state((xmlNode *) state, data_set);
 
         } else if (pcmk__str_eq((const char *)state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
             unpack_node_state(state, data_set);
         }
     }
 
     while (unpack_node_history(status, FALSE, data_set) == EAGAIN) {
         crm_trace("Another pass through node resource histories is needed");
     }
 
     // Now catch any nodes we didn't see
     unpack_node_history(status,
                         pcmk_is_set(data_set->flags, pe_flag_stonith_enabled),
                         data_set);
 
     /* Now that we know where resources are, we can schedule stops of containers
      * with failed bundle connections
      */
     if (data_set->stop_needed != NULL) {
         for (GList *item = data_set->stop_needed; item; item = item->next) {
             pe_resource_t *container = item->data;
             pe_node_t *node = pe__current_node(container);
 
             if (node) {
                 stop_action(container, node, FALSE);
             }
         }
         g_list_free(data_set->stop_needed);
         data_set->stop_needed = NULL;
     }
 
     /* Now that we know status of all Pacemaker Remote connections and nodes,
      * we can stop connections for node shutdowns, and check the online status
      * of remote/guest nodes that didn't have any node history to unpack.
      */
     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *this_node = gIter->data;
 
         if (!pe__is_guest_or_remote_node(this_node)) {
             continue;
         }
         if (this_node->details->shutdown
             && (this_node->details->remote_rsc != NULL)) {
             pe__set_next_role(this_node->details->remote_rsc, RSC_ROLE_STOPPED,
                               "remote shutdown");
         }
         if (!this_node->details->unpacked) {
             determine_remote_online_status(data_set, this_node);
         }
     }
 
     return TRUE;
 }
 
 static gboolean
 determine_online_status_no_fencing(pe_working_set_t * data_set, xmlNode * node_state,
                                    pe_node_t * this_node)
 {
     gboolean online = FALSE;
     const char *join = crm_element_value(node_state, XML_NODE_JOIN_STATE);
     const char *is_peer = crm_element_value(node_state, XML_NODE_IS_PEER);
     const char *in_cluster = crm_element_value(node_state, XML_NODE_IN_CLUSTER);
     const char *exp_state = crm_element_value(node_state, XML_NODE_EXPECTED);
 
     if (!crm_is_true(in_cluster)) {
         crm_trace("Node is down: in_cluster=%s",
                   pcmk__s(in_cluster, "<null>"));
 
     } else if (pcmk__str_eq(is_peer, ONLINESTATUS, pcmk__str_casei)) {
         if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
             online = TRUE;
         } else {
             crm_debug("Node is not ready to run resources: %s", join);
         }
 
     } else if (this_node->details->expected_up == FALSE) {
         crm_trace("Controller is down: "
                   "in_cluster=%s is_peer=%s join=%s expected=%s",
                   pcmk__s(in_cluster, "<null>"), pcmk__s(is_peer, "<null>"),
                   pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
 
     } else {
         /* mark it unclean */
         pe_fence_node(data_set, this_node, "peer is unexpectedly down", FALSE);
         crm_info("in_cluster=%s is_peer=%s join=%s expected=%s",
                  pcmk__s(in_cluster, "<null>"), pcmk__s(is_peer, "<null>"),
                  pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
     }
     return online;
 }
 
 static gboolean
 determine_online_status_fencing(pe_working_set_t * data_set, xmlNode * node_state,
                                 pe_node_t * this_node)
 {
     gboolean online = FALSE;
     gboolean do_terminate = FALSE;
     bool crmd_online = FALSE;
     const char *join = crm_element_value(node_state, XML_NODE_JOIN_STATE);
     const char *is_peer = crm_element_value(node_state, XML_NODE_IS_PEER);
     const char *in_cluster = crm_element_value(node_state, XML_NODE_IN_CLUSTER);
     const char *exp_state = crm_element_value(node_state, XML_NODE_EXPECTED);
     const char *terminate = pe_node_attribute_raw(this_node, "terminate");
 
 /*
   - XML_NODE_IN_CLUSTER    ::= true|false
   - XML_NODE_IS_PEER       ::= online|offline
   - XML_NODE_JOIN_STATE    ::= member|down|pending|banned
   - XML_NODE_EXPECTED      ::= member|down
 */
 
     if (crm_is_true(terminate)) {
         do_terminate = TRUE;
 
     } else if (terminate != NULL && strlen(terminate) > 0) {
         /* could be a time() value */
         char t = terminate[0];
 
         if (t != '0' && isdigit(t)) {
             do_terminate = TRUE;
         }
     }
 
     crm_trace("%s: in_cluster=%s is_peer=%s join=%s expected=%s term=%d",
               this_node->details->uname, pcmk__s(in_cluster, "<null>"),
               pcmk__s(is_peer, "<null>"), pcmk__s(join, "<null>"),
               pcmk__s(exp_state, "<null>"), do_terminate);
 
     online = crm_is_true(in_cluster);
     crmd_online = pcmk__str_eq(is_peer, ONLINESTATUS, pcmk__str_casei);
     if (exp_state == NULL) {
         exp_state = CRMD_JOINSTATE_DOWN;
     }
 
     if (this_node->details->shutdown) {
         crm_debug("%s is shutting down", this_node->details->uname);
 
         /* Slightly different criteria since we can't shut down a dead peer */
         online = crmd_online;
 
     } else if (in_cluster == NULL) {
         pe_fence_node(data_set, this_node, "peer has not been seen by the cluster", FALSE);
 
     } else if (pcmk__str_eq(join, CRMD_JOINSTATE_NACK, pcmk__str_casei)) {
         pe_fence_node(data_set, this_node,
                       "peer failed Pacemaker membership criteria", FALSE);
 
     } else if (do_terminate == FALSE && pcmk__str_eq(exp_state, CRMD_JOINSTATE_DOWN, pcmk__str_casei)) {
 
         if (crm_is_true(in_cluster) || crmd_online) {
             crm_info("- Node %s is not ready to run resources", this_node->details->uname);
             this_node->details->standby = TRUE;
             this_node->details->pending = TRUE;
 
         } else {
             crm_trace("%s is down or still coming up", this_node->details->uname);
         }
 
     } else if (do_terminate && pcmk__str_eq(join, CRMD_JOINSTATE_DOWN, pcmk__str_casei)
                && crm_is_true(in_cluster) == FALSE && !crmd_online) {
         crm_info("Node %s was just shot", this_node->details->uname);
         online = FALSE;
 
     } else if (crm_is_true(in_cluster) == FALSE) {
         // Consider `priority-fencing-delay` for lost nodes
         pe_fence_node(data_set, this_node, "peer is no longer part of the cluster", TRUE);
 
     } else if (!crmd_online) {
         pe_fence_node(data_set, this_node, "peer process is no longer available", FALSE);
 
         /* Everything is running at this point, now check join state */
     } else if (do_terminate) {
         pe_fence_node(data_set, this_node, "termination was requested", FALSE);
 
     } else if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
         crm_info("Node %s is active", this_node->details->uname);
 
     } else if (pcmk__strcase_any_of(join, CRMD_JOINSTATE_PENDING, CRMD_JOINSTATE_DOWN, NULL)) {
         crm_info("Node %s is not ready to run resources", this_node->details->uname);
         this_node->details->standby = TRUE;
         this_node->details->pending = TRUE;
 
     } else {
         pe_fence_node(data_set, this_node, "peer was in an unknown state", FALSE);
         crm_warn("%s: in-cluster=%s is-peer=%s join=%s expected=%s term=%d shutdown=%d",
                  this_node->details->uname, pcmk__s(in_cluster, "<null>"),
                  pcmk__s(is_peer, "<null>"), pcmk__s(join, "<null>"),
                  pcmk__s(exp_state, "<null>"), do_terminate,
                  this_node->details->shutdown);
     }
 
     return online;
 }
 
 static void
 determine_remote_online_status(pe_working_set_t * data_set, pe_node_t * this_node)
 {
     pe_resource_t *rsc = this_node->details->remote_rsc;
     pe_resource_t *container = NULL;
     pe_node_t *host = NULL;
 
     /* If there is a node state entry for a (former) Pacemaker Remote node
      * but no resource creating that node, the node's connection resource will
      * be NULL. Consider it an offline remote node in that case.
      */
     if (rsc == NULL) {
         this_node->details->online = FALSE;
         goto remote_online_done;
     }
 
     container = rsc->container;
 
     if (container && pcmk__list_of_1(rsc->running_on)) {
         host = rsc->running_on->data;
     }
 
     /* If the resource is currently started, mark it online. */
     if (rsc->role == RSC_ROLE_STARTED) {
         crm_trace("%s node %s presumed ONLINE because connection resource is started",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = TRUE;
     }
 
     /* consider this node shutting down if transitioning start->stop */
     if (rsc->role == RSC_ROLE_STARTED && rsc->next_role == RSC_ROLE_STOPPED) {
         crm_trace("%s node %s shutting down because connection resource is stopping",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->shutdown = TRUE;
     }
 
     /* Now check all the failure conditions. */
     if(container && pcmk_is_set(container->flags, pe_rsc_failed)) {
         crm_trace("Guest node %s UNCLEAN because guest resource failed",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
 
     } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
         crm_trace("%s node %s OFFLINE because connection resource failed",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
 
     } else if (rsc->role == RSC_ROLE_STOPPED
         || (container && container->role == RSC_ROLE_STOPPED)) {
 
         crm_trace("%s node %s OFFLINE because its resource is stopped",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = FALSE;
 
     } else if (host && (host->details->online == FALSE)
                && host->details->unclean) {
         crm_trace("Guest node %s UNCLEAN because host is unclean",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
     }
 
 remote_online_done:
     crm_trace("Remote node %s online=%s",
         this_node->details->id, this_node->details->online ? "TRUE" : "FALSE");
 }
 
 static void
 determine_online_status(xmlNode * node_state, pe_node_t * this_node, pe_working_set_t * data_set)
 {
     gboolean online = FALSE;
     const char *exp_state = crm_element_value(node_state, XML_NODE_EXPECTED);
 
     CRM_CHECK(this_node != NULL, return);
 
     this_node->details->shutdown = FALSE;
     this_node->details->expected_up = FALSE;
 
     if (pe__shutdown_requested(this_node)) {
         this_node->details->shutdown = TRUE;
 
     } else if (pcmk__str_eq(exp_state, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
         this_node->details->expected_up = TRUE;
     }
 
     if (this_node->details->type == node_ping) {
         this_node->details->unclean = FALSE;
         online = FALSE;         /* As far as resource management is concerned,
                                  * the node is safely offline.
                                  * Anyone caught abusing this logic will be shot
                                  */
 
     } else if (!pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
         online = determine_online_status_no_fencing(data_set, node_state, this_node);
 
     } else {
         online = determine_online_status_fencing(data_set, node_state, this_node);
     }
 
     if (online) {
         this_node->details->online = TRUE;
 
     } else {
         /* remove node from contention */
         this_node->fixed = TRUE;
         this_node->weight = -INFINITY;
     }
 
     if (online && this_node->details->shutdown) {
         /* don't run resources here */
         this_node->fixed = TRUE;
         this_node->weight = -INFINITY;
     }
 
     if (this_node->details->type == node_ping) {
         crm_info("Node %s is not a Pacemaker node", this_node->details->uname);
 
     } else if (this_node->details->unclean) {
         pe_proc_warn("Node %s is unclean", this_node->details->uname);
 
     } else if (this_node->details->online) {
         crm_info("Node %s is %s", this_node->details->uname,
                  this_node->details->shutdown ? "shutting down" :
                  this_node->details->pending ? "pending" :
                  this_node->details->standby ? "standby" :
                  this_node->details->maintenance ? "maintenance" : "online");
 
     } else {
         crm_trace("Node %s is offline", this_node->details->uname);
     }
 }
 
 /*!
  * \internal
  * \brief Find the end of a resource's name, excluding any clone suffix
  *
  * \param[in] id  Resource ID to check
  *
  * \return Pointer to last character of resource's base name
  */
 const char *
 pe_base_name_end(const char *id)
 {
     if (!pcmk__str_empty(id)) {
         const char *end = id + strlen(id) - 1;
 
         for (const char *s = end; s > id; --s) {
             switch (*s) {
                 case '0':
                 case '1':
                 case '2':
                 case '3':
                 case '4':
                 case '5':
                 case '6':
                 case '7':
                 case '8':
                 case '9':
                     break;
                 case ':':
                     return (s == end)? s : (s - 1);
                 default:
                     return end;
             }
         }
         return end;
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get a resource name excluding any clone suffix
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_strip(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     char *basename = NULL;
 
     CRM_ASSERT(end);
     basename = strndup(last_rsc_id, end - last_rsc_id + 1);
     CRM_ASSERT(basename);
     return basename;
 }
 
 /*!
  * \internal
  * \brief Get the name of the first instance of a cloned resource
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name plus :0
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_zero(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     size_t base_name_len = end - last_rsc_id + 1;
     char *zero = NULL;
 
     CRM_ASSERT(end);
     zero = calloc(base_name_len + 3, sizeof(char));
     CRM_ASSERT(zero);
     memcpy(zero, last_rsc_id, base_name_len);
     zero[base_name_len] = ':';
     zero[base_name_len + 1] = '0';
     return zero;
 }
 
 static pe_resource_t *
 create_fake_resource(const char *rsc_id, xmlNode * rsc_entry, pe_working_set_t * data_set)
 {
     pe_resource_t *rsc = NULL;
     xmlNode *xml_rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE);
 
     copy_in_properties(xml_rsc, rsc_entry);
     crm_xml_add(xml_rsc, XML_ATTR_ID, rsc_id);
     crm_log_xml_debug(xml_rsc, "Orphan resource");
 
-    if (!common_unpack(xml_rsc, &rsc, NULL, data_set)) {
+    if (pe__unpack_resource(xml_rsc, &rsc, NULL, data_set) != pcmk_rc_ok) {
         return NULL;
     }
 
     if (xml_contains_remote_node(xml_rsc)) {
         pe_node_t *node;
 
         crm_debug("Detected orphaned remote node %s", rsc_id);
         node = pe_find_node(data_set->nodes, rsc_id);
         if (node == NULL) {
 	        node = pe_create_node(rsc_id, rsc_id, "remote", NULL, data_set);
         }
         link_rsc2remotenode(data_set, rsc);
 
         if (node) {
             crm_trace("Setting node %s as shutting down due to orphaned connection resource", rsc_id);
             node->details->shutdown = TRUE;
         }
     }
 
     if (crm_element_value(rsc_entry, XML_RSC_ATTR_CONTAINER)) {
         /* This orphaned rsc needs to be mapped to a container. */
         crm_trace("Detected orphaned container filler %s", rsc_id);
         pe__set_resource_flags(rsc, pe_rsc_orphan_container_filler);
     }
     pe__set_resource_flags(rsc, pe_rsc_orphan);
     data_set->resources = g_list_append(data_set->resources, rsc);
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Create orphan instance for anonymous clone resource history
  */
 static pe_resource_t *
 create_anonymous_orphan(pe_resource_t *parent, const char *rsc_id,
                         pe_node_t *node, pe_working_set_t *data_set)
 {
     pe_resource_t *top = pe__create_clone_child(parent, data_set);
 
     // find_rsc() because we might be a cloned group
     pe_resource_t *orphan = top->fns->find_rsc(top, rsc_id, NULL, pe_find_clone);
 
     pe_rsc_debug(parent, "Created orphan %s for %s: %s on %s",
                  top->id, parent->id, rsc_id, node->details->uname);
     return orphan;
 }
 
 /*!
  * \internal
  * \brief Check a node for an instance of an anonymous clone
  *
  * Return a child instance of the specified anonymous clone, in order of
  * preference: (1) the instance running on the specified node, if any;
  * (2) an inactive instance (i.e. within the total of clone-max instances);
  * (3) a newly created orphan (i.e. clone-max instances are already active).
  *
  * \param[in] data_set  Cluster information
  * \param[in] node      Node on which to check for instance
  * \param[in] parent    Clone to check
  * \param[in] rsc_id    Name of cloned resource in history (without instance)
  */
 static pe_resource_t *
 find_anonymous_clone(pe_working_set_t * data_set, pe_node_t * node, pe_resource_t * parent,
                      const char *rsc_id)
 {
     GList *rIter = NULL;
     pe_resource_t *rsc = NULL;
     pe_resource_t *inactive_instance = NULL;
     gboolean skip_inactive = FALSE;
 
     CRM_ASSERT(parent != NULL);
     CRM_ASSERT(pe_rsc_is_clone(parent));
     CRM_ASSERT(!pcmk_is_set(parent->flags, pe_rsc_unique));
 
     // Check for active (or partially active, for cloned groups) instance
     pe_rsc_trace(parent, "Looking for %s on %s in %s", rsc_id, node->details->uname, parent->id);
     for (rIter = parent->children; rsc == NULL && rIter; rIter = rIter->next) {
         GList *locations = NULL;
         pe_resource_t *child = rIter->data;
 
         /* Check whether this instance is already known to be active or pending
          * anywhere, at this stage of unpacking. Because this function is called
          * for a resource before the resource's individual operation history
          * entries are unpacked, locations will generally not contain the
          * desired node.
          *
          * However, there are three exceptions:
          * (1) when child is a cloned group and we have already unpacked the
          *     history of another member of the group on the same node;
          * (2) when we've already unpacked the history of another numbered
          *     instance on the same node (which can happen if globally-unique
          *     was flipped from true to false); and
          * (3) when we re-run calculations on the same data set as part of a
          *     simulation.
          */
         child->fns->location(child, &locations, 2);
         if (locations) {
             /* We should never associate the same numbered anonymous clone
              * instance with multiple nodes, and clone instances can't migrate,
              * so there must be only one location, regardless of history.
              */
             CRM_LOG_ASSERT(locations->next == NULL);
 
             if (((pe_node_t *)locations->data)->details == node->details) {
                 /* This child instance is active on the requested node, so check
                  * for a corresponding configured resource. We use find_rsc()
                  * instead of child because child may be a cloned group, and we
                  * need the particular member corresponding to rsc_id.
                  *
                  * If the history entry is orphaned, rsc will be NULL.
                  */
                 rsc = parent->fns->find_rsc(child, rsc_id, NULL, pe_find_clone);
                 if (rsc) {
                     /* If there are multiple instance history entries for an
                      * anonymous clone in a single node's history (which can
                      * happen if globally-unique is switched from true to
                      * false), we want to consider the instances beyond the
                      * first as orphans, even if there are inactive instance
                      * numbers available.
                      */
                     if (rsc->running_on) {
                         crm_notice("Active (now-)anonymous clone %s has "
                                    "multiple (orphan) instance histories on %s",
                                    parent->id, node->details->uname);
                         skip_inactive = TRUE;
                         rsc = NULL;
                     } else {
                         pe_rsc_trace(parent, "Resource %s, active", rsc->id);
                     }
                 }
             }
             g_list_free(locations);
 
         } else {
             pe_rsc_trace(parent, "Resource %s, skip inactive", child->id);
             if (!skip_inactive && !inactive_instance
                 && !pcmk_is_set(child->flags, pe_rsc_block)) {
                 // Remember one inactive instance in case we don't find active
                 inactive_instance = parent->fns->find_rsc(child, rsc_id, NULL,
                                                           pe_find_clone);
 
                 /* ... but don't use it if it was already associated with a
                  * pending action on another node
                  */
                 if (inactive_instance && inactive_instance->pending_node
                     && (inactive_instance->pending_node->details != node->details)) {
                     inactive_instance = NULL;
                 }
             }
         }
     }
 
     if ((rsc == NULL) && !skip_inactive && (inactive_instance != NULL)) {
         pe_rsc_trace(parent, "Resource %s, empty slot", inactive_instance->id);
         rsc = inactive_instance;
     }
 
     /* If the resource has "requires" set to "quorum" or "nothing", and we don't
      * have a clone instance for every node, we don't want to consume a valid
      * instance number for unclean nodes. Such instances may appear to be active
      * according to the history, but should be considered inactive, so we can
      * start an instance elsewhere. Treat such instances as orphans.
      *
      * An exception is instances running on guest nodes -- since guest node
      * "fencing" is actually just a resource stop, requires shouldn't apply.
      *
      * @TODO Ideally, we'd use an inactive instance number if it is not needed
      * for any clean instances. However, we don't know that at this point.
      */
     if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)
         && (!node->details->online || node->details->unclean)
         && !pe__is_guest_node(node)
         && !pe__is_universal_clone(parent, data_set)) {
 
         rsc = NULL;
     }
 
     if (rsc == NULL) {
         rsc = create_anonymous_orphan(parent, rsc_id, node, data_set);
         pe_rsc_trace(parent, "Resource %s, orphan", rsc->id);
     }
     return rsc;
 }
 
 static pe_resource_t *
 unpack_find_resource(pe_working_set_t * data_set, pe_node_t * node, const char *rsc_id,
                      xmlNode * rsc_entry)
 {
     pe_resource_t *rsc = NULL;
     pe_resource_t *parent = NULL;
 
     crm_trace("looking for %s", rsc_id);
     rsc = pe_find_resource(data_set->resources, rsc_id);
 
     if (rsc == NULL) {
         /* If we didn't find the resource by its name in the operation history,
          * check it again as a clone instance. Even when clone-max=0, we create
          * a single :0 orphan to match against here.
          */
         char *clone0_id = clone_zero(rsc_id);
         pe_resource_t *clone0 = pe_find_resource(data_set->resources, clone0_id);
 
         if (clone0 && !pcmk_is_set(clone0->flags, pe_rsc_unique)) {
             rsc = clone0;
             parent = uber_parent(clone0);
             crm_trace("%s found as %s (%s)", rsc_id, clone0_id, parent->id);
         } else {
             crm_trace("%s is not known as %s either (orphan)",
                       rsc_id, clone0_id);
         }
         free(clone0_id);
 
     } else if (rsc->variant > pe_native) {
         crm_trace("Resource history for %s is orphaned because it is no longer primitive",
                   rsc_id);
         return NULL;
 
     } else {
         parent = uber_parent(rsc);
     }
 
     if (pe_rsc_is_anon_clone(parent)) {
 
         if (pe_rsc_is_bundled(parent)) {
             rsc = pe__find_bundle_replica(parent->parent, node);
         } else {
             char *base = clone_strip(rsc_id);
 
             rsc = find_anonymous_clone(data_set, node, parent, base);
             free(base);
             CRM_ASSERT(rsc != NULL);
         }
     }
 
     if (rsc && !pcmk__str_eq(rsc_id, rsc->id, pcmk__str_casei)
         && !pcmk__str_eq(rsc_id, rsc->clone_name, pcmk__str_casei)) {
 
         pcmk__str_update(&rsc->clone_name, rsc_id);
         pe_rsc_debug(rsc, "Internally renamed %s on %s to %s%s",
                      rsc_id, node->details->uname, rsc->id,
                      (pcmk_is_set(rsc->flags, pe_rsc_orphan)? " (ORPHAN)" : ""));
     }
     return rsc;
 }
 
 static pe_resource_t *
 process_orphan_resource(xmlNode * rsc_entry, pe_node_t * node, pe_working_set_t * data_set)
 {
     pe_resource_t *rsc = NULL;
     const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
 
     crm_debug("Detected orphan resource %s on %s", rsc_id, node->details->uname);
     rsc = create_fake_resource(rsc_id, rsc_entry, data_set);
     if (rsc == NULL) {
         return NULL;
     }
 
     if (!pcmk_is_set(data_set->flags, pe_flag_stop_rsc_orphans)) {
         pe__clear_resource_flags(rsc, pe_rsc_managed);
 
     } else {
         CRM_CHECK(rsc != NULL, return NULL);
         pe_rsc_trace(rsc, "Added orphan %s", rsc->id);
         resource_location(rsc, NULL, -INFINITY, "__orphan_do_not_run__", data_set);
     }
     return rsc;
 }
 
 static void
 process_rsc_state(pe_resource_t * rsc, pe_node_t * node,
                   enum action_fail_response on_fail,
                   xmlNode * migrate_op, pe_working_set_t * data_set)
 {
     pe_node_t *tmpnode = NULL;
     char *reason = NULL;
     enum action_fail_response save_on_fail = action_fail_ignore;
 
     CRM_ASSERT(rsc);
     pe_rsc_trace(rsc, "Resource %s is %s on %s: on_fail=%s",
                  rsc->id, role2text(rsc->role), node->details->uname, fail2text(on_fail));
 
     /* process current state */
     if (rsc->role != RSC_ROLE_UNKNOWN) {
         pe_resource_t *iter = rsc;
 
         while (iter) {
             if (g_hash_table_lookup(iter->known_on, node->details->id) == NULL) {
                 pe_node_t *n = pe__copy_node(node);
 
                 pe_rsc_trace(rsc, "%s%s%s known on %s",
                              rsc->id,
                              ((rsc->clone_name == NULL)? "" : " also known as "),
                              ((rsc->clone_name == NULL)? "" : rsc->clone_name),
                              n->details->uname);
                 g_hash_table_insert(iter->known_on, (gpointer) n->details->id, n);
             }
             if (pcmk_is_set(iter->flags, pe_rsc_unique)) {
                 break;
             }
             iter = iter->parent;
         }
     }
 
     /* If a managed resource is believed to be running, but node is down ... */
     if (rsc->role > RSC_ROLE_STOPPED
         && node->details->online == FALSE
         && node->details->maintenance == FALSE
         && pcmk_is_set(rsc->flags, pe_rsc_managed)) {
 
         gboolean should_fence = FALSE;
 
         /* If this is a guest node, fence it (regardless of whether fencing is
          * enabled, because guest node fencing is done by recovery of the
          * container resource rather than by the fencer). Mark the resource
          * we're processing as failed. When the guest comes back up, its
          * operation history in the CIB will be cleared, freeing the affected
          * resource to run again once we are sure we know its state.
          */
         if (pe__is_guest_node(node)) {
             pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             should_fence = TRUE;
 
         } else if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
             if (pe__is_remote_node(node) && node->details->remote_rsc
                 && !pcmk_is_set(node->details->remote_rsc->flags, pe_rsc_failed)) {
 
                 /* Setting unseen means that fencing of the remote node will
                  * occur only if the connection resource is not going to start
                  * somewhere. This allows connection resources on a failed
                  * cluster node to move to another node without requiring the
                  * remote nodes to be fenced as well.
                  */
                 node->details->unseen = TRUE;
                 reason = crm_strdup_printf("%s is active there (fencing will be"
                                            " revoked if remote connection can "
                                            "be re-established elsewhere)",
                                            rsc->id);
             }
             should_fence = TRUE;
         }
 
         if (should_fence) {
             if (reason == NULL) {
                reason = crm_strdup_printf("%s is thought to be active there", rsc->id);
             }
             pe_fence_node(data_set, node, reason, FALSE);
         }
         free(reason);
     }
 
     /* In order to calculate priority_fencing_delay correctly, save the failure information and pass it to native_add_running(). */
     save_on_fail = on_fail;
 
     if (node->details->unclean) {
         /* No extra processing needed
          * Also allows resources to be started again after a node is shot
          */
         on_fail = action_fail_ignore;
     }
 
     switch (on_fail) {
         case action_fail_ignore:
             /* nothing to do */
             break;
 
         case action_fail_demote:
             pe__set_resource_flags(rsc, pe_rsc_failed);
             demote_action(rsc, node, FALSE);
             break;
 
         case action_fail_fence:
             /* treat it as if it is still running
              * but also mark the node as unclean
              */
             reason = crm_strdup_printf("%s failed there", rsc->id);
             pe_fence_node(data_set, node, reason, FALSE);
             free(reason);
             break;
 
         case action_fail_standby:
             node->details->standby = TRUE;
             node->details->standby_onfail = TRUE;
             break;
 
         case action_fail_block:
             /* is_managed == FALSE will prevent any
              * actions being sent for the resource
              */
             pe__clear_resource_flags(rsc, pe_rsc_managed);
             pe__set_resource_flags(rsc, pe_rsc_block);
             break;
 
         case action_fail_migrate:
             /* make sure it comes up somewhere else
              * or not at all
              */
             resource_location(rsc, node, -INFINITY, "__action_migration_auto__", data_set);
             break;
 
         case action_fail_stop:
             pe__set_next_role(rsc, RSC_ROLE_STOPPED, "on-fail=stop");
             break;
 
         case action_fail_recover:
             if (rsc->role != RSC_ROLE_STOPPED && rsc->role != RSC_ROLE_UNKNOWN) {
                 pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case action_fail_restart_container:
             pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             if (rsc->container && pe_rsc_is_bundled(rsc)) {
                 /* A bundle's remote connection can run on a different node than
                  * the bundle's container. We don't necessarily know where the
                  * container is running yet, so remember it and add a stop
                  * action for it later.
                  */
                 data_set->stop_needed = g_list_prepend(data_set->stop_needed,
                                                        rsc->container);
             } else if (rsc->container) {
                 stop_action(rsc->container, node, FALSE);
             } else if (rsc->role != RSC_ROLE_STOPPED && rsc->role != RSC_ROLE_UNKNOWN) {
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case action_fail_reset_remote:
             pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
                 tmpnode = NULL;
                 if (rsc->is_remote_node) {
                     tmpnode = pe_find_node(data_set->nodes, rsc->id);
                 }
                 if (tmpnode &&
                     pe__is_remote_node(tmpnode) &&
                     tmpnode->details->remote_was_fenced == 0) {
 
                     /* The remote connection resource failed in a way that
                      * should result in fencing the remote node.
                      */
                     pe_fence_node(data_set, tmpnode,
                                   "remote connection is unrecoverable", FALSE);
                 }
             }
 
             /* require the stop action regardless if fencing is occurring or not. */
             if (rsc->role > RSC_ROLE_STOPPED) {
                 stop_action(rsc, node, FALSE);
             }
 
             /* if reconnect delay is in use, prevent the connection from exiting the
              * "STOPPED" role until the failure is cleared by the delay timeout. */
             if (rsc->remote_reconnect_ms) {
                 pe__set_next_role(rsc, RSC_ROLE_STOPPED, "remote reset");
             }
             break;
     }
 
     /* ensure a remote-node connection failure forces an unclean remote-node
      * to be fenced. By setting unseen = FALSE, the remote-node failure will
      * result in a fencing operation regardless if we're going to attempt to 
      * reconnect to the remote-node in this transition or not. */
     if (pcmk_is_set(rsc->flags, pe_rsc_failed) && rsc->is_remote_node) {
         tmpnode = pe_find_node(data_set->nodes, rsc->id);
         if (tmpnode && tmpnode->details->unclean) {
             tmpnode->details->unseen = FALSE;
         }
     }
 
     if (rsc->role != RSC_ROLE_STOPPED && rsc->role != RSC_ROLE_UNKNOWN) {
         if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
             if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
                 pcmk__config_warn("Detected active orphan %s running on %s",
                                   rsc->id, node->details->uname);
             } else {
                 pcmk__config_warn("Resource '%s' must be stopped manually on "
                                   "%s because cluster is configured not to "
                                   "stop active orphans",
                                   rsc->id, node->details->uname);
             }
         }
 
         native_add_running(rsc, node, data_set, (save_on_fail != action_fail_ignore));
         switch (on_fail) {
             case action_fail_ignore:
                 break;
             case action_fail_demote:
             case action_fail_block:
                 pe__set_resource_flags(rsc, pe_rsc_failed);
                 break;
             default:
                 pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
                 break;
         }
 
     } else if (rsc->clone_name && strchr(rsc->clone_name, ':') != NULL) {
         /* Only do this for older status sections that included instance numbers
          * Otherwise stopped instances will appear as orphans
          */
         pe_rsc_trace(rsc, "Resetting clone_name %s for %s (stopped)", rsc->clone_name, rsc->id);
         free(rsc->clone_name);
         rsc->clone_name = NULL;
 
     } else {
         GList *possible_matches = pe__resource_actions(rsc, node, RSC_STOP,
                                                        FALSE);
         GList *gIter = possible_matches;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pe_action_t *stop = (pe_action_t *) gIter->data;
 
             pe__set_action_flags(stop, pe_action_optional);
         }
 
         g_list_free(possible_matches);
     }
 
     /* A successful stop after migrate_to on the migration source doesn't make
      * the partially migrated resource stopped on the migration target.
      */
     if (rsc->role == RSC_ROLE_STOPPED
         && rsc->partial_migration_source
         && rsc->partial_migration_source->details == node->details
         && rsc->partial_migration_target
         && rsc->running_on) {
 
         rsc->role = RSC_ROLE_STARTED;
     }
 }
 
 /* create active recurring operations as optional */
 static void
 process_recurring(pe_node_t * node, pe_resource_t * rsc,
                   int start_index, int stop_index,
                   GList *sorted_op_list, pe_working_set_t * data_set)
 {
     int counter = -1;
     const char *task = NULL;
     const char *status = NULL;
     GList *gIter = sorted_op_list;
 
     CRM_ASSERT(rsc);
     pe_rsc_trace(rsc, "%s: Start index %d, stop index = %d", rsc->id, start_index, stop_index);
 
     for (; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         guint interval_ms = 0;
         char *key = NULL;
         const char *id = ID(rsc_op);
 
         counter++;
 
         if (node->details->online == FALSE) {
             pe_rsc_trace(rsc, "Skipping %s/%s: node is offline", rsc->id, node->details->uname);
             break;
 
             /* Need to check if there's a monitor for role="Stopped" */
         } else if (start_index < stop_index && counter <= stop_index) {
             pe_rsc_trace(rsc, "Skipping %s/%s: resource is not active", id, node->details->uname);
             continue;
 
         } else if (counter < start_index) {
             pe_rsc_trace(rsc, "Skipping %s/%s: old %d", id, node->details->uname, counter);
             continue;
         }
 
         crm_element_value_ms(rsc_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
         if (interval_ms == 0) {
             pe_rsc_trace(rsc, "Skipping %s/%s: non-recurring", id, node->details->uname);
             continue;
         }
 
         status = crm_element_value(rsc_op, XML_LRM_ATTR_OPSTATUS);
         if (pcmk__str_eq(status, "-1", pcmk__str_casei)) {
             pe_rsc_trace(rsc, "Skipping %s/%s: status", id, node->details->uname);
             continue;
         }
         task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
         /* create the action */
         key = pcmk__op_key(rsc->id, task, interval_ms);
         pe_rsc_trace(rsc, "Creating %s/%s", key, node->details->uname);
         custom_action(rsc, key, task, node, TRUE, TRUE, data_set);
     }
 }
 
 void
 calculate_active_ops(GList *sorted_op_list, int *start_index, int *stop_index)
 {
     int counter = -1;
     int implied_monitor_start = -1;
     int implied_clone_start = -1;
     const char *task = NULL;
     const char *status = NULL;
     GList *gIter = sorted_op_list;
 
     *stop_index = -1;
     *start_index = -1;
 
     for (; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         counter++;
 
         task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
         status = crm_element_value(rsc_op, XML_LRM_ATTR_OPSTATUS);
 
         if (pcmk__str_eq(task, CRMD_ACTION_STOP, pcmk__str_casei)
             && pcmk__str_eq(status, "0", pcmk__str_casei)) {
             *stop_index = counter;
 
         } else if (pcmk__strcase_any_of(task, CRMD_ACTION_START, CRMD_ACTION_MIGRATED, NULL)) {
             *start_index = counter;
 
         } else if ((implied_monitor_start <= *stop_index) && pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
             const char *rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC);
 
             if (pcmk__strcase_any_of(rc, "0", "8", NULL)) {
                 implied_monitor_start = counter;
             }
         } else if (pcmk__strcase_any_of(task, CRMD_ACTION_PROMOTE, CRMD_ACTION_DEMOTE, NULL)) {
             implied_clone_start = counter;
         }
     }
 
     if (*start_index == -1) {
         if (implied_clone_start != -1) {
             *start_index = implied_clone_start;
         } else if (implied_monitor_start != -1) {
             *start_index = implied_monitor_start;
         }
     }
 }
 
 // If resource history entry has shutdown lock, remember lock node and time
 static void
 unpack_shutdown_lock(xmlNode *rsc_entry, pe_resource_t *rsc, pe_node_t *node,
                      pe_working_set_t *data_set)
 {
     time_t lock_time = 0;   // When lock started (i.e. node shutdown time)
 
     if ((crm_element_value_epoch(rsc_entry, XML_CONFIG_ATTR_SHUTDOWN_LOCK,
                                  &lock_time) == pcmk_ok) && (lock_time != 0)) {
 
         if ((data_set->shutdown_lock > 0)
             && (get_effective_time(data_set)
                 > (lock_time + data_set->shutdown_lock))) {
             pe_rsc_info(rsc, "Shutdown lock for %s on %s expired",
                         rsc->id, node->details->uname);
             pe__clear_resource_history(rsc, node, data_set);
         } else {
             rsc->lock_node = node;
             rsc->lock_time = lock_time;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one lrm_resource entry from a node's CIB status
  *
  * \param[in] node       Node whose status is being unpacked
  * \param[in] rsc_entry  lrm_resource XML being unpacked
  * \param[in] data_set   Cluster working set
  *
  * \return Resource corresponding to the entry, or NULL if no operation history
  */
 static pe_resource_t *
 unpack_lrm_resource(pe_node_t *node, xmlNode *lrm_resource,
                     pe_working_set_t *data_set)
 {
     GList *gIter = NULL;
     int stop_index = -1;
     int start_index = -1;
     enum rsc_role_e req_role = RSC_ROLE_UNKNOWN;
 
     const char *task = NULL;
     const char *rsc_id = ID(lrm_resource);
 
     pe_resource_t *rsc = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     xmlNode *migrate_op = NULL;
     xmlNode *rsc_op = NULL;
     xmlNode *last_failure = NULL;
 
     enum action_fail_response on_fail = action_fail_ignore;
     enum rsc_role_e saved_role = RSC_ROLE_UNKNOWN;
 
     if (rsc_id == NULL) {
         crm_warn("Ignoring malformed " XML_LRM_TAG_RESOURCE
                  " entry without id");
         return NULL;
     }
     crm_trace("Unpacking " XML_LRM_TAG_RESOURCE " for %s on %s",
               rsc_id, node->details->uname);
 
     // Build a list of individual lrm_rsc_op entries, so we can sort them
     for (rsc_op = first_named_child(lrm_resource, XML_LRM_TAG_RSC_OP);
          rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) {
 
         op_list = g_list_prepend(op_list, rsc_op);
     }
 
     if (!pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         }
     }
 
     /* find the resource */
     rsc = unpack_find_resource(data_set, node, rsc_id, lrm_resource);
     if (rsc == NULL) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         } else {
             rsc = process_orphan_resource(lrm_resource, node, data_set);
         }
     }
     CRM_ASSERT(rsc != NULL);
 
     // Check whether the resource is "shutdown-locked" to this node
     if (pcmk_is_set(data_set->flags, pe_flag_shutdown_lock)) {
         unpack_shutdown_lock(lrm_resource, rsc, node, data_set);
     }
 
     /* process operations */
     saved_role = rsc->role;
     rsc->role = RSC_ROLE_UNKNOWN;
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
         if (pcmk__str_eq(task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) {
             migrate_op = rsc_op;
         }
 
         unpack_rsc_op(rsc, node, rsc_op, &last_failure, &on_fail, data_set);
     }
 
     /* create active recurring operations as optional */
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
     process_recurring(node, rsc, start_index, stop_index, sorted_op_list, data_set);
 
     /* no need to free the contents */
     g_list_free(sorted_op_list);
 
     process_rsc_state(rsc, node, on_fail, migrate_op, data_set);
 
     if (get_target_role(rsc, &req_role)) {
         if (rsc->next_role == RSC_ROLE_UNKNOWN || req_role < rsc->next_role) {
             pe__set_next_role(rsc, req_role, XML_RSC_ATTR_TARGET_ROLE);
 
         } else if (req_role > rsc->next_role) {
             pe_rsc_info(rsc, "%s: Not overwriting calculated next role %s"
                         " with requested next role %s",
                         rsc->id, role2text(rsc->next_role), role2text(req_role));
         }
     }
 
     if (saved_role > rsc->role) {
         rsc->role = saved_role;
     }
 
     return rsc;
 }
 
 static void
 handle_orphaned_container_fillers(xmlNode * lrm_rsc_list, pe_working_set_t * data_set)
 {
     xmlNode *rsc_entry = NULL;
     for (rsc_entry = pcmk__xe_first_child(lrm_rsc_list); rsc_entry != NULL;
          rsc_entry = pcmk__xe_next(rsc_entry)) {
 
         pe_resource_t *rsc;
         pe_resource_t *container;
         const char *rsc_id;
         const char *container_id;
 
         if (!pcmk__str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, pcmk__str_casei)) {
             continue;
         }
 
         container_id = crm_element_value(rsc_entry, XML_RSC_ATTR_CONTAINER);
         rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
         if (container_id == NULL || rsc_id == NULL) {
             continue;
         }
 
         container = pe_find_resource(data_set->resources, container_id);
         if (container == NULL) {
             continue;
         }
 
         rsc = pe_find_resource(data_set->resources, rsc_id);
         if (rsc == NULL ||
             !pcmk_is_set(rsc->flags, pe_rsc_orphan_container_filler) ||
             rsc->container != NULL) {
             continue;
         }
 
         pe_rsc_trace(rsc, "Mapped container of orphaned resource %s to %s",
                      rsc->id, container_id);
         rsc->container = container;
         container->fillers = g_list_append(container->fillers, rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one node's lrm status section
  *
  * \param[in] node      Node whose status is being unpacked
  * \param[in] xml       CIB node state XML
  * \param[in] data_set  Cluster working set
  */
 static void
 unpack_node_lrm(pe_node_t *node, xmlNode *xml, pe_working_set_t *data_set)
 {
     bool found_orphaned_container_filler = false;
 
     // Drill down to lrm_resources section
     xml = find_xml_node(xml, XML_CIB_TAG_LRM, FALSE);
     if (xml == NULL) {
         return;
     }
     xml = find_xml_node(xml, XML_LRM_TAG_RESOURCES, FALSE);
     if (xml == NULL) {
         return;
     }
 
     // Unpack each lrm_resource entry
     for (xmlNode *rsc_entry = first_named_child(xml, XML_LRM_TAG_RESOURCE);
          rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
 
         pe_resource_t *rsc = unpack_lrm_resource(node, rsc_entry, data_set);
 
         if ((rsc != NULL)
             && pcmk_is_set(rsc->flags, pe_rsc_orphan_container_filler)) {
             found_orphaned_container_filler = true;
         }
     }
 
     /* Now that all resource state has been unpacked for this node, map any
      * orphaned container fillers to their container resource.
      */
     if (found_orphaned_container_filler) {
         handle_orphaned_container_fillers(xml, data_set);
     }
 }
 
 static void
 set_active(pe_resource_t * rsc)
 {
     pe_resource_t *top = uber_parent(rsc);
 
     if (top && pcmk_is_set(top->flags, pe_rsc_promotable)) {
         rsc->role = RSC_ROLE_UNPROMOTED;
     } else {
         rsc->role = RSC_ROLE_STARTED;
     }
 }
 
 static void
 set_node_score(gpointer key, gpointer value, gpointer user_data)
 {
     pe_node_t *node = value;
     int *score = user_data;
 
     node->weight = *score;
 }
 
 #define STATUS_PATH_MAX 1024
 static xmlNode *
 find_lrm_op(const char *resource, const char *op, const char *node, const char *source,
             int target_rc, pe_working_set_t *data_set)
 {
     int offset = 0;
     char xpath[STATUS_PATH_MAX];
     xmlNode *xml = NULL;
 
     offset += snprintf(xpath + offset, STATUS_PATH_MAX - offset, "//node_state[@uname='%s']", node);
     offset +=
         snprintf(xpath + offset, STATUS_PATH_MAX - offset, "//" XML_LRM_TAG_RESOURCE "[@id='%s']",
                  resource);
 
     /* Need to check against transition_magic too? */
     if (source && pcmk__str_eq(op, CRMD_ACTION_MIGRATE, pcmk__str_casei)) {
         offset +=
             snprintf(xpath + offset, STATUS_PATH_MAX - offset,
                      "/" XML_LRM_TAG_RSC_OP "[@operation='%s' and @migrate_target='%s']", op,
                      source);
     } else if (source && pcmk__str_eq(op, CRMD_ACTION_MIGRATED, pcmk__str_casei)) {
         offset +=
             snprintf(xpath + offset, STATUS_PATH_MAX - offset,
                      "/" XML_LRM_TAG_RSC_OP "[@operation='%s' and @migrate_source='%s']", op,
                      source);
     } else {
         offset +=
             snprintf(xpath + offset, STATUS_PATH_MAX - offset,
                      "/" XML_LRM_TAG_RSC_OP "[@operation='%s']", op);
     }
 
     CRM_LOG_ASSERT(offset > 0);
     xml = get_xpath_object(xpath, data_set->input, LOG_DEBUG);
 
     if (xml && target_rc >= 0) {
         int rc = PCMK_OCF_UNKNOWN_ERROR;
         int status = PCMK_EXEC_ERROR;
 
         crm_element_value_int(xml, XML_LRM_ATTR_RC, &rc);
         crm_element_value_int(xml, XML_LRM_ATTR_OPSTATUS, &status);
         if ((rc != target_rc) || (status != PCMK_EXEC_DONE)) {
             return NULL;
         }
     }
     return xml;
 }
 
 static xmlNode *
 find_lrm_resource(const char *rsc_id, const char *node_name,
                   pe_working_set_t *data_set)
 {
     int offset = 0;
     char xpath[STATUS_PATH_MAX];
     xmlNode *xml = NULL;
 
     offset += snprintf(xpath + offset, STATUS_PATH_MAX - offset,
                        "//node_state[@uname='%s']", node_name);
     offset +=
         snprintf(xpath + offset, STATUS_PATH_MAX - offset,
                  "//" XML_LRM_TAG_RESOURCE "[@id='%s']", rsc_id);
 
     CRM_LOG_ASSERT(offset > 0);
     xml = get_xpath_object(xpath, data_set->input, LOG_DEBUG);
 
     return xml;
 }
 
 static bool
 unknown_on_node(const char *rsc_id, const char *node_name,
                 pe_working_set_t *data_set)
 {
     xmlNode *lrm_resource = NULL;
 
     lrm_resource = find_lrm_resource(rsc_id, node_name, data_set);
 
     /* If the resource has no lrm_rsc_op history on the node, that means its
      * state is unknown there.
      */
     return (lrm_resource == NULL
             || first_named_child(lrm_resource, XML_LRM_TAG_RSC_OP) == NULL);
 }
 
 /*!
  * \brief Check whether a probe/monitor indicating the resource was not running
  * on a node happened after some event
  *
  * \param[in] rsc_id    Resource being checked
  * \param[in] node_name Node being checked
  * \param[in] xml_op    Event that monitor is being compared to
  * \param[in] data_set  Cluster working set
  *
  * \return true if such a monitor happened after event, false otherwise
  */
 static bool
 monitor_not_running_after(const char *rsc_id, const char *node_name,
                           xmlNode *xml_op, bool same_node,
                           pe_working_set_t *data_set)
 {
     /* Any probe/monitor operation on the node indicating it was not running
      * there
      */
     xmlNode *monitor = find_lrm_op(rsc_id, CRMD_ACTION_STATUS, node_name,
                                    NULL, PCMK_OCF_NOT_RUNNING, data_set);
 
     return (monitor && pe__is_newer_op(monitor, xml_op, same_node) > 0);
 }
 
 /*!
  * \brief Check whether any non-monitor operation on a node happened after some
  * event
  *
  * \param[in] rsc_id    Resource being checked
  * \param[in] node_name Node being checked
  * \param[in] xml_op    Event that non-monitor is being compared to
  * \param[in] same_node Whether the operations are on the same node
  * \param[in] data_set  Cluster working set
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 non_monitor_after(const char *rsc_id, const char *node_name, xmlNode *xml_op,
                   bool same_node, pe_working_set_t *data_set)
 {
     xmlNode *lrm_resource = NULL;
 
     lrm_resource = find_lrm_resource(rsc_id, node_name, data_set);
     if (lrm_resource == NULL) {
         return false;
     }
 
     for (xmlNode *op = first_named_child(lrm_resource, XML_LRM_TAG_RSC_OP);
          op != NULL; op = crm_next_same_xml(op)) {
         const char * task = NULL;
 
         if (op == xml_op) {
             continue;
         }
 
         task = crm_element_value(op, XML_LRM_ATTR_TASK);
 
         if (pcmk__str_any_of(task, CRMD_ACTION_START, CRMD_ACTION_STOP,
                              CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, NULL)
             && pe__is_newer_op(op, xml_op, same_node) > 0) {
             return true;
         }
     }
 
     return false;
 }
 
 /*!
  * \brief Check whether the resource has newer state on a node after a migration
  * attempt
  *
  * \param[in] rsc_id       Resource being checked
  * \param[in] node_name    Node being checked
  * \param[in] migrate_to   Any migrate_to event that is being compared to
  * \param[in] migrate_from Any migrate_from event that is being compared to
  * \param[in] data_set     Cluster working set
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 newer_state_after_migrate(const char *rsc_id, const char *node_name,
                           xmlNode *migrate_to, xmlNode *migrate_from,
                           pe_working_set_t *data_set)
 {
     xmlNode *xml_op = migrate_to;
     const char *source = NULL;
     const char *target = NULL;
     bool same_node = false;
 
     if (migrate_from) {
         xml_op = migrate_from;
     }
 
     source = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_SOURCE);
     target = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_TARGET);
 
     /* It's preferred to compare to the migrate event on the same node if
      * existing, since call ids are more reliable.
      */
     if (pcmk__str_eq(node_name, target, pcmk__str_casei)) {
         if (migrate_from) {
            xml_op = migrate_from;
            same_node = true;
 
         } else {
            xml_op = migrate_to;
         }
 
     } else if (pcmk__str_eq(node_name, source, pcmk__str_casei)) {
         if (migrate_to) {
            xml_op = migrate_to;
            same_node = true;
 
         } else {
            xml_op = migrate_from;
         }
     }
 
     /* If there's any newer non-monitor operation on the node, or any newer
      * probe/monitor operation on the node indicating it was not running there,
      * the migration events potentially no longer matter for the node.
      */
     return non_monitor_after(rsc_id, node_name, xml_op, same_node, data_set)
            || monitor_not_running_after(rsc_id, node_name, xml_op, same_node,
                                         data_set);
 }
 
 static void
 unpack_migrate_to_success(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op,
                           pe_working_set_t *data_set)
 {
     /* A successful migration sequence is:
      *    migrate_to on source node
      *    migrate_from on target node
      *    stop on source node
      *
      * But there could be scenarios like (It's easier to produce with cluster
      * property batch-limit=1):
      *
      * - rscA is live-migrating from node1 to node2.
      *
      * - Before migrate_to on node1 returns, put node2 into standby.
      *
      * - Transition aborts upon return of successful migrate_to on node1. New
      *   transition is going to stop the rscA on both nodes and start it on
      *   node1.
      *
      * - While it is stopping on node1, run something that is going to make
      *   the transition abort again like:
      *   crm_resource  --resource rscA --ban --node node2
      *
      * - Transition aborts upon return of stop on node1.
      *
      * Now although there's a stop on node1, it's still a partial migration and
      * rscA is still potentially active on node2.
      *
      * So even if a migrate_to is followed by a stop, we still need to check
      * whether there's a corresponding migrate_from or any newer operation on
      * the target.
      *
      * If no migrate_from has happened, the migration is considered to be
      * "partial". If the migrate_from failed, make sure the resource gets
      * stopped on both source and target (if up).
      *
      * If the migrate_to and migrate_from both succeeded (which also implies the
      * resource is no longer running on the source), but there is no stop, the
      * migration is considered to be "dangling". Schedule a stop on the source
      * in this case.
      */
     int from_rc = 0;
     int from_status = 0;
     pe_node_t *target_node = NULL;
     pe_node_t *source_node = NULL;
     xmlNode *migrate_from = NULL;
     const char *source = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_SOURCE);
     const char *target = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_TARGET);
     bool source_newer_op = false;
     bool target_newer_state = false;
 
     // Sanity check
     CRM_CHECK(source && target && !strcmp(source, node->details->uname), return);
 
     /* If there's any newer non-monitor operation on the source, this migrate_to
      * potentially no longer matters for the source.
      */
     source_newer_op = non_monitor_after(rsc->id, source, xml_op, true,
                                         data_set);
 
     // Check whether there was a migrate_from action on the target
     migrate_from = find_lrm_op(rsc->id, CRMD_ACTION_MIGRATED, target,
                                source, -1, data_set);
 
     /* Even if there's a newer non-monitor operation on the source, we still
      * need to check how this migrate_to might matter for the target.
      */
     if (source_newer_op && migrate_from) {
         return;
     }
 
     /* If the resource has newer state on the target after the migration
      * events, this migrate_to no longer matters for the target.
      */
     target_newer_state = newer_state_after_migrate(rsc->id, target, xml_op,
                                                    migrate_from, data_set);
 
     if (source_newer_op && target_newer_state) {
         return;
     }
 
     // Clones are not allowed to migrate, so role can't be promoted
     rsc->role = RSC_ROLE_STARTED;
 
     target_node = pe_find_node(data_set->nodes, target);
     source_node = pe_find_node(data_set->nodes, source);
 
     if (migrate_from) {
         crm_element_value_int(migrate_from, XML_LRM_ATTR_RC, &from_rc);
         crm_element_value_int(migrate_from, XML_LRM_ATTR_OPSTATUS, &from_status);
         pe_rsc_trace(rsc, "%s op on %s exited with status=%d, rc=%d",
                      ID(migrate_from), target, from_status, from_rc);
     }
 
     if (migrate_from && from_rc == PCMK_OCF_OK
         && (from_status == PCMK_EXEC_DONE)) {
         /* The migrate_to and migrate_from both succeeded, so mark the migration
          * as "dangling". This will be used to schedule a stop action on the
          * source without affecting the target.
          */
         pe_rsc_trace(rsc, "Detected dangling migration op: %s on %s", ID(xml_op),
                      source);
         rsc->role = RSC_ROLE_STOPPED;
         rsc->dangling_migrations = g_list_prepend(rsc->dangling_migrations, node);
 
     } else if (migrate_from && (from_status != PCMK_EXEC_PENDING)) { // Failed
         /* If the resource has newer state on the target, this migrate_to no
          * longer matters for the target.
          */
         if (!target_newer_state
             && target_node && target_node->details->online) {
             pe_rsc_trace(rsc, "Marking active on %s %p %d", target, target_node,
                          target_node->details->online);
             native_add_running(rsc, target_node, data_set, TRUE);
 
         } else {
             /* With the earlier bail logic, migrate_from != NULL here implies
              * source_newer_op is false, meaning this migrate_to still matters
              * for the source.
              * Consider it failed here - forces a restart, prevents migration
              */
             pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             pe__clear_resource_flags(rsc, pe_rsc_allow_migrate);
         }
 
     } else { // Pending, or complete but erased
         /* If the resource has newer state on the target, this migrate_to no
          * longer matters for the target.
          */
         if (!target_newer_state
             && target_node && target_node->details->online) {
             pe_rsc_trace(rsc, "Marking active on %s %p %d", target, target_node,
                          target_node->details->online);
 
             native_add_running(rsc, target_node, data_set, FALSE);
             if (source_node && source_node->details->online) {
                 /* This is a partial migration: the migrate_to completed
                  * successfully on the source, but the migrate_from has not
                  * completed. Remember the source and target; if the newly
                  * chosen target remains the same when we schedule actions
                  * later, we may continue with the migration.
                  */
                 rsc->partial_migration_target = target_node;
                 rsc->partial_migration_source = source_node;
             }
         } else if (!source_newer_op) {
             /* This migrate_to matters for the source only if it's the last
              * non-monitor operation here.
              * Consider it failed here - forces a restart, prevents migration
              */
             pe__set_resource_flags(rsc, pe_rsc_failed|pe_rsc_stop);
             pe__clear_resource_flags(rsc, pe_rsc_allow_migrate);
         }
     }
 }
 
 static void
 unpack_migrate_to_failure(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op,
                           pe_working_set_t *data_set)
 {
     xmlNode *target_migrate_from = NULL;
     const char *source = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_SOURCE);
     const char *target = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_TARGET);
 
     // Sanity check
     CRM_CHECK(source && target && !strcmp(source, node->details->uname), return);
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     rsc->role = RSC_ROLE_STARTED;
 
     // Check for migrate_from on the target
     target_migrate_from = find_lrm_op(rsc->id, CRMD_ACTION_MIGRATED, target,
                                       source, PCMK_OCF_OK, data_set);
 
     if (/* If the resource state is unknown on the target, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(rsc->id, target, data_set)
         /* If the resource has newer state on the target after the migration
          * events, this migrate_to no longer matters for the target.
          */
         && !newer_state_after_migrate(rsc->id, target, xml_op, target_migrate_from,
                                       data_set)) {
         /* The resource has no newer state on the target, so assume it's still
          * active there.
          * (if it is up).
          */
         pe_node_t *target_node = pe_find_node(data_set->nodes, target);
 
         if (target_node && target_node->details->online) {
             native_add_running(rsc, target_node, data_set, FALSE);
         }
 
     } else if (!non_monitor_after(rsc->id, source, xml_op, true, data_set)) {
         /* We know the resource has newer state on the target, but this
          * migrate_to still matters for the source as long as there's no newer
          * non-monitor operation there.
          */
 
         // Mark node as having dangling migration so we can force a stop later
         rsc->dangling_migrations = g_list_prepend(rsc->dangling_migrations, node);
     }
 }
 
 static void
 unpack_migrate_from_failure(pe_resource_t *rsc, pe_node_t *node,
                             xmlNode *xml_op, pe_working_set_t *data_set)
 {
     xmlNode *source_migrate_to = NULL;
     const char *source = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_SOURCE);
     const char *target = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_TARGET);
 
     // Sanity check
     CRM_CHECK(source && target && !strcmp(target, node->details->uname), return);
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     rsc->role = RSC_ROLE_STARTED;
 
     // Check for a migrate_to on the source
     source_migrate_to = find_lrm_op(rsc->id, CRMD_ACTION_MIGRATE,
                                     source, target, PCMK_OCF_OK, data_set);
 
     if (/* If the resource state is unknown on the source, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(rsc->id, source, data_set)
         /* If the resource has newer state on the source after the migration
          * events, this migrate_from no longer matters for the source.
          */
         && !newer_state_after_migrate(rsc->id, source, source_migrate_to, xml_op,
                                       data_set)) {
         /* The resource has no newer state on the source, so assume it's still
          * active there (if it is up).
          */
         pe_node_t *source_node = pe_find_node(data_set->nodes, source);
 
         if (source_node && source_node->details->online) {
             native_add_running(rsc, source_node, data_set, TRUE);
         }
     }
 }
 
 static void
 record_failed_op(xmlNode *op, const pe_node_t *node,
                  const pe_resource_t *rsc, pe_working_set_t *data_set)
 {
     xmlNode *xIter = NULL;
     const char *op_key = crm_element_value(op, XML_LRM_ATTR_TASK_KEY);
 
     if (node->details->online == FALSE) {
         return;
     }
 
     for (xIter = data_set->failed->children; xIter; xIter = xIter->next) {
         const char *key = crm_element_value(xIter, XML_LRM_ATTR_TASK_KEY);
         const char *uname = crm_element_value(xIter, XML_ATTR_UNAME);
 
         if(pcmk__str_eq(op_key, key, pcmk__str_casei) && pcmk__str_eq(uname, node->details->uname, pcmk__str_casei)) {
             crm_trace("Skipping duplicate entry %s on %s", op_key, node->details->uname);
             return;
         }
     }
 
     crm_trace("Adding entry %s on %s", op_key, node->details->uname);
     crm_xml_add(op, XML_ATTR_UNAME, node->details->uname);
     crm_xml_add(op, XML_LRM_ATTR_RSCID, rsc->id);
     add_node_copy(data_set->failed, op);
 }
 
 static const char *get_op_key(xmlNode *xml_op)
 {
     const char *key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     if(key == NULL) {
         key = ID(xml_op);
     }
     return key;
 }
 
 static const char *
 last_change_str(xmlNode *xml_op)
 {
     time_t when;
     const char *when_s = NULL;
 
     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                 &when) == pcmk_ok) {
         when_s = pcmk__epoch2str(&when);
         if (when_s) {
             // Skip day of week to make message shorter
             when_s = strchr(when_s, ' ');
             if (when_s) {
                 ++when_s;
             }
         }
     }
     return ((when_s && *when_s)? when_s : "unknown time");
 }
 
 /*!
  * \internal
  * \brief Compare two on-fail values
  *
  * \param[in] first   One on-fail value to compare
  * \param[in] second  The other on-fail value to compare
  *
  * \return A negative number if second is more severe than first, zero if they
  *         are equal, or a positive number if first is more severe than second.
  * \note This is only needed until the action_fail_response values can be
  *       renumbered at the next API compatibility break.
  */
 static int
 cmp_on_fail(enum action_fail_response first, enum action_fail_response second)
 {
     switch (first) {
         case action_fail_demote:
             switch (second) {
                 case action_fail_ignore:
                     return 1;
                 case action_fail_demote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case action_fail_reset_remote:
             switch (second) {
                 case action_fail_ignore:
                 case action_fail_demote:
                 case action_fail_recover:
                     return 1;
                 case action_fail_reset_remote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case action_fail_restart_container:
             switch (second) {
                 case action_fail_ignore:
                 case action_fail_demote:
                 case action_fail_recover:
                 case action_fail_reset_remote:
                     return 1;
                 case action_fail_restart_container:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         default:
             break;
     }
     switch (second) {
         case action_fail_demote:
             return (first == action_fail_ignore)? -1 : 1;
 
         case action_fail_reset_remote:
             switch (first) {
                 case action_fail_ignore:
                 case action_fail_demote:
                 case action_fail_recover:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         case action_fail_restart_container:
             switch (first) {
                 case action_fail_ignore:
                 case action_fail_demote:
                 case action_fail_recover:
                 case action_fail_reset_remote:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         default:
             break;
     }
     return first - second;
 }
 
 static void
 unpack_rsc_op_failure(pe_resource_t * rsc, pe_node_t * node, int rc, xmlNode * xml_op, xmlNode ** last_failure,
                       enum action_fail_response * on_fail, pe_working_set_t * data_set)
 {
     bool is_probe = false;
     pe_action_t *action = NULL;
 
     const char *key = get_op_key(xml_op);
     const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     const char *exit_reason = crm_element_value(xml_op,
                                                 XML_LRM_ATTR_EXIT_REASON);
 
     CRM_ASSERT(rsc);
     CRM_CHECK(task != NULL, return);
 
     *last_failure = xml_op;
 
     is_probe = pcmk_xe_is_probe(xml_op);
 
     if (exit_reason == NULL) {
         exit_reason = "";
     }
 
     if (!pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)
         && (rc == PCMK_OCF_NOT_INSTALLED)) {
         crm_trace("Unexpected result (%s%s%s) was recorded for "
                   "%s of %s on %s at %s " CRM_XS " rc=%d id=%s",
                   services_ocf_exitcode_str(rc),
                   (*exit_reason? ": " : ""), exit_reason,
                   (is_probe? "probe" : task), rsc->id, node->details->uname,
                   last_change_str(xml_op), rc, ID(xml_op));
     } else {
         crm_warn("Unexpected result (%s%s%s) was recorded for "
                   "%s of %s on %s at %s " CRM_XS " rc=%d id=%s",
                  services_ocf_exitcode_str(rc),
                  (*exit_reason? ": " : ""), exit_reason,
                  (is_probe? "probe" : task), rsc->id, node->details->uname,
                  last_change_str(xml_op), rc, ID(xml_op));
 
         if (is_probe && (rc != PCMK_OCF_OK)
             && (rc != PCMK_OCF_NOT_RUNNING)
             && (rc != PCMK_OCF_RUNNING_PROMOTED)) {
 
             /* A failed (not just unexpected) probe result could mean the user
              * didn't know resources will be probed even where they can't run.
              */
             crm_notice("If it is not possible for %s to run on %s, see "
                        "the resource-discovery option for location constraints",
                        rsc->id, node->details->uname);
         }
 
         record_failed_op(xml_op, node, rsc, data_set);
     }
 
     action = custom_action(rsc, strdup(key), task, NULL, TRUE, FALSE, data_set);
     if (cmp_on_fail(*on_fail, action->on_fail) < 0) {
         pe_rsc_trace(rsc, "on-fail %s -> %s for %s (%s)", fail2text(*on_fail),
                      fail2text(action->on_fail), action->uuid, key);
         *on_fail = action->on_fail;
     }
 
     if (!strcmp(task, CRMD_ACTION_STOP)) {
         resource_location(rsc, node, -INFINITY, "__stop_fail__", data_set);
 
     } else if (!strcmp(task, CRMD_ACTION_MIGRATE)) {
         unpack_migrate_to_failure(rsc, node, xml_op, data_set);
 
     } else if (!strcmp(task, CRMD_ACTION_MIGRATED)) {
         unpack_migrate_from_failure(rsc, node, xml_op, data_set);
 
     } else if (!strcmp(task, CRMD_ACTION_PROMOTE)) {
         rsc->role = RSC_ROLE_PROMOTED;
 
     } else if (!strcmp(task, CRMD_ACTION_DEMOTE)) {
         if (action->on_fail == action_fail_block) {
             rsc->role = RSC_ROLE_PROMOTED;
             pe__set_next_role(rsc, RSC_ROLE_STOPPED,
                               "demote with on-fail=block");
 
         } else if(rc == PCMK_OCF_NOT_RUNNING) {
             rsc->role = RSC_ROLE_STOPPED;
 
         } else {
             /* Staying in the promoted role would put the scheduler and
              * controller into a loop. Setting the role to unpromoted is not
              * dangerous because the resource will be stopped as part of
              * recovery, and any promotion will be ordered after that stop.
              */
             rsc->role = RSC_ROLE_UNPROMOTED;
         }
     }
 
     if(is_probe && rc == PCMK_OCF_NOT_INSTALLED) {
         /* leave stopped */
         pe_rsc_trace(rsc, "Leaving %s stopped", rsc->id);
         rsc->role = RSC_ROLE_STOPPED;
 
     } else if (rsc->role < RSC_ROLE_STARTED) {
         pe_rsc_trace(rsc, "Setting %s active", rsc->id);
         set_active(rsc);
     }
 
     pe_rsc_trace(rsc, "Resource %s: role=%s, unclean=%s, on_fail=%s, fail_role=%s",
                  rsc->id, role2text(rsc->role),
                  pcmk__btoa(node->details->unclean),
                  fail2text(action->on_fail), role2text(action->fail_role));
 
     if (action->fail_role != RSC_ROLE_STARTED && rsc->next_role < action->fail_role) {
         pe__set_next_role(rsc, action->fail_role, "failure");
     }
 
     if (action->fail_role == RSC_ROLE_STOPPED) {
         int score = -INFINITY;
 
         pe_resource_t *fail_rsc = rsc;
 
         if (fail_rsc->parent) {
             pe_resource_t *parent = uber_parent(fail_rsc);
 
             if (pe_rsc_is_clone(parent)
                 && !pcmk_is_set(parent->flags, pe_rsc_unique)) {
                 /* For clone resources, if a child fails on an operation
                  * with on-fail = stop, all the resources fail.  Do this by preventing
                  * the parent from coming up again. */
                 fail_rsc = parent;
             }
         }
         crm_notice("%s will not be started under current conditions",
                    fail_rsc->id);
         /* make sure it doesn't come up again */
         if (fail_rsc->allowed_nodes != NULL) {
             g_hash_table_destroy(fail_rsc->allowed_nodes);
         }
         fail_rsc->allowed_nodes = pe__node_list2table(data_set->nodes);
         g_hash_table_foreach(fail_rsc->allowed_nodes, set_node_score, &score);
     }
 
     pe_free_action(action);
 }
 
 /*!
  * \internal
  * \brief Remap informational monitor results and operation status
  *
  * For the monitor results, certain OCF codes are for providing extended information
  * to the user about services that aren't yet failed but not entirely healthy either.
  * These must be treated as the "normal" result by Pacemaker.
  *
  * For operation status, the action result can be used to determine an appropriate
  * status for the purposes of responding to the action.  The status provided by the
  * executor is not directly usable since the executor does not know what was expected.
  *
  * \param[in]     xml_op     Operation history entry XML from CIB status
  * \param[in,out] rsc        Resource that operation history entry is for
  * \param[in]     node       Node where operation was executed
  * \param[in]     data_set   Current cluster working set
  * \param[in,out] on_fail    What should be done about the result
  * \param[in]     target_rc  Expected return code of operation
  * \param[in,out] rc         Actual return code of operation
  * \param[in,out] status     Operation execution status
  *
  * \note If the result is remapped and the node is not shutting down or failed,
  *       the operation will be recorded in the data set's list of failed operations
  *       to highlight it for the user.
  *
  * \note This may update the resource's current and next role.
  */
 static void
 remap_operation(xmlNode *xml_op, pe_resource_t *rsc, pe_node_t *node,
                 pe_working_set_t *data_set, enum action_fail_response *on_fail,
                 int target_rc, int *rc, int *status) {
     bool is_probe = false;
     const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     const char *key = get_op_key(xml_op);
     const char *exit_reason = crm_element_value(xml_op,
                                                 XML_LRM_ATTR_EXIT_REASON);
 
     if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_none)) {
         int remapped_rc = pcmk__effective_rc(*rc);
 
         if (*rc != remapped_rc) {
             crm_trace("Remapping monitor result %d to %d", *rc, remapped_rc);
             if (!node->details->shutdown || node->details->online) {
                 record_failed_op(xml_op, node, rsc, data_set);
             }
 
             *rc = remapped_rc;
         }
     }
 
     if (!pe_rsc_is_bundled(rsc) && pcmk_xe_mask_probe_failure(xml_op)) {
         *status = PCMK_EXEC_DONE;
         *rc = PCMK_OCF_NOT_RUNNING;
     }
 
     /* If the executor reported an operation status of anything but done or
      * error, consider that final. But for done or error, we know better whether
      * it should be treated as a failure or not, because we know the expected
      * result.
      */
     if (*status != PCMK_EXEC_DONE && *status != PCMK_EXEC_ERROR) {
         return;
     }
 
     CRM_ASSERT(rsc);
     CRM_CHECK(task != NULL,
               *status = PCMK_EXEC_ERROR; return);
 
     *status = PCMK_EXEC_DONE;
 
     if (exit_reason == NULL) {
         exit_reason = "";
     }
 
     is_probe = pcmk_xe_is_probe(xml_op);
 
     if (is_probe) {
         task = "probe";
     }
 
     if (target_rc < 0) {
         /* Pre-1.0 Pacemaker versions, and Pacemaker 1.1.6 or earlier with
          * Heartbeat 2.0.7 or earlier as the cluster layer, did not include the
          * target_rc in the transition key, which (along with the similar case
          * of a corrupted transition key in the CIB) will be reported to this
          * function as -1. Pacemaker 2.0+ does not support rolling upgrades from
          * those versions or processing of saved CIB files from those versions,
          * so we do not need to care much about this case.
          */
         *status = PCMK_EXEC_ERROR;
         crm_warn("Expected result not found for %s on %s (corrupt or obsolete CIB?)",
                  key, node->details->uname);
 
     } else if (target_rc != *rc) {
         *status = PCMK_EXEC_ERROR;
         pe_rsc_debug(rsc, "%s on %s: expected %d (%s), got %d (%s%s%s)",
                      key, node->details->uname,
                      target_rc, services_ocf_exitcode_str(target_rc),
                      *rc, services_ocf_exitcode_str(*rc),
                      (*exit_reason? ": " : ""), exit_reason);
     }
 
     switch (*rc) {
         case PCMK_OCF_OK:
             if (is_probe && (target_rc == PCMK_OCF_NOT_RUNNING)) {
                 *status = PCMK_EXEC_DONE;
                 pe_rsc_info(rsc, "Probe found %s active on %s at %s",
                             rsc->id, node->details->uname,
                             last_change_str(xml_op));
             }
             break;
 
         case PCMK_OCF_NOT_RUNNING:
             if (is_probe || (target_rc == *rc)
                 || !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
 
                 *status = PCMK_EXEC_DONE;
                 rsc->role = RSC_ROLE_STOPPED;
 
                 /* clear any previous failure actions */
                 *on_fail = action_fail_ignore;
                 pe__set_next_role(rsc, RSC_ROLE_UNKNOWN, "not running");
             }
             break;
 
         case PCMK_OCF_RUNNING_PROMOTED:
             if (is_probe && (*rc != target_rc)) {
                 *status = PCMK_EXEC_DONE;
                 pe_rsc_info(rsc,
                             "Probe found %s active and promoted on %s at %s",
                             rsc->id, node->details->uname,
                             last_change_str(xml_op));
             }
             rsc->role = RSC_ROLE_PROMOTED;
             break;
 
         case PCMK_OCF_DEGRADED_PROMOTED:
         case PCMK_OCF_FAILED_PROMOTED:
             rsc->role = RSC_ROLE_PROMOTED;
             *status = PCMK_EXEC_ERROR;
             break;
 
         case PCMK_OCF_NOT_CONFIGURED:
             *status = PCMK_EXEC_ERROR_FATAL;
             break;
 
         case PCMK_OCF_UNIMPLEMENT_FEATURE: {
             guint interval_ms = 0;
             crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
 
             if (interval_ms > 0) {
                 *status = PCMK_EXEC_NOT_SUPPORTED;
                 break;
             }
         }
             // fall through
 
         case PCMK_OCF_NOT_INSTALLED:
         case PCMK_OCF_INVALID_PARAM:
         case PCMK_OCF_INSUFFICIENT_PRIV:
             if (!pe_can_fence(data_set, node)
                 && !strcmp(task, CRMD_ACTION_STOP)) {
                 /* If a stop fails and we can't fence, there's nothing else we can do */
                 pe_proc_err("No further recovery can be attempted for %s "
                             "because %s on %s failed (%s%s%s) at %s "
                             CRM_XS " rc=%d id=%s", rsc->id, task,
                             node->details->uname, services_ocf_exitcode_str(*rc),
                             (*exit_reason? ": " : ""), exit_reason,
                             last_change_str(xml_op), *rc, ID(xml_op));
                 pe__clear_resource_flags(rsc, pe_rsc_managed);
                 pe__set_resource_flags(rsc, pe_rsc_block);
             }
             *status = PCMK_EXEC_ERROR_HARD;
             break;
 
         default:
             if (*status == PCMK_EXEC_DONE) {
                 crm_info("Treating unknown exit status %d from %s of %s "
                          "on %s at %s as failure",
                          *rc, task, rsc->id, node->details->uname,
                          last_change_str(xml_op));
                 *status = PCMK_EXEC_ERROR;
             }
             break;
     }
 
     pe_rsc_trace(rsc, "Remapped %s status to '%s'",
                  key, pcmk_exec_status_str(*status));
 }
 
 // return TRUE if start or monitor last failure but parameters changed
 static bool
 should_clear_for_param_change(xmlNode *xml_op, const char *task,
                               pe_resource_t *rsc, pe_node_t *node,
                               pe_working_set_t *data_set)
 {
     if (!strcmp(task, "start") || !strcmp(task, "monitor")) {
 
         if (pe__bundle_needs_remote_name(rsc, data_set)) {
             /* We haven't allocated resources yet, so we can't reliably
              * substitute addr parameters for the REMOTE_CONTAINER_HACK.
              * When that's needed, defer the check until later.
              */
             pe__add_param_check(xml_op, rsc, node, pe_check_last_failure,
                                 data_set);
 
         } else {
             op_digest_cache_t *digest_data = NULL;
 
             digest_data = rsc_action_digest_cmp(rsc, xml_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, get_op_key(xml_op), node->details->id);
                     break;
                 case RSC_DIGEST_MATCH:
                     break;
                 default:
                     return TRUE;
             }
         }
     }
     return FALSE;
 }
 
 // Order action after fencing of remote node, given connection rsc
 static void
 order_after_remote_fencing(pe_action_t *action, pe_resource_t *remote_conn,
                            pe_working_set_t *data_set)
 {
     pe_node_t *remote_node = pe_find_node(data_set->nodes, remote_conn->id);
 
     if (remote_node) {
         pe_action_t *fence = pe_fence_op(remote_node, NULL, TRUE, NULL,
                                          FALSE, data_set);
 
         order_actions(fence, action, pe_order_implies_then);
     }
 }
 
 static bool
 should_ignore_failure_timeout(pe_resource_t *rsc, xmlNode *xml_op,
                               const char *task, guint interval_ms,
                               bool is_last_failure, pe_working_set_t *data_set)
 {
     /* Clearing failures of recurring monitors has special concerns. The
      * executor reports only changes in the monitor result, so if the
      * monitor is still active and still getting the same failure result,
      * that will go undetected after the failure is cleared.
      *
      * Also, the operation history will have the time when the recurring
      * monitor result changed to the given code, not the time when the
      * result last happened.
      *
      * @TODO We probably should clear such failures only when the failure
      * timeout has passed since the last occurrence of the failed result.
      * However we don't record that information. We could maybe approximate
      * that by clearing only if there is a more recent successful monitor or
      * stop result, but we don't even have that information at this point
      * since we are still unpacking the resource's operation history.
      *
      * This is especially important for remote connection resources with a
      * reconnect interval, so in that case, we skip clearing failures
      * if the remote node hasn't been fenced.
      */
     if (rsc->remote_reconnect_ms
         && pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)
         && (interval_ms != 0) && pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
 
         pe_node_t *remote_node = pe_find_node(data_set->nodes, rsc->id);
 
         if (remote_node && !remote_node->details->remote_was_fenced) {
             if (is_last_failure) {
                 crm_info("Waiting to clear monitor failure for remote node %s"
                          " until fencing has occurred", rsc->id);
             }
             return TRUE;
         }
     }
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Check operation age and schedule failure clearing when appropriate
  *
  * This function has two distinct purposes. The first is to check whether an
  * operation history entry is expired (i.e. the resource has a failure timeout,
  * the entry is older than the timeout, and the resource either has no fail
  * count or its fail count is entirely older than the timeout). The second is to
  * schedule fail count clearing when appropriate (i.e. the operation is expired
  * and either the resource has an expired fail count or the operation is a
  * last_failure for a remote connection resource with a reconnect interval,
  * or the operation is a last_failure for a start or monitor operation and the
  * resource's parameters have changed since the operation).
  *
  * \param[in] rsc       Resource that operation happened to
  * \param[in] node      Node that operation happened on
  * \param[in] rc        Actual result of operation
  * \param[in] xml_op    Operation history entry XML
  * \param[in] data_set  Current working set
  *
  * \return TRUE if operation history entry is expired, FALSE otherwise
  */
 static bool
 check_operation_expiry(pe_resource_t *rsc, pe_node_t *node, int rc,
                        xmlNode *xml_op, pe_working_set_t *data_set)
 {
     bool expired = FALSE;
     bool is_last_failure = pcmk__ends_with(ID(xml_op), "_last_failure_0");
     time_t last_run = 0;
     guint interval_ms = 0;
     int unexpired_fail_count = 0;
     const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     const char *clear_reason = NULL;
 
     crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
 
     if ((rsc->failure_timeout > 0)
         && (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                     &last_run) == 0)) {
 
         // Resource has a failure-timeout, and history entry has a timestamp
 
         time_t now = get_effective_time(data_set);
         time_t last_failure = 0;
 
         // Is this particular operation history older than the failure timeout?
         if ((now >= (last_run + rsc->failure_timeout))
             && !should_ignore_failure_timeout(rsc, xml_op, task, interval_ms,
                                               is_last_failure, data_set)) {
             expired = TRUE;
         }
 
         // Does the resource as a whole have an unexpired fail count?
         unexpired_fail_count = pe_get_failcount(node, rsc, &last_failure,
                                                 pe_fc_effective, xml_op,
                                                 data_set);
 
         // Update scheduler recheck time according to *last* failure
         crm_trace("%s@%lld is %sexpired @%lld with unexpired_failures=%d timeout=%ds"
                   " last-failure@%lld",
                   ID(xml_op), (long long) last_run, (expired? "" : "not "),
                   (long long) now, unexpired_fail_count, rsc->failure_timeout,
                   (long long) last_failure);
         last_failure += rsc->failure_timeout + 1;
         if (unexpired_fail_count && (now < last_failure)) {
             pe__update_recheck_time(last_failure, data_set);
         }
     }
 
     if (expired) {
         if (pe_get_failcount(node, rsc, NULL, pe_fc_default, xml_op, data_set)) {
 
             // There is a fail count ignoring timeout
 
             if (unexpired_fail_count == 0) {
                 // There is no fail count considering timeout
                 clear_reason = "it expired";
 
             } else {
                 /* This operation is old, but there is an unexpired fail count.
                  * In a properly functioning cluster, this should only be
                  * possible if this operation is not a failure (otherwise the
                  * fail count should be expired too), so this is really just a
                  * failsafe.
                  */
                 expired = FALSE;
             }
 
         } else if (is_last_failure && rsc->remote_reconnect_ms) {
             /* Clear any expired last failure when reconnect interval is set,
              * even if there is no fail count.
              */
             clear_reason = "reconnect interval is set";
         }
     }
 
     if (!expired && is_last_failure
         && should_clear_for_param_change(xml_op, task, rsc, node, data_set)) {
         clear_reason = "resource parameters have changed";
     }
 
     if (clear_reason != NULL) {
         // Schedule clearing of the fail count
         pe_action_t *clear_op = pe__clear_failcount(rsc, node, clear_reason,
                                                     data_set);
 
         if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)
             && rsc->remote_reconnect_ms) {
             /* If we're clearing a remote connection due to a reconnect
              * interval, we want to wait until any scheduled fencing
              * completes.
              *
              * We could limit this to remote_node->details->unclean, but at
              * this point, that's always true (it won't be reliable until
              * after unpack_node_history() is done).
              */
             crm_info("Clearing %s failure will wait until any scheduled "
                      "fencing of %s completes", task, rsc->id);
             order_after_remote_fencing(clear_op, rsc, data_set);
         }
     }
 
     if (expired && (interval_ms == 0) && pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
         switch(rc) {
             case PCMK_OCF_OK:
             case PCMK_OCF_NOT_RUNNING:
             case PCMK_OCF_RUNNING_PROMOTED:
             case PCMK_OCF_DEGRADED:
             case PCMK_OCF_DEGRADED_PROMOTED:
                 // Don't expire probes that return these values
                 expired = FALSE;
                 break;
         }
     }
 
     return expired;
 }
 
 int pe__target_rc_from_xml(xmlNode *xml_op)
 {
     int target_rc = 0;
     const char *key = crm_element_value(xml_op, XML_ATTR_TRANSITION_KEY);
 
     if (key == NULL) {
         return -1;
     }
     decode_transition_key(key, NULL, NULL, NULL, &target_rc);
     return target_rc;
 }
 
 static enum action_fail_response
 get_action_on_fail(pe_resource_t *rsc, const char *key, const char *task, pe_working_set_t * data_set) 
 {
     enum action_fail_response result = action_fail_recover;
     pe_action_t *action = custom_action(rsc, strdup(key), task, NULL, TRUE, FALSE, data_set);
 
     result = action->on_fail;
     pe_free_action(action);
 
     return result;
 }
 
 static void
 update_resource_state(pe_resource_t * rsc, pe_node_t * node, xmlNode * xml_op, const char * task, int rc,
                       xmlNode * last_failure, enum action_fail_response * on_fail, pe_working_set_t * data_set)
 {
     gboolean clear_past_failure = FALSE;
 
     CRM_ASSERT(rsc);
     CRM_ASSERT(xml_op);
 
     if (rc == PCMK_OCF_NOT_INSTALLED || (!pe_rsc_is_bundled(rsc) && pcmk_xe_mask_probe_failure(xml_op))) {
         rsc->role = RSC_ROLE_STOPPED;
 
     } else if (rc == PCMK_OCF_NOT_RUNNING) {
         clear_past_failure = TRUE;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
         if (last_failure) {
             const char *op_key = get_op_key(xml_op);
             const char *last_failure_key = get_op_key(last_failure);
 
             if (pcmk__str_eq(op_key, last_failure_key, pcmk__str_casei)) {
                 clear_past_failure = TRUE;
             }
         }
 
         if (rsc->role < RSC_ROLE_STARTED) {
             set_active(rsc);
         }
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_START, pcmk__str_casei)) {
         rsc->role = RSC_ROLE_STARTED;
         clear_past_failure = TRUE;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_STOP, pcmk__str_casei)) {
         rsc->role = RSC_ROLE_STOPPED;
         clear_past_failure = TRUE;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_PROMOTE, pcmk__str_casei)) {
         rsc->role = RSC_ROLE_PROMOTED;
         clear_past_failure = TRUE;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_DEMOTE, pcmk__str_casei)) {
 
         if (*on_fail == action_fail_demote) {
             // Demote clears an error only if on-fail=demote
             clear_past_failure = TRUE;
         }
         rsc->role = RSC_ROLE_UNPROMOTED;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) {
         rsc->role = RSC_ROLE_STARTED;
         clear_past_failure = TRUE;
 
     } else if (pcmk__str_eq(task, CRMD_ACTION_MIGRATE, pcmk__str_casei)) {
         unpack_migrate_to_success(rsc, node, xml_op, data_set);
 
     } else if (rsc->role < RSC_ROLE_STARTED) {
         pe_rsc_trace(rsc, "%s active on %s", rsc->id, node->details->uname);
         set_active(rsc);
     }
 
     /* clear any previous failure actions */
     if (clear_past_failure) {
         switch (*on_fail) {
             case action_fail_stop:
             case action_fail_fence:
             case action_fail_migrate:
             case action_fail_standby:
                 pe_rsc_trace(rsc, "%s.%s is not cleared by a completed stop",
                              rsc->id, fail2text(*on_fail));
                 break;
 
             case action_fail_block:
             case action_fail_ignore:
             case action_fail_demote:
             case action_fail_recover:
             case action_fail_restart_container:
                 *on_fail = action_fail_ignore;
                 pe__set_next_role(rsc, RSC_ROLE_UNKNOWN, "clear past failures");
                 break;
             case action_fail_reset_remote:
                 if (rsc->remote_reconnect_ms == 0) {
                     /* With no reconnect interval, the connection is allowed to
                      * start again after the remote node is fenced and
                      * completely stopped. (With a reconnect interval, we wait
                      * for the failure to be cleared entirely before attempting
                      * to reconnect.)
                      */
                     *on_fail = action_fail_ignore;
                     pe__set_next_role(rsc, RSC_ROLE_UNKNOWN,
                                       "clear past failures and reset remote");
                 }
                 break;
         }
     }
 }
 
 static void
 unpack_rsc_op(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op,
               xmlNode **last_failure, enum action_fail_response *on_fail,
               pe_working_set_t *data_set)
 {
     int rc = 0;
     int old_rc = 0;
     int task_id = 0;
     int target_rc = 0;
     int old_target_rc = 0;
     int status = PCMK_EXEC_UNKNOWN;
     guint interval_ms = 0;
     const char *task = NULL;
     const char *task_key = NULL;
     const char *exit_reason = NULL;
     bool expired = false;
     pe_resource_t *parent = rsc;
     enum action_fail_response failure_strategy = action_fail_recover;
     bool maskable_probe_failure = false;
 
     CRM_CHECK(rsc && node && xml_op, return);
 
     target_rc = pe__target_rc_from_xml(xml_op);
     task_key = get_op_key(xml_op);
     task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
     exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
     if (exit_reason == NULL) {
         exit_reason = "";
     }
 
     crm_element_value_int(xml_op, XML_LRM_ATTR_RC, &rc);
     crm_element_value_int(xml_op, XML_LRM_ATTR_CALLID, &task_id);
     crm_element_value_int(xml_op, XML_LRM_ATTR_OPSTATUS, &status);
     crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
 
     CRM_CHECK(task != NULL, return);
     CRM_CHECK((status >= PCMK_EXEC_PENDING) && (status <= PCMK_EXEC_MAX),
               return);
 
     if (!strcmp(task, CRMD_ACTION_NOTIFY) ||
         !strcmp(task, CRMD_ACTION_METADATA)) {
         /* safe to ignore these */
         return;
     }
 
     if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) {
         parent = uber_parent(rsc);
     }
 
     pe_rsc_trace(rsc, "Unpacking task %s/%s (call_id=%d, status=%d, rc=%d) on %s (role=%s)",
                  task_key, task, task_id, status, rc, node->details->uname, role2text(rsc->role));
 
     if (node->details->unclean) {
         pe_rsc_trace(rsc, "Node %s (where %s is running) is unclean."
                      " Further action depends on the value of the stop's on-fail attribute",
                      node->details->uname, rsc->id);
     }
 
     /* It should be possible to call remap_operation() first then call
      * check_operation_expiry() only if rc != target_rc, because there should
      * never be a fail count without at least one unexpected result in the
      * resource history. That would be more efficient by avoiding having to call
      * check_operation_expiry() for expected results.
      *
      * However, we do have such configurations in the scheduler regression
      * tests, even if it shouldn't be possible with the current code. It's
      * probably a good idea anyway, but that would require updating the test
      * inputs to something currently possible.
      */
 
     if ((status != PCMK_EXEC_NOT_INSTALLED)
         && check_operation_expiry(rsc, node, rc, xml_op, data_set)) {
         expired = true;
     }
 
     old_rc = rc;
     old_target_rc = target_rc;
 
     remap_operation(xml_op, rsc, node, data_set, on_fail, target_rc,
                     &rc, &status);
 
     maskable_probe_failure = !pe_rsc_is_bundled(rsc) && pcmk_xe_mask_probe_failure(xml_op);
 
     if (expired && maskable_probe_failure && old_rc != old_target_rc) {
         if (rsc->role <= RSC_ROLE_STOPPED) {
             rsc->role = RSC_ROLE_UNKNOWN;
         }
 
         goto done;
 
     } else if (expired && (rc != target_rc)) {
         const char *magic = crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC);
 
         if (interval_ms == 0) {
             crm_notice("Ignoring expired %s failure on %s "
                        CRM_XS " actual=%d expected=%d magic=%s",
                        task_key, node->details->uname, rc, target_rc, magic);
             goto done;
 
         } else if(node->details->online && node->details->unclean == FALSE) {
             /* Reschedule the recurring monitor. schedule_cancel() won't work at
              * this stage, so as a hacky workaround, forcibly change the restart
              * digest so pcmk__check_action_config() does what we want later.
              *
              * @TODO We should skip this if there is a newer successful monitor.
              *       Also, this causes rescheduling only if the history entry
              *       has an op-digest (which the expire-non-blocked-failure
              *       scheduler regression test doesn't, but that may not be a
              *       realistic scenario in production).
              */
             crm_notice("Rescheduling %s after failure expired on %s "
                        CRM_XS " actual=%d expected=%d magic=%s",
                        task_key, node->details->uname, rc, target_rc, magic);
             crm_xml_add(xml_op, XML_LRM_ATTR_RESTART_DIGEST, "calculated-failure-timeout");
             goto done;
         }
     }
 
     if (maskable_probe_failure) {
         crm_notice("Treating probe result '%s' for %s on %s as 'not running'",
                    services_ocf_exitcode_str(old_rc), rsc->id, node->details->uname);
         update_resource_state(rsc, node, xml_op, task, target_rc, *last_failure,
                               on_fail, data_set);
         crm_xml_add(xml_op, XML_ATTR_UNAME, node->details->uname);
 
         record_failed_op(xml_op, node, rsc, data_set);
         resource_location(parent, node, -INFINITY, "masked-probe-failure", data_set);
         goto done;
     }
 
     switch (status) {
         case PCMK_EXEC_CANCELLED:
             // Should never happen
             pe_err("Resource history contains cancellation '%s' "
                    "(%s of %s on %s at %s)",
                    ID(xml_op), task, rsc->id, node->details->uname,
                    last_change_str(xml_op));
             goto done;
 
         case PCMK_EXEC_PENDING:
             if (!strcmp(task, CRMD_ACTION_START)) {
                 pe__set_resource_flags(rsc, pe_rsc_start_pending);
                 set_active(rsc);
 
             } else if (!strcmp(task, CRMD_ACTION_PROMOTE)) {
                 rsc->role = RSC_ROLE_PROMOTED;
 
             } else if (!strcmp(task, CRMD_ACTION_MIGRATE) && node->details->unclean) {
                 /* If a pending migrate_to action is out on a unclean node,
                  * we have to force the stop action on the target. */
                 const char *migrate_target = crm_element_value(xml_op, XML_LRM_ATTR_MIGRATE_TARGET);
                 pe_node_t *target = pe_find_node(data_set->nodes, migrate_target);
                 if (target) {
                     stop_action(rsc, target, FALSE);
                 }
             }
 
             if (rsc->pending_task == NULL) {
                 if ((interval_ms != 0) || strcmp(task, CRMD_ACTION_STATUS)) {
                     rsc->pending_task = strdup(task);
                     rsc->pending_node = node;
                 } else {
                     /* Pending probes are not printed, even if pending
                      * operations are requested. If someone ever requests that
                      * behavior, enable the below and the corresponding part of
                      * native.c:native_pending_task().
                      */
 #if 0
                     rsc->pending_task = strdup("probe");
                     rsc->pending_node = node;
 #endif
                 }
             }
             goto done;
 
         case PCMK_EXEC_DONE:
             pe_rsc_trace(rsc, "%s of %s on %s completed at %s " CRM_XS " id=%s",
                          task, rsc->id, node->details->uname,
                          last_change_str(xml_op), ID(xml_op));
             update_resource_state(rsc, node, xml_op, task, rc, *last_failure, on_fail, data_set);
             goto done;
 
         case PCMK_EXEC_NOT_INSTALLED:
             failure_strategy = get_action_on_fail(rsc, task_key, task, data_set);
             if (failure_strategy == action_fail_ignore) {
                 crm_warn("Cannot ignore failed %s of %s on %s: "
                          "Resource agent doesn't exist "
                          CRM_XS " status=%d rc=%d id=%s",
                          task, rsc->id, node->details->uname, status, rc,
                          ID(xml_op));
                 /* Also for printing it as "FAILED" by marking it as pe_rsc_failed later */
                 *on_fail = action_fail_migrate;
             }
             resource_location(parent, node, -INFINITY, "hard-error", data_set);
             unpack_rsc_op_failure(rsc, node, rc, xml_op, last_failure, on_fail, data_set);
             goto done;
 
         case PCMK_EXEC_NOT_CONNECTED:
             if (pe__is_guest_or_remote_node(node)
                 && pcmk_is_set(node->details->remote_rsc->flags, pe_rsc_managed)) {
                 /* We should never get into a situation where a managed remote
                  * connection resource is considered OK but a resource action
                  * behind the connection gets a "not connected" status. But as a
                  * fail-safe in case a bug or unusual circumstances do lead to
                  * that, ensure the remote connection is considered failed.
                  */
                 pe__set_resource_flags(node->details->remote_rsc,
                                        pe_rsc_failed|pe_rsc_stop);
             }
             break; // Not done, do error handling
 
         case PCMK_EXEC_ERROR:
         case PCMK_EXEC_ERROR_HARD:
         case PCMK_EXEC_ERROR_FATAL:
         case PCMK_EXEC_TIMEOUT:
         case PCMK_EXEC_NOT_SUPPORTED:
         case PCMK_EXEC_INVALID:
             break; // Not done, do error handling
 
         case PCMK_EXEC_NO_FENCE_DEVICE:
         case PCMK_EXEC_NO_SECRETS:
             status = PCMK_EXEC_ERROR_HARD;
             break; // Not done, do error handling
     }
 
     failure_strategy = get_action_on_fail(rsc, task_key, task, data_set);
     if ((failure_strategy == action_fail_ignore)
         || (failure_strategy == action_fail_restart_container
             && !strcmp(task, CRMD_ACTION_STOP))) {
 
         crm_warn("Pretending failed %s (%s%s%s) of %s on %s at %s "
                  "succeeded " CRM_XS " rc=%d id=%s",
                  task, services_ocf_exitcode_str(rc),
                  (*exit_reason? ": " : ""), exit_reason, rsc->id,
                  node->details->uname, last_change_str(xml_op), rc,
                  ID(xml_op));
 
         update_resource_state(rsc, node, xml_op, task, target_rc, *last_failure,
                               on_fail, data_set);
         crm_xml_add(xml_op, XML_ATTR_UNAME, node->details->uname);
         pe__set_resource_flags(rsc, pe_rsc_failure_ignored);
 
         record_failed_op(xml_op, node, rsc, data_set);
 
         if ((failure_strategy == action_fail_restart_container)
             && cmp_on_fail(*on_fail, action_fail_recover) <= 0) {
             *on_fail = failure_strategy;
         }
 
     } else {
         unpack_rsc_op_failure(rsc, node, rc, xml_op, last_failure, on_fail,
                               data_set);
 
         if (status == PCMK_EXEC_ERROR_HARD) {
             do_crm_log(rc != PCMK_OCF_NOT_INSTALLED?LOG_ERR:LOG_NOTICE,
                        "Preventing %s from restarting on %s because "
                        "of hard failure (%s%s%s)" CRM_XS " rc=%d id=%s",
                        parent->id, node->details->uname,
                        services_ocf_exitcode_str(rc),
                        (*exit_reason? ": " : ""), exit_reason,
                        rc, ID(xml_op));
             resource_location(parent, node, -INFINITY, "hard-error", data_set);
 
         } else if (status == PCMK_EXEC_ERROR_FATAL) {
             crm_err("Preventing %s from restarting anywhere because "
                     "of fatal failure (%s%s%s) " CRM_XS " rc=%d id=%s",
                     parent->id, services_ocf_exitcode_str(rc),
                     (*exit_reason? ": " : ""), exit_reason,
                     rc, ID(xml_op));
             resource_location(parent, NULL, -INFINITY, "fatal-error", data_set);
         }
     }
 
 done:
     pe_rsc_trace(rsc, "Resource %s after %s: role=%s, next=%s",
                  rsc->id, task, role2text(rsc->role),
                  role2text(rsc->next_role));
 }
 
 static void
 add_node_attrs(xmlNode *xml_obj, pe_node_t *node, bool overwrite,
                pe_working_set_t *data_set)
 {
     const char *cluster_name = NULL;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .role = RSC_ROLE_UNKNOWN,
         .now = data_set->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     g_hash_table_insert(node->details->attrs,
                         strdup(CRM_ATTR_UNAME), strdup(node->details->uname));
 
     g_hash_table_insert(node->details->attrs, strdup(CRM_ATTR_ID),
                         strdup(node->details->id));
     if (pcmk__str_eq(node->details->id, data_set->dc_uuid, pcmk__str_casei)) {
         data_set->dc_node = node;
         node->details->is_dc = TRUE;
         g_hash_table_insert(node->details->attrs,
                             strdup(CRM_ATTR_IS_DC), strdup(XML_BOOLEAN_TRUE));
     } else {
         g_hash_table_insert(node->details->attrs,
                             strdup(CRM_ATTR_IS_DC), strdup(XML_BOOLEAN_FALSE));
     }
 
     cluster_name = g_hash_table_lookup(data_set->config_hash, "cluster-name");
     if (cluster_name) {
         g_hash_table_insert(node->details->attrs, strdup(CRM_ATTR_CLUSTER_NAME),
                             strdup(cluster_name));
     }
 
     pe__unpack_dataset_nvpairs(xml_obj, XML_TAG_ATTR_SETS, &rule_data,
                                node->details->attrs, NULL, overwrite, data_set);
 
     if (pe_node_attribute_raw(node, CRM_ATTR_SITE_NAME) == NULL) {
         const char *site_name = pe_node_attribute_raw(node, "site-name");
 
         if (site_name) {
             g_hash_table_insert(node->details->attrs,
                                 strdup(CRM_ATTR_SITE_NAME),
                                 strdup(site_name));
 
         } else if (cluster_name) {
             /* Default to cluster-name if unset */
             g_hash_table_insert(node->details->attrs,
                                 strdup(CRM_ATTR_SITE_NAME),
                                 strdup(cluster_name));
         }
     }
 }
 
 static GList *
 extract_operations(const char *node, const char *rsc, xmlNode * rsc_entry, gboolean active_filter)
 {
     int counter = -1;
     int stop_index = -1;
     int start_index = -1;
 
     xmlNode *rsc_op = NULL;
 
     GList *gIter = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     /* extract operations */
     op_list = NULL;
     sorted_op_list = NULL;
 
     for (rsc_op = pcmk__xe_first_child(rsc_entry);
          rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) {
 
         if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP,
                          pcmk__str_none)) {
             crm_xml_add(rsc_op, "resource", rsc);
             crm_xml_add(rsc_op, XML_ATTR_UNAME, node);
             op_list = g_list_prepend(op_list, rsc_op);
         }
     }
 
     if (op_list == NULL) {
         /* if there are no operations, there is nothing to do */
         return NULL;
     }
 
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     /* create active recurring operations as optional */
     if (active_filter == FALSE) {
         return sorted_op_list;
     }
 
     op_list = NULL;
 
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         counter++;
 
         if (start_index < stop_index) {
             crm_trace("Skipping %s: not active", ID(rsc_entry));
             break;
 
         } else if (counter < start_index) {
             crm_trace("Skipping %s: old", ID(rsc_op));
             continue;
         }
         op_list = g_list_append(op_list, rsc_op);
     }
 
     g_list_free(sorted_op_list);
     return op_list;
 }
 
 GList *
 find_operations(const char *rsc, const char *node, gboolean active_filter,
                 pe_working_set_t * data_set)
 {
     GList *output = NULL;
     GList *intermediate = NULL;
 
     xmlNode *tmp = NULL;
     xmlNode *status = find_xml_node(data_set->input, XML_CIB_TAG_STATUS, TRUE);
 
     pe_node_t *this_node = NULL;
 
     xmlNode *node_state = NULL;
 
     for (node_state = pcmk__xe_first_child(status); node_state != NULL;
          node_state = pcmk__xe_next(node_state)) {
 
         if (pcmk__str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, pcmk__str_none)) {
             const char *uname = crm_element_value(node_state, XML_ATTR_UNAME);
 
             if (node != NULL && !pcmk__str_eq(uname, node, pcmk__str_casei)) {
                 continue;
             }
 
             this_node = pe_find_node(data_set->nodes, uname);
             if(this_node == NULL) {
                 CRM_LOG_ASSERT(this_node != NULL);
                 continue;
 
             } else if (pe__is_guest_or_remote_node(this_node)) {
                 determine_remote_online_status(data_set, this_node);
 
             } else {
                 determine_online_status(node_state, this_node, data_set);
             }
 
             if (this_node->details->online
                 || pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
                 /* offline nodes run no resources...
                  * unless stonith is enabled in which case we need to
                  *   make sure rsc start events happen after the stonith
                  */
                 xmlNode *lrm_rsc = NULL;
 
                 tmp = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
                 tmp = find_xml_node(tmp, XML_LRM_TAG_RESOURCES, FALSE);
 
                 for (lrm_rsc = pcmk__xe_first_child(tmp); lrm_rsc != NULL;
                      lrm_rsc = pcmk__xe_next(lrm_rsc)) {
 
                     if (pcmk__str_eq((const char *)lrm_rsc->name,
                                      XML_LRM_TAG_RESOURCE, pcmk__str_none)) {
 
                         const char *rsc_id = crm_element_value(lrm_rsc, XML_ATTR_ID);
 
                         if (rsc != NULL && !pcmk__str_eq(rsc_id, rsc, pcmk__str_casei)) {
                             continue;
                         }
 
                         intermediate = extract_operations(uname, rsc_id, lrm_rsc, active_filter);
                         output = g_list_concat(output, intermediate);
                     }
                 }
             }
         }
     }
 
     return output;
 }