diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c
index 9985ec4551..d2ff9503d1 100644
--- a/daemons/based/based_callbacks.c
+++ b/daemons/based/based_callbacks.c
@@ -1,1761 +1,1761 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <stdint.h>     // uint32_t, uint64_t, UINT64_C()
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>   // PRIu64
 
 #include <glib.h>
 #include <libxml/tree.h>
 
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/cluster/internal.h>
 
 #include <crm/common/xml.h>
 #include <crm/common/remote_internal.h>
 
 #include <pacemaker-based.h>
 
 #define EXIT_ESCALATION_MS 10000
 
 static unsigned long cib_local_bcast_num = 0;
 
 typedef struct cib_local_notify_s {
     xmlNode *notify_src;
     char *client_id;
     gboolean from_peer;
     gboolean sync_reply;
 } cib_local_notify_t;
 
 int next_client_id = 0;
 
 gboolean legacy_mode = FALSE;
 
 qb_ipcs_service_t *ipcs_ro = NULL;
 qb_ipcs_service_t *ipcs_rw = NULL;
 qb_ipcs_service_t *ipcs_shm = NULL;
 
 static int cib_process_command(xmlNode *request,
                                const cib__operation_t *operation,
                                cib__op_fn_t op_function, xmlNode **reply,
                                xmlNode **cib_diff, bool privileged);
 
 static gboolean cib_common_callback(qb_ipcs_connection_t *c, void *data,
                                     size_t size, gboolean privileged);
 
 gboolean
 cib_legacy_mode(void)
 {
     return legacy_mode;
 }
 
 static int32_t
 cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
 {
     if (cib_shutdown_flag) {
         crm_info("Ignoring new IPC client [%d] during shutdown",
                  pcmk__client_pid(c));
         return -ECONNREFUSED;
     }
 
     if (pcmk__new_client(c, uid, gid) == NULL) {
         return -ENOMEM;
     }
     return 0;
 }
 
 static int32_t
 cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size)
 {
     pcmk__client_t *client = pcmk__find_client(c);
 
     crm_trace("%p message from %s", c, client->id);
     return cib_common_callback(c, data, size, TRUE);
 }
 
 static int32_t
 cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size)
 {
     pcmk__client_t *client = pcmk__find_client(c);
 
     crm_trace("%p message from %s", c, client->id);
     return cib_common_callback(c, data, size, FALSE);
 }
 
 /* Error code means? */
 static int32_t
 cib_ipc_closed(qb_ipcs_connection_t * c)
 {
     pcmk__client_t *client = pcmk__find_client(c);
 
     if (client == NULL) {
         return 0;
     }
     crm_trace("Connection %p", c);
     pcmk__free_client(client);
     return 0;
 }
 
 static void
 cib_ipc_destroy(qb_ipcs_connection_t * c)
 {
     crm_trace("Connection %p", c);
     cib_ipc_closed(c);
     if (cib_shutdown_flag) {
         cib_shutdown(0);
     }
 }
 
 struct qb_ipcs_service_handlers ipc_ro_callbacks = {
     .connection_accept = cib_ipc_accept,
     .connection_created = NULL,
     .msg_process = cib_ipc_dispatch_ro,
     .connection_closed = cib_ipc_closed,
     .connection_destroyed = cib_ipc_destroy
 };
 
 struct qb_ipcs_service_handlers ipc_rw_callbacks = {
     .connection_accept = cib_ipc_accept,
     .connection_created = NULL,
     .msg_process = cib_ipc_dispatch_rw,
     .connection_closed = cib_ipc_closed,
     .connection_destroyed = cib_ipc_destroy
 };
 
 /*!
  * \internal
  * \brief Create reply XML for a CIB request
  *
  * \param[in] op            CIB operation type
  * \param[in] call_id       CIB call ID
  * \param[in] client_id     CIB client ID
  * \param[in] call_options  Group of <tt>enum cib_call_options</tt> flags
  * \param[in] rc            Request return code
  * \param[in] call_data     Request output data
  *
  * \return Reply XML
  *
  * \note The caller is responsible for freeing the return value using
  *       \p free_xml().
  */
 static xmlNode *
 create_cib_reply(const char *op, const char *call_id, const char *client_id,
                  int call_options, int rc, xmlNode *call_data)
 {
     xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_CIB_REPLY);
 
     crm_xml_add(reply, PCMK__XA_T, PCMK__VALUE_CIB);
     crm_xml_add(reply, PCMK__XA_CIB_OP, op);
     crm_xml_add(reply, PCMK__XA_CIB_CALLID, call_id);
     crm_xml_add(reply, PCMK__XA_CIB_CLIENTID, client_id);
     crm_xml_add_int(reply, PCMK__XA_CIB_CALLOPT, call_options);
     crm_xml_add_int(reply, PCMK__XA_CIB_RC, rc);
 
     if (call_data != NULL) {
         xmlNode *wrapper = pcmk__xe_create(reply, PCMK__XE_CIB_CALLDATA);
 
         crm_trace("Attaching reply output");
         pcmk__xml_copy(wrapper, call_data);
     }
 
     crm_log_xml_explicit(reply, "cib:reply");
     return reply;
 }
 
 static void
 do_local_notify(const xmlNode *notify_src, const char *client_id,
                 bool sync_reply, bool from_peer)
 {
     int rid = 0;
     int call_id = 0;
     pcmk__client_t *client_obj = NULL;
 
     CRM_ASSERT(notify_src && client_id);
 
     crm_element_value_int(notify_src, PCMK__XA_CIB_CALLID, &call_id);
 
     client_obj = pcmk__find_client_by_id(client_id);
     if (client_obj == NULL) {
         crm_debug("Could not send response %d: client %s not found",
                   call_id, client_id);
         return;
     }
 
     if (sync_reply) {
         if (client_obj->ipcs) {
             CRM_LOG_ASSERT(client_obj->request_id);
 
             rid = client_obj->request_id;
             client_obj->request_id = 0;
 
             crm_trace("Sending response %d to client %s%s",
                       rid, pcmk__client_name(client_obj),
                       (from_peer? " (originator of delegated request)" : ""));
         } else {
             crm_trace("Sending response (call %d) to client %s%s",
                       call_id, pcmk__client_name(client_obj),
                       (from_peer? " (originator of delegated request)" : ""));
         }
 
     } else {
         crm_trace("Sending event %d to client %s%s",
                   call_id, pcmk__client_name(client_obj),
                   (from_peer? " (originator of delegated request)" : ""));
     }
 
     switch (PCMK__CLIENT_TYPE(client_obj)) {
         case pcmk__client_ipc:
             {
                 int rc = pcmk__ipc_send_xml(client_obj, rid, notify_src,
                                             (sync_reply? crm_ipc_flags_none
                                              : crm_ipc_server_event));
 
                 if (rc != pcmk_rc_ok) {
                     crm_warn("%s reply to client %s failed: %s " CRM_XS " rc=%d",
                              (sync_reply? "Synchronous" : "Asynchronous"),
                              pcmk__client_name(client_obj), pcmk_rc_str(rc),
                              rc);
                 }
             }
             break;
 #ifdef HAVE_GNUTLS_GNUTLS_H
         case pcmk__client_tls:
 #endif
         case pcmk__client_tcp:
             pcmk__remote_send_xml(client_obj->remote, notify_src);
             break;
         default:
             crm_err("Unknown transport for client %s "
                     CRM_XS " flags=%#016" PRIx64,
                     pcmk__client_name(client_obj), client_obj->flags);
     }
 }
 
 void
 cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request,
                            pcmk__client_t *cib_client, gboolean privileged)
 {
     const char *op = crm_element_value(op_request, PCMK__XA_CIB_OP);
     int call_options = cib_none;
 
     crm_element_value_int(op_request, PCMK__XA_CIB_CALLOPT, &call_options);
 
     /* Requests with cib_transaction set should not be sent to based directly
      * (outside of a commit-transaction request)
      */
     if (pcmk_is_set(call_options, cib_transaction)) {
         return;
     }
 
     if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) {
         if (flags & crm_ipc_client_response) {
             xmlNode *ack = pcmk__xe_create(NULL, __func__);
 
             crm_xml_add(ack, PCMK__XA_CIB_OP, CRM_OP_REGISTER);
             crm_xml_add(ack, PCMK__XA_CIB_CLIENTID, cib_client->id);
             pcmk__ipc_send_xml(cib_client, id, ack, flags);
             cib_client->request_id = 0;
             free_xml(ack);
         }
         return;
 
     } else if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
         /* Update the notify filters for this client */
         int on_off = 0;
         crm_exit_t status = CRM_EX_OK;
         uint64_t bit = UINT64_C(0);
         const char *type = crm_element_value(op_request,
                                              PCMK__XA_CIB_NOTIFY_TYPE);
 
         crm_element_value_int(op_request, PCMK__XA_CIB_NOTIFY_ACTIVATE,
                               &on_off);
 
         crm_debug("Setting %s callbacks %s for client %s",
                   type, (on_off? "on" : "off"), pcmk__client_name(cib_client));
 
         if (pcmk__str_eq(type, PCMK__VALUE_CIB_POST_NOTIFY, pcmk__str_none)) {
             bit = cib_notify_post;
 
         } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_PRE_NOTIFY,
                                 pcmk__str_none)) {
             bit = cib_notify_pre;
 
         } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_UPDATE_CONFIRMATION,
                                 pcmk__str_none)) {
             bit = cib_notify_confirm;
 
         } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_DIFF_NOTIFY,
                                 pcmk__str_none)) {
             bit = cib_notify_diff;
 
         } else {
             status = CRM_EX_INVALID_PARAM;
         }
 
         if (bit != 0) {
             if (on_off) {
                 pcmk__set_client_flags(cib_client, bit);
             } else {
                 pcmk__clear_client_flags(cib_client, bit);
             }
         }
 
         pcmk__ipc_send_ack(cib_client, id, flags, PCMK__XE_ACK, NULL, status);
         return;
     }
 
     cib_process_request(op_request, privileged, cib_client);
 }
 
 int32_t
 cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged)
 {
     uint32_t id = 0;
     uint32_t flags = 0;
     int call_options = 0;
     pcmk__client_t *cib_client = pcmk__find_client(c);
     xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags);
 
     if (op_request) {
         crm_element_value_int(op_request, PCMK__XA_CIB_CALLOPT, &call_options);
     }
 
     if (op_request == NULL) {
         crm_trace("Invalid message from %p", c);
         pcmk__ipc_send_ack(cib_client, id, flags, PCMK__XE_NACK, NULL,
                            CRM_EX_PROTOCOL);
         return 0;
 
     } else if(cib_client == NULL) {
         crm_trace("Invalid client %p", c);
         return 0;
     }
 
     if (pcmk_is_set(call_options, cib_sync_call)) {
         CRM_LOG_ASSERT(flags & crm_ipc_client_response);
         CRM_LOG_ASSERT(cib_client->request_id == 0);    /* This means the client has two synchronous events in-flight */
         cib_client->request_id = id;    /* Reply only to the last one */
     }
 
     if (cib_client->name == NULL) {
         const char *value = crm_element_value(op_request,
                                               PCMK__XA_CIB_CLIENTNAME);
 
         if (value == NULL) {
             cib_client->name = pcmk__itoa(cib_client->pid);
         } else {
             cib_client->name = pcmk__str_copy(value);
             if (crm_is_daemon_name(value)) {
                 pcmk__set_client_flags(cib_client, cib_is_daemon);
             }
         }
     }
 
     /* Allow cluster daemons more leeway before being evicted */
     if (pcmk_is_set(cib_client->flags, cib_is_daemon)) {
         const char *qmax = cib_config_lookup(PCMK_OPT_CLUSTER_IPC_LIMIT);
 
         if (pcmk__set_client_queue_max(cib_client, qmax)) {
             crm_trace("IPC threshold for client %s[%u] is now %u",
                       pcmk__client_name(cib_client), cib_client->pid,
                       cib_client->queue_max);
         }
     }
 
     crm_xml_add(op_request, PCMK__XA_CIB_CLIENTID, cib_client->id);
     crm_xml_add(op_request, PCMK__XA_CIB_CLIENTNAME, cib_client->name);
 
     CRM_LOG_ASSERT(cib_client->user != NULL);
     pcmk__update_acl_user(op_request, PCMK__XA_CIB_USER, cib_client->user);
 
     cib_common_callback_worker(id, flags, op_request, cib_client, privileged);
     free_xml(op_request);
 
     return 0;
 }
 
 static uint64_t ping_seq = 0;
 static char *ping_digest = NULL;
 static bool ping_modified_since = FALSE;
 
 static gboolean
 cib_digester_cb(gpointer data)
 {
     if (based_is_primary) {
         char buffer[32];
         xmlNode *ping = pcmk__xe_create(NULL, PCMK__XE_PING);
 
         ping_seq++;
         free(ping_digest);
         ping_digest = NULL;
         ping_modified_since = FALSE;
         snprintf(buffer, 32, "%" PRIu64, ping_seq);
         crm_trace("Requesting peer digests (%s)", buffer);
 
         crm_xml_add(ping, PCMK__XA_T, PCMK__VALUE_CIB);
         crm_xml_add(ping, PCMK__XA_CIB_OP, CRM_OP_PING);
         crm_xml_add(ping, PCMK__XA_CIB_PING_ID, buffer);
 
         crm_xml_add(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
         send_cluster_message(NULL, crm_msg_cib, ping, TRUE);
 
         free_xml(ping);
     }
     return FALSE;
 }
 
 static void
 process_ping_reply(xmlNode *reply) 
 {
     uint64_t seq = 0;
     const char *host = crm_element_value(reply, PCMK__XA_SRC);
 
     xmlNode *wrapper = pcmk__xe_first_child(reply, PCMK__XE_CIB_CALLDATA, NULL,
                                             NULL);
     xmlNode *pong = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 
     const char *seq_s = crm_element_value(pong, PCMK__XA_CIB_PING_ID);
     const char *digest = crm_element_value(pong, PCMK__XA_DIGEST);
 
     if (seq_s == NULL) {
         crm_debug("Ignoring ping reply with no " PCMK__XA_CIB_PING_ID);
         return;
 
     } else {
         long long seq_ll;
 
         if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) {
             return;
         }
         seq = (uint64_t) seq_ll;
     }
 
     if(digest == NULL) {
         crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host);
 
     } else if(seq != ping_seq) {
         crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host);
 
     } else if(ping_modified_since) {
         crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host);
 
     } else {
         const char *version = crm_element_value(pong, PCMK_XA_CRM_FEATURE_SET);
 
         if(ping_digest == NULL) {
             crm_trace("Calculating new digest");
             ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version);
         }
 
         crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest);
         if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) {
             xmlNode *wrapper = pcmk__xe_first_child(pong, PCMK__XE_CIB_CALLDATA,
                                                     NULL, NULL);
             xmlNode *remote_cib = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 
             const char *admin_epoch_s = NULL;
             const char *epoch_s = NULL;
             const char *num_updates_s = NULL;
 
             if (remote_cib != NULL) {
                 admin_epoch_s = crm_element_value(remote_cib,
                                                   PCMK_XA_ADMIN_EPOCH);
                 epoch_s = crm_element_value(remote_cib, PCMK_XA_EPOCH);
                 num_updates_s = crm_element_value(remote_cib,
                                                   PCMK_XA_NUM_UPDATES);
             }
 
             crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p",
                        crm_element_value(the_cib, PCMK_XA_ADMIN_EPOCH),
                        crm_element_value(the_cib, PCMK_XA_EPOCH),
                        crm_element_value(the_cib, PCMK_XA_NUM_UPDATES),
                        ping_digest, host,
                        pcmk__s(admin_epoch_s, "_"),
                        pcmk__s(epoch_s, "_"),
                        pcmk__s(num_updates_s, "_"),
                        digest, remote_cib);
 
             if(remote_cib && remote_cib->children) {
                 // Additional debug
                 xml_calculate_changes(the_cib, remote_cib);
                 pcmk__log_xml_changes(LOG_INFO, remote_cib);
                 crm_trace("End of differences");
             }
 
             free_xml(remote_cib);
             sync_our_cib(reply, FALSE);
         }
     }
 }
 
 static void
 local_notify_destroy_callback(gpointer data)
 {
     cib_local_notify_t *notify = data;
 
     free_xml(notify->notify_src);
     free(notify->client_id);
     free(notify);
 }
 
 static void
 check_local_notify(int bcast_id)
 {
     const cib_local_notify_t *notify = NULL;
 
     if (!local_notify_queue) {
         return;
     }
 
     notify = pcmk__intkey_table_lookup(local_notify_queue, bcast_id);
 
     if (notify) {
         do_local_notify(notify->notify_src, notify->client_id, notify->sync_reply,
                         notify->from_peer);
         pcmk__intkey_table_remove(local_notify_queue, bcast_id);
     }
 }
 
 static void
 queue_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply,
                    gboolean from_peer)
 {
     cib_local_notify_t *notify = pcmk__assert_alloc(1,
                                                     sizeof(cib_local_notify_t));
 
     notify->notify_src = notify_src;
     notify->client_id = pcmk__str_copy(client_id);
     notify->sync_reply = sync_reply;
     notify->from_peer = from_peer;
 
     if (!local_notify_queue) {
         local_notify_queue = pcmk__intkey_table(local_notify_destroy_callback);
     }
     pcmk__intkey_table_insert(local_notify_queue, cib_local_bcast_num, notify);
     // cppcheck doesn't know notify will get freed when hash table is destroyed
     // cppcheck-suppress memleak
 }
 
 static void
 parse_local_options_v1(const pcmk__client_t *cib_client,
                        const cib__operation_t *operation, int call_options,
                        const char *host, const char *op, gboolean *local_notify,
                        gboolean *needs_reply, gboolean *process,
                        gboolean *needs_forward)
 {
     if (pcmk_is_set(operation->flags, cib__op_attr_modifies)
         && !pcmk_is_set(call_options, cib_inhibit_bcast)) {
         /* we need to send an update anyway */
         *needs_reply = TRUE;
     } else {
         *needs_reply = FALSE;
     }
 
     if (host == NULL && (call_options & cib_scope_local)) {
         crm_trace("Processing locally scoped %s op from client %s",
                   op, pcmk__client_name(cib_client));
         *local_notify = TRUE;
 
     } else if ((host == NULL) && based_is_primary) {
         crm_trace("Processing %s op locally from client %s as primary",
                   op, pcmk__client_name(cib_client));
         *local_notify = TRUE;
 
     } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
         crm_trace("Processing locally addressed %s op from client %s",
                   op, pcmk__client_name(cib_client));
         *local_notify = TRUE;
 
     } else if (stand_alone) {
         *needs_forward = FALSE;
         *local_notify = TRUE;
         *process = TRUE;
 
     } else {
         crm_trace("%s op from %s needs to be forwarded to client %s",
                   op, pcmk__client_name(cib_client),
                   pcmk__s(host, "the primary instance"));
         *needs_forward = TRUE;
         *process = FALSE;
     }
 }
 
 static void
 parse_local_options_v2(const pcmk__client_t *cib_client,
                        const cib__operation_t *operation, int call_options,
                        const char *host, const char *op, gboolean *local_notify,
                        gboolean *needs_reply, gboolean *process,
                        gboolean *needs_forward)
 {
     // Process locally and notify local client
     *process = TRUE;
     *needs_reply = FALSE;
     *local_notify = TRUE;
     *needs_forward = FALSE;
 
     if (pcmk_is_set(operation->flags, cib__op_attr_local)) {
         /* Always process locally if cib__op_attr_local is set.
          *
          * @COMPAT: Currently host is ignored. At a compatibility break, throw
          * an error (from cib_process_request() or earlier) if host is not NULL or
          * OUR_NODENAME.
          */
         crm_trace("Processing always-local %s op from client %s",
                   op, pcmk__client_name(cib_client));
 
         if (!pcmk__str_eq(host, OUR_NODENAME,
                           pcmk__str_casei|pcmk__str_null_matches)) {
 
             crm_warn("Operation '%s' is always local but its target host is "
                      "set to '%s'",
                      op, host);
         }
         return;
     }
 
     if (pcmk_is_set(operation->flags, cib__op_attr_modifies)
         || !pcmk__str_eq(host, OUR_NODENAME,
                          pcmk__str_casei|pcmk__str_null_matches)) {
 
         // Forward modifying and non-local requests via cluster
         *process = FALSE;
         *needs_reply = FALSE;
         *local_notify = FALSE;
         *needs_forward = TRUE;
 
         crm_trace("%s op from %s needs to be forwarded to %s",
                   op, pcmk__client_name(cib_client),
                   pcmk__s(host, "all nodes"));
         return;
     }
 
     if (stand_alone) {
         crm_trace("Processing %s op from client %s (stand-alone)",
                   op, pcmk__client_name(cib_client));
 
     } else {
         crm_trace("Processing %saddressed %s op from client %s",
                   ((host != NULL)? "locally " : "un"),
                   op, pcmk__client_name(cib_client));
     }
 }
 
 static void
 parse_local_options(const pcmk__client_t *cib_client,
                     const cib__operation_t *operation, int call_options,
                     const char *host, const char *op, gboolean *local_notify,
                     gboolean *needs_reply, gboolean *process,
                     gboolean *needs_forward)
 {
     if(cib_legacy_mode()) {
         parse_local_options_v1(cib_client, operation, call_options, host,
                                op, local_notify, needs_reply, process,
                                needs_forward);
     } else {
         parse_local_options_v2(cib_client, operation, call_options, host,
                                op, local_notify, needs_reply, process,
                                needs_forward);
     }
 }
 
 static gboolean
 parse_peer_options_v1(const cib__operation_t *operation, xmlNode *request,
                       gboolean *local_notify, gboolean *needs_reply,
                       gboolean *process)
 {
     const char *op = NULL;
     const char *host = NULL;
     const char *delegated = NULL;
     const char *originator = crm_element_value(request, PCMK__XA_SRC);
     const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO);
 
     gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
 
     if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) {
         *needs_reply = FALSE;
         if (is_reply) {
             *local_notify = TRUE;
             crm_trace("Processing global/peer update from %s"
                       " that originated from us", originator);
         } else {
             crm_trace("Processing global/peer update from %s", originator);
         }
         return TRUE;
     }
 
     op = crm_element_value(request, PCMK__XA_CIB_OP);
     crm_trace("Processing legacy %s request sent by %s", op, originator);
 
     if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) {
         /* Always process these */
         *local_notify = FALSE;
         if (reply_to == NULL || is_reply) {
             *process = TRUE;
         }
         if (is_reply) {
             *needs_reply = FALSE;
         }
         return *process;
     }
 
     if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
         process_ping_reply(request);
         return FALSE;
     }
 
     if (is_reply) {
         crm_trace("Forward reply sent from %s to local clients", originator);
         *process = FALSE;
         *needs_reply = FALSE;
         *local_notify = TRUE;
         return TRUE;
     }
 
     host = crm_element_value(request, PCMK__XA_CIB_HOST);
     if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
         crm_trace("Processing %s request sent to us from %s", op, originator);
         return TRUE;
 
     } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
         crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator);
         *needs_reply = TRUE;
         return TRUE;
 
     } else if ((host == NULL) && based_is_primary) {
         crm_trace("Processing %s request sent to primary instance from %s",
                   op, originator);
         return TRUE;
     }
 
     delegated = crm_element_value(request, PCMK__XA_CIB_DELEGATED_FROM);
     if (delegated != NULL) {
         crm_trace("Ignoring message for primary instance");
 
     } else if (host != NULL) {
         /* this is for a specific instance and we're not it */
         crm_trace("Ignoring msg for instance on %s", host);
 
     } else if ((reply_to == NULL) && !based_is_primary) {
         // This is for the primary instance, and we're not it
         crm_trace("Ignoring reply for primary instance");
 
     } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) {
         if (reply_to != NULL) {
             crm_debug("Processing %s from %s", op, originator);
             *needs_reply = FALSE;
 
         } else {
             crm_debug("Processing %s reply from %s", op, originator);
         }
         return TRUE;
 
     } else {
         crm_err("Nothing for us to do?");
         crm_log_xml_err(request, "Peer[inbound]");
     }
 
     return FALSE;
 }
 
 static gboolean
 parse_peer_options_v2(const cib__operation_t *operation, xmlNode *request,
                       gboolean *local_notify, gboolean *needs_reply,
                       gboolean *process)
 {
     const char *host = NULL;
     const char *delegated = crm_element_value(request,
                                               PCMK__XA_CIB_DELEGATED_FROM);
     const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
     const char *originator = crm_element_value(request, PCMK__XA_SRC);
     const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO);
 
     gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
 
     if (originator == NULL) { // Shouldn't be possible
         originator = "peer";
     }
 
     if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) {
         // sync_our_cib() sets PCMK__XA_CIB_ISREPLYTO
         if (reply_to) {
             delegated = reply_to;
         }
         goto skip_is_reply;
 
     } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL,
                             pcmk__str_none)) {
         // Nothing to do
 
     } else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
         process_ping_reply(request);
         return FALSE;
 
     } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) {
         /* Only the DC (node with the oldest software) should process
          * this operation if PCMK__XA_CIB_SCHEMA_MAX is unset.
          *
          * If the DC is happy it will then send out another
          * PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual
          * upgrade.
          *
          * Except this time PCMK__XA_CIB_SCHEMA_MAX will be set which puts a
          * limit on how far newer nodes will go
          */
         const char *max = crm_element_value(request, PCMK__XA_CIB_SCHEMA_MAX);
         const char *upgrade_rc = crm_element_value(request,
                                                    PCMK__XA_CIB_UPGRADE_RC);
 
         crm_trace("Parsing upgrade %s for %s with max=%s and upgrade_rc=%s",
                   (is_reply? "reply" : "request"),
                   (based_is_primary? "primary" : "secondary"),
                   pcmk__s(max, "none"), pcmk__s(upgrade_rc, "none"));
 
         if (upgrade_rc != NULL) {
             // Our upgrade request was rejected by DC, notify clients of result
             crm_xml_add(request, PCMK__XA_CIB_RC, upgrade_rc);
 
         } else if ((max == NULL) && based_is_primary) {
             /* We are the DC, check if this upgrade is allowed */
             goto skip_is_reply;
 
         } else if(max) {
             /* Ok, go ahead and upgrade to 'max' */
             goto skip_is_reply;
 
         } else {
             // Ignore broadcast client requests when we're not primary
             return FALSE;
         }
 
     } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) {
         crm_info("Detected legacy %s global update from %s", op, originator);
         send_sync_request(NULL);
         legacy_mode = TRUE;
         return FALSE;
 
     } else if (is_reply
                && pcmk_is_set(operation->flags, cib__op_attr_modifies)) {
         crm_trace("Ignoring legacy %s reply sent from %s to local clients", op, originator);
         return FALSE;
 
     } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) {
         *local_notify = FALSE;
         if (reply_to == NULL) {
             *process = TRUE;
         } else { // Not possible?
             crm_debug("Ignoring shutdown request from %s because reply_to=%s",
                       originator, reply_to);
         }
         return *process;
     }
 
     if (is_reply) {
         crm_trace("Will notify local clients for %s reply from %s",
                   op, originator);
         *process = FALSE;
         *needs_reply = FALSE;
         *local_notify = TRUE;
         return TRUE;
     }
 
   skip_is_reply:
     *process = TRUE;
     *needs_reply = FALSE;
 
     *local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei);
 
     host = crm_element_value(request, PCMK__XA_CIB_HOST);
     if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
         crm_trace("Processing %s request sent to us from %s", op, originator);
         *needs_reply = TRUE;
         return TRUE;
 
     } else if (host != NULL) {
         crm_trace("Ignoring %s request intended for CIB manager on %s",
                   op, host);
         return FALSE;
 
     } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
         *needs_reply = TRUE;
     }
 
     crm_trace("Processing %s request broadcast by %s call %s on %s "
               "(local clients will%s be notified)", op,
               pcmk__s(crm_element_value(request, PCMK__XA_CIB_CLIENTNAME),
                       "client"),
               pcmk__s(crm_element_value(request, PCMK__XA_CIB_CALLID),
                       "without ID"),
               originator, (*local_notify? "" : "not"));
     return TRUE;
 }
 
 static gboolean
 parse_peer_options(const cib__operation_t *operation, xmlNode *request,
                    gboolean *local_notify, gboolean *needs_reply,
                    gboolean *process)
 {
     /* TODO: What happens when an update comes in after node A
      * requests the CIB from node B, but before it gets the reply (and
      * sends out the replace operation)
      */
     if(cib_legacy_mode()) {
         return parse_peer_options_v1(operation, request, local_notify,
                                      needs_reply, process);
     } else {
         return parse_peer_options_v2(operation, request, local_notify,
                                      needs_reply, process);
     }
 }
 
 /*!
  * \internal
  * \brief Forward a CIB request to the appropriate target host(s)
  *
  * \param[in] request  CIB request to forward
  */
 static void
 forward_request(xmlNode *request)
 {
     const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
     const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION);
     const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
     const char *originator = crm_element_value(request, PCMK__XA_SRC);
     const char *client_name = crm_element_value(request,
                                                 PCMK__XA_CIB_CLIENTNAME);
     const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID);
     crm_node_t *peer = NULL;
 
     int log_level = LOG_INFO;
 
     if (pcmk__str_eq(op, PCMK__CIB_REQUEST_NOOP, pcmk__str_none)) {
         log_level = LOG_DEBUG;
     }
 
     do_crm_log(log_level,
                "Forwarding %s operation for section %s to %s (origin=%s/%s/%s)",
                pcmk__s(op, "invalid"),
                pcmk__s(section, "all"),
                pcmk__s(host, (cib_legacy_mode()? "primary" : "all")),
                pcmk__s(originator, "local"),
                pcmk__s(client_name, "unspecified"),
                pcmk__s(call_id, "unspecified"));
 
     crm_xml_add(request, PCMK__XA_CIB_DELEGATED_FROM, OUR_NODENAME);
 
     if (host != NULL) {
         peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster);
     }
     send_cluster_message(peer, crm_msg_cib, request, FALSE);
 
     // Return the request to its original state
     pcmk__xe_remove_attr(request, PCMK__XA_CIB_DELEGATED_FROM);
 }
 
 static gboolean
 send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast)
 {
     CRM_ASSERT(msg != NULL);
 
     if (broadcast) {
         /* @COMPAT: Legacy code
          *
          * This successful call modified the CIB, and the change needs to be
          * broadcast (sent via cluster to all nodes).
          */
         int diff_add_updates = 0;
         int diff_add_epoch = 0;
         int diff_add_admin_epoch = 0;
 
         int diff_del_updates = 0;
         int diff_del_epoch = 0;
         int diff_del_admin_epoch = 0;
 
         const char *digest = NULL;
         int format = 1;
 
         xmlNode *wrapper = NULL;
 
         CRM_LOG_ASSERT(result_diff != NULL);
         digest = crm_element_value(result_diff, PCMK__XA_DIGEST);
         crm_element_value_int(result_diff, PCMK_XA_FORMAT, &format);
 
         cib_diff_version_details(result_diff,
                                  &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates,
                                  &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);
 
         crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s",
                   diff_del_admin_epoch, diff_del_epoch, diff_del_updates,
                   diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest);
 
         crm_xml_add(msg, PCMK__XA_CIB_ISREPLYTO, originator);
         pcmk__xe_set_bool_attr(msg, PCMK__XA_CIB_UPDATE, true);
         crm_xml_add(msg, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_APPLY_PATCH);
         crm_xml_add(msg, PCMK__XA_CIB_USER, CRM_DAEMON_USER);
 
         if (format == 1) {
             CRM_ASSERT(digest != NULL);
         }
 
         wrapper = pcmk__xe_create(msg, PCMK__XA_CIB_UPDATE_DIFF);
         pcmk__xml_copy(wrapper, result_diff);
 
         crm_log_xml_explicit(msg, "copy");
         return send_cluster_message(NULL, crm_msg_cib, msg, TRUE);
 
     } else if (originator != NULL) {
         /* send reply via HA to originating node */
         crm_trace("Sending request result to %s only", originator);
         crm_xml_add(msg, PCMK__XA_CIB_ISREPLYTO, originator);
         return send_cluster_message(pcmk__get_node(0, originator, NULL,
                                                    pcmk__node_search_cluster),
                                     crm_msg_cib, msg, FALSE);
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Handle an IPC or CPG message containing a request
  *
  * \param[in,out] request        Request XML
  * \param[in] privileged         Whether privileged commands may be run
  *                               (see cib_server_ops[] definition)
  * \param[in] cib_client         IPC client that sent request (or NULL if CPG)
  *
  * \return Legacy Pacemaker return code
  */
 int
 cib_process_request(xmlNode *request, gboolean privileged,
                     const pcmk__client_t *cib_client)
 {
     // @TODO: Break into multiple smaller functions
     int call_options = 0;
 
     gboolean process = TRUE;        // Whether to process request locally now
     gboolean is_update = TRUE;      // Whether request would modify CIB
     gboolean needs_reply = TRUE;    // Whether to build a reply
     gboolean local_notify = FALSE;  // Whether to notify (local) requester
     gboolean needs_forward = FALSE; // Whether to forward request somewhere else
 
     xmlNode *op_reply = NULL;
     xmlNode *result_diff = NULL;
 
     int rc = pcmk_ok;
     const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
     const char *originator = crm_element_value(request, PCMK__XA_SRC);
     const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
     const char *target = NULL;
     const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID);
     const char *client_id = crm_element_value(request, PCMK__XA_CIB_CLIENTID);
     const char *client_name = crm_element_value(request,
                                                 PCMK__XA_CIB_CLIENTNAME);
     const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO);
 
     const cib__operation_t *operation = NULL;
     cib__op_fn_t op_function = NULL;
 
     crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options);
 
     if ((host != NULL) && (*host == '\0')) {
         host = NULL;
     }
 
     // @TODO: Improve trace messages. Target is accurate only for legacy mode.
     if (host) {
         target = host;
 
     } else if (call_options & cib_scope_local) {
         target = "local host";
 
     } else {
         target = "primary";
     }
 
     if (cib_client == NULL) {
         crm_trace("Processing peer %s operation from %s/%s on %s intended for %s (reply=%s)",
                   op, client_name, call_id, originator, target, reply_to);
     } else {
         crm_xml_add(request, PCMK__XA_SRC, OUR_NODENAME);
         crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target);
     }
 
     rc = cib__get_operation(op, &operation);
     rc = pcmk_rc2legacy(rc);
     if (rc != pcmk_ok) {
         /* TODO: construct error reply? */
         crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc));
         return rc;
     }
 
     op_function = based_get_op_function(operation);
     if (op_function == NULL) {
         crm_err("Operation %s not supported by CIB manager", op);
         return -EOPNOTSUPP;
     }
 
     if (cib_client != NULL) {
         parse_local_options(cib_client, operation, call_options, host, op,
                             &local_notify, &needs_reply, &process,
                             &needs_forward);
 
     } else if (!parse_peer_options(operation, request, &local_notify,
                                    &needs_reply, &process)) {
         return rc;
     }
 
     if (pcmk_is_set(call_options, cib_transaction)) {
         /* All requests in a transaction are processed locally against a working
          * CIB copy, and we don't notify for individual requests because the
          * entire transaction is atomic.
          *
          * We still call the option parser functions above, for the sake of log
          * messages and checking whether we're the target for peer requests.
          */
         process = TRUE;
         needs_reply = FALSE;
         local_notify = FALSE;
         needs_forward = FALSE;
     }
 
     is_update = pcmk_is_set(operation->flags, cib__op_attr_modifies);
 
     if (pcmk_is_set(call_options, cib_discard_reply)) {
         /* If the request will modify the CIB, and we are in legacy mode, we
          * need to build a reply so we can broadcast a diff, even if the
          * requester doesn't want one.
          */
         needs_reply = is_update && cib_legacy_mode();
         local_notify = FALSE;
         crm_trace("Client is not interested in the reply");
     }
 
     if (needs_forward) {
         forward_request(request);
         return rc;
     }
 
     if (cib_status != pcmk_ok) {
         rc = cib_status;
         crm_err("Operation ignored, cluster configuration is invalid."
                 " Please repair and restart: %s", pcmk_strerror(cib_status));
 
         op_reply = create_cib_reply(op, call_id, client_id, call_options, rc,
                                     the_cib);
 
     } else if (process) {
         time_t finished = 0;
         time_t now = time(NULL);
         int level = LOG_INFO;
         const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION);
         const char *admin_epoch_s = NULL;
         const char *epoch_s = NULL;
         const char *num_updates_s = NULL;
 
         rc = cib_process_command(request, operation, op_function, &op_reply,
                                  &result_diff, privileged);
 
         if (!is_update) {
             level = LOG_TRACE;
 
         } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) {
             switch (rc) {
                 case pcmk_ok:
                     level = LOG_INFO;
                     break;
                 case -pcmk_err_old_data:
                 case -pcmk_err_diff_resync:
                 case -pcmk_err_diff_failed:
                     level = LOG_TRACE;
                     break;
                 default:
                     level = LOG_ERR;
             }
 
         } else if (rc != pcmk_ok) {
             level = LOG_WARNING;
         }
 
         if (the_cib != NULL) {
             admin_epoch_s = crm_element_value(the_cib, PCMK_XA_ADMIN_EPOCH);
             epoch_s = crm_element_value(the_cib, PCMK_XA_EPOCH);
             num_updates_s = crm_element_value(the_cib, PCMK_XA_NUM_UPDATES);
         }
 
         do_crm_log(level,
                    "Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)",
                    op, section ? section : "'all'", pcmk_strerror(rc), rc,
                    originator ? originator : "local", client_name, call_id,
                    pcmk__s(admin_epoch_s, "0"),
                    pcmk__s(epoch_s, "0"),
                    pcmk__s(num_updates_s, "0"));
 
         finished = time(NULL);
         if ((finished - now) > 3) {
             crm_trace("%s operation took %lds to complete", op, (long)(finished - now));
             crm_write_blackbox(0, NULL);
         }
 
         if (op_reply == NULL && (needs_reply || local_notify)) {
             crm_err("Unexpected NULL reply to message");
             crm_log_xml_err(request, "null reply");
             needs_reply = FALSE;
             local_notify = FALSE;
         }
     }
 
     if (is_update && !cib_legacy_mode()) {
         crm_trace("Completed pre-sync update from %s/%s/%s%s",
                   originator ? originator : "local", client_name, call_id,
                   local_notify?" with local notification":"");
 
     } else if (!needs_reply || stand_alone) {
         // This was a non-originating secondary update
         crm_trace("Completed update as secondary");
 
     } else if (cib_legacy_mode() &&
                rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) {
         gboolean broadcast = FALSE;
 
         cib_local_bcast_num++;
         crm_xml_add_int(request, PCMK__XA_CIB_LOCAL_NOTIFY_ID,
                         cib_local_bcast_num);
         broadcast = send_peer_reply(request, result_diff, originator, TRUE);
 
         if (broadcast && client_id && local_notify && op_reply) {
 
             /* If we have been asked to sync the reply,
              * and a bcast msg has gone out, we queue the local notify
              * until we know the bcast message has been received */
             local_notify = FALSE;
             crm_trace("Queuing local %ssync notification for %s",
                       (call_options & cib_sync_call) ? "" : "a-", client_id);
 
             queue_local_notify(op_reply, client_id,
                                pcmk_is_set(call_options, cib_sync_call),
                                (cib_client == NULL));
             op_reply = NULL;    /* the reply is queued, so don't free here */
         }
 
     } else if ((cib_client == NULL)
                && !pcmk_is_set(call_options, cib_discard_reply)) {
 
         if (is_update == FALSE || result_diff == NULL) {
             crm_trace("Request not broadcast: R/O call");
 
         } else if (call_options & cib_inhibit_bcast) {
             crm_trace("Request not broadcast: inhibited");
 
         } else if (rc != pcmk_ok) {
             crm_trace("Request not broadcast: call failed: %s", pcmk_strerror(rc));
 
         } else {
             crm_trace("Directing reply to %s", originator);
         }
 
         send_peer_reply(op_reply, result_diff, originator, FALSE);
     }
 
     if (local_notify && client_id) {
         crm_trace("Performing local %ssync notification for %s",
                   (pcmk_is_set(call_options, cib_sync_call)? "" : "a"),
                   client_id);
         if (process == FALSE) {
             do_local_notify(request, client_id,
                             pcmk_is_set(call_options, cib_sync_call),
                             (cib_client == NULL));
         } else {
             do_local_notify(op_reply, client_id,
                             pcmk_is_set(call_options, cib_sync_call),
                             (cib_client == NULL));
         }
     }
 
     free_xml(op_reply);
     free_xml(result_diff);
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Get a CIB operation's input from the request XML
  *
  * \param[in]  request  CIB request XML
  * \param[in]  type     CIB operation type
  * \param[out] section  Where to store CIB section name
  *
  * \return Input XML for CIB operation
  *
  * \note If not \c NULL, the return value is a non-const pointer to part of
  *       \p request. The caller should not free it directly.
  */
 static xmlNode *
 prepare_input(const xmlNode *request, enum cib__op_type type,
               const char **section)
 {
     xmlNode *wrapper = NULL;
     xmlNode *input = NULL;
 
     *section = NULL;
 
     switch (type) {
         case cib__op_apply_patch:
             {
                 const char *wrapper_name = PCMK__XE_CIB_CALLDATA;
 
                 if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) {
                     wrapper_name = PCMK__XA_CIB_UPDATE_DIFF;
                 }
                 wrapper = pcmk__xe_first_child(request, wrapper_name, NULL,
                                                NULL);
                 input = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
             }
             break;
 
         default:
             wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, NULL,
                                            NULL);
             input = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
             *section = crm_element_value(request, PCMK__XA_CIB_SECTION);
             break;
     }
 
     // Grab the specified section
     if ((*section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) {
         input = pcmk_find_cib_element(input, *section);
     }
 
     return input;
 }
 
 // v1 and v2 patch formats
 #define XPATH_CONFIG_CHANGE         \
     "//" PCMK_XE_CRM_CONFIG " | "   \
     "//" PCMK_XE_CHANGE             \
     "[contains(@" PCMK_XA_PATH ",'/" PCMK_XE_CRM_CONFIG "/')]"
 
 static bool
 contains_config_change(xmlNode *diff)
 {
     bool changed = false;
 
     if (diff) {
         xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE);
 
         if (numXpathResults(xpathObj) > 0) {
             changed = true;
         }
         freeXpathObject(xpathObj);
     }
     return changed;
 }
 
 static int
 cib_process_command(xmlNode *request, const cib__operation_t *operation,
                     cib__op_fn_t op_function, xmlNode **reply,
                     xmlNode **cib_diff, bool privileged)
 {
     xmlNode *input = NULL;
     xmlNode *output = NULL;
     xmlNode *result_cib = NULL;
 
     int call_options = 0;
 
     const char *op = NULL;
     const char *section = NULL;
     const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID);
     const char *client_id = crm_element_value(request, PCMK__XA_CIB_CLIENTID);
     const char *client_name = crm_element_value(request,
                                                 PCMK__XA_CIB_CLIENTNAME);
     const char *originator = crm_element_value(request, PCMK__XA_SRC);
 
     int rc = pcmk_ok;
 
     bool config_changed = false;
     bool manage_counters = true;
 
     static mainloop_timer_t *digest_timer = NULL;
 
     CRM_ASSERT(cib_status == pcmk_ok);
 
     if(digest_timer == NULL) {
         digest_timer = mainloop_timer_add("digester", 5000, FALSE, cib_digester_cb, NULL);
     }
 
     *reply = NULL;
     *cib_diff = NULL;
 
     /* Start processing the request... */
     op = crm_element_value(request, PCMK__XA_CIB_OP);
     crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options);
 
     if (!privileged && pcmk_is_set(operation->flags, cib__op_attr_privileged)) {
         rc = -EACCES;
         crm_trace("Failed due to lack of privileges: %s", pcmk_strerror(rc));
         goto done;
     }
 
     input = prepare_input(request, operation->type, &section);
 
     if (!pcmk_is_set(operation->flags, cib__op_attr_modifies)) {
         rc = cib_perform_op(NULL, op, call_options, op_function, true, section,
                             request, input, false, &config_changed, &the_cib,
                             &result_cib, NULL, &output);
 
         CRM_CHECK(result_cib == NULL, free_xml(result_cib));
         goto done;
     }
 
     /* @COMPAT: Handle a valid write action (legacy)
      *
      * @TODO: Re-evaluate whether this is all truly legacy. The cib_force_diff
      * portion is. However, PCMK__XA_CIB_UPDATE may be set by a sync operation
      * even in non-legacy mode, and manage_counters tells xml_create_patchset()
      * whether to update version/epoch info.
      */
     if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) {
         manage_counters = false;
         cib__set_call_options(call_options, "call", cib_force_diff);
         crm_trace("Global update detected");
 
         CRM_LOG_ASSERT(pcmk__str_any_of(op,
                                         PCMK__CIB_REQUEST_APPLY_PATCH,
                                         PCMK__CIB_REQUEST_REPLACE,
                                         NULL));
     }
 
     ping_modified_since = TRUE;
     if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
         crm_trace("Skipping update: inhibit broadcast");
         manage_counters = false;
     }
 
     // result_cib must not be modified after cib_perform_op() returns
     rc = cib_perform_op(NULL, op, call_options, op_function, false, section,
                         request, input, manage_counters, &config_changed,
                         &the_cib, &result_cib, cib_diff, &output);
 
     // @COMPAT: Legacy code
     if (!manage_counters) {
         int format = 1;
 
         // If the diff is NULL at this point, it's because nothing changed
         if (*cib_diff != NULL) {
             crm_element_value_int(*cib_diff, PCMK_XA_FORMAT, &format);
         }
 
         if (format == 1) {
             config_changed = cib__config_changed_v1(NULL, NULL, cib_diff);
         }
     }
 
     /* Always write to disk for successful ops with the flag set. This also
      * negates the need to detect ordering changes.
      */
     if ((rc == pcmk_ok)
         && pcmk_is_set(operation->flags, cib__op_attr_writes_through)) {
 
         config_changed = true;
     }
 
     if ((rc == pcmk_ok)
         && !pcmk_any_flags_set(call_options, cib_dryrun|cib_transaction)) {
 
         if (result_cib != the_cib) {
             if (pcmk_is_set(operation->flags, cib__op_attr_writes_through)) {
                 config_changed = true;
             }
 
             crm_trace("Activating %s->%s%s",
                       crm_element_value(the_cib, PCMK_XA_NUM_UPDATES),
                       crm_element_value(result_cib, PCMK_XA_NUM_UPDATES),
                       (config_changed? " changed" : ""));
 
             rc = activateCibXml(result_cib, config_changed, op);
             if (rc != pcmk_ok) {
                 crm_err("Failed to activate new CIB: %s", pcmk_strerror(rc));
             }
         }
 
         if ((rc == pcmk_ok) && contains_config_change(*cib_diff)) {
             cib_read_config(config_hash, result_cib);
         }
 
         /* @COMPAT Nodes older than feature set 3.19.0 don't support
          * transactions. In a mixed-version cluster with nodes <3.19.0, we must
          * sync the updated CIB, so that the older nodes receive the changes.
          * Any node that has already applied the transaction will ignore the
          * synced CIB.
          *
          * To ensure the updated CIB is synced from only one node, we sync it
          * from the originator.
          */
         if ((operation->type == cib__op_commit_transact)
             && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei)
             && compare_version(crm_element_value(the_cib,
                                                  PCMK_XA_CRM_FEATURE_SET),
                                "3.19.0") < 0) {
 
             sync_our_cib(request, TRUE);
         }
 
         mainloop_timer_stop(digest_timer);
         mainloop_timer_start(digest_timer);
 
     } else if (rc == -pcmk_err_schema_validation) {
         CRM_ASSERT(result_cib != the_cib);
 
         if (output != NULL) {
             crm_log_xml_info(output, "cib:output");
             free_xml(output);
         }
 
         output = result_cib;
 
     } else {
         crm_trace("Not activating %d %d %s", rc,
                   pcmk_is_set(call_options, cib_dryrun),
                   crm_element_value(result_cib, PCMK_XA_NUM_UPDATES));
 
         if (result_cib != the_cib) {
             free_xml(result_cib);
         }
     }
 
     if (!pcmk_any_flags_set(call_options,
                             cib_dryrun|cib_inhibit_notify|cib_transaction)) {
         crm_trace("Sending notifications %d",
                   pcmk_is_set(call_options, cib_dryrun));
         cib_diff_notify(op, rc, call_id, client_id, client_name, originator,
                         input, *cib_diff);
     }
 
     pcmk__log_xml_patchset(LOG_TRACE, *cib_diff);
 
   done:
     if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) {
         *reply = create_cib_reply(op, call_id, client_id, call_options, rc,
                                   output);
     }
 
     if (output != the_cib) {
         free_xml(output);
     }
     crm_trace("done");
     return rc;
 }
 
 void
 cib_peer_callback(xmlNode * msg, void *private_data)
 {
     const char *reason = NULL;
     const char *originator = crm_element_value(msg, PCMK__XA_SRC);
 
     if (cib_legacy_mode()
         && pcmk__str_eq(originator, OUR_NODENAME,
                         pcmk__str_casei|pcmk__str_null_matches)) {
         /* message is from ourselves */
         int bcast_id = 0;
 
         if (crm_element_value_int(msg, PCMK__XA_CIB_LOCAL_NOTIFY_ID,
                                   &bcast_id) == 0) {
             check_local_notify(bcast_id);
         }
         return;
 
     } else if (crm_peer_cache == NULL) {
         reason = "membership not established";
         goto bail;
     }
 
     if (crm_element_value(msg, PCMK__XA_CIB_CLIENTNAME) == NULL) {
         crm_xml_add(msg, PCMK__XA_CIB_CLIENTNAME, originator);
     }
 
     /* crm_log_xml_trace(msg, "Peer[inbound]"); */
     cib_process_request(msg, TRUE, NULL);
     return;
 
   bail:
     if (reason) {
         const char *op = crm_element_value(msg, PCMK__XA_CIB_OP);
 
         crm_warn("Discarding %s message from %s: %s", op, originator, reason);
     }
 }
 
 static gboolean
 cib_force_exit(gpointer data)
 {
     crm_notice("Forcing exit!");
     terminate_cib(__func__, CRM_EX_ERROR);
     return FALSE;
 }
 
 static void
 disconnect_remote_client(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk__client_t *a_client = value;
 
     crm_err("Can't disconnect client %s: Not implemented",
             pcmk__client_name(a_client));
 }
 
 static void
 initiate_exit(void)
 {
     int active = 0;
     xmlNode *leaving = NULL;
 
     active = crm_active_peers();
     if (active < 2) { // This is the last active node
         terminate_cib(__func__, 0);
         return;
     }
 
     crm_info("Sending shutdown request to %d peers", active);
 
     leaving = pcmk__xe_create(NULL, PCMK__XE_EXIT_NOTIFICATION);
     crm_xml_add(leaving, PCMK__XA_T, PCMK__VALUE_CIB);
     crm_xml_add(leaving, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SHUTDOWN);
 
     send_cluster_message(NULL, crm_msg_cib, leaving, TRUE);
     free_xml(leaving);
 
     g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL);
 }
 
 void
 cib_shutdown(int nsig)
 {
     struct qb_ipcs_stats srv_stats;
 
     if (cib_shutdown_flag == FALSE) {
         int disconnects = 0;
         qb_ipcs_connection_t *c = NULL;
 
         cib_shutdown_flag = TRUE;
 
         c = qb_ipcs_connection_first_get(ipcs_rw);
         while (c != NULL) {
             qb_ipcs_connection_t *last = c;
 
             c = qb_ipcs_connection_next_get(ipcs_rw, last);
 
             crm_debug("Disconnecting r/w client %p...", last);
             qb_ipcs_disconnect(last);
             qb_ipcs_connection_unref(last);
             disconnects++;
         }
 
         c = qb_ipcs_connection_first_get(ipcs_ro);
         while (c != NULL) {
             qb_ipcs_connection_t *last = c;
 
             c = qb_ipcs_connection_next_get(ipcs_ro, last);
 
             crm_debug("Disconnecting r/o client %p...", last);
             qb_ipcs_disconnect(last);
             qb_ipcs_connection_unref(last);
             disconnects++;
         }
 
         c = qb_ipcs_connection_first_get(ipcs_shm);
         while (c != NULL) {
             qb_ipcs_connection_t *last = c;
 
             c = qb_ipcs_connection_next_get(ipcs_shm, last);
 
             crm_debug("Disconnecting non-blocking r/w client %p...", last);
             qb_ipcs_disconnect(last);
             qb_ipcs_connection_unref(last);
             disconnects++;
         }
 
         disconnects += pcmk__ipc_client_count();
 
         crm_debug("Disconnecting %d remote clients", pcmk__ipc_client_count());
         pcmk__foreach_ipc_client(disconnect_remote_client, NULL);
         crm_info("Disconnected %d clients", disconnects);
     }
 
     qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE);
 
     if (pcmk__ipc_client_count() == 0) {
         crm_info("All clients disconnected (%d)", srv_stats.active_connections);
         initiate_exit();
 
     } else {
         crm_info("Waiting on %d clients to disconnect (%d)",
                  pcmk__ipc_client_count(), srv_stats.active_connections);
     }
 }
 
 extern int remote_fd;
 extern int remote_tls_fd;
 
 /*!
  * \internal
  * \brief Close remote sockets, free the global CIB and quit
  *
  * \param[in] caller           Name of calling function (for log message)
  * \param[in] fast             If -1, skip disconnect; if positive, exit that
  */
 void
 terminate_cib(const char *caller, int fast)
 {
     crm_info("%s: Exiting%s...", caller,
              (fast > 0)? " fast" : mainloop ? " from mainloop" : "");
 
     if (remote_fd > 0) {
         close(remote_fd);
         remote_fd = 0;
     }
     if (remote_tls_fd > 0) {
         close(remote_tls_fd);
         remote_tls_fd = 0;
     }
 
     uninitializeCib();
 
     if (fast > 0) {
         /* Quit fast on error */
         pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
         crm_exit(fast);
 
     } else if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) {
         /* Quit via returning from the main loop. If fast == -1, we skip the
          * disconnect here, and it will be done when the main loop returns
          * (this allows the peer status callback to avoid messing with the
          * peer caches).
          */
         if (fast == 0) {
-            crm_cluster_disconnect(crm_cluster);
+            pcmk_cluster_disconnect(crm_cluster);
         }
         g_main_loop_quit(mainloop);
 
     } else {
         /* Quit via clean exit. Even the peer status callback can disconnect
          * here, because we're not returning control to the caller. */
-        crm_cluster_disconnect(crm_cluster);
+        pcmk_cluster_disconnect(crm_cluster);
         pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
         crm_exit(CRM_EX_OK);
     }
 }
diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c
index 72fa289ec5..a9e7e2af6c 100644
--- a/daemons/based/pacemaker-based.c
+++ b/daemons/based/pacemaker-based.c
@@ -1,450 +1,450 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <pwd.h>
 #include <grp.h>
 #include <bzlib.h>
 #include <sys/types.h>
 
 #include <glib.h>
 #include <libxml/tree.h>
 
 #include <crm/crm.h>
 #include <crm/cib/internal.h>
 #include <crm/cluster/internal.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/output_internal.h>
 #include <crm/common/xml.h>
 
 #include <pacemaker-based.h>
 
 #define SUMMARY "daemon for managing the configuration of a Pacemaker cluster"
 
 extern int init_remote_listener(int port, gboolean encrypted);
 gboolean cib_shutdown_flag = FALSE;
 int cib_status = pcmk_ok;
 
 crm_cluster_t *crm_cluster = NULL;
 
 GMainLoop *mainloop = NULL;
 gchar *cib_root = NULL;
 static gboolean preserve_status = FALSE;
 
 gboolean cib_writes_enabled = TRUE;
 gboolean stand_alone = FALSE;
 
 int remote_fd = 0;
 int remote_tls_fd = 0;
 
 GHashTable *config_hash = NULL;
 GHashTable *local_notify_queue = NULL;
 
 static void cib_init(void);
 void cib_shutdown(int nsig);
 static bool startCib(const char *filename);
 extern int write_cib_contents(gpointer p);
 
 static crm_exit_t exit_code = CRM_EX_OK;
 
 static void
 cib_enable_writes(int nsig)
 {
     crm_info("(Re)enabling disk writes");
     cib_writes_enabled = TRUE;
 }
 
 /*!
  * \internal
  * \brief Set up options, users, and groups for stand-alone mode
  *
  * \param[out] error  GLib error object
  *
  * \return Standard Pacemaker return code
  */
 static int
 setup_stand_alone(GError **error)
 {
     int rc = 0;
     struct passwd *pwentry = NULL;
 
     preserve_status = TRUE;
     cib_writes_enabled = FALSE;
 
     errno = 0;
     pwentry = getpwnam(CRM_DAEMON_USER);
     if (pwentry == NULL) {
         exit_code = CRM_EX_FATAL;
         if (errno != 0) {
             g_set_error(error, PCMK__EXITC_ERROR, exit_code,
                         "Error getting password DB entry for %s: %s",
                         CRM_DAEMON_USER, strerror(errno));
             return errno;
         }
         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
                     "Password DB entry for '%s' not found", CRM_DAEMON_USER);
         return ENXIO;
     }
 
     rc = setgid(pwentry->pw_gid);
     if (rc < 0) {
         exit_code = CRM_EX_FATAL;
         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
                     "Could not set group to %d: %s",
                     pwentry->pw_gid, strerror(errno));
         return errno;
     }
 
     rc = initgroups(CRM_DAEMON_USER, pwentry->pw_gid);
     if (rc < 0) {
         exit_code = CRM_EX_FATAL;
         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
                     "Could not setup groups for user %d: %s",
                     pwentry->pw_uid, strerror(errno));
         return errno;
     }
 
     rc = setuid(pwentry->pw_uid);
     if (rc < 0) {
         exit_code = CRM_EX_FATAL;
         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
                     "Could not set user to %d: %s",
                     pwentry->pw_uid, strerror(errno));
         return errno;
     }
     return pcmk_rc_ok;
 }
 
 /* @COMPAT Deprecated since 2.1.8. Use pcmk_list_cluster_options() or
  * crm_attribute --list-options=cluster instead of querying daemon metadata.
  */
 static int
 based_metadata(pcmk__output_t *out)
 {
     return pcmk__daemon_metadata(out, "pacemaker-based",
                                  "Cluster Information Base manager options",
                                  "Cluster options used by Pacemaker's Cluster "
                                  "Information Base manager",
                                  pcmk__opt_based);
 }
 
 static GOptionEntry entries[] = {
     { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone,
       "(Advanced use only) Run in stand-alone mode", NULL },
 
     { "disk-writes", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
       &cib_writes_enabled,
       "(Advanced use only) Enable disk writes (enabled by default unless in "
       "stand-alone mode)", NULL },
 
     { "cib-root", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &cib_root,
       "(Advanced use only) Directory where the CIB XML file should be located "
       "(default: " CRM_CONFIG_DIR ")", NULL },
 
     { NULL }
 };
 
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
 {
     GOptionContext *context = NULL;
 
     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
     pcmk__add_main_args(context, entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_rc_ok;
     crm_ipc_t *old_instance = NULL;
 
     pcmk__output_t *out = NULL;
 
     GError *error = NULL;
 
     GOptionGroup *output_group = NULL;
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
     gchar **processed_args = pcmk__cmdline_preproc(argv, "r");
     GOptionContext *context = build_arg_context(args, &output_group);
 
     crm_log_preinit(NULL, argc, argv);
 
     pcmk__register_formats(output_group, formats);
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     if (rc != pcmk_rc_ok) {
         exit_code = CRM_EX_ERROR;
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "Error creating output format %s: %s",
                     args->output_ty, pcmk_rc_str(rc));
         goto done;
     }
 
     if (args->version) {
         out->version(out, false);
         goto done;
     }
 
     mainloop_add_signal(SIGTERM, cib_shutdown);
     mainloop_add_signal(SIGPIPE, cib_enable_writes);
 
     cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL);
 
     if ((g_strv_length(processed_args) >= 2)
         && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) {
 
         rc = based_metadata(out);
         if (rc != pcmk_rc_ok) {
             exit_code = CRM_EX_FATAL;
             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                         "Unable to display metadata: %s", pcmk_rc_str(rc));
         }
         goto done;
     }
 
     pcmk__cli_init_logging("pacemaker-based", args->verbosity);
     crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
     crm_notice("Starting Pacemaker CIB manager");
 
     old_instance = crm_ipc_new(PCMK__SERVER_BASED_RO, 0);
     if (old_instance == NULL) {
         /* crm_ipc_new() will have already logged an error message with
          * crm_err()
          */
         exit_code = CRM_EX_FATAL;
         goto done;
     }
 
     if (pcmk__connect_generic_ipc(old_instance) == pcmk_rc_ok) {
         /* IPC end-point already up */
         crm_ipc_close(old_instance);
         crm_ipc_destroy(old_instance);
         crm_err("pacemaker-based is already active, aborting startup");
         goto done;
     } else {
         /* not up or not authentic, we'll proceed either way */
         crm_ipc_destroy(old_instance);
         old_instance = NULL;
     }
 
     if (stand_alone) {
         rc = setup_stand_alone(&error);
         if (rc != pcmk_rc_ok) {
             goto done;
         }
     }
 
     if (cib_root == NULL) {
         cib_root = g_strdup(CRM_CONFIG_DIR);
     } else {
         crm_notice("Using custom config location: %s", cib_root);
     }
 
     if (!pcmk__daemon_can_write(cib_root, NULL)) {
         exit_code = CRM_EX_FATAL;
         crm_err("Terminating due to bad permissions on %s", cib_root);
         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                     "Bad permissions on %s (see logs for details)", cib_root);
         goto done;
     }
 
     crm_peer_init();
 
     // Read initial CIB, connect to cluster, and start IPC servers
     cib_init();
 
     // Run the main loop
     mainloop = g_main_loop_new(NULL, FALSE);
     crm_notice("Pacemaker CIB manager successfully started and accepting connections");
     g_main_loop_run(mainloop);
 
     /* If main loop returned, clean up and exit. We disconnect in case
      * terminate_cib() was called with fast=-1.
      */
-    crm_cluster_disconnect(crm_cluster);
+    pcmk_cluster_disconnect(crm_cluster);
     pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
 
 done:
     g_strfreev(processed_args);
     pcmk__free_arg_context(context);
 
     crm_peer_destroy();
 
     if (local_notify_queue != NULL) {
         g_hash_table_destroy(local_notify_queue);
     }
 
     if (config_hash != NULL) {
         g_hash_table_destroy(config_hash);
     }
     pcmk__client_cleanup();
     pcmk_cluster_free(crm_cluster);
     g_free(cib_root);
 
     pcmk__output_and_clear_error(&error, out);
 
     if (out != NULL) {
         out->finish(out, exit_code, true, NULL);
         pcmk__output_free(out);
     }
     pcmk__unregister_formats();
     crm_exit(exit_code);
 }
 
 #if SUPPORT_COROSYNC
 static void
 cib_cs_dispatch(cpg_handle_t handle,
                  const struct cpg_name *groupName,
                  uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
 {
     uint32_t kind = 0;
     xmlNode *xml = NULL;
     const char *from = NULL;
     char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from);
 
     if(data == NULL) {
         return;
     }
     if (kind == crm_class_cluster) {
         xml = pcmk__xml_parse(data);
         if (xml == NULL) {
             crm_err("Invalid XML: '%.120s'", data);
             free(data);
             return;
         }
         crm_xml_add(xml, PCMK__XA_SRC, from);
         cib_peer_callback(xml, NULL);
     }
 
     free_xml(xml);
     free(data);
 }
 
 static void
 cib_cs_destroy(gpointer user_data)
 {
     if (cib_shutdown_flag) {
         crm_info("Corosync disconnection complete");
     } else {
         crm_crit("Lost connection to cluster layer, shutting down");
         terminate_cib(__func__, CRM_EX_DISCONNECT);
     }
 }
 #endif
 
 static void
 cib_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data)
 {
     switch (type) {
         case crm_status_processes:
             if (cib_legacy_mode()
                 && !pcmk_is_set(node->processes, crm_get_cluster_proc())) {
 
                 uint32_t old = data? *(const uint32_t *)data : 0;
 
                 if ((node->processes ^ old) & crm_proc_cpg) {
                     crm_info("Attempting to disable legacy mode after %s left the cluster",
                              node->uname);
                     legacy_mode = FALSE;
                 }
             }
             break;
 
         case crm_status_uname:
         case crm_status_nstate:
             if (cib_shutdown_flag && (crm_active_peers() < 2)
                 && (pcmk__ipc_client_count() == 0)) {
 
                 crm_info("No more peers");
                 terminate_cib(__func__, -1);
             }
             break;
     }
 }
 
 static void
 cib_init(void)
 {
     crm_cluster = pcmk_cluster_new();
 
 #if SUPPORT_COROSYNC
     if (is_corosync_cluster()) {
         crm_cluster->destroy = cib_cs_destroy;
         crm_cluster->cpg.cpg_deliver_fn = cib_cs_dispatch;
         crm_cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership;
     }
 #endif // SUPPORT_COROSYNC
 
     config_hash = pcmk__strkey_table(free, free);
 
     if (startCib("cib.xml") == FALSE) {
         crm_crit("Cannot start CIB... terminating");
         crm_exit(CRM_EX_NOINPUT);
     }
 
     if (!stand_alone) {
         crm_set_status_callback(&cib_peer_update_callback);
 
         if (pcmk_cluster_connect(crm_cluster) != pcmk_rc_ok) {
             crm_crit("Cannot sign in to the cluster... terminating");
             crm_exit(CRM_EX_FATAL);
         }
     }
 
     pcmk__serve_based_ipc(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks,
                           &ipc_rw_callbacks);
 
     if (stand_alone) {
         based_is_primary = true;
     }
 }
 
 static bool
 startCib(const char *filename)
 {
     gboolean active = FALSE;
     xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status);
 
     if (activateCibXml(cib, TRUE, "start") == 0) {
         int port = 0;
 
         active = TRUE;
 
         cib_read_config(config_hash, cib);
 
         pcmk__scan_port(crm_element_value(cib, PCMK_XA_REMOTE_TLS_PORT), &port);
         if (port >= 0) {
             remote_tls_fd = init_remote_listener(port, TRUE);
         }
 
         pcmk__scan_port(crm_element_value(cib, PCMK_XA_REMOTE_CLEAR_PORT),
                         &port);
         if (port >= 0) {
             remote_fd = init_remote_listener(port, FALSE);
         }
     }
     return active;
 }
diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c
index 953e73b456..4e33e38da0 100644
--- a/daemons/controld/controld_control.c
+++ b/daemons/controld/controld_control.c
@@ -1,689 +1,689 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/pengine/rules.h>
 #include <crm/cluster/internal.h>
 #include <crm/cluster/election_internal.h>
 #include <crm/common/ipc_internal.h>
 
 #include <pacemaker-controld.h>
 
 static qb_ipcs_service_t *ipcs = NULL;
 
 static crm_trigger_t *config_read_trigger = NULL;
 
 #if SUPPORT_COROSYNC
 extern gboolean crm_connect_corosync(crm_cluster_t * cluster);
 #endif
 
 static void crm_shutdown(int nsig);
 static gboolean crm_read_options(gpointer user_data);
 
 /*	 A_HA_CONNECT	*/
 void
 do_ha_control(long long action,
               enum crmd_fsa_cause cause,
               enum crmd_fsa_state cur_state,
               enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     gboolean registered = FALSE;
     static crm_cluster_t *cluster = NULL;
 
     if (cluster == NULL) {
         cluster = pcmk_cluster_new();
     }
 
     if (action & A_HA_DISCONNECT) {
-        crm_cluster_disconnect(cluster);
+        pcmk_cluster_disconnect(cluster);
         crm_info("Disconnected from the cluster");
 
         controld_set_fsa_input_flags(R_HA_DISCONNECTED);
     }
 
     if (action & A_HA_CONNECT) {
         crm_set_status_callback(&peer_update_callback);
         crm_set_autoreap(FALSE);
 
 #if SUPPORT_COROSYNC
         if (is_corosync_cluster()) {
             registered = crm_connect_corosync(cluster);
         }
 #endif // SUPPORT_COROSYNC
 
         if (registered) {
             controld_election_init(cluster->uname);
             controld_globals.our_nodename = cluster->uname;
             controld_globals.our_uuid = cluster->uuid;
             if(cluster->uuid == NULL) {
                 crm_err("Could not obtain local uuid");
                 registered = FALSE;
             }
         }
 
         if (!registered) {
             controld_set_fsa_input_flags(R_HA_DISCONNECTED);
             register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
             return;
         }
 
         populate_cib_nodes(node_update_none, __func__);
         controld_clear_fsa_input_flags(R_HA_DISCONNECTED);
         crm_info("Connected to the cluster");
     }
 
     if (action & ~(A_HA_CONNECT | A_HA_DISCONNECT)) {
         crm_err("Unexpected action %s in %s", fsa_action2string(action),
                 __func__);
     }
 }
 
 /*	 A_SHUTDOWN	*/
 void
 do_shutdown(long long action,
             enum crmd_fsa_cause cause,
             enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     /* just in case */
     controld_set_fsa_input_flags(R_SHUTDOWN);
     controld_disconnect_fencer(FALSE);
 }
 
 /*	 A_SHUTDOWN_REQ	*/
 void
 do_shutdown_req(long long action,
                 enum crmd_fsa_cause cause,
                 enum crmd_fsa_state cur_state,
                 enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     xmlNode *msg = NULL;
 
     controld_set_fsa_input_flags(R_SHUTDOWN);
     //controld_set_fsa_input_flags(R_STAYDOWN);
     crm_info("Sending shutdown request to all peers (DC is %s)",
              pcmk__s(controld_globals.dc_name, "not set"));
     msg = create_request(CRM_OP_SHUTDOWN_REQ, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
 
     if (send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) {
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
     free_xml(msg);
 }
 
 void
 crmd_fast_exit(crm_exit_t exit_code)
 {
     if (pcmk_is_set(controld_globals.fsa_input_register, R_STAYDOWN)) {
         crm_warn("Inhibiting respawn "CRM_XS" remapping exit code %d to %d",
                  exit_code, CRM_EX_FATAL);
         exit_code = CRM_EX_FATAL;
 
     } else if ((exit_code == CRM_EX_OK)
                && pcmk_is_set(controld_globals.fsa_input_register,
                               R_IN_RECOVERY)) {
         crm_err("Could not recover from internal error");
         exit_code = CRM_EX_ERROR;
     }
 
     if (controld_globals.logger_out != NULL) {
         controld_globals.logger_out->finish(controld_globals.logger_out,
                                             exit_code, true, NULL);
         pcmk__output_free(controld_globals.logger_out);
         controld_globals.logger_out = NULL;
     }
 
     crm_exit(exit_code);
 }
 
 crm_exit_t
 crmd_exit(crm_exit_t exit_code)
 {
     GMainLoop *mloop = controld_globals.mainloop;
 
     static bool in_progress = FALSE;
 
     if (in_progress && (exit_code == CRM_EX_OK)) {
         crm_debug("Exit is already in progress");
         return exit_code;
 
     } else if(in_progress) {
         crm_notice("Error during shutdown process, exiting now with status %d (%s)",
                    exit_code, crm_exit_str(exit_code));
         crm_write_blackbox(SIGTRAP, NULL);
         crmd_fast_exit(exit_code);
     }
 
     in_progress = TRUE;
     crm_trace("Preparing to exit with status %d (%s)",
               exit_code, crm_exit_str(exit_code));
 
     /* Suppress secondary errors resulting from us disconnecting everything */
     controld_set_fsa_input_flags(R_HA_DISCONNECTED);
 
 /* Close all IPC servers and clients to ensure any and all shared memory files are cleaned up */
 
     if(ipcs) {
         crm_trace("Closing IPC server");
         mainloop_del_ipc_server(ipcs);
         ipcs = NULL;
     }
 
     controld_close_attrd_ipc();
     controld_shutdown_schedulerd_ipc();
     controld_disconnect_fencer(TRUE);
 
     if ((exit_code == CRM_EX_OK) && (controld_globals.mainloop == NULL)) {
         crm_debug("No mainloop detected");
         exit_code = CRM_EX_ERROR;
     }
 
     /* On an error, just get out.
      *
      * Otherwise, make the effort to have mainloop exit gracefully so
      * that it (mostly) cleans up after itself and valgrind has less
      * to report on - allowing real errors stand out
      */
     if (exit_code != CRM_EX_OK) {
         crm_notice("Forcing immediate exit with status %d (%s)",
                    exit_code, crm_exit_str(exit_code));
         crm_write_blackbox(SIGTRAP, NULL);
         crmd_fast_exit(exit_code);
     }
 
 /* Clean up as much memory as possible for valgrind */
 
     for (GList *iter = controld_globals.fsa_message_queue; iter != NULL;
          iter = iter->next) {
         fsa_data_t *fsa_data = (fsa_data_t *) iter->data;
 
         crm_info("Dropping %s: [ state=%s cause=%s origin=%s ]",
                  fsa_input2string(fsa_data->fsa_input),
                  fsa_state2string(controld_globals.fsa_state),
                  fsa_cause2string(fsa_data->fsa_cause), fsa_data->origin);
         delete_fsa_input(fsa_data);
     }
 
     controld_clear_fsa_input_flags(R_MEMBERSHIP);
 
     g_list_free(controld_globals.fsa_message_queue);
     controld_globals.fsa_message_queue = NULL;
 
     controld_free_node_pending_timers();
     controld_election_fini();
 
     /* Tear down the CIB manager connection, but don't free it yet -- it could
      * be used when we drain the mainloop later.
      */
 
     controld_disconnect_cib_manager();
 
     verify_stopped(controld_globals.fsa_state, LOG_WARNING);
     controld_clear_fsa_input_flags(R_LRM_CONNECTED);
     lrm_state_destroy_all();
 
     mainloop_destroy_trigger(config_read_trigger);
     config_read_trigger = NULL;
 
     controld_destroy_fsa_trigger();
     controld_destroy_transition_trigger();
 
     pcmk__client_cleanup();
     crm_peer_destroy();
 
     controld_free_fsa_timers();
     te_cleanup_stonith_history_sync(NULL, TRUE);
     controld_free_sched_timer();
 
     free(controld_globals.our_nodename);
     controld_globals.our_nodename = NULL;
 
     free(controld_globals.our_uuid);
     controld_globals.our_uuid = NULL;
 
     free(controld_globals.dc_name);
     controld_globals.dc_name = NULL;
 
     free(controld_globals.dc_version);
     controld_globals.dc_version = NULL;
 
     free(controld_globals.cluster_name);
     controld_globals.cluster_name = NULL;
 
     free(controld_globals.te_uuid);
     controld_globals.te_uuid = NULL;
 
     free_max_generation();
     controld_destroy_failed_sync_table();
     controld_destroy_outside_events_table();
 
     mainloop_destroy_signal(SIGPIPE);
     mainloop_destroy_signal(SIGUSR1);
     mainloop_destroy_signal(SIGTERM);
     mainloop_destroy_signal(SIGTRAP);
     /* leave SIGCHLD engaged as we might still want to drain some service-actions */
 
     if (mloop) {
         GMainContext *ctx = g_main_loop_get_context(controld_globals.mainloop);
 
         /* Don't re-enter this block */
         controld_globals.mainloop = NULL;
 
         /* no signals on final draining anymore */
         mainloop_destroy_signal(SIGCHLD);
 
         crm_trace("Draining mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx));
 
         {
             int lpc = 0;
 
             while((g_main_context_pending(ctx) && lpc < 10)) {
                 lpc++;
                 crm_trace("Iteration %d", lpc);
                 g_main_context_dispatch(ctx);
             }
         }
 
         crm_trace("Closing mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx));
         g_main_loop_quit(mloop);
 
         /* Won't do anything yet, since we're inside it now */
         g_main_loop_unref(mloop);
     } else {
         mainloop_destroy_signal(SIGCHLD);
     }
 
     cib_delete(controld_globals.cib_conn);
     controld_globals.cib_conn = NULL;
 
     throttle_fini();
 
     /* Graceful */
     crm_trace("Done preparing for exit with status %d (%s)",
               exit_code, crm_exit_str(exit_code));
     return exit_code;
 }
 
 /*	 A_EXIT_0, A_EXIT_1	*/
 void
 do_exit(long long action,
         enum crmd_fsa_cause cause,
         enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     crm_exit_t exit_code = CRM_EX_OK;
 
     if (pcmk_is_set(action, A_EXIT_1)) {
         exit_code = CRM_EX_ERROR;
         crm_err("Exiting now due to errors");
     }
     verify_stopped(cur_state, LOG_ERR);
     crmd_exit(exit_code);
 }
 
 static void sigpipe_ignore(int nsig) { return; }
 
 /*	 A_STARTUP	*/
 void
 do_startup(long long action,
            enum crmd_fsa_cause cause,
            enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     crm_debug("Registering Signal Handlers");
     mainloop_add_signal(SIGTERM, crm_shutdown);
     mainloop_add_signal(SIGPIPE, sigpipe_ignore);
 
     config_read_trigger = mainloop_add_trigger(G_PRIORITY_HIGH,
                                                crm_read_options, NULL);
 
     controld_init_fsa_trigger();
     controld_init_transition_trigger();
 
     crm_debug("Creating CIB manager and executor objects");
     controld_globals.cib_conn = cib_new();
 
     lrm_state_init_local();
     if (controld_init_fsa_timers() == FALSE) {
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 }
 
 // \return libqb error code (0 on success, -errno on error)
 static int32_t
 accept_controller_client(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
 {
     crm_trace("Accepting new IPC client connection");
     if (pcmk__new_client(c, uid, gid) == NULL) {
         return -ENOMEM;
     }
     return 0;
 }
 
 // \return libqb error code (0 on success, -errno on error)
 static int32_t
 dispatch_controller_ipc(qb_ipcs_connection_t * c, void *data, size_t size)
 {
     uint32_t id = 0;
     uint32_t flags = 0;
     pcmk__client_t *client = pcmk__find_client(c);
 
     xmlNode *msg = pcmk__client_data2xml(client, data, &id, &flags);
 
     if (msg == NULL) {
         pcmk__ipc_send_ack(client, id, flags, PCMK__XE_ACK, NULL,
                            CRM_EX_PROTOCOL);
         return 0;
     }
     pcmk__ipc_send_ack(client, id, flags, PCMK__XE_ACK, NULL,
                        CRM_EX_INDETERMINATE);
 
     CRM_ASSERT(client->user != NULL);
     pcmk__update_acl_user(msg, PCMK__XA_CRM_USER, client->user);
 
     crm_xml_add(msg, PCMK__XA_CRM_SYS_FROM, client->id);
     if (controld_authorize_ipc_message(msg, client, NULL)) {
         crm_trace("Processing IPC message from client %s",
                   pcmk__client_name(client));
         route_message(C_IPC_MESSAGE, msg);
     }
 
     controld_trigger_fsa();
     free_xml(msg);
     return 0;
 }
 
 static int32_t
 ipc_client_disconnected(qb_ipcs_connection_t *c)
 {
     pcmk__client_t *client = pcmk__find_client(c);
 
     if (client) {
         crm_trace("Disconnecting %sregistered client %s (%p/%p)",
                   (client->userdata? "" : "un"), pcmk__client_name(client),
                   c, client);
         free(client->userdata);
         pcmk__free_client(client);
         controld_trigger_fsa();
     }
     return 0;
 }
 
 static void
 ipc_connection_destroyed(qb_ipcs_connection_t *c)
 {
     crm_trace("Connection %p", c);
     ipc_client_disconnected(c);
 }
 
 /*	 A_STOP	*/
 void
 do_stop(long long action,
         enum crmd_fsa_cause cause,
         enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     crm_trace("Closing IPC server");
     mainloop_del_ipc_server(ipcs); ipcs = NULL;
     register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
 }
 
 /*	 A_STARTED	*/
 void
 do_started(long long action,
            enum crmd_fsa_cause cause,
            enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     static struct qb_ipcs_service_handlers crmd_callbacks = {
         .connection_accept = accept_controller_client,
         .connection_created = NULL,
         .msg_process = dispatch_controller_ipc,
         .connection_closed = ipc_client_disconnected,
         .connection_destroyed = ipc_connection_destroyed
     };
 
     if (cur_state != S_STARTING) {
         crm_err("Start cancelled... %s", fsa_state2string(cur_state));
         return;
 
     } else if (!pcmk_is_set(controld_globals.fsa_input_register,
                             R_MEMBERSHIP)) {
         crm_info("Delaying start, no membership data (%.16llx)", R_MEMBERSHIP);
 
         crmd_fsa_stall(TRUE);
         return;
 
     } else if (!pcmk_is_set(controld_globals.fsa_input_register,
                             R_LRM_CONNECTED)) {
         crm_info("Delaying start, not connected to executor (%.16llx)", R_LRM_CONNECTED);
 
         crmd_fsa_stall(TRUE);
         return;
 
     } else if (!pcmk_is_set(controld_globals.fsa_input_register,
                             R_CIB_CONNECTED)) {
         crm_info("Delaying start, CIB not connected (%.16llx)", R_CIB_CONNECTED);
 
         crmd_fsa_stall(TRUE);
         return;
 
     } else if (!pcmk_is_set(controld_globals.fsa_input_register,
                             R_READ_CONFIG)) {
         crm_info("Delaying start, Config not read (%.16llx)", R_READ_CONFIG);
 
         crmd_fsa_stall(TRUE);
         return;
 
     } else if (!pcmk_is_set(controld_globals.fsa_input_register, R_PEER_DATA)) {
 
         crm_info("Delaying start, No peer data (%.16llx)", R_PEER_DATA);
         crmd_fsa_stall(TRUE);
         return;
     }
 
     crm_debug("Init server comms");
     ipcs = pcmk__serve_controld_ipc(&crmd_callbacks);
     if (ipcs == NULL) {
         crm_err("Failed to create IPC server: shutting down and inhibiting respawn");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     } else {
         crm_notice("Pacemaker controller successfully started and accepting connections");
     }
     controld_set_fsa_input_flags(R_ST_REQUIRED);
     controld_timer_fencer_connect(GINT_TO_POINTER(TRUE));
 
     controld_clear_fsa_input_flags(R_STARTING);
     register_fsa_input(msg_data->fsa_cause, I_PENDING, NULL);
 }
 
 /*	 A_RECOVER	*/
 void
 do_recover(long long action,
            enum crmd_fsa_cause cause,
            enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     controld_set_fsa_input_flags(R_IN_RECOVERY);
     crm_warn("Fast-tracking shutdown in response to errors");
 
     register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
 }
 
 static void
 config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     const char *value = NULL;
     GHashTable *config_hash = NULL;
     crm_time_t *now = crm_time_new(NULL);
     xmlNode *crmconfig = NULL;
     xmlNode *alerts = NULL;
 
     if (rc != pcmk_ok) {
         fsa_data_t *msg_data = NULL;
 
         crm_err("Local CIB query resulted in an error: %s", pcmk_strerror(rc));
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
 
         if (rc == -EACCES || rc == -pcmk_err_schema_validation) {
             crm_err("The cluster is mis-configured - shutting down and staying down");
             controld_set_fsa_input_flags(R_STAYDOWN);
         }
         goto bail;
     }
 
     crmconfig = output;
     if ((crmconfig != NULL) && !pcmk__xe_is(crmconfig, PCMK_XE_CRM_CONFIG)) {
         crmconfig = pcmk__xe_first_child(crmconfig, PCMK_XE_CRM_CONFIG, NULL,
                                          NULL);
     }
     if (!crmconfig) {
         fsa_data_t *msg_data = NULL;
 
         crm_err("Local CIB query for " PCMK_XE_CRM_CONFIG " section failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
         goto bail;
     }
 
     crm_debug("Call %d : Parsing CIB options", call_id);
     config_hash = pcmk__strkey_table(free, free);
     pe_unpack_nvpairs(crmconfig, crmconfig, PCMK_XE_CLUSTER_PROPERTY_SET, NULL,
                       config_hash, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, FALSE, now,
                       NULL);
 
     // Validate all options, and use defaults if not already present in hash
     pcmk__validate_cluster_options(config_hash);
 
     /* Validate the watchdog timeout in the context of the local node
      * environment. If invalid, the controller will exit with a fatal error.
      *
      * We do this via a wrapper in the controller, so that we call
      * pcmk__valid_stonith_watchdog_timeout() only if watchdog fencing is
      * enabled for the local node. Otherwise, we may exit unnecessarily.
      *
      * A validator function in libcrmcommon can't act as such a wrapper, because
      * it doesn't have a stonith API connection or the local node name.
      */
     value = g_hash_table_lookup(config_hash, PCMK_OPT_STONITH_WATCHDOG_TIMEOUT);
     controld_verify_stonith_watchdog_timeout(value);
 
     value = g_hash_table_lookup(config_hash, PCMK_OPT_NO_QUORUM_POLICY);
     if (pcmk__str_eq(value, PCMK_VALUE_FENCE_LEGACY, pcmk__str_casei)
         && (pcmk__locate_sbd() != 0)) {
         controld_set_global_flags(controld_no_quorum_suicide);
     }
 
     value = g_hash_table_lookup(config_hash, PCMK_OPT_SHUTDOWN_LOCK);
     if (crm_is_true(value)) {
         controld_set_global_flags(controld_shutdown_lock_enabled);
     } else {
         controld_clear_global_flags(controld_shutdown_lock_enabled);
     }
 
     value = g_hash_table_lookup(config_hash, PCMK_OPT_SHUTDOWN_LOCK_LIMIT);
     pcmk_parse_interval_spec(value, &controld_globals.shutdown_lock_limit);
     controld_globals.shutdown_lock_limit /= 1000;
 
     value = g_hash_table_lookup(config_hash, PCMK_OPT_NODE_PENDING_TIMEOUT);
     pcmk_parse_interval_spec(value, &controld_globals.node_pending_timeout);
     controld_globals.node_pending_timeout /= 1000;
 
     value = g_hash_table_lookup(config_hash, PCMK_OPT_CLUSTER_NAME);
     pcmk__str_update(&(controld_globals.cluster_name), value);
 
     // Let subcomponents initialize their own static variables
     controld_configure_election(config_hash);
     controld_configure_fencing(config_hash);
     controld_configure_fsa_timers(config_hash);
     controld_configure_throttle(config_hash);
 
     alerts = pcmk__xe_first_child(output, PCMK_XE_ALERTS, NULL, NULL);
     crmd_unpack_alerts(alerts);
 
     controld_set_fsa_input_flags(R_READ_CONFIG);
     controld_trigger_fsa();
 
     g_hash_table_destroy(config_hash);
   bail:
     crm_time_free(now);
 }
 
 /*!
  * \internal
  * \brief Trigger read and processing of the configuration
  *
  * \param[in] fn    Calling function name
  * \param[in] line  Line number where call occurred
  */
 void
 controld_trigger_config_as(const char *fn, int line)
 {
     if (config_read_trigger != NULL) {
         crm_trace("%s:%d - Triggered config processing", fn, line);
         mainloop_set_trigger(config_read_trigger);
     }
 }
 
 gboolean
 crm_read_options(gpointer user_data)
 {
     cib_t *cib_conn = controld_globals.cib_conn;
     int call_id = cib_conn->cmds->query(cib_conn,
                                         "//" PCMK_XE_CRM_CONFIG
                                         " | //" PCMK_XE_ALERTS,
                                         NULL, cib_xpath|cib_scope_local);
 
     fsa_register_cib_callback(call_id, NULL, config_query_callback);
     crm_trace("Querying the CIB... call %d", call_id);
     return TRUE;
 }
 
 /*	 A_READCONFIG	*/
 void
 do_read_config(long long action,
                enum crmd_fsa_cause cause,
                enum crmd_fsa_state cur_state,
                enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     throttle_init();
     controld_trigger_config();
 }
 
 static void
 crm_shutdown(int nsig)
 {
     const char *value = NULL;
     guint default_period_ms = 0;
 
     if ((controld_globals.mainloop == NULL)
         || !g_main_loop_is_running(controld_globals.mainloop)) {
         crmd_exit(CRM_EX_OK);
         return;
     }
 
     if (pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) {
         crm_err("Escalating shutdown");
         register_fsa_input_before(C_SHUTDOWN, I_ERROR, NULL);
         return;
     }
 
     controld_set_fsa_input_flags(R_SHUTDOWN);
     register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL);
 
     /* If shutdown timer doesn't have a period set, use the default
      *
      * @TODO: Evaluate whether this is still necessary. As long as
      * config_query_callback() has been run at least once, it doesn't look like
      * anything could have changed the timer period since then.
      */
     value = pcmk__cluster_option(NULL, PCMK_OPT_SHUTDOWN_ESCALATION);
     pcmk_parse_interval_spec(value, &default_period_ms);
     controld_shutdown_start_countdown(default_period_ms);
 }
diff --git a/include/crm/cluster.h b/include/crm/cluster.h
index 1962e6a455..117070225d 100644
--- a/include/crm/cluster.h
+++ b/include/crm/cluster.h
@@ -1,241 +1,242 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_CLUSTER__H
 #  define PCMK__CRM_CLUSTER__H
 
 #  include <stdint.h>           // uint32_t, uint64_t
 #  include <glib.h>             // gboolean, GHashTable
 #  include <libxml/tree.h>      // xmlNode
 #  include <crm/common/xml.h>
 #  include <crm/common/util.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 #  if SUPPORT_COROSYNC
 #    include <corosync/cpg.h>
 #  endif
 
 extern gboolean crm_have_quorum;
 extern GHashTable *crm_peer_cache;
 extern GHashTable *crm_remote_peer_cache;
 extern unsigned long long crm_peer_seq;
 
 #define CRM_NODE_LOST      "lost"
 #define CRM_NODE_MEMBER    "member"
 
 enum crm_join_phase {
     /* @COMPAT: crm_join_nack_quiet can be replaced by crm_node_t:user_data
      *          at a compatibility break.
      */
     //! Not allowed to join, but don't send a nack message
     crm_join_nack_quiet = -2,
 
     crm_join_nack       = -1,
     crm_join_none       = 0,
     crm_join_welcomed   = 1,
     crm_join_integrated = 2,
     crm_join_finalized  = 3,
     crm_join_confirmed  = 4,
 };
 
 enum crm_node_flags {
     /* node is not a cluster node and should not be considered for cluster membership */
     crm_remote_node          = 0x0001,
 
     /* node's cache entry is dirty */
     crm_node_dirty           = 0x0010,
 };
 
 typedef struct crm_peer_node_s {
     char *uname;                // Node name as known to cluster
 
     /* @COMPAT This is less than ideal since the value is not a valid XML ID
      * (for Corosync, it's the string equivalent of the node's numeric node ID,
      * but XML IDs can't start with a number) and the three elements should have
      * different IDs.
      *
      * Ideally, we would use something like node-NODEID, node_state-NODEID, and
      * transient_attributes-NODEID as the element IDs. Unfortunately changing it
      * would be impractical due to backward compatibility; older nodes in a
      * rolling upgrade will always write and expect the value in the old format.
      *
      * This is also named poorly, since the value is not a UUID, but at least
      * that can be changed at an API compatibility break.
      */
     /*! Value of the PCMK_XA_ID XML attribute to use with the node's
      * PCMK_XE_NODE, PCMK_XE_NODE_STATE, and PCMK_XE_TRANSIENT_ATTRIBUTES
      * XML elements in the CIB
      */
     char *uuid;
 
     char *state;                // @TODO change to enum
     uint64_t flags;             // Bitmask of crm_node_flags
     uint64_t last_seen;         // Only needed by cluster nodes
     uint32_t processes;         // @TODO most not needed, merge into flags
 
     /* @TODO When we can break public API compatibility, we can make the rest of
      * these members separate structs and use void *cluster_data and
      * void *user_data here instead, to abstract the cluster layer further.
      */
 
     // Currently only needed by corosync stack
     uint32_t id;                // Node ID
     time_t when_lost;           // When CPG membership was last lost
 
     // Only used by controller
     enum crm_join_phase join;
     char *expected;
 
     time_t peer_lost;
     char *conn_host;
 
     time_t when_member;         // Since when node has been a cluster member
     time_t when_online;         // Since when peer has been online in CPG
 } crm_node_t;
 
 void crm_peer_init(void);
 void crm_peer_destroy(void);
 
 typedef struct crm_cluster_s {
     char *uuid;
     char *uname;
     uint32_t nodeid;
 
     void (*destroy) (gpointer);
 
 #  if SUPPORT_COROSYNC
     /* @TODO When we can break public API compatibility, make these members a
      * separate struct and use void *cluster_data here instead, to abstract the
      * cluster layer further.
      */
     struct cpg_name group;
     cpg_callbacks_t cpg;
     cpg_handle_t cpg_handle;
 #  endif
 
 } crm_cluster_t;
 
 int pcmk_cluster_connect(crm_cluster_t *cluster);
+int pcmk_cluster_disconnect(crm_cluster_t *cluster);
 void crm_cluster_disconnect(crm_cluster_t *cluster);
 
 crm_cluster_t *pcmk_cluster_new(void);
 void pcmk_cluster_free(crm_cluster_t *cluster);
 
 enum crm_ais_msg_class {
     crm_class_cluster = 0,
 };
 
 enum crm_ais_msg_types {
     crm_msg_none     = 0,
     crm_msg_ais      = 1,
     crm_msg_lrmd     = 2,
     crm_msg_cib      = 3,
     crm_msg_crmd     = 4,
     crm_msg_attrd    = 5,
     crm_msg_stonithd = 6,
     crm_msg_te       = 7,
     crm_msg_pe       = 8,
     crm_msg_stonith_ng = 9,
 };
 
 gboolean send_cluster_message(const crm_node_t *node,
                               enum crm_ais_msg_types service,
                               const xmlNode *data, gboolean ordered);
 
 int crm_remote_peer_cache_size(void);
 
 /* Initialize and refresh the remote peer cache from a cib config */
 void crm_remote_peer_cache_refresh(xmlNode *cib);
 crm_node_t *crm_remote_peer_get(const char *node_name);
 void crm_remote_peer_cache_remove(const char *node_name);
 
 guint crm_active_peers(void);
 gboolean crm_is_peer_active(const crm_node_t * node);
 guint reap_crm_member(uint32_t id, const char *name);
 
 #  if SUPPORT_COROSYNC
 uint32_t get_local_nodeid(cpg_handle_t handle);
 
 void pcmk_cpg_membership(cpg_handle_t handle,
                          const struct cpg_name *groupName,
                          const struct cpg_address *member_list, size_t member_list_entries,
                          const struct cpg_address *left_list, size_t left_list_entries,
                          const struct cpg_address *joined_list, size_t joined_list_entries);
 gboolean crm_is_corosync_peer_active(const crm_node_t * node);
 gboolean send_cluster_text(enum crm_ais_msg_class msg_class, const char *data,
                            gboolean local, const crm_node_t *node,
                            enum crm_ais_msg_types dest);
 char *pcmk_message_common_cs(cpg_handle_t handle, uint32_t nodeid, uint32_t pid, void *msg,
                         uint32_t *kind, const char **from);
 #  endif
 
 const char *crm_peer_uuid(crm_node_t *node);
 const char *crm_peer_uname(const char *uuid);
 
 enum crm_status_type {
     crm_status_uname,
     crm_status_nstate,
     crm_status_processes,
 };
 
 enum crm_ais_msg_types text2msg_type(const char *text);
 void crm_set_status_callback(void (*dispatch) (enum crm_status_type, crm_node_t *, const void *));
 void crm_set_autoreap(gboolean autoreap);
 
 enum cluster_type_e {
     pcmk_cluster_unknown     = 0x0001,
     pcmk_cluster_invalid     = 0x0002,
     // 0x0004 was heartbeat
     // 0x0010 was corosync 1 with plugin
     pcmk_cluster_corosync    = 0x0020,
     // 0x0040 was corosync 1 with CMAN
 };
 
 enum cluster_type_e get_cluster_type(void);
 const char *name_for_cluster_type(enum cluster_type_e type);
 
 gboolean is_corosync_cluster(void);
 
 const char *get_local_node_name(void);
 char *get_node_name(uint32_t nodeid);
 
 /*!
  * \brief Get log-friendly string equivalent of a join phase
  *
  * \param[in] phase  Join phase
  *
  * \return Log-friendly string equivalent of \p phase
  */
 static inline const char *
 crm_join_phase_str(enum crm_join_phase phase)
 {
     switch (phase) {
         case crm_join_nack_quiet:   return "nack_quiet";
         case crm_join_nack:         return "nack";
         case crm_join_none:         return "none";
         case crm_join_welcomed:     return "welcomed";
         case crm_join_integrated:   return "integrated";
         case crm_join_finalized:    return "finalized";
         case crm_join_confirmed:    return "confirmed";
         default:                    return "invalid";
     }
 }
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/cluster/compat.h>
 #endif
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/cluster/cluster.c b/lib/cluster/cluster.c
index a355c38244..5e1407bab5 100644
--- a/lib/cluster/cluster.c
+++ b/lib/cluster/cluster.c
@@ -1,415 +1,435 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <dlfcn.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <time.h>
 #include <sys/param.h>
 #include <sys/types.h>
 
 #include <crm/crm.h>
 
 #include <crm/common/ipc.h>
 #include <crm/common/xml.h>
 #include <crm/cluster/internal.h>
 #include "crmcluster_private.h"
 
 CRM_TRACE_INIT_DATA(cluster);
 
 /*!
  * \brief Get (and set if needed) a node's UUID
  *
  * \param[in,out] peer  Node to check
  *
  * \return Node UUID of \p peer, or NULL if unknown
  */
 const char *
 crm_peer_uuid(crm_node_t *peer)
 {
     char *uuid = NULL;
 
     // Check simple cases first, to avoid any calls that might block
     if (peer == NULL) {
         return NULL;
     }
     if (peer->uuid != NULL) {
         return peer->uuid;
     }
 
     switch (get_cluster_type()) {
         case pcmk_cluster_corosync:
 #if SUPPORT_COROSYNC
             uuid = pcmk__corosync_uuid(peer);
 #endif
             break;
 
         case pcmk_cluster_unknown:
         case pcmk_cluster_invalid:
             crm_err("Unsupported cluster type");
             break;
     }
 
     peer->uuid = uuid;
     return peer->uuid;
 }
 
 /*!
  * \internal
  * \brief Connect to the cluster layer
  *
  * \param[in,out] cluster  Initialized cluster object to connect
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk_cluster_connect(crm_cluster_t *cluster)
 {
     enum cluster_type_e type = get_cluster_type();
 
     crm_notice("Connecting to %s cluster infrastructure",
                name_for_cluster_type(type));
 
     switch (type) {
         case pcmk_cluster_corosync:
 #if SUPPORT_COROSYNC
             crm_peer_init();
             return pcmk__corosync_connect(cluster);
 #else
             break;
 #endif // SUPPORT_COROSYNC
         default:
             break;
     }
 
     crm_err("Failed to connect to unsupported cluster type %s",
             name_for_cluster_type(type));
     return EPROTONOSUPPORT;
 }
 
 /*!
  * \brief Disconnect from the cluster layer
  *
  * \param[in,out] cluster  Cluster object to disconnect
+ *
+ * \return Standard Pacemaker return code
  */
-void
-crm_cluster_disconnect(crm_cluster_t *cluster)
+int
+pcmk_cluster_disconnect(crm_cluster_t *cluster)
 {
     enum cluster_type_e type = get_cluster_type();
 
     crm_info("Disconnecting from %s cluster infrastructure",
              name_for_cluster_type(type));
+
     switch (type) {
         case pcmk_cluster_corosync:
 #if SUPPORT_COROSYNC
             crm_peer_destroy();
             pcmk__corosync_disconnect(cluster);
-#endif // SUPPORT_COROSYNC
+            return pcmk_rc_ok;
+#else
             break;
+#endif // SUPPORT_COROSYNC
         default:
             break;
     }
+
+    crm_err("Failed to disconnect from unsupported cluster type %s",
+            name_for_cluster_type(type));
+    return EPROTONOSUPPORT;
+}
+
+/*!
+ * \brief Disconnect from the cluster layer
+ *
+ * \param[in,out] cluster  Cluster object to disconnect
+ */
+void
+crm_cluster_disconnect(crm_cluster_t *cluster)
+{
+    pcmk_cluster_disconnect(cluster);
 }
 
 /*!
  * \brief Allocate a new \p crm_cluster_t object
  *
  * \return A newly allocated \p crm_cluster_t object (guaranteed not \c NULL)
  * \note The caller is responsible for freeing the return value using
  *       \p pcmk_cluster_free().
  */
 crm_cluster_t *
 pcmk_cluster_new(void)
 {
     return (crm_cluster_t *) pcmk__assert_alloc(1, sizeof(crm_cluster_t));
 }
 
 /*!
  * \brief Free a \p crm_cluster_t object and its dynamically allocated members
  *
  * \param[in,out] cluster  Cluster object to free
  */
 void
 pcmk_cluster_free(crm_cluster_t *cluster)
 {
     if (cluster == NULL) {
         return;
     }
     free(cluster->uuid);
     free(cluster->uname);
     free(cluster);
 }
 
 /*!
  * \brief Send an XML message via the cluster messaging layer
  *
  * \param[in] node     Cluster node to send message to
  * \param[in] service  Message type to use in message host info
  * \param[in] data     XML message to send
  * \param[in] ordered  Ignored for currently supported messaging layers
  *
  * \return TRUE on success, otherwise FALSE
  */
 gboolean
 send_cluster_message(const crm_node_t *node, enum crm_ais_msg_types service,
                      const xmlNode *data, gboolean ordered)
 {
     switch (get_cluster_type()) {
         case pcmk_cluster_corosync:
 #if SUPPORT_COROSYNC
             return pcmk__cpg_send_xml(data, node, service);
 #endif
             break;
         default:
             break;
     }
     return FALSE;
 }
 
 /*!
  * \brief Get the local node's name
  *
  * \return Local node's name
  * \note This will fatally exit if local node name cannot be known.
  */
 const char *
 get_local_node_name(void)
 {
     static char *name = NULL;
 
     if (name == NULL) {
         name = get_node_name(0);
     }
     return name;
 }
 
 /*!
  * \brief Get the node name corresponding to a cluster node ID
  *
  * \param[in] nodeid  Node ID to check (or 0 for local node)
  *
  * \return Node name corresponding to \p nodeid
  * \note This will fatally exit if \p nodeid is 0 and local node name cannot be
  *       known.
  */
 char *
 get_node_name(uint32_t nodeid)
 {
     char *name = NULL;
     enum cluster_type_e stack = get_cluster_type();
 
     switch (stack) {
         case pcmk_cluster_corosync:
 #if SUPPORT_COROSYNC
             name = pcmk__corosync_name(0, nodeid);
             break;
 #endif // SUPPORT_COROSYNC
 
         default:
             crm_err("Unknown cluster type: %s (%d)", name_for_cluster_type(stack), stack);
     }
 
     if ((name == NULL) && (nodeid == 0)) {
         name = pcmk_hostname();
         if (name == NULL) {
             // @TODO Maybe let the caller decide what to do
             crm_err("Could not obtain the local %s node name",
                     name_for_cluster_type(stack));
             crm_exit(CRM_EX_FATAL);
         }
         crm_notice("Defaulting to uname -n for the local %s node name",
                    name_for_cluster_type(stack));
     }
 
     if (name == NULL) {
         crm_notice("Could not obtain a node name for %s node with "
                    PCMK_XA_ID " %u", name_for_cluster_type(stack), nodeid);
     }
     return name;
 }
 
 /*!
  * \brief Get the node name corresponding to a node UUID
  *
  * \param[in] uuid  UUID of desired node
  *
  * \return name of desired node
  *
  * \note This relies on the remote peer cache being populated with all
  *       remote nodes in the cluster, so callers should maintain that cache.
  */
 const char *
 crm_peer_uname(const char *uuid)
 {
     GHashTableIter iter;
     crm_node_t *node = NULL;
 
     CRM_CHECK(uuid != NULL, return NULL);
 
     /* remote nodes have the same uname and uuid */
     if (g_hash_table_lookup(crm_remote_peer_cache, uuid)) {
         return uuid;
     }
 
     /* avoid blocking calls where possible */
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
         if (pcmk__str_eq(node->uuid, uuid, pcmk__str_casei)) {
             if (node->uname != NULL) {
                 return node->uname;
             }
             break;
         }
     }
     node = NULL;
 
     if (is_corosync_cluster()) {
         long long id;
 
         if ((pcmk__scan_ll(uuid, &id, 0LL) != pcmk_rc_ok)
             || (id < 1LL) || (id > UINT32_MAX))  {
             crm_err("Invalid Corosync node ID '%s'", uuid);
             return NULL;
         }
 
         node = pcmk__search_node_caches((uint32_t) id, NULL,
                                         pcmk__node_search_cluster);
         if (node != NULL) {
             crm_info("Setting uuid for node %s[%u] to %s",
                      node->uname, node->id, uuid);
             node->uuid = strdup(uuid);
             return node->uname;
         }
         return NULL;
     }
 
     return NULL;
 }
 
 /*!
  * \brief  Get a log-friendly string equivalent of a cluster type
  *
  * \param[in] type  Cluster type
  *
  * \return Log-friendly string corresponding to \p type
  */
 const char *
 name_for_cluster_type(enum cluster_type_e type)
 {
     switch (type) {
         case pcmk_cluster_corosync:
             return "corosync";
         case pcmk_cluster_unknown:
             return "unknown";
         case pcmk_cluster_invalid:
             return "invalid";
     }
     crm_err("Invalid cluster type: %d", type);
     return "invalid";
 }
 
 /*!
  * \brief Get (and validate) the local cluster type
  *
  * \return Local cluster type
  * \note This will fatally exit if the local cluster type is invalid.
  */
 enum cluster_type_e
 get_cluster_type(void)
 {
     bool detected = false;
     const char *cluster = NULL;
     static enum cluster_type_e cluster_type = pcmk_cluster_unknown;
 
     /* Return the previous calculation, if any */
     if (cluster_type != pcmk_cluster_unknown) {
         return cluster_type;
     }
 
     cluster = pcmk__env_option(PCMK__ENV_CLUSTER_TYPE);
 
 #if SUPPORT_COROSYNC
     /* If nothing is defined in the environment, try corosync (if supported) */
     if (cluster == NULL) {
         crm_debug("Testing with Corosync");
         cluster_type = pcmk__corosync_detect();
         if (cluster_type != pcmk_cluster_unknown) {
             detected = true;
             goto done;
         }
     }
 #endif
 
     /* Something was defined in the environment, test it against what we support */
     crm_info("Verifying cluster type: '%s'",
              ((cluster == NULL)? "-unspecified-" : cluster));
     if (cluster == NULL) {
 
 #if SUPPORT_COROSYNC
     } else if (pcmk__str_eq(cluster, "corosync", pcmk__str_casei)) {
         cluster_type = pcmk_cluster_corosync;
 #endif
 
     } else {
         cluster_type = pcmk_cluster_invalid;
         goto done; /* Keep the compiler happy when no stacks are supported */
     }
 
   done:
     if (cluster_type == pcmk_cluster_unknown) {
         crm_notice("Could not determine the current cluster type");
 
     } else if (cluster_type == pcmk_cluster_invalid) {
         crm_notice("This installation does not support the '%s' cluster infrastructure: terminating.",
                    cluster);
         crm_exit(CRM_EX_FATAL);
 
     } else {
         crm_info("%s an active '%s' cluster",
                  (detected? "Detected" : "Assuming"),
                  name_for_cluster_type(cluster_type));
     }
 
     return cluster_type;
 }
 
 /*!
  * \brief Check whether the local cluster is a Corosync cluster
  *
  * \return TRUE if the local cluster is a Corosync cluster, otherwise FALSE
  */
 gboolean
 is_corosync_cluster(void)
 {
     return get_cluster_type() == pcmk_cluster_corosync;
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/cluster/compat.h>
 
 void
 set_uuid(xmlNode *xml, const char *attr, crm_node_t *node)
 {
     crm_xml_add(xml, attr, crm_peer_uuid(node));
 }
 
 gboolean
 crm_cluster_connect(crm_cluster_t *cluster)
 {
     return pcmk_cluster_connect(cluster) == pcmk_rc_ok;
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API