summaryrefslogtreecommitdiffstats
path: root/daemons/based
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/based')
-rw-r--r--daemons/based/Makefile.am47
-rw-r--r--daemons/based/based_callbacks.c1696
-rw-r--r--daemons/based/based_common.c352
-rw-r--r--daemons/based/based_io.c473
-rw-r--r--daemons/based/based_messages.c427
-rw-r--r--daemons/based/based_notify.c305
-rw-r--r--daemons/based/based_remote.c680
-rw-r--r--daemons/based/cib.pam6
-rw-r--r--daemons/based/pacemaker-based.c442
-rw-r--r--daemons/based/pacemaker-based.h150
10 files changed, 4578 insertions, 0 deletions
diff --git a/daemons/based/Makefile.am b/daemons/based/Makefile.am
new file mode 100644
index 0000000..053d93c
--- /dev/null
+++ b/daemons/based/Makefile.am
@@ -0,0 +1,47 @@
+#
+# Copyright 2004-2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+include $(top_srcdir)/mk/common.mk
+
+EXTRA_DIST = cib.pam
+
+halibdir = $(CRM_DAEMON_DIR)
+
+COMMONLIBS = $(top_builddir)/lib/common/libcrmcommon.la \
+ $(top_builddir)/lib/cib/libcib.la
+
+halib_PROGRAMS = pacemaker-based
+
+noinst_HEADERS = pacemaker-based.h
+
+pacemaker_based_CFLAGS = $(CFLAGS_HARDENED_EXE)
+pacemaker_based_LDFLAGS = $(LDFLAGS_HARDENED_EXE)
+
+pacemaker_based_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \
+ $(COMMONLIBS) $(CLUSTERLIBS)
+
+pacemaker_based_SOURCES = pacemaker-based.c \
+ based_callbacks.c \
+ based_common.c \
+ based_io.c \
+ based_messages.c \
+ based_notify.c \
+ based_remote.c
+
+clean-generic:
+ rm -f *.log *.debug *.xml *~
+
+if BUILD_LEGACY_LINKS
+install-exec-hook:
+ $(MKDIR_P) -- $(DESTDIR)$(CRM_DAEMON_DIR)
+ cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib && $(LN_S) pacemaker-based cib
+
+uninstall-hook:
+ cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib
+endif
diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c
new file mode 100644
index 0000000..3726caa
--- /dev/null
+++ b/daemons/based/based_callbacks.c
@@ -0,0 +1,1696 @@
+/*
+ * Copyright 2004-2023 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 <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/msg_xml.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
+#define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->uname)
+
+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 void cib_process_request(xmlNode *request, gboolean privileged,
+ const pcmk__client_t *cib_client);
+
+static int cib_process_command(xmlNode *request, xmlNode **reply,
+ xmlNode **cib_diff, gboolean 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 -EPERM;
+ }
+
+ if (pcmk__new_client(c, uid, gid) == NULL) {
+ return -EIO;
+ }
+ 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
+};
+
+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, F_CIB_OPERATION);
+
+ if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) {
+ if (flags & crm_ipc_client_response) {
+ xmlNode *ack = create_xml_node(NULL, __func__);
+
+ crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER);
+ crm_xml_add(ack, F_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, T_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, F_CIB_NOTIFY_TYPE);
+
+ crm_element_value_int(op_request, F_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, T_CIB_POST_NOTIFY, pcmk__str_casei)) {
+ bit = cib_notify_post;
+
+ } else if (pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) {
+ bit = cib_notify_pre;
+
+ } else if (pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) {
+ bit = cib_notify_confirm;
+
+ } else if (pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) {
+ bit = cib_notify_diff;
+
+ } else if (pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) {
+ bit = cib_notify_replace;
+
+ } 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, "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, F_CIB_CALLOPTS, &call_options);
+ }
+
+ if (op_request == NULL) {
+ crm_trace("Invalid message from %p", c);
+ pcmk__ipc_send_ack(cib_client, id, flags, "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, F_CIB_CLIENTNAME);
+
+ if (value == NULL) {
+ cib_client->name = pcmk__itoa(cib_client->pid);
+ } else {
+ cib_client->name = strdup(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("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, F_CIB_CLIENTID, cib_client->id);
+ crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name);
+
+ CRM_LOG_ASSERT(cib_client->user != NULL);
+ pcmk__update_acl_user(op_request, F_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 = create_xml_node(NULL, "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, F_TYPE, "cib");
+ crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING);
+ crm_xml_add(ping, F_CIB_PING_ID, buffer);
+
+ crm_xml_add(ping, XML_ATTR_CRM_VERSION, 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, F_ORIG);
+
+ xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA);
+ const char *seq_s = crm_element_value(pong, F_CIB_PING_ID);
+ const char *digest = crm_element_value(pong, XML_ATTR_DIGEST);
+
+ if (seq_s == NULL) {
+ crm_debug("Ignoring ping reply with no " F_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, XML_ATTR_CRM_VERSION);
+
+ 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 *remote_cib = get_message_xml(pong, F_CIB_CALLDATA);
+
+ crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p",
+ crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(the_cib, XML_ATTR_GENERATION),
+ crm_element_value(the_cib, XML_ATTR_NUMUPDATES),
+ ping_digest, host,
+ remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_",
+ remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_",
+ remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_",
+ digest, remote_cib);
+
+ if(remote_cib && remote_cib->children) {
+ // Additional debug
+ xml_calculate_changes(the_cib, remote_cib);
+
+ pcmk__output_set_log_level(logger_out, LOG_INFO);
+ pcmk__xml_show_changes(logger_out, remote_cib);
+ crm_trace("End of differences");
+ }
+
+ free_xml(remote_cib);
+ sync_our_cib(reply, FALSE);
+ }
+ }
+}
+
+static void
+do_local_notify(xmlNode * notify_src, const char *client_id,
+ gboolean sync_reply, gboolean 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, F_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);
+ }
+}
+
+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)
+{
+ 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 = calloc(1, sizeof(cib_local_notify_t));
+
+ notify->notify_src = notify_src;
+ notify->client_id = strdup(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, int call_type,
+ int call_options, const char *host, const char *op,
+ gboolean *local_notify, gboolean *needs_reply,
+ gboolean *process, gboolean *needs_forward)
+{
+ if (cib_op_modifies(call_type)
+ && !(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, int call_type,
+ int call_options, const char *host, const char *op,
+ gboolean *local_notify, gboolean *needs_reply,
+ gboolean *process, gboolean *needs_forward)
+{
+ if (cib_op_modifies(call_type)) {
+ if (pcmk__str_any_of(op, PCMK__CIB_REQUEST_PRIMARY,
+ PCMK__CIB_REQUEST_SECONDARY, NULL)) {
+ /* Always handle these locally */
+ *process = TRUE;
+ *needs_reply = FALSE;
+ *local_notify = TRUE;
+ *needs_forward = FALSE;
+ return;
+
+ } else {
+ /* Redirect all other updates via CPG */
+ *needs_reply = TRUE;
+ *needs_forward = TRUE;
+ *process = FALSE;
+ 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"));
+ return;
+ }
+ }
+
+
+ *process = TRUE;
+ *needs_reply = FALSE;
+ *local_notify = TRUE;
+ *needs_forward = FALSE;
+
+ if (stand_alone) {
+ crm_trace("Processing %s op from client %s (stand-alone)",
+ op, pcmk__client_name(cib_client));
+
+ } else if (host == NULL) {
+ crm_trace("Processing unaddressed %s op from client %s",
+ op, pcmk__client_name(cib_client));
+
+ } 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));
+
+ } else {
+ crm_trace("%s op from %s needs to be forwarded to client %s",
+ op, pcmk__client_name(cib_client), host);
+ *needs_forward = TRUE;
+ *process = FALSE;
+ }
+}
+
+static void
+parse_local_options(const pcmk__client_t *cib_client, int call_type,
+ 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, call_type, call_options, host,
+ op, local_notify, needs_reply, process, needs_forward);
+ } else {
+ parse_local_options_v2(cib_client, call_type, call_options, host,
+ op, local_notify, needs_reply, process, needs_forward);
+ }
+}
+
+static gboolean
+parse_peer_options_v1(int call_type, xmlNode * request,
+ gboolean * local_notify, gboolean * needs_reply, gboolean * process,
+ gboolean * needs_forward)
+{
+ const char *op = NULL;
+ const char *host = NULL;
+ const char *delegated = NULL;
+ const char *originator = crm_element_value(request, F_ORIG);
+ const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
+
+ gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
+
+ if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_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, F_CIB_OPERATION);
+ crm_trace("Processing %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, F_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, F_CIB_DELEGATED);
+ 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(int call_type, xmlNode * request,
+ gboolean * local_notify, gboolean * needs_reply, gboolean * process,
+ gboolean * needs_forward)
+{
+ const char *host = NULL;
+ const char *delegated = crm_element_value(request, F_CIB_DELEGATED);
+ const char *op = crm_element_value(request, F_CIB_OPERATION);
+ const char *originator = crm_element_value(request, F_ORIG);
+ const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
+
+ gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
+
+ if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) {
+ /* sync_our_cib() sets F_CIB_ISREPLY */
+ 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 F_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 F_CIB_SCHEMA_MAX will be set which puts a
+ * limit on how far newer nodes will go
+ */
+ const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX);
+ const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC);
+
+ crm_trace("Parsing %s operation%s for %s with max=%s and upgrade_rc=%s",
+ op, (is_reply? " reply" : ""),
+ (based_is_primary? "primary" : "secondary"),
+ (max? max : "none"), (upgrade_rc? upgrade_rc : "none"));
+
+ if (upgrade_rc != NULL) {
+ // Our upgrade request was rejected by DC, notify clients of result
+ crm_xml_add(request, F_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 DC
+ return FALSE;
+ }
+
+ } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_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 && cib_op_modifies(call_type)) {
+ 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)) {
+ /* Legacy handling */
+ crm_debug("Legacy handling of %s message from %s", op, originator);
+ *local_notify = FALSE;
+ if (reply_to == NULL) {
+ *process = TRUE;
+ }
+ return *process;
+ }
+
+ if(is_reply) {
+ crm_trace("Handling %s reply sent from %s to local clients", 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, F_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) {
+ /* this is for a specific instance and we're not it */
+ crm_trace("Ignoring %s operation for instance 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 sent to everyone by %s/%s on %s %s", op,
+ crm_element_value(request, F_CIB_CLIENTNAME),
+ crm_element_value(request, F_CIB_CALLID),
+ originator, (*local_notify)?"(notify)":"");
+ return TRUE;
+}
+
+static gboolean
+parse_peer_options(int call_type, xmlNode * request,
+ gboolean * local_notify, gboolean * needs_reply, gboolean * process,
+ gboolean * needs_forward)
+{
+ /* 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(
+ call_type, request, local_notify, needs_reply, process, needs_forward);
+ } else {
+ return parse_peer_options_v2(
+ call_type, request, local_notify, needs_reply, process, needs_forward);
+ }
+}
+
+static void
+forward_request(xmlNode *request, int call_options)
+{
+ const char *op = crm_element_value(request, F_CIB_OPERATION);
+ const char *host = crm_element_value(request, F_CIB_HOST);
+
+ crm_xml_add(request, F_CIB_DELEGATED, OUR_NODENAME);
+
+ if (host != NULL) {
+ crm_trace("Forwarding %s op to %s", op, host);
+ send_cluster_message(crm_get_peer(0, host), crm_msg_cib, request, FALSE);
+
+ } else {
+ crm_trace("Forwarding %s op to primary instance", op);
+ send_cluster_message(NULL, crm_msg_cib, request, FALSE);
+ }
+
+ /* Return the request to its original state */
+ xml_remove_prop(request, F_CIB_DELEGATED);
+
+ if (call_options & cib_discard_reply) {
+ crm_trace("Client not interested in reply");
+ }
+}
+
+static gboolean
+send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast)
+{
+ CRM_ASSERT(msg != NULL);
+
+ if (broadcast) {
+ /* this (successful) call modified the CIB _and_ the
+ * change needs to be broadcast...
+ * send via HA to other 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;
+
+ CRM_LOG_ASSERT(result_diff != NULL);
+ digest = crm_element_value(result_diff, XML_ATTR_DIGEST);
+ crm_element_value_int(result_diff, "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, F_CIB_ISREPLY, originator);
+ pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true);
+ crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH);
+ crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER);
+
+ if (format == 1) {
+ CRM_ASSERT(digest != NULL);
+ }
+
+ add_message_xml(msg, F_CIB_UPDATE_DIFF, 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, F_CIB_ISREPLY, originator);
+ return send_cluster_message(crm_get_peer(0, originator), 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)
+ */
+static void
+cib_process_request(xmlNode *request, gboolean privileged,
+ const pcmk__client_t *cib_client)
+{
+ int call_type = 0;
+ 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, F_CIB_OPERATION);
+ const char *originator = crm_element_value(request, F_ORIG);
+ const char *host = crm_element_value(request, F_CIB_HOST);
+ const char *target = NULL;
+ const char *call_id = crm_element_value(request, F_CIB_CALLID);
+ const char *client_id = crm_element_value(request, F_CIB_CLIENTID);
+ const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
+ const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
+
+ crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
+
+ if ((host != NULL) && (*host == '\0')) {
+ host = NULL;
+ }
+
+ 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, F_ORIG, OUR_NODENAME);
+ crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target);
+ }
+
+ rc = cib_get_operation_id(op, &call_type);
+ if (rc != pcmk_ok) {
+ /* TODO: construct error reply? */
+ crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc));
+ return;
+ }
+
+ if (cib_client != NULL) {
+ parse_local_options(cib_client, call_type, call_options, host, op,
+ &local_notify, &needs_reply, &process, &needs_forward);
+
+ } else if (parse_peer_options(call_type, request, &local_notify,
+ &needs_reply, &process, &needs_forward) == FALSE) {
+ return;
+ }
+
+ is_update = cib_op_modifies(call_type);
+
+ if (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;
+ }
+
+ if (needs_forward) {
+ const char *section = crm_element_value(request, F_CIB_SECTION);
+ 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)",
+ op,
+ section ? section : "'all'",
+ pcmk__s(host, (cib_legacy_mode() ? "primary" : "all")),
+ originator ? originator : "local",
+ client_name, call_id);
+
+ forward_request(request, call_options);
+ return;
+ }
+
+ if (cib_status != pcmk_ok) {
+ const char *call = crm_element_value(request, F_CIB_CALLID);
+
+ rc = cib_status;
+ crm_err("Operation ignored, cluster configuration is invalid."
+ " Please repair and restart: %s", pcmk_strerror(cib_status));
+
+ op_reply = create_xml_node(NULL, "cib-reply");
+ crm_xml_add(op_reply, F_TYPE, T_CIB);
+ crm_xml_add(op_reply, F_CIB_OPERATION, op);
+ crm_xml_add(op_reply, F_CIB_CALLID, call);
+ crm_xml_add(op_reply, F_CIB_CLIENTID, client_id);
+ crm_xml_add_int(op_reply, F_CIB_CALLOPTS, call_options);
+ crm_xml_add_int(op_reply, F_CIB_RC, rc);
+
+ crm_trace("Attaching reply output");
+ add_message_xml(op_reply, F_CIB_CALLDATA, the_cib);
+
+ crm_log_xml_explicit(op_reply, "cib:reply");
+
+ } else if (process) {
+ time_t finished = 0;
+ time_t now = time(NULL);
+ int level = LOG_INFO;
+ const char *section = crm_element_value(request, F_CIB_SECTION);
+
+ rc = cib_process_command(request, &op_reply, &result_diff, privileged);
+
+ if (!is_update) {
+ level = LOG_TRACE;
+
+ } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_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;
+ }
+
+ 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,
+ the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0",
+ the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0",
+ the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "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, F_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 (call_options & cib_discard_reply) {
+ crm_trace("Caller isn't interested in reply");
+
+ } else if (cib_client == NULL) {
+ 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;
+}
+
+static char *
+calculate_section_digest(const char *xpath, xmlNode * xml_obj)
+{
+ xmlNode *xml_section = NULL;
+
+ if (xml_obj == NULL) {
+ return NULL;
+ }
+
+ xml_section = get_xpath_object(xpath, xml_obj, LOG_TRACE);
+ if (xml_section == NULL) {
+ return NULL;
+ }
+ return calculate_xml_versioned_digest(xml_section, FALSE, TRUE, CRM_FEATURE_SET);
+
+}
+
+// v1 and v2 patch formats
+#define XPATH_CONFIG_CHANGE \
+ "//" XML_CIB_TAG_CRMCONFIG " | " \
+ "//" XML_DIFF_CHANGE \
+ "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]"
+
+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, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged)
+{
+ xmlNode *input = NULL;
+ xmlNode *output = NULL;
+ xmlNode *result_cib = NULL;
+ xmlNode *current_cib = NULL;
+
+ int call_type = 0;
+ int call_options = 0;
+
+ const char *op = NULL;
+ const char *section = NULL;
+ const char *call_id = crm_element_value(request, F_CIB_CALLID);
+ const char *client_id = crm_element_value(request, F_CIB_CLIENTID);
+ const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
+ const char *origin = crm_element_value(request, F_ORIG);
+
+ int rc = pcmk_ok;
+ int rc2 = pcmk_ok;
+
+ gboolean send_r_notify = FALSE;
+ gboolean config_changed = FALSE;
+ gboolean manage_counters = TRUE;
+
+ static mainloop_timer_t *digest_timer = NULL;
+
+ char *current_nodes_digest = NULL;
+ char *current_alerts_digest = NULL;
+ char *current_status_digest = NULL;
+ uint32_t change_section = cib_change_section_nodes
+ |cib_change_section_alerts
+ |cib_change_section_status;
+
+ 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;
+ current_cib = the_cib;
+
+ /* Start processing the request... */
+ op = crm_element_value(request, F_CIB_OPERATION);
+ crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
+ rc = cib_get_operation_id(op, &call_type);
+
+ if (rc == pcmk_ok && privileged == FALSE) {
+ rc = cib_op_can_run(call_type, call_options, privileged);
+ }
+
+ rc2 = cib_op_prepare(call_type, request, &input, &section);
+ if (rc == pcmk_ok) {
+ rc = rc2;
+ }
+
+ if (rc != pcmk_ok) {
+ crm_trace("Call setup failed: %s", pcmk_strerror(rc));
+ goto done;
+
+ } else if (cib_op_modifies(call_type) == FALSE) {
+ rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE,
+ section, request, input, FALSE, &config_changed,
+ current_cib, &result_cib, NULL, &output);
+
+ CRM_CHECK(result_cib == NULL, free_xml(result_cib));
+ goto done;
+ }
+
+ /* Handle a valid write action */
+ if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
+ /* legacy code */
+ manage_counters = FALSE;
+ cib__set_call_options(call_options, "call", cib_force_diff);
+ crm_trace("Global update detected");
+
+ CRM_CHECK(call_type == 3 || call_type == 4, crm_err("Call type: %d", call_type);
+ crm_log_xml_err(request, "bad op"));
+ }
+
+ ping_modified_since = TRUE;
+ if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
+ crm_trace("Skipping update: inhibit broadcast");
+ manage_counters = FALSE;
+ }
+
+ if (!pcmk_is_set(call_options, cib_dryrun)
+ && pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
+ // Copying large CIBs accounts for a huge percentage of our CIB usage
+ cib__set_call_options(call_options, "call", cib_zero_copy);
+ } else {
+ cib__clear_call_options(call_options, "call", cib_zero_copy);
+ }
+
+#define XPATH_CONFIG "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION
+#define XPATH_NODES XPATH_CONFIG "/" XML_CIB_TAG_NODES
+#define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS
+#define XPATH_STATUS "//" XML_TAG_CIB "/" XML_CIB_TAG_STATUS
+
+ // Calculate the hash value of the section before the change
+ if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) {
+ current_nodes_digest = calculate_section_digest(XPATH_NODES,
+ current_cib);
+ current_alerts_digest = calculate_section_digest(XPATH_ALERTS,
+ current_cib);
+ current_status_digest = calculate_section_digest(XPATH_STATUS,
+ current_cib);
+ crm_trace("current-digest %s:%s:%s", current_nodes_digest,
+ current_alerts_digest, current_status_digest);
+ }
+
+ // result_cib must not be modified after cib_perform_op() returns
+ rc = cib_perform_op(op, call_options, cib_op_func(call_type), FALSE,
+ section, request, input, manage_counters,
+ &config_changed, current_cib, &result_cib, cib_diff,
+ &output);
+
+ if (!manage_counters) {
+ int format = 1;
+
+ /* Legacy code
+ * If the diff is NULL at this point, it's because nothing changed
+ */
+ if (*cib_diff != NULL) {
+ crm_element_value_int(*cib_diff, "format", &format);
+ }
+
+ if (format == 1) {
+ config_changed = cib__config_changed_v1(NULL, NULL, cib_diff);
+ }
+ }
+
+ /* Always write to disk for successful replace and upgrade ops. This also
+ * negates the need to detect ordering changes.
+ */
+ if ((rc == pcmk_ok)
+ && pcmk__str_any_of(op,
+ PCMK__CIB_REQUEST_REPLACE,
+ PCMK__CIB_REQUEST_UPGRADE,
+ NULL)) {
+ config_changed = TRUE;
+ }
+
+ if (rc == pcmk_ok && !pcmk_is_set(call_options, cib_dryrun)) {
+ crm_trace("Activating %s->%s%s%s",
+ crm_element_value(current_cib, XML_ATTR_NUMUPDATES),
+ crm_element_value(result_cib, XML_ATTR_NUMUPDATES),
+ (pcmk_is_set(call_options, cib_zero_copy)? " zero-copy" : ""),
+ (config_changed? " changed" : ""));
+ if (!pcmk_is_set(call_options, cib_zero_copy)) {
+ rc = activateCibXml(result_cib, config_changed, op);
+ crm_trace("Activated %s (%d)",
+ crm_element_value(current_cib, XML_ATTR_NUMUPDATES), rc);
+ }
+
+ if ((rc == pcmk_ok) && contains_config_change(*cib_diff)) {
+ cib_read_config(config_hash, result_cib);
+ }
+
+ if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) {
+ char *result_nodes_digest = NULL;
+ char *result_alerts_digest = NULL;
+ char *result_status_digest = NULL;
+
+ /* Calculate the hash value of the changed section. */
+ result_nodes_digest = calculate_section_digest(XPATH_NODES,
+ result_cib);
+ result_alerts_digest = calculate_section_digest(XPATH_ALERTS,
+ result_cib);
+ result_status_digest = calculate_section_digest(XPATH_STATUS,
+ result_cib);
+ crm_trace("result-digest %s:%s:%s", result_nodes_digest,
+ result_alerts_digest, result_status_digest);
+
+ if (pcmk__str_eq(current_nodes_digest, result_nodes_digest,
+ pcmk__str_none)) {
+ change_section =
+ pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,
+ "CIB change section",
+ "change_section", change_section,
+ cib_change_section_nodes, "nodes");
+ }
+
+ if (pcmk__str_eq(current_alerts_digest, result_alerts_digest,
+ pcmk__str_none)) {
+ change_section =
+ pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,
+ "CIB change section",
+ "change_section", change_section,
+ cib_change_section_alerts, "alerts");
+ }
+
+ if (pcmk__str_eq(current_status_digest, result_status_digest,
+ pcmk__str_none)) {
+ change_section =
+ pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,
+ "CIB change section",
+ "change_section", change_section,
+ cib_change_section_status, "status");
+ }
+
+ if (change_section != cib_change_section_none) {
+ send_r_notify = TRUE;
+ }
+
+ free(result_nodes_digest);
+ free(result_alerts_digest);
+ free(result_status_digest);
+
+ } else if (pcmk__str_eq(PCMK__CIB_REQUEST_ERASE, op, pcmk__str_none)) {
+ send_r_notify = TRUE;
+ }
+
+ mainloop_timer_stop(digest_timer);
+ mainloop_timer_start(digest_timer);
+
+ } else if (rc == -pcmk_err_schema_validation) {
+ CRM_ASSERT(!pcmk_is_set(call_options, cib_zero_copy));
+
+ 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, XML_ATTR_NUMUPDATES));
+ if (!pcmk_is_set(call_options, cib_zero_copy)) {
+ free_xml(result_cib);
+ }
+ }
+
+ if ((call_options & (cib_inhibit_notify|cib_dryrun)) == 0) {
+ crm_trace("Sending notifications %d",
+ pcmk_is_set(call_options, cib_dryrun));
+ cib_diff_notify(op, rc, call_id, client_id, client_name, origin, input,
+ *cib_diff);
+ }
+
+ if (send_r_notify) {
+ cib_replace_notify(op, rc, call_id, client_id, client_name, origin,
+ the_cib, *cib_diff, change_section);
+ }
+
+ pcmk__output_set_log_level(logger_out, LOG_TRACE);
+ logger_out->message(logger_out, "xml-patchset", *cib_diff);
+
+ done:
+ if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) {
+ const char *caller = crm_element_value(request, F_CIB_CLIENTID);
+
+ *reply = create_xml_node(NULL, "cib-reply");
+ crm_xml_add(*reply, F_TYPE, T_CIB);
+ crm_xml_add(*reply, F_CIB_OPERATION, op);
+ crm_xml_add(*reply, F_CIB_CALLID, call_id);
+ crm_xml_add(*reply, F_CIB_CLIENTID, caller);
+ crm_xml_add_int(*reply, F_CIB_CALLOPTS, call_options);
+ crm_xml_add_int(*reply, F_CIB_RC, rc);
+
+ if (output != NULL) {
+ crm_trace("Attaching reply output");
+ add_message_xml(*reply, F_CIB_CALLDATA, output);
+ }
+
+ crm_log_xml_explicit(*reply, "cib:reply");
+ }
+
+ crm_trace("cleanup");
+
+ if (cib_op_modifies(call_type) == FALSE && output != current_cib) {
+ free_xml(output);
+ output = NULL;
+ }
+
+ if (call_type >= 0) {
+ cib_op_cleanup(call_type, call_options, &input, &output);
+ }
+
+ free(current_nodes_digest);
+ free(current_alerts_digest);
+ free(current_status_digest);
+
+ 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, F_ORIG);
+
+ 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, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) {
+ check_local_notify(bcast_id);
+ }
+ return;
+
+ } else if (crm_peer_cache == NULL) {
+ reason = "membership not established";
+ goto bail;
+ }
+
+ if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) {
+ crm_xml_add(msg, F_CIB_CLIENTNAME, originator);
+ }
+
+ /* crm_log_xml_trace(msg, "Peer[inbound]"); */
+ cib_process_request(msg, TRUE, NULL);
+ return;
+
+ bail:
+ if (reason) {
+ const char *seq = crm_element_value(msg, F_SEQ);
+ const char *op = crm_element_value(msg, F_CIB_OPERATION);
+
+ crm_warn("Discarding %s message (%s) from %s: %s", op, seq, 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) {
+ terminate_cib(__func__, 0);
+ return;
+ }
+
+ crm_info("Sending disconnect notification to %d peers...", active);
+
+ leaving = create_xml_node(NULL, "exit-notification");
+ crm_xml_add(leaving, F_TYPE, "cib");
+ crm_xml_add(leaving, F_CIB_OPERATION, 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 (logger_out != NULL) {
+ logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
+ pcmk__output_free(logger_out);
+ logger_out = NULL;
+ }
+
+ 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);
+ }
+ 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__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
+ crm_exit(CRM_EX_OK);
+ }
+}
diff --git a/daemons/based/based_common.c b/daemons/based/based_common.c
new file mode 100644
index 0000000..7e68cf0
--- /dev/null
+++ b/daemons/based/based_common.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2008-2023 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 <errno.h>
+#include <fcntl.h>
+
+#include <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/cluster.h>
+
+#include <crm/common/xml.h>
+
+#include <pacemaker-based.h>
+
+gboolean stand_alone = FALSE;
+
+extern int cib_perform_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff,
+ gboolean privileged);
+
+static xmlNode *
+cib_prepare_common(xmlNode * root, const char *section)
+{
+ xmlNode *data = NULL;
+
+ /* extract the CIB from the fragment */
+ if (root == NULL) {
+ return NULL;
+
+ } else if (pcmk__strcase_any_of(crm_element_name(root), XML_TAG_FRAGMENT,
+ F_CRM_DATA, F_CIB_CALLDATA, NULL)) {
+ data = first_named_child(root, XML_TAG_CIB);
+
+ } else {
+ data = root;
+ }
+
+ /* grab the section specified for the command */
+ if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) {
+ data = pcmk_find_cib_element(data, section);
+ }
+
+ /* crm_log_xml_trace(root, "cib:input"); */
+ return data;
+}
+
+static int
+cib_prepare_none(xmlNode * request, xmlNode ** data, const char **section)
+{
+ *data = NULL;
+ *section = crm_element_value(request, F_CIB_SECTION);
+ return pcmk_ok;
+}
+
+static int
+cib_prepare_data(xmlNode * request, xmlNode ** data, const char **section)
+{
+ xmlNode *input_fragment = get_message_xml(request, F_CIB_CALLDATA);
+
+ *section = crm_element_value(request, F_CIB_SECTION);
+ *data = cib_prepare_common(input_fragment, *section);
+ /* crm_log_xml_debug(*data, "data"); */
+ return pcmk_ok;
+}
+
+static int
+cib_prepare_sync(xmlNode * request, xmlNode ** data, const char **section)
+{
+ *data = NULL;
+ *section = crm_element_value(request, F_CIB_SECTION);
+ return pcmk_ok;
+}
+
+static int
+cib_prepare_diff(xmlNode * request, xmlNode ** data, const char **section)
+{
+ xmlNode *input_fragment = NULL;
+
+ *data = NULL;
+ *section = NULL;
+
+ if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
+ input_fragment = get_message_xml(request, F_CIB_UPDATE_DIFF);
+ } else {
+ input_fragment = get_message_xml(request, F_CIB_CALLDATA);
+ }
+
+ CRM_CHECK(input_fragment != NULL, crm_log_xml_warn(request, "no input"));
+ *data = cib_prepare_common(input_fragment, NULL);
+ return pcmk_ok;
+}
+
+static int
+cib_cleanup_query(int options, xmlNode ** data, xmlNode ** output)
+{
+ CRM_LOG_ASSERT(*data == NULL);
+ if ((options & cib_no_children)
+ || pcmk__str_eq(crm_element_name(*output), "xpath-query", pcmk__str_casei)) {
+ free_xml(*output);
+ }
+ return pcmk_ok;
+}
+
+static int
+cib_cleanup_data(int options, xmlNode ** data, xmlNode ** output)
+{
+ free_xml(*output);
+ *data = NULL;
+ return pcmk_ok;
+}
+
+static int
+cib_cleanup_output(int options, xmlNode ** data, xmlNode ** output)
+{
+ free_xml(*output);
+ return pcmk_ok;
+}
+
+static int
+cib_cleanup_none(int options, xmlNode ** data, xmlNode ** output)
+{
+ CRM_LOG_ASSERT(*data == NULL);
+ CRM_LOG_ASSERT(*output == NULL);
+ return pcmk_ok;
+}
+
+static cib_operation_t cib_server_ops[] = {
+ // Booleans are modifies_cib, needs_privileges
+ {
+ NULL, FALSE, FALSE,
+ cib_prepare_none, cib_cleanup_none, cib_process_default
+ },
+ {
+ PCMK__CIB_REQUEST_QUERY, FALSE, FALSE,
+ cib_prepare_none, cib_cleanup_query, cib_process_query
+ },
+ {
+ PCMK__CIB_REQUEST_MODIFY, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_modify
+ },
+ {
+ PCMK__CIB_REQUEST_APPLY_PATCH, TRUE, TRUE,
+ cib_prepare_diff, cib_cleanup_data, cib_server_process_diff
+ },
+ {
+ PCMK__CIB_REQUEST_REPLACE, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_replace_svr
+ },
+ {
+ PCMK__CIB_REQUEST_CREATE, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_create
+ },
+ {
+ PCMK__CIB_REQUEST_DELETE, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_delete
+ },
+ {
+ PCMK__CIB_REQUEST_SYNC_TO_ALL, FALSE, TRUE,
+ cib_prepare_sync, cib_cleanup_none, cib_process_sync
+ },
+ {
+ PCMK__CIB_REQUEST_BUMP, TRUE, TRUE,
+ cib_prepare_none, cib_cleanup_output, cib_process_bump
+ },
+ {
+ PCMK__CIB_REQUEST_ERASE, TRUE, TRUE,
+ cib_prepare_none, cib_cleanup_output, cib_process_erase
+ },
+ {
+ PCMK__CIB_REQUEST_NOOP, FALSE, FALSE,
+ cib_prepare_none, cib_cleanup_none, cib_process_default
+ },
+ {
+ PCMK__CIB_REQUEST_ABS_DELETE, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_delete_absolute
+ },
+ {
+ PCMK__CIB_REQUEST_UPGRADE, TRUE, TRUE,
+ cib_prepare_none, cib_cleanup_output, cib_process_upgrade_server
+ },
+ {
+ PCMK__CIB_REQUEST_SECONDARY, FALSE, TRUE,
+ cib_prepare_none, cib_cleanup_none, cib_process_readwrite
+ },
+ {
+ PCMK__CIB_REQUEST_ALL_SECONDARY, FALSE, TRUE,
+ cib_prepare_none, cib_cleanup_none, cib_process_readwrite
+ },
+ {
+ PCMK__CIB_REQUEST_SYNC_TO_ONE, FALSE, TRUE,
+ cib_prepare_sync, cib_cleanup_none, cib_process_sync_one
+ },
+ {
+ PCMK__CIB_REQUEST_PRIMARY, TRUE, TRUE,
+ cib_prepare_data, cib_cleanup_data, cib_process_readwrite
+ },
+ {
+ PCMK__CIB_REQUEST_IS_PRIMARY, FALSE, TRUE,
+ cib_prepare_none, cib_cleanup_none, cib_process_readwrite
+ },
+ {
+ PCMK__CIB_REQUEST_SHUTDOWN, FALSE, TRUE,
+ cib_prepare_sync, cib_cleanup_none, cib_process_shutdown_req
+ },
+ {
+ CRM_OP_PING, FALSE, FALSE,
+ cib_prepare_none, cib_cleanup_output, cib_process_ping
+ },
+};
+
+int
+cib_get_operation_id(const char *op, int *operation)
+{
+ static GHashTable *operation_hash = NULL;
+
+ if (operation_hash == NULL) {
+ int lpc = 0;
+ int max_msg_types = PCMK__NELEM(cib_server_ops);
+
+ operation_hash = pcmk__strkey_table(NULL, free);
+ for (lpc = 1; lpc < max_msg_types; lpc++) {
+ int *value = malloc(sizeof(int));
+
+ if(value) {
+ *value = lpc;
+ g_hash_table_insert(operation_hash, (gpointer) cib_server_ops[lpc].operation, value);
+ }
+ }
+ }
+
+ if (op != NULL) {
+ int *value = g_hash_table_lookup(operation_hash, op);
+
+ if (value) {
+ *operation = *value;
+ return pcmk_ok;
+ }
+ }
+ crm_err("Operation %s is not valid", op);
+ *operation = -1;
+ return -EINVAL;
+}
+
+xmlNode *
+cib_msg_copy(xmlNode * msg, gboolean with_data)
+{
+ int lpc = 0;
+ const char *field = NULL;
+ const char *value = NULL;
+ xmlNode *value_struct = NULL;
+
+ static const char *field_list[] = {
+ F_XML_TAGNAME,
+ F_TYPE,
+ F_CIB_CLIENTID,
+ F_CIB_CALLOPTS,
+ F_CIB_CALLID,
+ F_CIB_OPERATION,
+ F_CIB_ISREPLY,
+ F_CIB_SECTION,
+ F_CIB_HOST,
+ F_CIB_RC,
+ F_CIB_DELEGATED,
+ F_CIB_OBJID,
+ F_CIB_OBJTYPE,
+ F_CIB_EXISTING,
+ F_CIB_SEENCOUNT,
+ F_CIB_TIMEOUT,
+ F_CIB_GLOBAL_UPDATE,
+ F_CIB_CLIENTNAME,
+ F_CIB_USER,
+ F_CIB_NOTIFY_TYPE,
+ F_CIB_NOTIFY_ACTIVATE
+ };
+
+ static const char *data_list[] = {
+ F_CIB_CALLDATA,
+ F_CIB_UPDATE,
+ F_CIB_UPDATE_RESULT
+ };
+
+ xmlNode *copy = create_xml_node(NULL, "copy");
+
+ CRM_ASSERT(copy != NULL);
+
+ for (lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) {
+ field = field_list[lpc];
+ value = crm_element_value(msg, field);
+ if (value != NULL) {
+ crm_xml_add(copy, field, value);
+ }
+ }
+ for (lpc = 0; with_data && lpc < PCMK__NELEM(data_list); lpc++) {
+ field = data_list[lpc];
+ value_struct = get_message_xml(msg, field);
+ if (value_struct != NULL) {
+ add_message_xml(copy, field, value_struct);
+ }
+ }
+
+ return copy;
+}
+
+cib_op_t *
+cib_op_func(int call_type)
+{
+ return &(cib_server_ops[call_type].fn);
+}
+
+gboolean
+cib_op_modifies(int call_type)
+{
+ return cib_server_ops[call_type].modifies_cib;
+}
+
+int
+cib_op_can_run(int call_type, int call_options, bool privileged)
+{
+ if (!privileged && cib_server_ops[call_type].needs_privileges) {
+ return -EACCES;
+ }
+ return pcmk_ok;
+}
+
+int
+cib_op_prepare(int call_type, xmlNode * request, xmlNode ** input, const char **section)
+{
+ crm_trace("Prepare %d", call_type);
+ return cib_server_ops[call_type].prepare(request, input, section);
+}
+
+int
+cib_op_cleanup(int call_type, int options, xmlNode ** input, xmlNode ** output)
+{
+ crm_trace("Cleanup %d", call_type);
+ return cib_server_ops[call_type].cleanup(options, input, output);
+}
diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c
new file mode 100644
index 0000000..fc34f39
--- /dev/null
+++ b/daemons/based/based_io.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2004-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <crm/crm.h>
+
+#include <crm/cib.h>
+#include <crm/common/util.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/cib/internal.h>
+#include <crm/cluster.h>
+
+#include <pacemaker-based.h>
+
+crm_trigger_t *cib_writer = NULL;
+
+int write_cib_contents(gpointer p);
+
+static void
+cib_rename(const char *old)
+{
+ int new_fd;
+ char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
+
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+ new_fd = mkstemp(new);
+ crm_err("Archiving unusable file %s as %s", old, new);
+ if ((new_fd < 0) || (rename(old, new) < 0)) {
+ crm_perror(LOG_ERR, "Couldn't rename %s as %s", old, new);
+ crm_err("Disabling disk writes and continuing");
+ cib_writes_enabled = FALSE;
+ }
+ if (new_fd > 0) {
+ close(new_fd);
+ }
+ free(new);
+}
+
+/*
+ * It is the callers responsibility to free the output of this function
+ */
+
+static xmlNode *
+retrieveCib(const char *filename, const char *sigfile)
+{
+ xmlNode *root = NULL;
+
+ crm_info("Reading cluster configuration file %s (digest: %s)",
+ filename, sigfile);
+ switch (cib_file_read_and_verify(filename, sigfile, &root)) {
+ case -pcmk_err_cib_corrupt:
+ crm_warn("Continuing but %s will NOT be used.", filename);
+ break;
+
+ case -pcmk_err_cib_modified:
+ /* Archive the original files so the contents are not lost */
+ crm_warn("Continuing but %s will NOT be used.", filename);
+ cib_rename(filename);
+ cib_rename(sigfile);
+ break;
+ }
+ return root;
+}
+
+/*
+ * for OSs without support for direntry->d_type, like Solaris
+ */
+#ifndef DT_UNKNOWN
+# define DT_UNKNOWN 0
+# define DT_FIFO 1
+# define DT_CHR 2
+# define DT_DIR 4
+# define DT_BLK 6
+# define DT_REG 8
+# define DT_LNK 10
+# define DT_SOCK 12
+# define DT_WHT 14
+#endif /*DT_UNKNOWN*/
+
+static int cib_archive_filter(const struct dirent * a)
+{
+ int rc = 0;
+ /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */
+ struct stat s;
+ char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name);
+
+ if(stat(a_path, &s) != 0) {
+ rc = errno;
+ crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_strerror(rc), rc);
+ rc = 0;
+
+ } else if ((s.st_mode & S_IFREG) != S_IFREG) {
+ unsigned char dtype;
+#ifdef HAVE_STRUCT_DIRENT_D_TYPE
+ dtype = a->d_type;
+#else
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG: dtype = DT_REG; break;
+ case S_IFDIR: dtype = DT_DIR; break;
+ case S_IFCHR: dtype = DT_CHR; break;
+ case S_IFBLK: dtype = DT_BLK; break;
+ case S_IFLNK: dtype = DT_LNK; break;
+ case S_IFIFO: dtype = DT_FIFO; break;
+ case S_IFSOCK: dtype = DT_SOCK; break;
+ default: dtype = DT_UNKNOWN; break;
+ }
+#endif
+ crm_trace("%s - wrong type (%d)", a->d_name, dtype);
+
+ } else if(strstr(a->d_name, "cib-") != a->d_name) {
+ crm_trace("%s - wrong prefix", a->d_name);
+
+ } else if (pcmk__ends_with_ext(a->d_name, ".sig")) {
+ crm_trace("%s - wrong suffix", a->d_name);
+
+ } else {
+ crm_debug("%s - candidate", a->d_name);
+ rc = 1;
+ }
+
+ free(a_path);
+ return rc;
+}
+
+static int cib_archive_sort(const struct dirent ** a, const struct dirent **b)
+{
+ /* Order by creation date - most recently created file first */
+ int rc = 0;
+ struct stat buf;
+
+ time_t a_age = 0;
+ time_t b_age = 0;
+
+ char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name);
+ char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name);
+
+ if(stat(a_path, &buf) == 0) {
+ a_age = buf.st_ctime;
+ }
+ if(stat(b_path, &buf) == 0) {
+ b_age = buf.st_ctime;
+ }
+
+ free(a_path);
+ free(b_path);
+
+ if(a_age > b_age) {
+ rc = 1;
+ } else if(a_age < b_age) {
+ rc = -1;
+ }
+
+ crm_trace("%s (%lu) vs. %s (%lu) : %d",
+ a[0]->d_name, (unsigned long)a_age,
+ b[0]->d_name, (unsigned long)b_age, rc);
+ return rc;
+}
+
+xmlNode *
+readCibXmlFile(const char *dir, const char *file, gboolean discard_status)
+{
+ struct dirent **namelist = NULL;
+
+ int lpc = 0;
+ char *sigfile = NULL;
+ char *sigfilepath = NULL;
+ char *filename = NULL;
+ const char *name = NULL;
+ const char *value = NULL;
+ const char *validation = NULL;
+ const char *use_valgrind = getenv("PCMK_valgrind_enabled");
+
+ xmlNode *root = NULL;
+ xmlNode *status = NULL;
+
+ sigfile = crm_strdup_printf("%s.sig", file);
+ if (pcmk__daemon_can_write(dir, file) == FALSE
+ || pcmk__daemon_can_write(dir, sigfile) == FALSE) {
+ cib_status = -EACCES;
+ return NULL;
+ }
+
+ filename = crm_strdup_printf("%s/%s", dir, file);
+ sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile);
+ free(sigfile);
+
+ cib_status = pcmk_ok;
+ root = retrieveCib(filename, sigfilepath);
+ free(filename);
+ free(sigfilepath);
+
+ if (root == NULL) {
+ crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root);
+ lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort);
+ if (lpc < 0) {
+ crm_perror(LOG_NOTICE, "scandir(%s) failed", cib_root);
+ }
+ }
+
+ while (root == NULL && lpc > 1) {
+ crm_debug("Testing %d candidates", lpc);
+
+ lpc--;
+
+ filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
+ sigfile = crm_strdup_printf("%s.sig", filename);
+
+ crm_info("Reading cluster configuration file %s (digest: %s)",
+ filename, sigfile);
+ if (cib_file_read_and_verify(filename, sigfile, &root) < 0) {
+ crm_warn("Continuing but %s will NOT be used.", filename);
+ } else {
+ crm_notice("Continuing with last valid configuration archive: %s", filename);
+ }
+
+ free(namelist[lpc]);
+ free(filename);
+ free(sigfile);
+ }
+ free(namelist);
+
+ if (root == NULL) {
+ root = createEmptyCib(0);
+ crm_warn("Continuing with an empty configuration.");
+ }
+
+ if (cib_writes_enabled && use_valgrind &&
+ (crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) {
+
+ cib_writes_enabled = FALSE;
+ crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
+ }
+
+ status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE);
+ if (discard_status && status != NULL) {
+ /* strip out the status section if there is one */
+ free_xml(status);
+ status = NULL;
+ }
+ if (status == NULL) {
+ create_xml_node(root, XML_CIB_TAG_STATUS);
+ }
+
+ /* Do this before schema validation happens */
+
+ /* fill in some defaults */
+ name = XML_ATTR_GENERATION_ADMIN;
+ value = crm_element_value(root, name);
+ if (value == NULL) {
+ crm_warn("No value for %s was specified in the configuration.", name);
+ crm_warn("The recommended course of action is to shutdown,"
+ " run crm_verify and fix any errors it reports.");
+ crm_warn("We will default to zero and continue but may get"
+ " confused about which configuration to use if"
+ " multiple nodes are powered up at the same time.");
+ crm_xml_add_int(root, name, 0);
+ }
+
+ name = XML_ATTR_GENERATION;
+ value = crm_element_value(root, name);
+ if (value == NULL) {
+ crm_xml_add_int(root, name, 0);
+ }
+
+ name = XML_ATTR_NUMUPDATES;
+ value = crm_element_value(root, name);
+ if (value == NULL) {
+ crm_xml_add_int(root, name, 0);
+ }
+
+ // Unset (DC should set appropriate value)
+ xml_remove_prop(root, XML_ATTR_DC_UUID);
+
+ if (discard_status) {
+ crm_log_xml_trace(root, "[on-disk]");
+ }
+
+ validation = crm_element_value(root, XML_ATTR_VALIDATION);
+ if (validate_xml(root, NULL, TRUE) == FALSE) {
+ crm_err("CIB does not validate with %s",
+ pcmk__s(validation, "no schema specified"));
+ cib_status = -pcmk_err_schema_validation;
+
+ } else if (validation == NULL) {
+ int version = 0;
+
+ update_validation(&root, &version, 0, FALSE, FALSE);
+ if (version > 0) {
+ crm_notice("Enabling %s validation on"
+ " the existing (sane) configuration", get_schema_name(version));
+ } else {
+ crm_err("CIB does not validate with any known schema");
+ cib_status = -pcmk_err_schema_validation;
+ }
+ }
+
+ return root;
+}
+
+gboolean
+uninitializeCib(void)
+{
+ xmlNode *tmp_cib = the_cib;
+
+ if (tmp_cib == NULL) {
+ crm_debug("The CIB has already been deallocated.");
+ return FALSE;
+ }
+
+ the_cib = NULL;
+
+ crm_debug("Deallocating the CIB.");
+
+ free_xml(tmp_cib);
+
+ crm_debug("The CIB has been deallocated.");
+
+ return TRUE;
+}
+
+/*
+ * This method will free the old CIB pointer on success and the new one
+ * on failure.
+ */
+int
+activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
+{
+ if (new_cib) {
+ xmlNode *saved_cib = the_cib;
+
+ CRM_ASSERT(new_cib != saved_cib);
+ the_cib = new_cib;
+ free_xml(saved_cib);
+ if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) {
+ crm_debug("Triggering CIB write for %s op", op);
+ mainloop_set_trigger(cib_writer);
+ }
+ return pcmk_ok;
+ }
+
+ crm_err("Ignoring invalid CIB");
+ if (the_cib) {
+ crm_warn("Reverting to last known CIB");
+ } else {
+ crm_crit("Could not write out new CIB and no saved version to revert to");
+ }
+ return -ENODATA;
+}
+
+static void
+cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
+{
+ const char *errmsg = "Could not write CIB to disk";
+
+ if ((exitcode != 0) && cib_writes_enabled) {
+ cib_writes_enabled = FALSE;
+ errmsg = "Disabling CIB disk writes after failure";
+ }
+
+ if ((signo == 0) && (exitcode == 0)) {
+ crm_trace("Disk write [%d] succeeded", (int) pid);
+
+ } else if (signo == 0) {
+ crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
+
+ } else {
+ crm_err("%s: process %d terminated with signal %d (%s)%s",
+ errmsg, (int) pid, signo, strsignal(signo),
+ (core? " and dumped core" : ""));
+ }
+
+ mainloop_trigger_complete(cib_writer);
+}
+
+int
+write_cib_contents(gpointer p)
+{
+ int exit_rc = pcmk_ok;
+ xmlNode *cib_local = NULL;
+
+ /* Make a copy of the CIB to write (possibly in a forked child) */
+ if (p) {
+ /* Synchronous write out */
+ cib_local = copy_xml(p);
+
+ } else {
+ int pid = 0;
+ int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0);
+
+ /* Turn it off before the fork() to avoid:
+ * - 2 processes writing to the same shared mem
+ * - the child needing to disable it
+ * (which would close it from underneath the parent)
+ * This way, the shared mem files are already closed
+ */
+ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
+
+ pid = fork();
+ if (pid < 0) {
+ crm_perror(LOG_ERR, "Disabling disk writes after fork failure");
+ cib_writes_enabled = FALSE;
+ return FALSE;
+ }
+
+ if (pid) {
+ /* Parent */
+ mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete);
+ if (bb_state == QB_LOG_STATE_ENABLED) {
+ /* Re-enable now that it it safe */
+ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
+ }
+
+ return -1; /* -1 means 'still work to do' */
+ }
+
+ /* Asynchronous write-out after a fork() */
+
+ /* In theory, we can scribble on the_cib here and not affect the parent,
+ * but let's be safe anyway.
+ */
+ cib_local = copy_xml(the_cib);
+ }
+
+ /* Write the CIB */
+ exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml");
+
+ /* A nonzero exit code will cause further writes to be disabled */
+ free_xml(cib_local);
+ if (p == NULL) {
+ crm_exit_t exit_code = CRM_EX_OK;
+
+ switch (exit_rc) {
+ case pcmk_ok:
+ exit_code = CRM_EX_OK;
+ break;
+ case pcmk_err_cib_modified:
+ exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest
+ break;
+ case pcmk_err_cib_backup: // Existing CIB couldn't be backed up
+ case pcmk_err_cib_save: // New CIB couldn't be saved
+ exit_code = CRM_EX_CANTCREAT;
+ break;
+ default:
+ exit_code = CRM_EX_ERROR;
+ break;
+ }
+
+ /* Use _exit() because exit() could affect the parent adversely */
+ _exit(exit_code);
+ }
+ return exit_rc;
+}
diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c
new file mode 100644
index 0000000..d46456c
--- /dev/null
+++ b/daemons/based/based_messages.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2004-2023 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 <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+
+#include <crm/common/xml.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/xml_internal.h>
+#include <crm/cluster/internal.h>
+
+#include <pacemaker-based.h>
+
+/* Maximum number of diffs to ignore while waiting for a resync */
+#define MAX_DIFF_RETRY 5
+
+bool based_is_primary = false;
+
+xmlNode *the_cib = NULL;
+
+int
+cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ const char *host = crm_element_value(req, F_ORIG);
+
+ *answer = NULL;
+
+ if (crm_element_value(req, F_CIB_ISREPLY) == NULL) {
+ crm_info("Peer %s is requesting to shut down", host);
+ return pcmk_ok;
+ }
+
+ if (cib_shutdown_flag == FALSE) {
+ crm_err("Peer %s mistakenly thinks we wanted to shut down", host);
+ return -EINVAL;
+ }
+
+ crm_info("Peer %s has acknowledged our shutdown request", host);
+ terminate_cib(__func__, 0);
+ return pcmk_ok;
+}
+
+int
+cib_process_default(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ int result = pcmk_ok;
+
+ crm_trace("Processing \"%s\" event", op);
+ *answer = NULL;
+
+ if (op == NULL) {
+ result = -EINVAL;
+ crm_err("No operation specified");
+
+ } else if (strcmp(PCMK__CIB_REQUEST_NOOP, op) != 0) {
+ result = -EPROTONOSUPPORT;
+ crm_err("Action [%s] is not supported by the CIB manager", op);
+ }
+ return result;
+}
+
+int
+cib_process_readwrite(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ int result = pcmk_ok;
+
+ crm_trace("Processing \"%s\" event", op);
+
+ if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) {
+ if (based_is_primary) {
+ result = pcmk_ok;
+ } else {
+ result = -EPERM;
+ }
+ return result;
+ }
+
+ if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) {
+ if (!based_is_primary) {
+ crm_info("We are now in R/W mode");
+ based_is_primary = true;
+ } else {
+ crm_debug("We are still in R/W mode");
+ }
+
+ } else if (based_is_primary) {
+ crm_info("We are now in R/O mode");
+ based_is_primary = false;
+ }
+
+ return result;
+}
+
+/* Set to 1 when a sync is requested, incremented when a diff is ignored,
+ * reset to 0 when a sync is received
+ */
+static int sync_in_progress = 0;
+
+void
+send_sync_request(const char *host)
+{
+ xmlNode *sync_me = create_xml_node(NULL, "sync-me");
+
+ crm_info("Requesting re-sync from %s", (host? host : "all peers"));
+ sync_in_progress = 1;
+
+ crm_xml_add(sync_me, F_TYPE, "cib");
+ crm_xml_add(sync_me, F_CIB_OPERATION, PCMK__CIB_REQUEST_SYNC_TO_ONE);
+ crm_xml_add(sync_me, F_CIB_DELEGATED,
+ stand_alone? "localhost" : crm_cluster->uname);
+
+ send_cluster_message(host ? crm_get_peer(0, host) : NULL, crm_msg_cib, sync_me, FALSE);
+ free_xml(sync_me);
+}
+
+int
+cib_process_ping(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ const char *host = crm_element_value(req, F_ORIG);
+ const char *seq = crm_element_value(req, F_CIB_PING_ID);
+ char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET);
+
+ crm_trace("Processing \"%s\" event %s from %s", op, seq, host);
+ *answer = create_xml_node(NULL, XML_CRM_TAG_PING);
+
+ crm_xml_add(*answer, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
+ crm_xml_add(*answer, XML_ATTR_DIGEST, digest);
+ crm_xml_add(*answer, F_CIB_PING_ID, seq);
+
+ pcmk__if_tracing(
+ {
+ // Append additional detail so the receiver can log the differences
+ add_message_xml(*answer, F_CIB_CALLDATA, the_cib);
+ },
+ {
+ // Always include at least the version details
+ const char *tag = TYPE(the_cib);
+ xmlNode *shallow = create_xml_node(NULL, tag);
+
+ copy_in_properties(shallow, the_cib);
+ add_message_xml(*answer, F_CIB_CALLDATA, shallow);
+ free_xml(shallow);
+ }
+ );
+
+ crm_info("Reporting our current digest to %s: %s for %s.%s.%s",
+ host, digest,
+ crm_element_value(existing_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(existing_cib, XML_ATTR_GENERATION),
+ crm_element_value(existing_cib, XML_ATTR_NUMUPDATES));
+
+ free(digest);
+
+ return pcmk_ok;
+}
+
+int
+cib_process_sync(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ return sync_our_cib(req, TRUE);
+}
+
+int
+cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ int rc = pcmk_ok;
+
+ *answer = NULL;
+
+ if(crm_element_value(req, F_CIB_SCHEMA_MAX)) {
+ /* The originator of an upgrade request sends it to the DC, without
+ * F_CIB_SCHEMA_MAX. If an upgrade is needed, the DC re-broadcasts the
+ * request with F_CIB_SCHEMA_MAX, and each node performs the upgrade
+ * (and notifies its local clients) here.
+ */
+ return cib_process_upgrade(
+ op, options, section, req, input, existing_cib, result_cib, answer);
+
+ } else {
+ int new_version = 0;
+ int current_version = 0;
+ xmlNode *scratch = copy_xml(existing_cib);
+ const char *host = crm_element_value(req, F_ORIG);
+ const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION);
+ const char *client_id = crm_element_value(req, F_CIB_CLIENTID);
+ const char *call_opts = crm_element_value(req, F_CIB_CALLOPTS);
+ const char *call_id = crm_element_value(req, F_CIB_CALLID);
+
+ crm_trace("Processing \"%s\" event", op);
+ if (value != NULL) {
+ current_version = get_schema_version(value);
+ }
+
+ rc = update_validation(&scratch, &new_version, 0, TRUE, TRUE);
+ if (new_version > current_version) {
+ xmlNode *up = create_xml_node(NULL, __func__);
+
+ rc = pcmk_ok;
+ crm_notice("Upgrade request from %s verified", host);
+
+ crm_xml_add(up, F_TYPE, "cib");
+ crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE);
+ crm_xml_add(up, F_CIB_SCHEMA_MAX, get_schema_name(new_version));
+ crm_xml_add(up, F_CIB_DELEGATED, host);
+ crm_xml_add(up, F_CIB_CLIENTID, client_id);
+ crm_xml_add(up, F_CIB_CALLOPTS, call_opts);
+ crm_xml_add(up, F_CIB_CALLID, call_id);
+
+ if (cib_legacy_mode() && based_is_primary) {
+ rc = cib_process_upgrade(
+ op, options, section, up, input, existing_cib, result_cib, answer);
+
+ } else {
+ send_cluster_message(NULL, crm_msg_cib, up, FALSE);
+ }
+
+ free_xml(up);
+
+ } else if(rc == pcmk_ok) {
+ rc = -pcmk_err_schema_unchanged;
+ }
+
+ if (rc != pcmk_ok) {
+ // Notify originating peer so it can notify its local clients
+ crm_node_t *origin = pcmk__search_cluster_node_cache(0, host);
+
+ crm_info("Rejecting upgrade request from %s: %s "
+ CRM_XS " rc=%d peer=%s", host, pcmk_strerror(rc), rc,
+ (origin? origin->uname : "lost"));
+
+ if (origin) {
+ xmlNode *up = create_xml_node(NULL, __func__);
+
+ crm_xml_add(up, F_TYPE, "cib");
+ crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE);
+ crm_xml_add(up, F_CIB_DELEGATED, host);
+ crm_xml_add(up, F_CIB_ISREPLY, host);
+ crm_xml_add(up, F_CIB_CLIENTID, client_id);
+ crm_xml_add(up, F_CIB_CALLOPTS, call_opts);
+ crm_xml_add(up, F_CIB_CALLID, call_id);
+ crm_xml_add_int(up, F_CIB_UPGRADE_RC, rc);
+ if (send_cluster_message(origin, crm_msg_cib, up, TRUE)
+ == FALSE) {
+ crm_warn("Could not send CIB upgrade result to %s", host);
+ }
+ free_xml(up);
+ }
+ }
+ free_xml(scratch);
+ }
+ return rc;
+}
+
+int
+cib_process_sync_one(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ return sync_our_cib(req, FALSE);
+}
+
+int
+cib_server_process_diff(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ int rc = pcmk_ok;
+
+ if (sync_in_progress > MAX_DIFF_RETRY) {
+ /* Don't ignore diffs forever; the last request may have been lost.
+ * If the diff fails, we'll ask for another full resync.
+ */
+ sync_in_progress = 0;
+ }
+
+ // The primary instance should never ignore a diff
+ if (sync_in_progress && !based_is_primary) {
+ 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;
+
+ cib_diff_version_details(input,
+ &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates,
+ &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);
+
+ sync_in_progress++;
+ crm_notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in progress)",
+ diff_del_admin_epoch, diff_del_epoch, diff_del_updates,
+ diff_add_admin_epoch, diff_add_epoch, diff_add_updates);
+ return -pcmk_err_diff_resync;
+ }
+
+ rc = cib_process_diff(op, options, section, req, input, existing_cib, result_cib, answer);
+ crm_trace("result: %s (%d), %s", pcmk_strerror(rc), rc,
+ (based_is_primary? "primary": "secondary"));
+
+ if ((rc == -pcmk_err_diff_resync) && !based_is_primary) {
+ free_xml(*result_cib);
+ *result_cib = NULL;
+ send_sync_request(NULL);
+
+ } else if (rc == -pcmk_err_diff_resync) {
+ rc = -pcmk_err_diff_failed;
+ if (options & cib_force_diff) {
+ crm_warn("Not requesting full refresh in R/W mode");
+ }
+
+ } else if ((rc != pcmk_ok) && !based_is_primary && cib_legacy_mode()) {
+ crm_warn("Requesting full CIB refresh because update failed: %s"
+ CRM_XS " rc=%d", pcmk_strerror(rc), rc);
+
+ pcmk__output_set_log_level(logger_out, LOG_INFO);
+ logger_out->message(logger_out, "xml-patchset", input);
+ free_xml(*result_cib);
+ *result_cib = NULL;
+ send_sync_request(NULL);
+ }
+
+ return rc;
+}
+
+int
+cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ const char *tag = crm_element_name(input);
+ int rc =
+ cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer);
+ if (rc == pcmk_ok && pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) {
+ sync_in_progress = 0;
+ }
+ return rc;
+}
+
+int
+cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ return -EINVAL;
+}
+
+int
+sync_our_cib(xmlNode * request, gboolean all)
+{
+ int result = pcmk_ok;
+ char *digest = NULL;
+ const char *host = crm_element_value(request, F_ORIG);
+ const char *op = crm_element_value(request, F_CIB_OPERATION);
+
+ xmlNode *replace_request = NULL;
+
+ CRM_CHECK(the_cib != NULL, return -EINVAL);
+
+ replace_request = cib_msg_copy(request, FALSE);
+ CRM_CHECK(replace_request != NULL, return -EINVAL);
+
+ crm_debug("Syncing CIB to %s", all ? "all peers" : host);
+ if (all == FALSE && host == NULL) {
+ crm_log_xml_err(request, "bad sync");
+ }
+
+ /* remove the "all == FALSE" condition
+ *
+ * sync_from was failing, the local client wasn't being notified
+ * because it didn't know it was a reply
+ * setting this does not prevent the other nodes from applying it
+ * if all == TRUE
+ */
+ if (host != NULL) {
+ crm_xml_add(replace_request, F_CIB_ISREPLY, host);
+ }
+ if (all) {
+ xml_remove_prop(replace_request, F_CIB_HOST);
+ }
+
+ crm_xml_add(replace_request, F_CIB_OPERATION, PCMK__CIB_REQUEST_REPLACE);
+ crm_xml_add(replace_request, "original_" F_CIB_OPERATION, op);
+ pcmk__xe_set_bool_attr(replace_request, F_CIB_GLOBAL_UPDATE, true);
+
+ crm_xml_add(replace_request, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
+ digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET);
+ crm_xml_add(replace_request, XML_ATTR_DIGEST, digest);
+
+ add_message_xml(replace_request, F_CIB_CALLDATA, the_cib);
+
+ if (send_cluster_message
+ (all ? NULL : crm_get_peer(0, host), crm_msg_cib, replace_request, FALSE) == FALSE) {
+ result = -ENOTCONN;
+ }
+ free_xml(replace_request);
+ free(digest);
+ return result;
+}
diff --git a/daemons/based/based_notify.c b/daemons/based/based_notify.c
new file mode 100644
index 0000000..5881f6d
--- /dev/null
+++ b/daemons/based/based_notify.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2004-2023 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 <inttypes.h> // PRIx64
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <time.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+
+#include <crm/common/xml.h>
+#include <crm/common/remote_internal.h>
+#include <pacemaker-based.h>
+
+struct cib_notification_s {
+ xmlNode *msg;
+ struct iovec *iov;
+ int32_t iov_size;
+};
+
+static void
+cib_notify_send_one(gpointer key, gpointer value, gpointer user_data)
+{
+ const char *type = NULL;
+ gboolean do_send = FALSE;
+ int rc = pcmk_rc_ok;
+
+ pcmk__client_t *client = value;
+ struct cib_notification_s *update = user_data;
+
+ if (client->ipcs == NULL && client->remote == NULL) {
+ crm_warn("Skipping client with NULL channel");
+ return;
+ }
+
+ type = crm_element_value(update->msg, F_SUBTYPE);
+ CRM_LOG_ASSERT(type != NULL);
+
+ if (pcmk_is_set(client->flags, cib_notify_diff)
+ && pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) {
+
+ do_send = TRUE;
+
+ } else if (pcmk_is_set(client->flags, cib_notify_replace)
+ && pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) {
+ do_send = TRUE;
+
+ } else if (pcmk_is_set(client->flags, cib_notify_confirm)
+ && pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) {
+ do_send = TRUE;
+
+ } else if (pcmk_is_set(client->flags, cib_notify_pre)
+ && pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) {
+ do_send = TRUE;
+
+ } else if (pcmk_is_set(client->flags, cib_notify_post)
+ && pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) {
+
+ do_send = TRUE;
+ }
+
+ if (do_send) {
+ switch (PCMK__CLIENT_TYPE(client)) {
+ case pcmk__client_ipc:
+ rc = pcmk__ipc_send_iov(client, update->iov,
+ crm_ipc_server_event);
+ if (rc != pcmk_rc_ok) {
+ crm_warn("Could not notify client %s: %s " CRM_XS " id=%s",
+ pcmk__client_name(client), pcmk_rc_str(rc),
+ client->id);
+ }
+ break;
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ case pcmk__client_tls:
+#endif
+ case pcmk__client_tcp:
+ crm_debug("Sent %s notification to client %s (id %s)",
+ type, pcmk__client_name(client), client->id);
+ pcmk__remote_send_xml(client->remote, update->msg);
+ break;
+ default:
+ crm_err("Unknown transport for client %s "
+ CRM_XS " flags=%#016" PRIx64,
+ pcmk__client_name(client), client->flags);
+ }
+ }
+}
+
+static void
+cib_notify_send(xmlNode * xml)
+{
+ struct iovec *iov;
+ struct cib_notification_s update;
+
+ ssize_t bytes = 0;
+ int rc = pcmk__ipc_prepare_iov(0, xml, 0, &iov, &bytes);
+
+ if (rc == pcmk_rc_ok) {
+ update.msg = xml;
+ update.iov = iov;
+ update.iov_size = bytes;
+ pcmk__foreach_ipc_client(cib_notify_send_one, &update);
+
+ } else {
+ crm_notice("Could not notify clients: %s " CRM_XS " rc=%d",
+ pcmk_rc_str(rc), rc);
+ }
+ pcmk_free_ipc_event(iov);
+}
+
+static void
+attach_cib_generation(xmlNode *msg, const char *field, xmlNode *a_cib)
+{
+ xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
+
+ if (a_cib != NULL) {
+ copy_in_properties(generation, a_cib);
+ }
+ add_message_xml(msg, field, generation);
+ free_xml(generation);
+}
+
+void
+cib_diff_notify(const char *op, int result, const char *call_id,
+ const char *client_id, const char *client_name,
+ const char *origin, xmlNode *update, xmlNode *diff)
+{
+ int add_updates = 0;
+ int add_epoch = 0;
+ int add_admin_epoch = 0;
+
+ int del_updates = 0;
+ int del_epoch = 0;
+ int del_admin_epoch = 0;
+
+ uint8_t log_level = LOG_TRACE;
+
+ xmlNode *update_msg = NULL;
+ const char *type = NULL;
+
+ if (diff == NULL) {
+ return;
+ }
+
+ if (result != pcmk_ok) {
+ log_level = LOG_WARNING;
+ }
+
+ cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates,
+ &del_admin_epoch, &del_epoch, &del_updates);
+
+ if ((add_admin_epoch != del_admin_epoch)
+ || (add_epoch != del_epoch)
+ || (add_updates != del_updates)) {
+
+ do_crm_log(log_level,
+ "Updated CIB generation %d.%d.%d to %d.%d.%d from client "
+ "%s%s%s (%s) (%s)",
+ del_admin_epoch, del_epoch, del_updates,
+ add_admin_epoch, add_epoch, add_updates,
+ client_name,
+ ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""),
+ pcmk__s(origin, "unspecified peer"), pcmk_strerror(result));
+
+ } else if ((add_admin_epoch != 0)
+ || (add_epoch != 0)
+ || (add_updates != 0)) {
+
+ do_crm_log(log_level,
+ "Local-only change to CIB generation %d.%d.%d from client "
+ "%s%s%s (%s) (%s)",
+ add_admin_epoch, add_epoch, add_updates,
+ client_name,
+ ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""),
+ pcmk__s(origin, "unspecified peer"), pcmk_strerror(result));
+ }
+
+ update_msg = create_xml_node(NULL, "notify");
+
+ crm_xml_add(update_msg, F_TYPE, T_CIB_NOTIFY);
+ crm_xml_add(update_msg, F_SUBTYPE, T_CIB_DIFF_NOTIFY);
+ crm_xml_add(update_msg, F_CIB_OPERATION, op);
+ crm_xml_add(update_msg, F_CIB_CLIENTID, client_id);
+ crm_xml_add(update_msg, F_CIB_CALLID, call_id);
+ crm_xml_add(update_msg, F_ORIG, origin);
+ crm_xml_add_int(update_msg, F_CIB_RC, result);
+
+ if (update != NULL) {
+ type = crm_element_name(update);
+ crm_trace("Setting type to update->name: %s", type);
+ } else {
+ type = crm_element_name(diff);
+ crm_trace("Setting type to new_obj->name: %s", type);
+ }
+ crm_xml_add(update_msg, F_CIB_OBJID, ID(diff));
+ crm_xml_add(update_msg, F_CIB_OBJTYPE, type);
+ attach_cib_generation(update_msg, "cib_generation", the_cib);
+
+ if (update != NULL) {
+ add_message_xml(update_msg, F_CIB_UPDATE, update);
+ }
+ add_message_xml(update_msg, F_CIB_UPDATE_RESULT, diff);
+
+ cib_notify_send(update_msg);
+ free_xml(update_msg);
+}
+
+void
+cib_replace_notify(const char *op, int result, const char *call_id,
+ const char *client_id, const char *client_name,
+ const char *origin, xmlNode *update, xmlNode *diff,
+ uint32_t change_section)
+{
+ xmlNode *replace_msg = NULL;
+
+ int add_updates = 0;
+ int add_epoch = 0;
+ int add_admin_epoch = 0;
+
+ int del_updates = 0;
+ int del_epoch = 0;
+ int del_admin_epoch = 0;
+
+ uint8_t log_level = LOG_INFO;
+
+ if (diff == NULL) {
+ return;
+ }
+
+ if (result != pcmk_ok) {
+ log_level = LOG_WARNING;
+ }
+
+ cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates,
+ &del_admin_epoch, &del_epoch, &del_updates);
+
+ if (del_updates < 0) {
+ crm_log_xml_debug(diff, "Bad replace diff");
+ }
+
+ if ((add_admin_epoch != del_admin_epoch)
+ || (add_epoch != del_epoch)
+ || (add_updates != del_updates)) {
+
+ do_crm_log(log_level,
+ "Replaced CIB generation %d.%d.%d with %d.%d.%d from client "
+ "%s%s%s (%s) (%s)",
+ del_admin_epoch, del_epoch, del_updates,
+ add_admin_epoch, add_epoch, add_updates,
+ client_name,
+ ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""),
+ pcmk__s(origin, "unspecified peer"), pcmk_strerror(result));
+
+ } else if ((add_admin_epoch != 0)
+ || (add_epoch != 0)
+ || (add_updates != 0)) {
+
+ do_crm_log(log_level,
+ "Local-only replace of CIB generation %d.%d.%d from client "
+ "%s%s%s (%s) (%s)",
+ add_admin_epoch, add_epoch, add_updates,
+ client_name,
+ ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""),
+ pcmk__s(origin, "unspecified peer"), pcmk_strerror(result));
+ }
+
+ replace_msg = create_xml_node(NULL, "notify-replace");
+
+ crm_xml_add(replace_msg, F_TYPE, T_CIB_NOTIFY);
+ crm_xml_add(replace_msg, F_SUBTYPE, T_CIB_REPLACE_NOTIFY);
+ crm_xml_add(replace_msg, F_CIB_OPERATION, op);
+ crm_xml_add(replace_msg, F_CIB_CLIENTID, client_id);
+ crm_xml_add(replace_msg, F_CIB_CALLID, call_id);
+ crm_xml_add(replace_msg, F_ORIG, origin);
+ crm_xml_add_int(replace_msg, F_CIB_RC, result);
+ crm_xml_add_ll(replace_msg, F_CIB_CHANGE_SECTION,
+ (long long) change_section);
+ attach_cib_generation(replace_msg, "cib-replace-generation", update);
+
+ /* We can include update and diff if a replace callback needs them. Until
+ * then, avoid the overhead.
+ */
+
+ crm_log_xml_trace(replace_msg, "CIB replaced");
+
+ cib_notify_send(replace_msg);
+ free_xml(replace_msg);
+}
diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c
new file mode 100644
index 0000000..38136d2
--- /dev/null
+++ b/daemons/based/based_remote.c
@@ -0,0 +1,680 @@
+/*
+ * Copyright 2004-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <crm/crm.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <inttypes.h> // PRIx64
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <netinet/ip.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/remote_internal.h>
+#include <crm/cib/internal.h>
+
+#include "pacemaker-based.h"
+
+/* #undef HAVE_PAM_PAM_APPL_H */
+/* #undef HAVE_GNUTLS_GNUTLS_H */
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+# include <gnutls/gnutls.h>
+#endif
+
+#include <pwd.h>
+#include <grp.h>
+#if HAVE_SECURITY_PAM_APPL_H
+# include <security/pam_appl.h>
+# define HAVE_PAM 1
+#else
+# if HAVE_PAM_PAM_APPL_H
+# include <pam/pam_appl.h>
+# define HAVE_PAM 1
+# endif
+#endif
+
+extern int remote_tls_fd;
+extern gboolean cib_shutdown_flag;
+
+int init_remote_listener(int port, gboolean encrypted);
+void cib_remote_connection_destroy(gpointer user_data);
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+gnutls_dh_params_t dh_params;
+gnutls_anon_server_credentials_t anon_cred_s;
+static void
+debug_log(int level, const char *str)
+{
+ fputs(str, stderr);
+}
+#endif
+
+#define REMOTE_AUTH_TIMEOUT 10000
+
+int num_clients;
+int authenticate_user(const char *user, const char *passwd);
+static int cib_remote_listen(gpointer data);
+static int cib_remote_msg(gpointer data);
+
+static void
+remote_connection_destroy(gpointer user_data)
+{
+ crm_info("No longer listening for remote connections");
+ return;
+}
+
+int
+init_remote_listener(int port, gboolean encrypted)
+{
+ int rc;
+ int *ssock = NULL;
+ struct sockaddr_in saddr;
+ int optval;
+
+ static struct mainloop_fd_callbacks remote_listen_fd_callbacks = {
+ .dispatch = cib_remote_listen,
+ .destroy = remote_connection_destroy,
+ };
+
+ if (port <= 0) {
+ /* don't start it */
+ return 0;
+ }
+
+ if (encrypted) {
+#ifndef HAVE_GNUTLS_GNUTLS_H
+ crm_warn("TLS support is not available");
+ return 0;
+#else
+ crm_notice("Starting TLS listener on port %d", port);
+ crm_gnutls_global_init();
+ /* gnutls_global_set_log_level (10); */
+ gnutls_global_set_log_function(debug_log);
+ if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) {
+ return -1;
+ }
+ gnutls_anon_allocate_server_credentials(&anon_cred_s);
+ gnutls_anon_set_server_dh_params(anon_cred_s, dh_params);
+#endif
+ } else {
+ crm_warn("Starting plain-text listener on port %d", port);
+ }
+#ifndef HAVE_PAM
+ crm_warn("PAM is _not_ enabled!");
+#endif
+
+ /* create server socket */
+ ssock = malloc(sizeof(int));
+ if(ssock == NULL) {
+ crm_perror(LOG_ERR, "Listener socket allocation failed");
+ return -1;
+ }
+
+ *ssock = socket(AF_INET, SOCK_STREAM, 0);
+ if (*ssock == -1) {
+ crm_perror(LOG_ERR, "Listener socket creation failed");
+ free(ssock);
+ return -1;
+ }
+
+ /* reuse address */
+ optval = 1;
+ rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+ if (rc < 0) {
+ crm_perror(LOG_WARNING,
+ "Local address reuse not allowed on listener socket");
+ }
+
+ /* bind server socket */
+ memset(&saddr, '\0', sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr.s_addr = INADDR_ANY;
+ saddr.sin_port = htons(port);
+ if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
+ crm_perror(LOG_ERR, "Cannot bind to listener socket");
+ close(*ssock);
+ free(ssock);
+ return -2;
+ }
+ if (listen(*ssock, 10) == -1) {
+ crm_perror(LOG_ERR, "Cannot listen on socket");
+ close(*ssock);
+ free(ssock);
+ return -3;
+ }
+
+ mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks);
+ crm_debug("Started listener on port %d", port);
+
+ return *ssock;
+}
+
+static int
+check_group_membership(const char *usr, const char *grp)
+{
+ int index = 0;
+ struct passwd *pwd = NULL;
+ struct group *group = NULL;
+
+ CRM_CHECK(usr != NULL, return FALSE);
+ CRM_CHECK(grp != NULL, return FALSE);
+
+ pwd = getpwnam(usr);
+ if (pwd == NULL) {
+ crm_err("No user named '%s' exists!", usr);
+ return FALSE;
+ }
+
+ group = getgrgid(pwd->pw_gid);
+ if (group != NULL && pcmk__str_eq(grp, group->gr_name, pcmk__str_none)) {
+ return TRUE;
+ }
+
+ group = getgrnam(grp);
+ if (group == NULL) {
+ crm_err("No group named '%s' exists!", grp);
+ return FALSE;
+ }
+
+ while (TRUE) {
+ char *member = group->gr_mem[index++];
+
+ if (member == NULL) {
+ break;
+
+ } else if (pcmk__str_eq(usr, member, pcmk__str_none)) {
+ return TRUE;
+ }
+ };
+
+ return FALSE;
+}
+
+static gboolean
+cib_remote_auth(xmlNode * login)
+{
+ const char *user = NULL;
+ const char *pass = NULL;
+ const char *tmp = NULL;
+
+ crm_log_xml_info(login, "Login: ");
+ if (login == NULL) {
+ return FALSE;
+ }
+
+ tmp = crm_element_name(login);
+ if (!pcmk__str_eq(tmp, "cib_command", pcmk__str_casei)) {
+ crm_err("Wrong tag: %s", tmp);
+ return FALSE;
+ }
+
+ tmp = crm_element_value(login, "op");
+ if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) {
+ crm_err("Wrong operation: %s", tmp);
+ return FALSE;
+ }
+
+ user = crm_element_value(login, "user");
+ pass = crm_element_value(login, "password");
+
+ if (!user || !pass) {
+ crm_err("missing auth credentials");
+ return FALSE;
+ }
+
+ /* Non-root daemons can only validate the password of the
+ * user they're running as
+ */
+ if (check_group_membership(user, CRM_DAEMON_GROUP) == FALSE) {
+ crm_err("User is not a member of the required group");
+ return FALSE;
+
+ } else if (authenticate_user(user, pass) == FALSE) {
+ crm_err("PAM auth failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+remote_auth_timeout_cb(gpointer data)
+{
+ pcmk__client_t *client = data;
+
+ client->remote->auth_timeout = 0;
+
+ if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
+ return FALSE;
+ }
+
+ mainloop_del_fd(client->remote->source);
+ crm_err("Remote client authentication timed out");
+
+ return FALSE;
+}
+
+static int
+cib_remote_listen(gpointer data)
+{
+ int csock = 0;
+ unsigned laddr;
+ struct sockaddr_storage addr;
+ char ipstr[INET6_ADDRSTRLEN];
+ int ssock = *(int *)data;
+ int rc;
+
+ pcmk__client_t *new_client = NULL;
+
+ static struct mainloop_fd_callbacks remote_client_fd_callbacks = {
+ .dispatch = cib_remote_msg,
+ .destroy = cib_remote_connection_destroy,
+ };
+
+ /* accept the connection */
+ laddr = sizeof(addr);
+ memset(&addr, 0, sizeof(addr));
+ csock = accept(ssock, (struct sockaddr *)&addr, &laddr);
+ if (csock == -1) {
+ crm_perror(LOG_ERR, "Could not accept socket connection");
+ return TRUE;
+ }
+
+ pcmk__sockaddr2str(&addr, ipstr);
+ crm_debug("New %s connection from %s",
+ ((ssock == remote_tls_fd)? "secure" : "clear-text"), ipstr);
+
+ rc = pcmk__set_nonblocking(csock);
+ if (rc != pcmk_rc_ok) {
+ crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d",
+ pcmk_rc_str(rc), rc);
+ close(csock);
+ return TRUE;
+ }
+
+ num_clients++;
+
+ new_client = pcmk__new_unauth_client(NULL);
+ new_client->remote = calloc(1, sizeof(pcmk__remote_t));
+
+ if (ssock == remote_tls_fd) {
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ pcmk__set_client_flags(new_client, pcmk__client_tls);
+
+ /* create gnutls session for the server socket */
+ new_client->remote->tls_session = pcmk__new_tls_session(csock,
+ GNUTLS_SERVER,
+ GNUTLS_CRD_ANON,
+ anon_cred_s);
+ if (new_client->remote->tls_session == NULL) {
+ close(csock);
+ return TRUE;
+ }
+#endif
+ } else {
+ pcmk__set_client_flags(new_client, pcmk__client_tcp);
+ new_client->remote->tcp_socket = csock;
+ }
+
+ // Require the client to authenticate within this time
+ new_client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
+ remote_auth_timeout_cb,
+ new_client);
+ crm_info("Remote CIB client pending authentication "
+ CRM_XS " %p id: %s", new_client, new_client->id);
+
+ new_client->remote->source =
+ mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client,
+ &remote_client_fd_callbacks);
+
+ return TRUE;
+}
+
+void
+cib_remote_connection_destroy(gpointer user_data)
+{
+ pcmk__client_t *client = user_data;
+ int csock = 0;
+
+ if (client == NULL) {
+ return;
+ }
+
+ crm_trace("Cleaning up after client %s disconnect",
+ pcmk__client_name(client));
+
+ num_clients--;
+ crm_trace("Num unfree'd clients: %d", num_clients);
+
+ switch (PCMK__CLIENT_TYPE(client)) {
+ case pcmk__client_tcp:
+ csock = client->remote->tcp_socket;
+ break;
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ case pcmk__client_tls:
+ if (client->remote->tls_session) {
+ void *sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session);
+
+ csock = GPOINTER_TO_INT(sock_ptr);
+ if (pcmk_is_set(client->flags,
+ pcmk__client_tls_handshake_complete)) {
+ gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_WR);
+ }
+ gnutls_deinit(*client->remote->tls_session);
+ gnutls_free(client->remote->tls_session);
+ client->remote->tls_session = NULL;
+ }
+ break;
+#endif
+ default:
+ crm_warn("Unknown transport for client %s "
+ CRM_XS " flags=%#016" PRIx64,
+ pcmk__client_name(client), client->flags);
+ }
+
+ if (csock > 0) {
+ close(csock);
+ }
+
+ pcmk__free_client(client);
+
+ crm_trace("Freed the cib client");
+
+ if (cib_shutdown_flag) {
+ cib_shutdown(0);
+ }
+ return;
+}
+
+static void
+cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command)
+{
+ const char *value = NULL;
+
+ value = crm_element_name(command);
+ if (!pcmk__str_eq(value, "cib_command", pcmk__str_casei)) {
+ crm_log_xml_trace(command, "Bad command: ");
+ return;
+ }
+
+ if (client->name == NULL) {
+ value = crm_element_value(command, F_CLIENTNAME);
+ if (value == NULL) {
+ client->name = strdup(client->id);
+ } else {
+ client->name = strdup(value);
+ }
+ }
+
+ /* unset dangerous options */
+ xml_remove_prop(command, F_ORIG);
+ xml_remove_prop(command, F_CIB_HOST);
+ xml_remove_prop(command, F_CIB_GLOBAL_UPDATE);
+
+ crm_xml_add(command, F_TYPE, T_CIB);
+ crm_xml_add(command, F_CIB_CLIENTID, client->id);
+ crm_xml_add(command, F_CIB_CLIENTNAME, client->name);
+ crm_xml_add(command, F_CIB_USER, client->user);
+
+ if (crm_element_value(command, F_CIB_CALLID) == NULL) {
+ char *call_uuid = crm_generate_uuid();
+
+ /* fix the command */
+ crm_xml_add(command, F_CIB_CALLID, call_uuid);
+ free(call_uuid);
+ }
+
+ if (crm_element_value(command, F_CIB_CALLOPTS) == NULL) {
+ crm_xml_add_int(command, F_CIB_CALLOPTS, 0);
+ }
+
+ crm_log_xml_trace(command, "Remote command: ");
+ cib_common_callback_worker(0, 0, command, client, TRUE);
+}
+
+static int
+cib_remote_msg(gpointer data)
+{
+ xmlNode *command = NULL;
+ pcmk__client_t *client = data;
+ int rc;
+ int timeout = 1000;
+
+ if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
+ timeout = -1;
+ }
+
+ crm_trace("Remote %s message received for client %s",
+ pcmk__client_type_str(PCMK__CLIENT_TYPE(client)),
+ pcmk__client_name(client));
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls)
+ && !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) {
+
+ int rc = pcmk__read_handshake_data(client);
+
+ if (rc == EAGAIN) {
+ /* No more data is available at the moment. Just return for now;
+ * we'll get invoked again once the client sends more.
+ */
+ return 0;
+ } else if (rc != pcmk_rc_ok) {
+ return -1;
+ }
+
+ crm_debug("TLS handshake with remote CIB client completed");
+ pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);
+ if (client->remote->auth_timeout) {
+ g_source_remove(client->remote->auth_timeout);
+ }
+
+ // Require the client to authenticate within this time
+ client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
+ remote_auth_timeout_cb,
+ client);
+ return 0;
+ }
+#endif
+
+ rc = pcmk__read_remote_message(client->remote, timeout);
+
+ /* must pass auth before we will process anything else */
+ if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) {
+ xmlNode *reg;
+ const char *user = NULL;
+
+ command = pcmk__remote_message_xml(client->remote);
+ if (cib_remote_auth(command) == FALSE) {
+ free_xml(command);
+ return -1;
+ }
+
+ crm_notice("Remote CIB client connection accepted");
+ pcmk__set_client_flags(client, pcmk__client_authenticated);
+ g_source_remove(client->remote->auth_timeout);
+ client->remote->auth_timeout = 0;
+ client->name = crm_element_value_copy(command, "name");
+
+ user = crm_element_value(command, "user");
+ if (user) {
+ client->user = strdup(user);
+ }
+
+ /* send ACK */
+ reg = create_xml_node(NULL, "cib_result");
+ crm_xml_add(reg, F_CIB_OPERATION, CRM_OP_REGISTER);
+ crm_xml_add(reg, F_CIB_CLIENTID, client->id);
+ pcmk__remote_send_xml(client->remote, reg);
+ free_xml(reg);
+ free_xml(command);
+ }
+
+ command = pcmk__remote_message_xml(client->remote);
+ while (command) {
+ crm_trace("Remote client message received");
+ cib_handle_remote_msg(client, command);
+ free_xml(command);
+ command = pcmk__remote_message_xml(client->remote);
+ }
+
+ if (rc == ENOTCONN) {
+ crm_trace("Remote CIB client disconnected while reading from it");
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_PAM
+static int
+construct_pam_passwd(int num_msg, const struct pam_message **msg,
+ struct pam_response **response, void *data)
+{
+ int count = 0;
+ struct pam_response *reply;
+ char *string = (char *)data;
+
+ CRM_CHECK(data, return PAM_CONV_ERR);
+ CRM_CHECK(num_msg == 1, return PAM_CONV_ERR); /* We only want to handle one message */
+
+ reply = calloc(1, sizeof(struct pam_response));
+ CRM_ASSERT(reply != NULL);
+
+ for (count = 0; count < num_msg; ++count) {
+ switch (msg[count]->msg_style) {
+ case PAM_TEXT_INFO:
+ crm_info("PAM: %s", msg[count]->msg);
+ break;
+ case PAM_PROMPT_ECHO_OFF:
+ case PAM_PROMPT_ECHO_ON:
+ reply[count].resp_retcode = 0;
+ reply[count].resp = string; /* We already made a copy */
+ break;
+ case PAM_ERROR_MSG:
+ /* In theory we'd want to print this, but then
+ * we see the password prompt in the logs
+ */
+ /* crm_err("PAM error: %s", msg[count]->msg); */
+ break;
+ default:
+ crm_err("Unhandled conversation type: %d", msg[count]->msg_style);
+ goto bail;
+ }
+ }
+
+ *response = reply;
+ reply = NULL;
+
+ return PAM_SUCCESS;
+
+ bail:
+ for (count = 0; count < num_msg; ++count) {
+ if (reply[count].resp != NULL) {
+ switch (msg[count]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ case PAM_PROMPT_ECHO_OFF:
+ /* Erase the data - it contained a password */
+ while (*(reply[count].resp)) {
+ *(reply[count].resp)++ = '\0';
+ }
+ free(reply[count].resp);
+ break;
+ }
+ reply[count].resp = NULL;
+ }
+ }
+ free(reply);
+ reply = NULL;
+
+ return PAM_CONV_ERR;
+}
+#endif
+
+int
+authenticate_user(const char *user, const char *passwd)
+{
+#ifndef HAVE_PAM
+ gboolean pass = TRUE;
+#else
+ int rc = 0;
+ gboolean pass = FALSE;
+ const void *p_user = NULL;
+
+ struct pam_conv p_conv;
+ struct pam_handle *pam_h = NULL;
+ static const char *pam_name = NULL;
+
+ if (pam_name == NULL) {
+ pam_name = getenv("CIB_pam_service");
+ }
+ if (pam_name == NULL) {
+ pam_name = "login";
+ }
+
+ p_conv.conv = construct_pam_passwd;
+ p_conv.appdata_ptr = strdup(passwd);
+
+ rc = pam_start(pam_name, user, &p_conv, &pam_h);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Could not initialize PAM: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+
+ rc = pam_authenticate(pam_h, 0);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Authentication failed for %s: %s (%d)", user, pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+
+ /* Make sure we authenticated the user we wanted to authenticate.
+ * Since we also run as non-root, it might be worth pre-checking
+ * the user has the same EID as us, since that the only user we
+ * can authenticate.
+ */
+ rc = pam_get_item(pam_h, PAM_USER, &p_user);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Internal PAM error: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+
+ } else if (p_user == NULL) {
+ crm_err("Unknown user authenticated.");
+ goto bail;
+
+ } else if (!pcmk__str_eq(p_user, user, pcmk__str_casei)) {
+ crm_err("User mismatch: %s vs. %s.", (const char *)p_user, (const char *)user);
+ goto bail;
+ }
+
+ rc = pam_acct_mgmt(pam_h, 0);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Access denied: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+ pass = TRUE;
+
+ bail:
+ pam_end(pam_h, rc);
+#endif
+ return pass;
+}
diff --git a/daemons/based/cib.pam b/daemons/based/cib.pam
new file mode 100644
index 0000000..5d0f655
--- /dev/null
+++ b/daemons/based/cib.pam
@@ -0,0 +1,6 @@
+# login: auth account password session
+# may require permission to read /etc/shadow
+auth include common-auth
+account include common-account
+password include common-password
+session include common-session
diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c
new file mode 100644
index 0000000..129997e
--- /dev/null
+++ b/daemons/based/pacemaker-based.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2004-2023 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 <libxml/parser.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.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;
+
+int remote_fd = 0;
+int remote_tls_fd = 0;
+
+GHashTable *config_hash = NULL;
+GHashTable *local_notify_queue = NULL;
+
+pcmk__output_t *logger_out = 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;
+}
+
+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,
+ "[metadata]");
+ 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;
+ }
+
+ rc = pcmk__log_output_new(&logger_out);
+ if (rc != pcmk_rc_ok) {
+ exit_code = CRM_EX_ERROR;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Error creating output format log: %s", pcmk_rc_str(rc));
+ goto done;
+ }
+ pcmk__output_set_log_level(logger_out, LOG_TRACE);
+
+ 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)) {
+ cib_metadata();
+ 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 (crm_ipc_connect(old_instance)) {
+ /* 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__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 = string2xml(data);
+ if (xml == NULL) {
+ crm_err("Invalid XML: '%.120s'", data);
+ free(data);
+ return;
+ }
+ crm_xml_add(xml, F_ORIG, from);
+ /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */
+ 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 (!crm_cluster_connect(crm_cluster)) {
+ 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, "remote-tls-port"), &port);
+ if (port >= 0) {
+ remote_tls_fd = init_remote_listener(port, TRUE);
+ }
+
+ pcmk__scan_port(crm_element_value(cib, "remote-clear-port"), &port);
+ if (port >= 0) {
+ remote_fd = init_remote_listener(port, FALSE);
+ }
+ }
+ return active;
+}
diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h
new file mode 100644
index 0000000..05e49b3
--- /dev/null
+++ b/daemons/based/pacemaker-based.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2004-2023 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 PACEMAKER_BASED__H
+# define PACEMAKER_BASED__H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/common/xml.h>
+#include <crm/cluster.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/mainloop.h>
+#include <crm/cib/internal.h>
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+# include <gnutls/gnutls.h>
+#endif
+
+// CIB-specific client flags
+enum cib_client_flags {
+ // Notifications
+ cib_notify_pre = (UINT64_C(1) << 0),
+ cib_notify_post = (UINT64_C(1) << 1),
+ cib_notify_replace = (UINT64_C(1) << 2),
+ cib_notify_confirm = (UINT64_C(1) << 3),
+ cib_notify_diff = (UINT64_C(1) << 4),
+
+ // Whether client is another cluster daemon
+ cib_is_daemon = (UINT64_C(1) << 12),
+};
+
+typedef struct cib_operation_s {
+ const char *operation;
+ gboolean modifies_cib;
+ gboolean needs_privileges;
+ int (*prepare) (xmlNode *, xmlNode **, const char **);
+ int (*cleanup) (int, xmlNode **, xmlNode **);
+ int (*fn) (const char *, int, const char *, xmlNode *,
+ xmlNode *, xmlNode *, xmlNode **, xmlNode **);
+} cib_operation_t;
+
+extern bool based_is_primary;
+extern GHashTable *config_hash;
+extern xmlNode *the_cib;
+extern crm_trigger_t *cib_writer;
+extern gboolean cib_writes_enabled;
+
+extern GMainLoop *mainloop;
+extern crm_cluster_t *crm_cluster;
+extern GHashTable *local_notify_queue;
+extern gboolean legacy_mode;
+extern gboolean stand_alone;
+extern gboolean cib_shutdown_flag;
+extern gchar *cib_root;
+extern int cib_status;
+extern pcmk__output_t *logger_out;
+
+extern struct qb_ipcs_service_handlers ipc_ro_callbacks;
+extern struct qb_ipcs_service_handlers ipc_rw_callbacks;
+extern qb_ipcs_service_t *ipcs_ro;
+extern qb_ipcs_service_t *ipcs_rw;
+extern qb_ipcs_service_t *ipcs_shm;
+
+void cib_peer_callback(xmlNode *msg, void *private_data);
+void cib_common_callback_worker(uint32_t id, uint32_t flags,
+ xmlNode *op_request, pcmk__client_t *cib_client,
+ gboolean privileged);
+void cib_shutdown(int nsig);
+void terminate_cib(const char *caller, int fast);
+gboolean cib_legacy_mode(void);
+
+gboolean uninitializeCib(void);
+xmlNode *readCibXmlFile(const char *dir, const char *file,
+ gboolean discard_status);
+int activateCibXml(xmlNode *doc, gboolean to_disk, const char *op);
+
+int cib_process_shutdown_req(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input,
+ xmlNode *existing_cib, xmlNode **result_cib,
+ xmlNode **answer);
+int cib_process_default(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_ping(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_readwrite(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_replace_svr(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_server_process_diff(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_sync(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_sync_one(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_delete_absolute(const char *op, int options,
+ const char *section, xmlNode *req,
+ xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer);
+int cib_process_upgrade_server(const char *op, int options, const char *section,
+ xmlNode *req, xmlNode *input,
+ xmlNode *existing_cib, xmlNode **result_cib,
+ xmlNode **answer);
+void send_sync_request(const char *host);
+int sync_our_cib(xmlNode *request, gboolean all);
+
+xmlNode *cib_msg_copy(xmlNode *msg, gboolean with_data);
+int cib_get_operation_id(const char *op, int *operation);
+cib_op_t *cib_op_func(int call_type);
+gboolean cib_op_modifies(int call_type);
+int cib_op_prepare(int call_type, xmlNode *request, xmlNode **input,
+ const char **section);
+int cib_op_cleanup(int call_type, int options, xmlNode **input,
+ xmlNode **output);
+int cib_op_can_run(int call_type, int call_options, bool privileged);
+void cib_diff_notify(const char *op, int result, const char *call_id,
+ const char *client_id, const char *client_name,
+ const char *origin, xmlNode *update, xmlNode *diff);
+void cib_replace_notify(const char *op, int result, const char *call_id,
+ const char *client_id, const char *client_name,
+ const char *origin, xmlNode *update, xmlNode *diff,
+ uint32_t change_section);
+
+static inline const char *
+cib_config_lookup(const char *opt)
+{
+ return g_hash_table_lookup(config_hash, opt);
+}
+
+#endif // PACEMAKER_BASED__H