summaryrefslogtreecommitdiffstats
path: root/lib/cib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cib')
-rw-r--r--lib/cib/Makefile.am28
-rw-r--r--lib/cib/cib_attrs.c732
-rw-r--r--lib/cib/cib_client.c750
-rw-r--r--lib/cib/cib_file.c919
-rw-r--r--lib/cib/cib_native.c502
-rw-r--r--lib/cib/cib_ops.c869
-rw-r--r--lib/cib/cib_remote.c638
-rw-r--r--lib/cib/cib_utils.c837
8 files changed, 5275 insertions, 0 deletions
diff --git a/lib/cib/Makefile.am b/lib/cib/Makefile.am
new file mode 100644
index 0000000..721fca1
--- /dev/null
+++ b/lib/cib/Makefile.am
@@ -0,0 +1,28 @@
+#
+# Copyright 2004-2018 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
+
+## libraries
+lib_LTLIBRARIES = libcib.la
+
+## SOURCES
+libcib_la_SOURCES = cib_ops.c cib_utils.c cib_client.c cib_native.c cib_attrs.c
+libcib_la_SOURCES += cib_file.c cib_remote.c
+
+libcib_la_LDFLAGS = -version-info 31:0:4
+libcib_la_CPPFLAGS = -I$(top_srcdir) $(AM_CPPFLAGS)
+
+libcib_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
+libcib_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
+
+libcib_la_LIBADD = $(top_builddir)/lib/pengine/libpe_rules.la \
+ $(top_builddir)/lib/common/libcrmcommon.la
+
+clean-generic:
+ rm -f *.log *.debug *.xml *~
diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c
new file mode 100644
index 0000000..5f3a722
--- /dev/null
+++ b/lib/cib/cib_attrs.c
@@ -0,0 +1,732 @@
+/*
+ * 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.
+ */
+
+#include <crm_internal.h>
+
+#include <sys/param.h>
+
+#include <crm/crm.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+#include <crm/common/output_internal.h>
+#include <crm/cib/internal.h>
+
+static pcmk__output_t *
+new_output_object(const char *ty)
+{
+ int rc = pcmk_rc_ok;
+ pcmk__output_t *out = NULL;
+ const char* argv[] = { "", NULL };
+ pcmk__supported_format_t formats[] = {
+ PCMK__SUPPORTED_FORMAT_LOG,
+ PCMK__SUPPORTED_FORMAT_TEXT,
+ { NULL, NULL, NULL }
+ };
+
+ pcmk__register_formats(NULL, formats);
+ rc = pcmk__output_new(&out, ty, NULL, (char**)argv);
+ if ((rc != pcmk_rc_ok) || (out == NULL)) {
+ crm_err("Can't out due to internal error: %s", pcmk_rc_str(rc));
+ return NULL;
+ }
+
+ return out;
+}
+
+static int
+find_attr(cib_t *cib, const char *section, const char *node_uuid,
+ const char *attr_set_type, const char *set_name, const char *attr_id,
+ const char *attr_name, const char *user_name, xmlNode **result)
+{
+ int rc = pcmk_rc_ok;
+
+ const char *xpath_base = NULL;
+ GString *xpath = NULL;
+ xmlNode *xml_search = NULL;
+ const char *set_type = NULL;
+ const char *node_type = NULL;
+
+ if (attr_set_type) {
+ set_type = attr_set_type;
+ } else {
+ set_type = XML_TAG_ATTR_SETS;
+ }
+
+ if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
+ node_uuid = NULL;
+ set_type = XML_CIB_TAG_PROPSET;
+
+ } else if (pcmk__strcase_any_of(section, XML_CIB_TAG_OPCONFIG, XML_CIB_TAG_RSCCONFIG,
+ NULL)) {
+ node_uuid = NULL;
+ set_type = XML_TAG_META_SETS;
+
+ } else if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
+ node_uuid = NULL;
+ section = XML_CIB_TAG_STATUS;
+ node_type = XML_CIB_TAG_TICKETS;
+
+ } else if (node_uuid == NULL) {
+ return EINVAL;
+ }
+
+ xpath_base = pcmk_cib_xpath_for(section);
+ if (xpath_base == NULL) {
+ crm_warn("%s CIB section not known", section);
+ return ENOMSG;
+ }
+
+ xpath = g_string_sized_new(1024);
+ g_string_append(xpath, xpath_base);
+
+ if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
+ pcmk__g_strcat(xpath, "//", node_type, NULL);
+
+ } else if (node_uuid) {
+ const char *node_type = XML_CIB_TAG_NODE;
+
+ if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
+ node_type = XML_CIB_TAG_STATE;
+ set_type = XML_TAG_TRANSIENT_NODEATTRS;
+ }
+ pcmk__g_strcat(xpath,
+ "//", node_type, "[@" XML_ATTR_ID "='", node_uuid, "']",
+ NULL);
+ }
+
+ pcmk__g_strcat(xpath, "//", set_type, NULL);
+ if (set_name) {
+ pcmk__g_strcat(xpath, "[@" XML_ATTR_ID "='", set_name, "']", NULL);
+ }
+
+ g_string_append(xpath, "//nvpair");
+
+ if (attr_id && attr_name) {
+ pcmk__g_strcat(xpath,
+ "[@" XML_ATTR_ID "='", attr_id, "' "
+ "and @" XML_ATTR_NAME "='", attr_name, "']", NULL);
+
+ } else if (attr_id) {
+ pcmk__g_strcat(xpath, "[@" XML_ATTR_ID "='", attr_id, "']", NULL);
+
+ } else if (attr_name) {
+ pcmk__g_strcat(xpath, "[@" XML_ATTR_NAME "='", attr_name, "']", NULL);
+ }
+
+ rc = cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, NULL,
+ (const char *) xpath->str, NULL, &xml_search,
+ cib_sync_call|cib_scope_local|cib_xpath, user_name);
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s, xpath=%s): %s",
+ attr_name, section, pcmk__s(node_uuid, "<null>"),
+ pcmk__s(set_name, "<null>"), (const char *) xpath->str,
+ pcmk_rc_str(rc));
+ } else {
+ rc = pcmk_rc_ok;
+ crm_log_xml_debug(xml_search, "Match");
+ }
+
+ g_string_free(xpath, TRUE);
+ *result = xml_search;
+ return rc;
+}
+
+static int
+handle_multiples(pcmk__output_t *out, xmlNode *search, const char *attr_name)
+{
+ if (xml_has_children(search)) {
+ xmlNode *child = NULL;
+ out->info(out, "Multiple attributes match name=%s", attr_name);
+
+ for (child = pcmk__xml_first_child(search); child != NULL;
+ child = pcmk__xml_next(child)) {
+ out->info(out, " Value: %s \t(id=%s)",
+ crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child));
+ }
+
+ return ENOTUNIQ;
+
+ } else {
+ return pcmk_rc_ok;
+ }
+}
+
+int
+cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section,
+ const char *node_uuid, const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, const char *attr_value,
+ const char *user_name, const char *node_type)
+{
+ const char *tag = NULL;
+ int rc = pcmk_rc_ok;
+ xmlNode *xml_top = NULL;
+ xmlNode *xml_obj = NULL;
+ xmlNode *xml_search = NULL;
+
+ char *local_attr_id = NULL;
+ char *local_set_name = NULL;
+
+ CRM_CHECK(section != NULL, return EINVAL);
+ CRM_CHECK(attr_value != NULL, return EINVAL);
+ CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL);
+
+ rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id,
+ attr_name, user_name, &xml_search);
+
+ if (rc == pcmk_rc_ok) {
+ if (handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) {
+ free_xml(xml_search);
+ return ENOTUNIQ;
+ } else {
+ pcmk__str_update(&local_attr_id, crm_element_value(xml_search, XML_ATTR_ID));
+ attr_id = local_attr_id;
+ free_xml(xml_search);
+ goto do_modify;
+ }
+
+ } else if (rc != ENXIO) {
+ free_xml(xml_search);
+ return rc;
+
+ /* } else if(attr_id == NULL) { */
+ /* return EINVAL; */
+
+ } else {
+ free_xml(xml_search);
+ crm_trace("%s does not exist, create it", attr_name);
+ if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
+ node_uuid = NULL;
+ section = XML_CIB_TAG_STATUS;
+ node_type = XML_CIB_TAG_TICKETS;
+
+ xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATUS);
+ xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
+
+ } else if (pcmk__str_eq(section, XML_CIB_TAG_NODES, pcmk__str_casei)) {
+
+ if (node_uuid == NULL) {
+ return EINVAL;
+ }
+
+ if (pcmk__str_eq(node_type, "remote", pcmk__str_casei)) {
+ xml_top = create_xml_node(xml_obj, XML_CIB_TAG_NODES);
+ xml_obj = create_xml_node(xml_top, XML_CIB_TAG_NODE);
+ crm_xml_add(xml_obj, XML_ATTR_TYPE, "remote");
+ crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid);
+ crm_xml_add(xml_obj, XML_ATTR_UNAME, node_uuid);
+ } else {
+ tag = XML_CIB_TAG_NODE;
+ }
+
+ } else if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
+ tag = XML_TAG_TRANSIENT_NODEATTRS;
+ if (node_uuid == NULL) {
+ return EINVAL;
+ }
+
+ xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATE);
+ crm_xml_add(xml_top, XML_ATTR_ID, node_uuid);
+ xml_obj = xml_top;
+
+ } else {
+ tag = section;
+ node_uuid = NULL;
+ }
+
+ if (set_name == NULL) {
+ if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
+ local_set_name = strdup(CIB_OPTIONS_FIRST);
+
+ } else if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
+ local_set_name = crm_strdup_printf("%s-%s", section,
+ XML_CIB_TAG_TICKETS);
+
+ } else if (node_uuid) {
+ local_set_name = crm_strdup_printf("%s-%s", section, node_uuid);
+
+ if (set_type) {
+ char *tmp_set_name = local_set_name;
+
+ local_set_name = crm_strdup_printf("%s-%s", tmp_set_name,
+ set_type);
+ free(tmp_set_name);
+ }
+ } else {
+ local_set_name = crm_strdup_printf("%s-options", section);
+ }
+ set_name = local_set_name;
+ }
+
+ if (attr_id == NULL) {
+ local_attr_id = crm_strdup_printf("%s-%s", set_name, attr_name);
+ crm_xml_sanitize_id(local_attr_id);
+ attr_id = local_attr_id;
+
+ } else if (attr_name == NULL) {
+ attr_name = attr_id;
+ }
+
+ crm_trace("Creating %s/%s", section, tag);
+ if (tag != NULL) {
+ xml_obj = create_xml_node(xml_obj, tag);
+ crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid);
+ if (xml_top == NULL) {
+ xml_top = xml_obj;
+ }
+ }
+
+ if (node_uuid == NULL && !pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) {
+ if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) {
+ xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_PROPSET);
+ } else {
+ xml_obj = create_xml_node(xml_obj, XML_TAG_META_SETS);
+ }
+
+ } else if (set_type) {
+ xml_obj = create_xml_node(xml_obj, set_type);
+
+ } else {
+ xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS);
+ }
+ crm_xml_add(xml_obj, XML_ATTR_ID, set_name);
+
+ if (xml_top == NULL) {
+ xml_top = xml_obj;
+ }
+ }
+
+ do_modify:
+ xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value);
+ if (xml_top == NULL) {
+ xml_top = xml_obj;
+ }
+
+ crm_log_xml_trace(xml_top, "update_attr");
+ rc = cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, xml_top,
+ NULL, call_options, user_name);
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+
+ out->err(out, "Error setting %s=%s (section=%s, set=%s): %s",
+ attr_name, attr_value, section, pcmk__s(set_name, "<null>"),
+ pcmk_rc_str(rc));
+ crm_log_xml_info(xml_top, "Update");
+ } else {
+ rc = pcmk_rc_ok;
+ }
+
+ free(local_set_name);
+ free(local_attr_id);
+ free_xml(xml_top);
+
+ return rc;
+}
+
+int
+cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section,
+ const char *node_uuid, const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, const char *user_name,
+ xmlNode **result)
+{
+ int rc = pcmk_rc_ok;
+
+ CRM_ASSERT(result != NULL);
+ CRM_CHECK(section != NULL, return EINVAL);
+
+ *result = NULL;
+
+ rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, attr_name,
+ user_name, result);
+
+ if (rc != pcmk_rc_ok) {
+ crm_trace("Query failed for attribute %s (section=%s node=%s set=%s): %s",
+ pcmk__s(attr_name, "with unspecified name"),
+ section, pcmk__s(set_name, "<null>"),
+ pcmk__s(node_uuid, "<null>"), pcmk_strerror(rc));
+ }
+
+ return rc;
+}
+
+int
+cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section,
+ const char *node_uuid, const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, const char *attr_value,
+ const char *user_name)
+{
+ int rc = pcmk_rc_ok;
+ xmlNode *xml_obj = NULL;
+ xmlNode *xml_search = NULL;
+ char *local_attr_id = NULL;
+
+ CRM_CHECK(section != NULL, return EINVAL);
+ CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL);
+
+ if (attr_id == NULL) {
+ rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id,
+ attr_name, user_name, &xml_search);
+
+ if (rc != pcmk_rc_ok || handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) {
+ free_xml(xml_search);
+ return rc;
+ } else {
+ pcmk__str_update(&local_attr_id, crm_element_value(xml_search, XML_ATTR_ID));
+ attr_id = local_attr_id;
+ free_xml(xml_search);
+ }
+ }
+
+ xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, attr_value);
+
+ rc = cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, xml_obj,
+ NULL, options, user_name);
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ } else {
+ rc = pcmk_rc_ok;
+ out->info(out, "Deleted %s %s: id=%s%s%s%s%s",
+ section, node_uuid ? "attribute" : "option", local_attr_id,
+ set_name ? " set=" : "", set_name ? set_name : "",
+ attr_name ? " name=" : "", attr_name ? attr_name : "");
+ }
+
+ free(local_attr_id);
+ free_xml(xml_obj);
+ return rc;
+}
+
+int
+find_nvpair_attr_delegate(cib_t *cib, const char *attr, const char *section,
+ const char *node_uuid, const char *attr_set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, gboolean to_console,
+ char **value, const char *user_name)
+{
+ pcmk__output_t *out = NULL;
+ xmlNode *xml_search = NULL;
+ int rc = pcmk_ok;
+
+ out = new_output_object(to_console ? "text" : "log");
+ if (out == NULL) {
+ return pcmk_err_generic;
+ }
+
+ rc = find_attr(cib, section, node_uuid, attr_set_type, set_name, attr_id,
+ attr_name, user_name, &xml_search);
+
+ if (rc == pcmk_rc_ok) {
+ rc = handle_multiples(out, xml_search, attr_name);
+
+ if (rc == pcmk_rc_ok) {
+ pcmk__str_update(value, crm_element_value(xml_search, attr));
+ }
+ }
+
+ out->finish(out, CRM_EX_OK, true, NULL);
+ free_xml(xml_search);
+ pcmk__output_free(out);
+ return pcmk_rc2legacy(rc);
+}
+
+int
+update_attr_delegate(cib_t *cib, int call_options, const char *section,
+ const char *node_uuid, const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, const char *attr_value,
+ gboolean to_console, const char *user_name, const char *node_type)
+{
+ pcmk__output_t *out = NULL;
+ int rc = pcmk_ok;
+
+ out = new_output_object(to_console ? "text" : "log");
+ if (out == NULL) {
+ return pcmk_err_generic;
+ }
+
+ rc = cib__update_node_attr(out, cib, call_options, section, node_uuid, set_type,
+ set_name, attr_id, attr_name, attr_value, user_name,
+ node_type);
+
+ out->finish(out, CRM_EX_OK, true, NULL);
+ pcmk__output_free(out);
+ return pcmk_rc2legacy(rc);
+}
+
+int
+read_attr_delegate(cib_t *cib, const char *section, const char *node_uuid,
+ const char *set_type, const char *set_name, const char *attr_id,
+ const char *attr_name, char **attr_value, gboolean to_console,
+ const char *user_name)
+{
+ pcmk__output_t *out = NULL;
+ xmlNode *result = NULL;
+ int rc = pcmk_ok;
+
+ out = new_output_object(to_console ? "text" : "log");
+ if (out == NULL) {
+ return pcmk_err_generic;
+ }
+
+ rc = cib__get_node_attrs(out, cib, section, node_uuid, set_type, set_name,
+ attr_id, attr_name, user_name, &result);
+
+ if (rc == pcmk_rc_ok) {
+ if (!xml_has_children(result)) {
+ pcmk__str_update(attr_value, crm_element_value(result, XML_NVPAIR_ATTR_VALUE));
+ } else {
+ rc = ENOTUNIQ;
+ }
+ }
+
+ out->finish(out, CRM_EX_OK, true, NULL);
+ free_xml(result);
+ pcmk__output_free(out);
+ return pcmk_rc2legacy(rc);
+}
+
+int
+delete_attr_delegate(cib_t *cib, int options, const char *section, const char *node_uuid,
+ const char *set_type, const char *set_name, const char *attr_id,
+ const char *attr_name, const char *attr_value, gboolean to_console,
+ const char *user_name)
+{
+ pcmk__output_t *out = NULL;
+ int rc = pcmk_ok;
+
+ out = new_output_object(to_console ? "text" : "log");
+ if (out == NULL) {
+ return pcmk_err_generic;
+ }
+
+ rc = cib__delete_node_attr(out, cib, options, section, node_uuid, set_type,
+ set_name, attr_id, attr_name, attr_value, user_name);
+
+ out->finish(out, CRM_EX_OK, true, NULL);
+ pcmk__output_free(out);
+ return pcmk_rc2legacy(rc);
+}
+
+/*!
+ * \internal
+ * \brief Parse node UUID from search result
+ *
+ * \param[in] result XML search result
+ * \param[out] uuid If non-NULL, where to store parsed UUID
+ * \param[out] is_remote If non-NULL, set TRUE if result is remote node
+ *
+ * \return pcmk_ok if UUID was successfully parsed, -ENXIO otherwise
+ */
+static int
+get_uuid_from_result(const xmlNode *result, char **uuid, int *is_remote)
+{
+ int rc = -ENXIO;
+ const char *tag;
+ const char *parsed_uuid = NULL;
+ int parsed_is_remote = FALSE;
+
+ if (result == NULL) {
+ return rc;
+ }
+
+ /* If there are multiple results, the first is sufficient */
+ tag = (const char *) (result->name);
+ if (pcmk__str_eq(tag, "xpath-query", pcmk__str_casei)) {
+ result = pcmk__xml_first_child(result);
+ CRM_CHECK(result != NULL, return rc);
+ tag = (const char *) (result->name);
+ }
+
+ if (pcmk__str_eq(tag, XML_CIB_TAG_NODE, pcmk__str_casei)) {
+ /* Result is <node> tag from <nodes> section */
+
+ if (pcmk__str_eq(crm_element_value(result, XML_ATTR_TYPE), "remote", pcmk__str_casei)) {
+ parsed_uuid = crm_element_value(result, XML_ATTR_UNAME);
+ parsed_is_remote = TRUE;
+ } else {
+ parsed_uuid = ID(result);
+ parsed_is_remote = FALSE;
+ }
+
+ } else if (pcmk__str_eq(tag, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) {
+ /* Result is <primitive> for ocf:pacemaker:remote resource */
+
+ parsed_uuid = ID(result);
+ parsed_is_remote = TRUE;
+
+ } else if (pcmk__str_eq(tag, XML_CIB_TAG_NVPAIR, pcmk__str_casei)) {
+ /* Result is remote-node parameter of <primitive> for guest node */
+
+ parsed_uuid = crm_element_value(result, XML_NVPAIR_ATTR_VALUE);
+ parsed_is_remote = TRUE;
+
+ } else if (pcmk__str_eq(tag, XML_CIB_TAG_STATE, pcmk__str_casei)) {
+ /* Result is <node_state> tag from <status> section */
+
+ parsed_uuid = crm_element_value(result, XML_ATTR_UNAME);
+ if (pcmk__xe_attr_is_true(result, XML_NODE_IS_REMOTE)) {
+ parsed_is_remote = TRUE;
+ }
+ }
+
+ if (parsed_uuid) {
+ if (uuid) {
+ *uuid = strdup(parsed_uuid);
+ }
+ if (is_remote) {
+ *is_remote = parsed_is_remote;
+ }
+ rc = pcmk_ok;
+ }
+
+ return rc;
+}
+
+/* Search string to find a node by name, as:
+ * - cluster or remote node in nodes section
+ * - remote node in resources section
+ * - guest node in resources section
+ * - orphaned remote node or bundle guest node in status section
+ */
+#define XPATH_UPPER_TRANS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define XPATH_LOWER_TRANS "abcdefghijklmnopqrstuvwxyz"
+#define XPATH_NODE \
+ "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \
+ "/" XML_CIB_TAG_NODE "[translate(@" XML_ATTR_UNAME ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
+ "|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
+ "/" XML_CIB_TAG_RESOURCE \
+ "[@class='ocf'][@provider='pacemaker'][@type='remote'][translate(@id,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
+ "|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \
+ "/" XML_CIB_TAG_RESOURCE "/" XML_TAG_META_SETS "/" XML_CIB_TAG_NVPAIR \
+ "[@name='" XML_RSC_ATTR_REMOTE_NODE "'][translate(@value,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \
+ "|/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS "/" XML_CIB_TAG_STATE \
+ "[@" XML_NODE_IS_REMOTE "='true'][translate(@" XML_ATTR_ID ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']"
+
+int
+query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node)
+{
+ int rc = pcmk_ok;
+ char *xpath_string;
+ xmlNode *xml_search = NULL;
+ char *host_lowercase = NULL;
+
+ CRM_ASSERT(uname != NULL);
+
+ host_lowercase = g_ascii_strdown(uname, -1);
+
+ if (uuid) {
+ *uuid = NULL;
+ }
+ if (is_remote_node) {
+ *is_remote_node = FALSE;
+ }
+
+ xpath_string = crm_strdup_printf(XPATH_NODE, host_lowercase, host_lowercase, host_lowercase, host_lowercase);
+ if (cib_internal_op(the_cib, PCMK__CIB_REQUEST_QUERY, NULL, xpath_string,
+ NULL, &xml_search,
+ cib_sync_call|cib_scope_local|cib_xpath,
+ NULL) == pcmk_ok) {
+ rc = get_uuid_from_result(xml_search, uuid, is_remote_node);
+ } else {
+ rc = -ENXIO;
+ }
+ free(xpath_string);
+ free_xml(xml_search);
+ g_free(host_lowercase);
+
+ if (rc != pcmk_ok) {
+ crm_debug("Could not map node name '%s' to a UUID: %s",
+ uname, pcmk_strerror(rc));
+ } else {
+ crm_info("Mapped node name '%s' to UUID %s", uname, (uuid? *uuid : ""));
+ }
+ return rc;
+}
+
+int
+query_node_uname(cib_t * the_cib, const char *uuid, char **uname)
+{
+ int rc = pcmk_ok;
+ xmlNode *a_child = NULL;
+ xmlNode *xml_obj = NULL;
+ xmlNode *fragment = NULL;
+ const char *child_name = NULL;
+
+ CRM_ASSERT(uname != NULL);
+ CRM_ASSERT(uuid != NULL);
+
+ rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment,
+ cib_sync_call | cib_scope_local);
+ if (rc != pcmk_ok) {
+ return rc;
+ }
+
+ xml_obj = fragment;
+ CRM_CHECK(pcmk__str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES, pcmk__str_casei),
+ return -ENOMSG);
+ CRM_ASSERT(xml_obj != NULL);
+ crm_log_xml_trace(xml_obj, "Result section");
+
+ rc = -ENXIO;
+ *uname = NULL;
+
+ for (a_child = pcmk__xml_first_child(xml_obj); a_child != NULL;
+ a_child = pcmk__xml_next(a_child)) {
+
+ if (pcmk__str_eq((const char *)a_child->name, XML_CIB_TAG_NODE,
+ pcmk__str_none)) {
+ child_name = ID(a_child);
+ if (pcmk__str_eq(uuid, child_name, pcmk__str_casei)) {
+ child_name = crm_element_value(a_child, XML_ATTR_UNAME);
+ if (child_name != NULL) {
+ *uname = strdup(child_name);
+ rc = pcmk_ok;
+ }
+ break;
+ }
+ }
+ }
+
+ free_xml(fragment);
+ return rc;
+}
+
+int
+set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value)
+{
+ int rc = pcmk_ok;
+ char *attr_id = NULL;
+
+ CRM_CHECK(uuid != NULL, return -EINVAL);
+ CRM_CHECK(standby_value != NULL, return -EINVAL);
+
+ if (pcmk__strcase_any_of(scope, "reboot", XML_CIB_TAG_STATUS, NULL)) {
+ scope = XML_CIB_TAG_STATUS;
+ attr_id = crm_strdup_printf("transient-standby-%.256s", uuid);
+
+ } else {
+ scope = XML_CIB_TAG_NODES;
+ attr_id = crm_strdup_printf("standby-%.256s", uuid);
+ }
+
+ rc = update_attr_delegate(the_cib, cib_sync_call, scope, uuid, NULL, NULL,
+ attr_id, "standby", standby_value, TRUE, NULL, NULL);
+
+ free(attr_id);
+ return rc;
+}
diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c
new file mode 100644
index 0000000..2d179e0
--- /dev/null
+++ b/lib/cib/cib_client.c
@@ -0,0 +1,750 @@
+/*
+ * Copyright 2004-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <pwd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+
+static GHashTable *cib_op_callback_table = NULL;
+
+#define op_common(cib) do { \
+ if(cib == NULL) { \
+ return -EINVAL; \
+ } else if(cib->delegate_fn == NULL) { \
+ return -EPROTONOSUPPORT; \
+ } \
+ } while(0)
+
+static int
+cib_client_set_op_callback(cib_t *cib,
+ void (*callback) (const xmlNode * msg, int call_id,
+ int rc, xmlNode * output))
+{
+ if (callback == NULL) {
+ crm_info("Un-Setting operation callback");
+
+ } else {
+ crm_trace("Setting operation callback");
+ }
+ cib->op_callback = callback;
+ return pcmk_ok;
+}
+
+static gint
+ciblib_GCompareFunc(gconstpointer a, gconstpointer b)
+{
+ int rc = 0;
+ const cib_notify_client_t *a_client = a;
+ const cib_notify_client_t *b_client = b;
+
+ CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
+ rc = strcmp(a_client->event, b_client->event);
+ if (rc == 0) {
+ if (a_client->callback == b_client->callback) {
+ return 0;
+ } else if (((long)a_client->callback) < ((long)b_client->callback)) {
+ crm_trace("callbacks for %s are not equal: %p < %p",
+ a_client->event, a_client->callback, b_client->callback);
+ return -1;
+ }
+ crm_trace("callbacks for %s are not equal: %p > %p",
+ a_client->event, a_client->callback, b_client->callback);
+ return 1;
+ }
+ return rc;
+}
+
+static int
+cib_client_add_notify_callback(cib_t * cib, const char *event,
+ void (*callback) (const char *event,
+ xmlNode * msg))
+{
+ GList *list_item = NULL;
+ cib_notify_client_t *new_client = NULL;
+
+ if ((cib->variant != cib_native) && (cib->variant != cib_remote)) {
+ return -EPROTONOSUPPORT;
+ }
+
+ crm_trace("Adding callback for %s events (%d)",
+ event, g_list_length(cib->notify_list));
+
+ new_client = calloc(1, sizeof(cib_notify_client_t));
+ new_client->event = event;
+ new_client->callback = callback;
+
+ list_item = g_list_find_custom(cib->notify_list, new_client,
+ ciblib_GCompareFunc);
+
+ if (list_item != NULL) {
+ crm_warn("Callback already present");
+ free(new_client);
+ return -EINVAL;
+
+ } else {
+ cib->notify_list = g_list_append(cib->notify_list, new_client);
+
+ cib->cmds->register_notification(cib, event, 1);
+
+ crm_trace("Callback added (%d)", g_list_length(cib->notify_list));
+ }
+ return pcmk_ok;
+}
+
+static int
+get_notify_list_event_count(cib_t *cib, const char *event)
+{
+ int count = 0;
+
+ for (GList *iter = g_list_first(cib->notify_list); iter != NULL;
+ iter = iter->next) {
+ cib_notify_client_t *client = (cib_notify_client_t *) iter->data;
+
+ if (strcmp(client->event, event) == 0) {
+ count++;
+ }
+ }
+ crm_trace("event(%s) count : %d", event, count);
+ return count;
+}
+
+static int
+cib_client_del_notify_callback(cib_t *cib, const char *event,
+ void (*callback) (const char *event,
+ xmlNode *msg))
+{
+ GList *list_item = NULL;
+ cib_notify_client_t *new_client = NULL;
+
+ if (cib->variant != cib_native && cib->variant != cib_remote) {
+ return -EPROTONOSUPPORT;
+ }
+
+ if (get_notify_list_event_count(cib, event) == 0) {
+ crm_debug("The callback of the event does not exist(%s)", event);
+ return pcmk_ok;
+ }
+
+ crm_debug("Removing callback for %s events", event);
+
+ new_client = calloc(1, sizeof(cib_notify_client_t));
+ new_client->event = event;
+ new_client->callback = callback;
+
+ list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
+
+ if (list_item != NULL) {
+ cib_notify_client_t *list_client = list_item->data;
+
+ cib->notify_list = g_list_remove(cib->notify_list, list_client);
+ free(list_client);
+
+ crm_trace("Removed callback");
+
+ } else {
+ crm_trace("Callback not present");
+ }
+
+ if (get_notify_list_event_count(cib, event) == 0) {
+ /* When there is not the registration of the event, the processing turns off a notice. */
+ cib->cmds->register_notification(cib, event, 0);
+ }
+
+ free(new_client);
+ return pcmk_ok;
+}
+
+static gboolean
+cib_async_timeout_handler(gpointer data)
+{
+ struct timer_rec_s *timer = data;
+
+ crm_debug("Async call %d timed out after %ds",
+ timer->call_id, timer->timeout);
+ cib_native_callback(timer->cib, NULL, timer->call_id, -ETIME);
+
+ // We remove the handler in remove_cib_op_callback()
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+cib_client_register_callback_full(cib_t *cib, int call_id, int timeout,
+ gboolean only_success, void *user_data,
+ const char *callback_name,
+ void (*callback)(xmlNode *, int, int,
+ xmlNode *, void *),
+ void (*free_func)(void *))
+{
+ cib_callback_client_t *blob = NULL;
+
+ if (call_id < 0) {
+ if (only_success == FALSE) {
+ callback(NULL, call_id, call_id, NULL, user_data);
+ } else {
+ crm_warn("CIB call failed: %s", pcmk_strerror(call_id));
+ }
+ if (user_data && free_func) {
+ free_func(user_data);
+ }
+ return FALSE;
+ }
+
+ blob = calloc(1, sizeof(cib_callback_client_t));
+ blob->id = callback_name;
+ blob->only_success = only_success;
+ blob->user_data = user_data;
+ blob->callback = callback;
+ blob->free_func = free_func;
+
+ if (timeout > 0) {
+ struct timer_rec_s *async_timer = NULL;
+
+ async_timer = calloc(1, sizeof(struct timer_rec_s));
+ blob->timer = async_timer;
+
+ async_timer->cib = cib;
+ async_timer->call_id = call_id;
+ async_timer->timeout = timeout * 1000;
+ async_timer->ref = g_timeout_add(async_timer->timeout,
+ cib_async_timeout_handler,
+ async_timer);
+ }
+
+ crm_trace("Adding callback %s for call %d", callback_name, call_id);
+ pcmk__intkey_table_insert(cib_op_callback_table, call_id, blob);
+
+ return TRUE;
+}
+
+static gboolean
+cib_client_register_callback(cib_t *cib, int call_id, int timeout,
+ gboolean only_success, void *user_data,
+ const char *callback_name,
+ void (*callback) (xmlNode *, int, int, xmlNode *,
+ void *))
+{
+ return cib_client_register_callback_full(cib, call_id, timeout,
+ only_success, user_data,
+ callback_name, callback, NULL);
+}
+
+static int
+cib_client_noop(cib_t * cib, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_NOOP, NULL, NULL, NULL, NULL,
+ call_options, NULL);
+}
+
+static int
+cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, call_options, NULL);
+}
+
+static int
+cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options)
+{
+ return cib->cmds->query_from(cib, NULL, section, output_data, call_options);
+}
+
+static int
+cib_client_query_from(cib_t * cib, const char *host, const char *section,
+ xmlNode ** output_data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, host, section, NULL,
+ output_data, call_options, NULL);
+}
+
+static int
+is_primary(cib_t *cib)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_IS_PRIMARY, NULL, NULL, NULL,
+ NULL, cib_scope_local|cib_sync_call, NULL);
+}
+
+static int
+set_secondary(cib_t *cib, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_SECONDARY, NULL, NULL, NULL,
+ NULL, call_options, NULL);
+}
+
+static int
+set_all_secondary(cib_t * cib, int call_options)
+{
+ return -EPROTONOSUPPORT;
+}
+
+static int
+set_primary(cib_t *cib, int call_options)
+{
+ op_common(cib);
+ crm_trace("Adding cib_scope_local to options");
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_PRIMARY, NULL, NULL, NULL,
+ NULL, call_options|cib_scope_local, NULL);
+}
+
+static int
+cib_client_bump_epoch(cib_t * cib, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_BUMP, NULL, NULL, NULL, NULL,
+ call_options, NULL);
+}
+
+static int
+cib_client_upgrade(cib_t * cib, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_UPGRADE, NULL, NULL, NULL,
+ NULL, call_options, NULL);
+}
+
+static int
+cib_client_sync(cib_t * cib, const char *section, int call_options)
+{
+ return cib->cmds->sync_from(cib, NULL, section, call_options);
+}
+
+static int
+cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC_TO_ALL, host, section,
+ NULL, NULL, call_options, NULL);
+}
+
+static int
+cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_CREATE, NULL, section, data,
+ NULL, call_options, NULL);
+}
+
+static int
+cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, data,
+ NULL, call_options, NULL);
+}
+
+static int
+cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_REPLACE, NULL, section, data,
+ NULL, call_options, NULL);
+}
+
+static int
+cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, data,
+ NULL, call_options, NULL);
+}
+
+static int
+cib_client_delete_absolute(cib_t * cib, const char *section, xmlNode * data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_ABS_DELETE, NULL, section,
+ data, NULL, call_options, NULL);
+}
+
+static int
+cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options)
+{
+ op_common(cib);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_ERASE, NULL, NULL, NULL,
+ output_data, call_options, NULL);
+}
+
+static void
+cib_destroy_op_callback(gpointer data)
+{
+ cib_callback_client_t *blob = data;
+
+ if (blob->timer && blob->timer->ref > 0) {
+ g_source_remove(blob->timer->ref);
+ }
+ free(blob->timer);
+
+ if (blob->user_data && blob->free_func) {
+ blob->free_func(blob->user_data);
+ }
+
+ free(blob);
+}
+
+static void
+destroy_op_callback_table(void)
+{
+ if (cib_op_callback_table != NULL) {
+ g_hash_table_destroy(cib_op_callback_table);
+ cib_op_callback_table = NULL;
+ }
+}
+
+char *
+get_shadow_file(const char *suffix)
+{
+ char *cib_home = NULL;
+ char *fullname = NULL;
+ char *name = crm_strdup_printf("shadow.%s", suffix);
+ const char *dir = getenv("CIB_shadow_dir");
+
+ if (dir == NULL) {
+ uid_t uid = geteuid();
+ struct passwd *pwent = getpwuid(uid);
+ const char *user = NULL;
+
+ if (pwent) {
+ user = pwent->pw_name;
+ } else {
+ user = getenv("USER");
+ crm_perror(LOG_ERR,
+ "Assuming %s because cannot get user details for user ID %d",
+ (user? user : "unprivileged user"), uid);
+ }
+
+ if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
+ dir = CRM_CONFIG_DIR;
+
+ } else {
+ const char *home = NULL;
+
+ if ((home = getenv("HOME")) == NULL) {
+ if (pwent) {
+ home = pwent->pw_dir;
+ }
+ }
+
+ dir = pcmk__get_tmpdir();
+ if (home && home[0] == '/') {
+ int rc = 0;
+
+ cib_home = crm_strdup_printf("%s/.cib", home);
+
+ rc = mkdir(cib_home, 0700);
+ if (rc < 0 && errno != EEXIST) {
+ crm_perror(LOG_ERR, "Couldn't create user-specific shadow directory: %s",
+ cib_home);
+ errno = 0;
+
+ } else {
+ dir = cib_home;
+ }
+ }
+ }
+ }
+
+ fullname = crm_strdup_printf("%s/%s", dir, name);
+ free(cib_home);
+ free(name);
+
+ return fullname;
+}
+
+cib_t *
+cib_shadow_new(const char *shadow)
+{
+ cib_t *new_cib = NULL;
+ char *shadow_file = NULL;
+
+ CRM_CHECK(shadow != NULL, return NULL);
+
+ shadow_file = get_shadow_file(shadow);
+ new_cib = cib_file_new(shadow_file);
+ free(shadow_file);
+
+ return new_cib;
+}
+
+/*!
+ * \brief Create a new CIB connection object, ignoring any active shadow CIB
+ *
+ * Create a new live, file, or remote CIB connection object based on the values
+ * of CIB-related environment variables (CIB_file, CIB_port, CIB_server,
+ * CIB_user, and CIB_passwd). The object will not be connected.
+ *
+ * \return Newly allocated CIB connection object
+ * \note The CIB API does not fully support opening multiple CIB connection
+ * objects simultaneously, so the returned object should be treated as a
+ * singleton.
+ */
+cib_t *
+cib_new_no_shadow(void)
+{
+ const char *shadow = getenv("CIB_shadow");
+ cib_t *cib = NULL;
+
+ unsetenv("CIB_shadow");
+ cib = cib_new();
+
+ if (shadow != NULL) {
+ setenv("CIB_shadow", shadow, 1);
+ }
+ return cib;
+}
+
+/*!
+ * \brief Create a new CIB connection object
+ *
+ * Create a new live, remote, file, or shadow file CIB connection object based
+ * on the values of CIB-related environment variables (CIB_shadow, CIB_file,
+ * CIB_port, CIB_server, CIB_user, and CIB_passwd). The object will not be
+ * connected.
+ *
+ * \return Newly allocated CIB connection object
+ * \note The CIB API does not fully support opening multiple CIB connection
+ * objects simultaneously, so the returned object should be treated as a
+ * singleton.
+ */
+/* @TODO Ensure all APIs support multiple simultaneous CIB connection objects
+ * (at least cib_free_callbacks() currently does not).
+ */
+cib_t *
+cib_new(void)
+{
+ const char *value = getenv("CIB_shadow");
+ int port;
+
+ if (value && value[0] != 0) {
+ return cib_shadow_new(value);
+ }
+
+ value = getenv("CIB_file");
+ if (value) {
+ return cib_file_new(value);
+ }
+
+ value = getenv("CIB_port");
+ if (value) {
+ gboolean encrypted = TRUE;
+ const char *server = getenv("CIB_server");
+ const char *user = getenv("CIB_user");
+ const char *pass = getenv("CIB_passwd");
+
+ /* We don't ensure port is valid (>= 0) because cib_new() currently
+ * can't return NULL in practice, and introducing a NULL return here
+ * could cause core dumps that would previously just cause signon()
+ * failures.
+ */
+ pcmk__scan_port(value, &port);
+
+ value = getenv("CIB_encrypted");
+ if (value && crm_is_true(value) == FALSE) {
+ crm_info("Disabling TLS");
+ encrypted = FALSE;
+ }
+
+ if (user == NULL) {
+ user = CRM_DAEMON_USER;
+ crm_info("Defaulting to user: %s", user);
+ }
+
+ if (server == NULL) {
+ server = "localhost";
+ crm_info("Defaulting to localhost");
+ }
+
+ return cib_remote_new(server, user, pass, port, encrypted);
+ }
+
+ return cib_native_new();
+}
+
+/*!
+ * \internal
+ * \brief Create a generic CIB connection instance
+ *
+ * \return Newly allocated and initialized cib_t instance
+ *
+ * \note This is called by each variant's cib_*_new() function before setting
+ * variant-specific values.
+ */
+cib_t *
+cib_new_variant(void)
+{
+ cib_t *new_cib = NULL;
+
+ new_cib = calloc(1, sizeof(cib_t));
+
+ if (new_cib == NULL) {
+ return NULL;
+ }
+
+ remove_cib_op_callback(0, TRUE); /* remove all */
+
+ new_cib->call_id = 1;
+ new_cib->variant = cib_undefined;
+
+ new_cib->type = cib_no_connection;
+ new_cib->state = cib_disconnected;
+
+ new_cib->op_callback = NULL;
+ new_cib->variant_opaque = NULL;
+ new_cib->notify_list = NULL;
+
+ /* the rest will get filled in by the variant constructor */
+ new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
+
+ if (new_cib->cmds == NULL) {
+ free(new_cib);
+ return NULL;
+ }
+
+ new_cib->cmds->set_op_callback = cib_client_set_op_callback;
+ new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
+ new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
+ new_cib->cmds->register_callback = cib_client_register_callback;
+ new_cib->cmds->register_callback_full = cib_client_register_callback_full;
+
+ new_cib->cmds->noop = cib_client_noop;
+ new_cib->cmds->ping = cib_client_ping;
+ new_cib->cmds->query = cib_client_query;
+ new_cib->cmds->sync = cib_client_sync;
+
+ new_cib->cmds->query_from = cib_client_query_from;
+ new_cib->cmds->sync_from = cib_client_sync_from;
+
+ new_cib->cmds->is_master = is_primary; // Deprecated method
+
+ new_cib->cmds->set_primary = set_primary;
+ new_cib->cmds->set_master = set_primary; // Deprecated method
+
+ new_cib->cmds->set_secondary = set_secondary;
+ new_cib->cmds->set_slave = set_secondary; // Deprecated method
+
+ new_cib->cmds->set_slave_all = set_all_secondary; // Deprecated method
+
+ new_cib->cmds->upgrade = cib_client_upgrade;
+ new_cib->cmds->bump_epoch = cib_client_bump_epoch;
+
+ new_cib->cmds->create = cib_client_create;
+ new_cib->cmds->modify = cib_client_modify;
+ new_cib->cmds->update = cib_client_modify; // Deprecated method
+ new_cib->cmds->replace = cib_client_replace;
+ new_cib->cmds->remove = cib_client_delete;
+ new_cib->cmds->erase = cib_client_erase;
+
+ new_cib->cmds->delete_absolute = cib_client_delete_absolute;
+
+ return new_cib;
+}
+
+void
+cib_free_notify(cib_t *cib)
+{
+
+ if (cib) {
+ GList *list = cib->notify_list;
+
+ while (list != NULL) {
+ cib_notify_client_t *client = g_list_nth_data(list, 0);
+
+ list = g_list_remove(list, client);
+ free(client);
+ }
+ cib->notify_list = NULL;
+ }
+}
+
+/*!
+ * \brief Free all callbacks for a CIB connection
+ *
+ * \param[in,out] cib CIB connection to clean up
+ */
+void
+cib_free_callbacks(cib_t *cib)
+{
+ cib_free_notify(cib);
+
+ destroy_op_callback_table();
+}
+
+/*!
+ * \brief Free all memory used by CIB connection
+ *
+ * \param[in,out] cib CIB connection to delete
+ */
+void
+cib_delete(cib_t *cib)
+{
+ cib_free_callbacks(cib);
+ if (cib) {
+ cib->cmds->free(cib);
+ }
+}
+
+void
+remove_cib_op_callback(int call_id, gboolean all_callbacks)
+{
+ if (all_callbacks) {
+ destroy_op_callback_table();
+ cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback);
+ } else {
+ pcmk__intkey_table_remove(cib_op_callback_table, call_id);
+ }
+}
+
+int
+num_cib_op_callbacks(void)
+{
+ if (cib_op_callback_table == NULL) {
+ return 0;
+ }
+ return g_hash_table_size(cib_op_callback_table);
+}
+
+static void
+cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
+{
+ int call = GPOINTER_TO_INT(key);
+ cib_callback_client_t *blob = value;
+
+ crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "without ID"));
+}
+
+void
+cib_dump_pending_callbacks(void)
+{
+ if (cib_op_callback_table == NULL) {
+ return;
+ }
+ return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
+}
+
+cib_callback_client_t*
+cib__lookup_id (int call_id)
+{
+ return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
+}
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
new file mode 100644
index 0000000..7d05965
--- /dev/null
+++ b/lib/cib/cib_file.c
@@ -0,0 +1,919 @@
+/*
+ * Original copyright 2004 International Business Machines
+ * Later changes 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 Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <pwd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+
+#define CIB_SERIES "cib"
+#define CIB_SERIES_MAX 100
+#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
+ created with hard links
+ */
+
+#define CIB_LIVE_NAME CIB_SERIES ".xml"
+
+enum cib_file_flags {
+ cib_file_flag_dirty = (1 << 0),
+ cib_file_flag_live = (1 << 1),
+};
+
+typedef struct cib_file_opaque_s {
+ uint32_t flags; // Group of enum cib_file_flags
+ char *filename;
+} cib_file_opaque_t;
+
+struct cib_func_entry {
+ const char *op;
+ gboolean read_only;
+ cib_op_t fn;
+};
+
+static struct cib_func_entry cib_file_ops[] = {
+ { PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query },
+ { PCMK__CIB_REQUEST_MODIFY, FALSE, cib_process_modify },
+ { PCMK__CIB_REQUEST_APPLY_PATCH, FALSE, cib_process_diff },
+ { PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump },
+ { PCMK__CIB_REQUEST_REPLACE, FALSE, cib_process_replace },
+ { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create },
+ { PCMK__CIB_REQUEST_DELETE, FALSE, cib_process_delete },
+ { PCMK__CIB_REQUEST_ERASE, FALSE, cib_process_erase },
+ { PCMK__CIB_REQUEST_UPGRADE, FALSE, cib_process_upgrade },
+};
+
+static xmlNode *in_mem_cib = NULL;
+
+/* cib_file_backup() and cib_file_write_with_digest() need to chown the
+ * written files only in limited circumstances, so these variables allow
+ * that to be indicated without affecting external callers
+ */
+static uid_t cib_file_owner = 0;
+static uid_t cib_file_group = 0;
+static gboolean cib_do_chown = FALSE;
+
+#define cib_set_file_flags(cibfile, flags_to_set) do { \
+ (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "CIB file", \
+ cibfile->filename, \
+ (cibfile)->flags, \
+ (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define cib_clear_file_flags(cibfile, flags_to_clear) do { \
+ (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "CIB file", \
+ cibfile->filename, \
+ (cibfile)->flags, \
+ (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+/*!
+ * \internal
+ * \brief Check whether a file is the live CIB
+ *
+ * \param[in] filename Name of file to check
+ *
+ * \return TRUE if file exists and its real path is same as live CIB's
+ */
+static gboolean
+cib_file_is_live(const char *filename)
+{
+ gboolean same = FALSE;
+
+ if (filename != NULL) {
+ // Canonicalize file names for true comparison
+ char *real_filename = NULL;
+
+ if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
+ char *real_livename = NULL;
+
+ if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
+ &real_livename) == pcmk_rc_ok) {
+ same = !strcmp(real_filename, real_livename);
+ free(real_livename);
+ }
+ free(real_filename);
+ }
+ }
+ return same;
+}
+
+static int
+cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
+ const char *section, xmlNode *data,
+ xmlNode **output_data, int call_options,
+ const char *user_name)
+{
+ int rc = pcmk_ok;
+ char *effective_user = NULL;
+ gboolean query = FALSE;
+ gboolean changed = FALSE;
+ xmlNode *request = NULL;
+ xmlNode *output = NULL;
+ xmlNode *cib_diff = NULL;
+ xmlNode *result_cib = NULL;
+ cib_op_t *fn = NULL;
+ int lpc = 0;
+ static int max_msg_types = PCMK__NELEM(cib_file_ops);
+ cib_file_opaque_t *private = cib->variant_opaque;
+
+ crm_info("Handling %s operation for %s as %s",
+ (op? op : "invalid"), (section? section : "entire CIB"),
+ (user_name? user_name : "default user"));
+
+ cib__set_call_options(call_options, "file operation",
+ cib_no_mtime|cib_inhibit_bcast|cib_scope_local);
+
+ if (cib->state == cib_disconnected) {
+ return -ENOTCONN;
+ }
+
+ if (output_data != NULL) {
+ *output_data = NULL;
+ }
+
+ if (op == NULL) {
+ return -EINVAL;
+ }
+
+ for (lpc = 0; lpc < max_msg_types; lpc++) {
+ if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) {
+ fn = &(cib_file_ops[lpc].fn);
+ query = cib_file_ops[lpc].read_only;
+ break;
+ }
+ }
+
+ if (fn == NULL) {
+ return -EPROTONOSUPPORT;
+ }
+
+ cib->call_id++;
+ request = cib_create_op(cib->call_id, op, host, section, data, call_options,
+ user_name);
+ if(user_name) {
+ crm_xml_add(request, XML_ACL_TAG_USER, user_name);
+ }
+
+ /* Mirror the logic in cib_prepare_common() */
+ 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);
+ }
+
+ rc = cib_perform_op(op, call_options, fn, query,
+ section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff,
+ &output);
+
+ free_xml(request);
+ if (rc == -pcmk_err_schema_validation) {
+ validate_xml_verbose(result_cib);
+ }
+
+ if (rc != pcmk_ok) {
+ free_xml(result_cib);
+
+ } else if (query == FALSE) {
+ pcmk__output_t *out = NULL;
+
+ rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
+ CRM_CHECK(rc == pcmk_ok, goto done);
+
+ pcmk__output_set_log_level(out, LOG_DEBUG);
+ rc = out->message(out, "xml-patchset", cib_diff);
+ out->finish(out, pcmk_rc2exitc(rc), true, NULL);
+ pcmk__output_free(out);
+ rc = pcmk_ok;
+
+ free_xml(in_mem_cib);
+ in_mem_cib = result_cib;
+ cib_set_file_flags(private, cib_file_flag_dirty);
+ }
+
+ if (cib->op_callback != NULL) {
+ cib->op_callback(NULL, cib->call_id, rc, output);
+ }
+
+ if ((output_data != NULL) && (output != NULL)) {
+ *output_data = (output == in_mem_cib)? copy_xml(output) : output;
+ }
+
+done:
+ free_xml(cib_diff);
+
+ if ((output_data == NULL) && (output != in_mem_cib)) {
+ /* Don't free output if we're still using it. (output_data != NULL)
+ * means we may have assigned *output_data = output above.
+ */
+ free_xml(output);
+ }
+ free(effective_user);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Read CIB from disk and validate it against XML schema
+ *
+ * \param[in] filename Name of file to read CIB from
+ *
+ * \return pcmk_ok on success,
+ * -ENXIO if file does not exist (or stat() otherwise fails), or
+ * -pcmk_err_schema_validation if XML doesn't parse or validate
+ * \note If filename is the live CIB, this will *not* verify its digest,
+ * though that functionality would be trivial to add here.
+ * Also, this will *not* verify that the file is writable,
+ * because some callers might not need to write.
+ */
+static int
+load_file_cib(const char *filename)
+{
+ struct stat buf;
+ xmlNode *root = NULL;
+
+ /* Ensure file is readable */
+ if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
+ return -ENXIO;
+ }
+
+ /* Parse XML from file */
+ root = filename2xml(filename);
+ if (root == NULL) {
+ return -pcmk_err_schema_validation;
+ }
+
+ /* Add a status section if not already present */
+ if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) {
+ create_xml_node(root, XML_CIB_TAG_STATUS);
+ }
+
+ /* Validate XML against its specified schema */
+ if (validate_xml(root, NULL, TRUE) == FALSE) {
+ const char *schema = crm_element_value(root, XML_ATTR_VALIDATION);
+
+ crm_err("CIB does not validate against %s", schema);
+ free_xml(root);
+ return -pcmk_err_schema_validation;
+ }
+
+ /* Remember the parsed XML for later use */
+ in_mem_cib = root;
+ return pcmk_ok;
+}
+
+static int
+cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
+{
+ int rc = pcmk_ok;
+ cib_file_opaque_t *private = cib->variant_opaque;
+
+ if (private->filename == NULL) {
+ rc = -EINVAL;
+ } else {
+ rc = load_file_cib(private->filename);
+ }
+
+ if (rc == pcmk_ok) {
+ crm_debug("Opened connection to local file '%s' for %s",
+ private->filename, name);
+ cib->state = cib_connected_command;
+ cib->type = cib_command;
+
+ } else {
+ crm_info("Connection to local file '%s' for %s failed: %s\n",
+ private->filename, name, pcmk_strerror(rc));
+ }
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Write out the in-memory CIB to a live CIB file
+ *
+ * param[in,out] path Full path to file to write
+ *
+ * \return 0 on success, -1 on failure
+ */
+static int
+cib_file_write_live(char *path)
+{
+ uid_t uid = geteuid();
+ struct passwd *daemon_pwent;
+ char *sep = strrchr(path, '/');
+ const char *cib_dirname, *cib_filename;
+ int rc = 0;
+
+ /* Get the desired uid/gid */
+ errno = 0;
+ daemon_pwent = getpwnam(CRM_DAEMON_USER);
+ if (daemon_pwent == NULL) {
+ crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
+ return -1;
+ }
+
+ /* If we're root, we can change the ownership;
+ * if we're daemon, anything we create will be OK;
+ * otherwise, block access so we don't create wrong owner
+ */
+ if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
+ crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
+ CRM_DAEMON_USER);
+ return 0;
+ }
+
+ /* fancy footwork to separate dirname from filename
+ * (we know the canonical name maps to the live CIB,
+ * but the given name might be relative, or symlinked)
+ */
+ if (sep == NULL) { /* no directory component specified */
+ cib_dirname = "./";
+ cib_filename = path;
+ } else if (sep == path) { /* given name is in / */
+ cib_dirname = "/";
+ cib_filename = path + 1;
+ } else { /* typical case; split given name into parts */
+ *sep = '\0';
+ cib_dirname = path;
+ cib_filename = sep + 1;
+ }
+
+ /* if we're root, we want to update the file ownership */
+ if (uid == 0) {
+ cib_file_owner = daemon_pwent->pw_uid;
+ cib_file_group = daemon_pwent->pw_gid;
+ cib_do_chown = TRUE;
+ }
+
+ /* write the file */
+ if (cib_file_write_with_digest(in_mem_cib, cib_dirname,
+ cib_filename) != pcmk_ok) {
+ rc = -1;
+ }
+
+ /* turn off file ownership changes, for other callers */
+ if (uid == 0) {
+ cib_do_chown = FALSE;
+ }
+
+ /* undo fancy stuff */
+ if ((sep != NULL) && (*sep == '\0')) {
+ *sep = '/';
+ }
+
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Sign-off method for CIB file variants
+ *
+ * This will write the file to disk if needed, and free the in-memory CIB. If
+ * the file is the live CIB, it will compute and write a signature as well.
+ *
+ * \param[in,out] cib CIB object to sign off
+ *
+ * \return pcmk_ok on success, pcmk_err_generic on failure
+ * \todo This method should refuse to write the live CIB if the CIB manager is
+ * running.
+ */
+static int
+cib_file_signoff(cib_t *cib)
+{
+ int rc = pcmk_ok;
+ cib_file_opaque_t *private = cib->variant_opaque;
+
+ crm_debug("Disconnecting from the CIB manager");
+ cib->state = cib_disconnected;
+ cib->type = cib_no_connection;
+
+ /* If the in-memory CIB has been changed, write it to disk */
+ if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
+
+ /* If this is the live CIB, write it out with a digest */
+ if (pcmk_is_set(private->flags, cib_file_flag_live)) {
+ if (cib_file_write_live(private->filename) < 0) {
+ rc = pcmk_err_generic;
+ }
+
+ /* Otherwise, it's a simple write */
+ } else {
+ gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2");
+
+ if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) {
+ rc = pcmk_err_generic;
+ }
+ }
+
+ if (rc == pcmk_ok) {
+ crm_info("Wrote CIB to %s", private->filename);
+ cib_clear_file_flags(private, cib_file_flag_dirty);
+ } else {
+ crm_err("Could not write CIB to %s", private->filename);
+ }
+ }
+
+ /* Free the in-memory CIB */
+ free_xml(in_mem_cib);
+ in_mem_cib = NULL;
+ return rc;
+}
+
+static int
+cib_file_free(cib_t *cib)
+{
+ int rc = pcmk_ok;
+
+ if (cib->state != cib_disconnected) {
+ rc = cib_file_signoff(cib);
+ }
+
+ if (rc == pcmk_ok) {
+ cib_file_opaque_t *private = cib->variant_opaque;
+
+ free(private->filename);
+ free(cib->cmds);
+ free(private);
+ free(cib);
+
+ } else {
+ fprintf(stderr, "Couldn't sign off: %d\n", rc);
+ }
+
+ return rc;
+}
+
+static int
+cib_file_inputfd(cib_t *cib)
+{
+ return -EPROTONOSUPPORT;
+}
+
+static int
+cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
+{
+ return -EPROTONOSUPPORT;
+}
+
+static int
+cib_file_set_connection_dnotify(cib_t *cib,
+ void (*dnotify) (gpointer user_data))
+{
+ return -EPROTONOSUPPORT;
+}
+
+/*!
+ * \internal
+ * \brief Get the given CIB connection's unique client identifier
+ *
+ * \param[in] cib CIB connection
+ * \param[out] async_id If not \p NULL, where to store asynchronous client ID
+ * \param[out] sync_id If not \p NULL, where to store synchronous client ID
+ *
+ * \return Legacy Pacemaker return code (specifically, \p -EPROTONOSUPPORT)
+ *
+ * \note This is the \p cib_file variant implementation of
+ * \p cib_api_operations_t:client_id().
+ * \note A \p cib_file object doesn't connect to the CIB and is never assigned a
+ * client ID.
+ */
+static int
+cib_file_client_id(const cib_t *cib, const char **async_id,
+ const char **sync_id)
+{
+ if (async_id != NULL) {
+ *async_id = NULL;
+ }
+ if (sync_id != NULL) {
+ *sync_id = NULL;
+ }
+ return -EPROTONOSUPPORT;
+}
+
+cib_t *
+cib_file_new(const char *cib_location)
+{
+ cib_file_opaque_t *private = NULL;
+ cib_t *cib = cib_new_variant();
+
+ if (cib == NULL) {
+ return NULL;
+ }
+
+ private = calloc(1, sizeof(cib_file_opaque_t));
+
+ if (private == NULL) {
+ free(cib);
+ return NULL;
+ }
+
+ cib->variant = cib_file;
+ cib->variant_opaque = private;
+
+ if (cib_location == NULL) {
+ cib_location = getenv("CIB_file");
+ CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible
+ }
+ private->flags = 0;
+ if (cib_file_is_live(cib_location)) {
+ cib_set_file_flags(private, cib_file_flag_live);
+ crm_trace("File %s detected as live CIB", cib_location);
+ }
+ private->filename = strdup(cib_location);
+
+ /* assign variant specific ops */
+ cib->delegate_fn = cib_file_perform_op_delegate;
+ cib->cmds->signon = cib_file_signon;
+ cib->cmds->signoff = cib_file_signoff;
+ cib->cmds->free = cib_file_free;
+ cib->cmds->inputfd = cib_file_inputfd;
+
+ cib->cmds->register_notification = cib_file_register_notification;
+ cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
+
+ cib->cmds->client_id = cib_file_client_id;
+
+ return cib;
+}
+
+/*!
+ * \internal
+ * \brief Compare the calculated digest of an XML tree against a signature file
+ *
+ * \param[in] root Root of XML tree to compare
+ * \param[in] sigfile Name of signature file containing digest to compare
+ *
+ * \return TRUE if digests match or signature file does not exist, else FALSE
+ */
+static gboolean
+cib_file_verify_digest(xmlNode *root, const char *sigfile)
+{
+ gboolean passed = FALSE;
+ char *expected;
+ int rc = pcmk__file_contents(sigfile, &expected);
+
+ switch (rc) {
+ case pcmk_rc_ok:
+ if (expected == NULL) {
+ crm_err("On-disk digest at %s is empty", sigfile);
+ return FALSE;
+ }
+ break;
+ case ENOENT:
+ crm_warn("No on-disk digest present at %s", sigfile);
+ return TRUE;
+ default:
+ crm_err("Could not read on-disk digest from %s: %s",
+ sigfile, pcmk_rc_str(rc));
+ return FALSE;
+ }
+ passed = pcmk__verify_digest(root, expected);
+ free(expected);
+ return passed;
+}
+
+/*!
+ * \internal
+ * \brief Read an XML tree from a file and verify its digest
+ *
+ * \param[in] filename Name of XML file to read
+ * \param[in] sigfile Name of signature file containing digest to compare
+ * \param[out] root If non-NULL, will be set to pointer to parsed XML tree
+ *
+ * \return 0 if file was successfully read, parsed and verified, otherwise:
+ * -errno on stat() failure,
+ * -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
+ * -pcmk_err_cib_modified if digests do not match
+ * \note If root is non-NULL, it is the caller's responsibility to free *root on
+ * successful return.
+ */
+int
+cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
+{
+ int s_res;
+ struct stat buf;
+ char *local_sigfile = NULL;
+ xmlNode *local_root = NULL;
+
+ CRM_ASSERT(filename != NULL);
+ if (root) {
+ *root = NULL;
+ }
+
+ /* Verify that file exists and its size is nonzero */
+ s_res = stat(filename, &buf);
+ if (s_res < 0) {
+ crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
+ return -errno;
+ } else if (buf.st_size == 0) {
+ crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
+ return -pcmk_err_cib_corrupt;
+ }
+
+ /* Parse XML */
+ local_root = filename2xml(filename);
+ if (local_root == NULL) {
+ crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
+ return -pcmk_err_cib_corrupt;
+ }
+
+ /* If sigfile is not specified, use original file name plus .sig */
+ if (sigfile == NULL) {
+ sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
+ }
+
+ /* Verify that digests match */
+ if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
+ free(local_sigfile);
+ free_xml(local_root);
+ return -pcmk_err_cib_modified;
+ }
+
+ free(local_sigfile);
+ if (root) {
+ *root = local_root;
+ } else {
+ free_xml(local_root);
+ }
+ return pcmk_ok;
+}
+
+/*!
+ * \internal
+ * \brief Back up a CIB
+ *
+ * \param[in] cib_dirname Directory containing CIB file and backups
+ * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
+ *
+ * \return 0 on success, -1 on error
+ */
+static int
+cib_file_backup(const char *cib_dirname, const char *cib_filename)
+{
+ int rc = 0;
+ unsigned int seq;
+ char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
+ char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
+ char *backup_path;
+ char *backup_digest;
+
+ // Determine backup and digest file names
+ if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
+ &seq) != pcmk_rc_ok) {
+ // @TODO maybe handle errors better ...
+ seq = 0;
+ }
+ backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
+ CIB_SERIES_BZIP);
+ backup_digest = crm_strdup_printf("%s.sig", backup_path);
+
+ /* Remove the old backups if they exist */
+ unlink(backup_path);
+ unlink(backup_digest);
+
+ /* Back up the CIB, by hard-linking it to the backup name */
+ if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
+ crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
+ cib_path, backup_path);
+ rc = -1;
+
+ /* Back up the CIB signature similarly */
+ } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
+ crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
+ cib_digest, backup_digest);
+ rc = -1;
+
+ /* Update the last counter and ensure everything is sync'd to media */
+ } else {
+ pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
+ CIB_SERIES_MAX);
+ if (cib_do_chown) {
+ int rc2;
+
+ if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
+ && (errno != ENOENT)) {
+ crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
+ rc = -1;
+ }
+ if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
+ && (errno != ENOENT)) {
+ crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
+ rc = -1;
+ }
+ rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
+ cib_file_owner, cib_file_group);
+ if (rc2 != pcmk_rc_ok) {
+ crm_err("Could not set owner of sequence file in %s: %s",
+ cib_dirname, pcmk_rc_str(rc2));
+ rc = -1;
+ }
+ }
+ pcmk__sync_directory(cib_dirname);
+ crm_info("Archived previous version as %s", backup_path);
+ }
+
+ free(cib_path);
+ free(cib_digest);
+ free(backup_path);
+ free(backup_digest);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Prepare CIB XML to be written to disk
+ *
+ * Set num_updates to 0, set cib-last-written to the current timestamp,
+ * and strip out the status section.
+ *
+ * \param[in,out] root Root of CIB XML tree
+ *
+ * \return void
+ */
+static void
+cib_file_prepare_xml(xmlNode *root)
+{
+ xmlNode *cib_status_root = NULL;
+
+ /* Always write out with num_updates=0 and current last-written timestamp */
+ crm_xml_add(root, XML_ATTR_NUMUPDATES, "0");
+ pcmk__xe_add_last_written(root);
+
+ /* Delete status section before writing to file, because
+ * we discard it on startup anyway, and users get confused by it */
+ cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE);
+ CRM_LOG_ASSERT(cib_status_root != NULL);
+ if (cib_status_root != NULL) {
+ free_xml(cib_status_root);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Write CIB to disk, along with a signature file containing its digest
+ *
+ * \param[in,out] cib_root Root of XML tree to write
+ * \param[in] cib_dirname Directory containing CIB and signature files
+ * \param[in] cib_filename Name (relative to cib_dirname) of file to write
+ *
+ * \return pcmk_ok on success,
+ * pcmk_err_cib_modified if existing cib_filename doesn't match digest,
+ * pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
+ * or pcmk_err_cib_save if new cib_filename couldn't be saved
+ */
+int
+cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
+ const char *cib_filename)
+{
+ int exit_rc = pcmk_ok;
+ int rc, fd;
+ char *digest = NULL;
+
+ /* Detect CIB version for diagnostic purposes */
+ const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION);
+ const char *admin_epoch = crm_element_value(cib_root,
+ XML_ATTR_GENERATION_ADMIN);
+
+ /* Determine full CIB and signature pathnames */
+ char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
+ char *digest_path = crm_strdup_printf("%s.sig", cib_path);
+
+ /* Create temporary file name patterns for writing out CIB and signature */
+ char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
+ char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
+
+ CRM_ASSERT((cib_path != NULL) && (digest_path != NULL)
+ && (tmp_cib != NULL) && (tmp_digest != NULL));
+
+ /* Ensure the admin didn't modify the existing CIB underneath us */
+ crm_trace("Reading cluster configuration file %s", cib_path);
+ rc = cib_file_read_and_verify(cib_path, NULL, NULL);
+ if ((rc != pcmk_ok) && (rc != -ENOENT)) {
+ crm_err("%s was manually modified while the cluster was active!",
+ cib_path);
+ exit_rc = pcmk_err_cib_modified;
+ goto cleanup;
+ }
+
+ /* Back up the existing CIB */
+ if (cib_file_backup(cib_dirname, cib_filename) < 0) {
+ exit_rc = pcmk_err_cib_backup;
+ goto cleanup;
+ }
+
+ crm_debug("Writing CIB to disk");
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+ cib_file_prepare_xml(cib_root);
+
+ /* Write the CIB to a temporary file, so we can deploy (near) atomically */
+ fd = mkstemp(tmp_cib);
+ if (fd < 0) {
+ crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
+ tmp_cib);
+ exit_rc = pcmk_err_cib_save;
+ goto cleanup;
+ }
+
+ /* Protect the temporary file */
+ if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
+ crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
+ tmp_cib);
+ exit_rc = pcmk_err_cib_save;
+ goto cleanup;
+ }
+ if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
+ crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
+ tmp_cib);
+ exit_rc = pcmk_err_cib_save;
+ goto cleanup;
+ }
+
+ /* Write out the CIB */
+ if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) {
+ crm_err("Changes couldn't be written to %s", tmp_cib);
+ exit_rc = pcmk_err_cib_save;
+ goto cleanup;
+ }
+
+ /* Calculate CIB digest */
+ digest = calculate_on_disk_digest(cib_root);
+ CRM_ASSERT(digest != NULL);
+ crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
+ (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
+
+ /* Write the CIB digest to a temporary file */
+ fd = mkstemp(tmp_digest);
+ if (fd < 0) {
+ crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
+ exit_rc = pcmk_err_cib_save;
+ goto cleanup;
+ }
+ if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
+ crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
+ tmp_cib);
+ exit_rc = pcmk_err_cib_save;
+ close(fd);
+ goto cleanup;
+ }
+ rc = pcmk__write_sync(fd, digest);
+ if (rc != pcmk_rc_ok) {
+ crm_err("Could not write digest to %s: %s",
+ tmp_digest, pcmk_rc_str(rc));
+ exit_rc = pcmk_err_cib_save;
+ close(fd);
+ goto cleanup;
+ }
+ close(fd);
+ crm_debug("Wrote digest %s to disk", digest);
+
+ /* Verify that what we wrote is sane */
+ crm_info("Reading cluster configuration file %s (digest: %s)",
+ tmp_cib, tmp_digest);
+ rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
+ CRM_ASSERT(rc == 0);
+
+ /* Rename temporary files to live, and sync directory changes to media */
+ crm_debug("Activating %s", tmp_cib);
+ if (rename(tmp_cib, cib_path) < 0) {
+ crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
+ exit_rc = pcmk_err_cib_save;
+ }
+ if (rename(tmp_digest, digest_path) < 0) {
+ crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
+ digest_path);
+ exit_rc = pcmk_err_cib_save;
+ }
+ pcmk__sync_directory(cib_dirname);
+
+ cleanup:
+ free(cib_path);
+ free(digest_path);
+ free(digest);
+ free(tmp_digest);
+ free(tmp_cib);
+ return exit_rc;
+}
diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c
new file mode 100644
index 0000000..4a87f56
--- /dev/null
+++ b/lib/cib/cib_native.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2004 International Business Machines
+ * Later changes 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.
+ */
+
+#include <crm_internal.h>
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <crm_internal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+
+#include <crm/msg_xml.h>
+#include <crm/common/mainloop.h>
+
+typedef struct cib_native_opaque_s {
+ char *token;
+ crm_ipc_t *ipc;
+ void (*dnotify_fn) (gpointer user_data);
+ mainloop_io_t *source;
+} cib_native_opaque_t;
+
+static int
+cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host,
+ const char *section, xmlNode *data,
+ xmlNode **output_data, int call_options,
+ const char *user_name)
+{
+ int rc = pcmk_ok;
+ int reply_id = 0;
+ enum crm_ipc_flags ipc_flags = crm_ipc_flags_none;
+
+ xmlNode *op_msg = NULL;
+ xmlNode *op_reply = NULL;
+
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ if (cib->state == cib_disconnected) {
+ return -ENOTCONN;
+ }
+
+ if (output_data != NULL) {
+ *output_data = NULL;
+ }
+
+ if (op == NULL) {
+ crm_err("No operation specified");
+ return -EINVAL;
+ }
+
+ if (call_options & cib_sync_call) {
+ pcmk__set_ipc_flags(ipc_flags, "client", crm_ipc_client_response);
+ }
+
+ cib->call_id++;
+ if (cib->call_id < 1) {
+ cib->call_id = 1;
+ }
+
+ op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options,
+ user_name);
+ if (op_msg == NULL) {
+ return -EPROTO;
+ }
+
+ crm_trace("Sending %s message to the CIB manager (timeout=%ds)", op, cib->call_timeout);
+ rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, cib->call_timeout * 1000, &op_reply);
+ free_xml(op_msg);
+
+ if (rc < 0) {
+ crm_err("Couldn't perform %s operation (timeout=%ds): %s (%d)", op,
+ cib->call_timeout, pcmk_strerror(rc), rc);
+ rc = -ECOMM;
+ goto done;
+ }
+
+ crm_log_xml_trace(op_reply, "Reply");
+
+ if (!(call_options & cib_sync_call)) {
+ crm_trace("Async call, returning %d", cib->call_id);
+ CRM_CHECK(cib->call_id != 0, return -ENOMSG);
+ free_xml(op_reply);
+ return cib->call_id;
+ }
+
+ rc = pcmk_ok;
+ crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id);
+ if (reply_id == cib->call_id) {
+ xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA);
+
+ crm_trace("Synchronous reply %d received", reply_id);
+ if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) {
+ rc = -EPROTO;
+ }
+
+ if (output_data == NULL || (call_options & cib_discard_reply)) {
+ crm_trace("Discarding reply");
+
+ } else if (tmp != NULL) {
+ *output_data = copy_xml(tmp);
+ }
+
+ } else if (reply_id <= 0) {
+ crm_err("Received bad reply: No id set");
+ crm_log_xml_err(op_reply, "Bad reply");
+ rc = -ENOMSG;
+ goto done;
+
+ } else {
+ crm_err("Received bad reply: %d (wanted %d)", reply_id, cib->call_id);
+ crm_log_xml_err(op_reply, "Old reply");
+ rc = -ENOMSG;
+ goto done;
+ }
+
+ if (op_reply == NULL && cib->state == cib_disconnected) {
+ rc = -ENOTCONN;
+
+ } else if (rc == pcmk_ok && op_reply == NULL) {
+ rc = -ETIME;
+ }
+
+ switch (rc) {
+ case pcmk_ok:
+ case -EPERM:
+ break;
+
+ /* This is an internal value that clients do not and should not care about */
+ case -pcmk_err_diff_resync:
+ rc = pcmk_ok;
+ break;
+
+ /* These indicate internal problems */
+ case -EPROTO:
+ case -ENOMSG:
+ crm_err("Call failed: %s", pcmk_strerror(rc));
+ if (op_reply) {
+ crm_log_xml_err(op_reply, "Invalid reply");
+ }
+ break;
+
+ default:
+ if (!pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
+ crm_warn("Call failed: %s", pcmk_strerror(rc));
+ }
+ }
+
+ done:
+ if (!crm_ipc_connected(native->ipc)) {
+ crm_err("The CIB manager disconnected");
+ cib->state = cib_disconnected;
+ }
+
+ free_xml(op_reply);
+ return rc;
+}
+
+static int
+cib_native_dispatch_internal(const char *buffer, ssize_t length,
+ gpointer userdata)
+{
+ const char *type = NULL;
+ xmlNode *msg = NULL;
+
+ cib_t *cib = userdata;
+
+ crm_trace("dispatching %p", userdata);
+
+ if (cib == NULL) {
+ crm_err("No CIB!");
+ return 0;
+ }
+
+ msg = string2xml(buffer);
+
+ if (msg == NULL) {
+ crm_warn("Received a NULL message from the CIB manager");
+ return 0;
+ }
+
+ /* do callbacks */
+ type = crm_element_value(msg, F_TYPE);
+ crm_trace("Activating %s callbacks...", type);
+ crm_log_xml_explicit(msg, "cib-reply");
+
+ if (pcmk__str_eq(type, T_CIB, pcmk__str_casei)) {
+ cib_native_callback(cib, msg, 0, 0);
+
+ } else if (pcmk__str_eq(type, T_CIB_NOTIFY, pcmk__str_casei)) {
+ g_list_foreach(cib->notify_list, cib_native_notify, msg);
+
+ } else {
+ crm_err("Unknown message type: %s", type);
+ }
+
+ free_xml(msg);
+ return 0;
+}
+
+static void
+cib_native_destroy(void *userdata)
+{
+ cib_t *cib = userdata;
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ crm_trace("destroying %p", userdata);
+ cib->state = cib_disconnected;
+ native->source = NULL;
+ native->ipc = NULL;
+
+ if (native->dnotify_fn) {
+ native->dnotify_fn(userdata);
+ }
+}
+
+static int
+cib_native_signoff(cib_t *cib)
+{
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ crm_debug("Disconnecting from the CIB manager");
+
+ cib_free_notify(cib);
+ remove_cib_op_callback(0, TRUE);
+
+ if (native->source != NULL) {
+ /* Attached to mainloop */
+ mainloop_del_ipc_client(native->source);
+ native->source = NULL;
+ native->ipc = NULL;
+
+ } else if (native->ipc) {
+ /* Not attached to mainloop */
+ crm_ipc_t *ipc = native->ipc;
+
+ native->ipc = NULL;
+ crm_ipc_close(ipc);
+ crm_ipc_destroy(ipc);
+ }
+
+ cib->state = cib_disconnected;
+ cib->type = cib_no_connection;
+
+ return pcmk_ok;
+}
+
+static int
+cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type,
+ int *async_fd)
+{
+ int rc = pcmk_ok;
+ const char *channel = NULL;
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ struct ipc_client_callbacks cib_callbacks = {
+ .dispatch = cib_native_dispatch_internal,
+ .destroy = cib_native_destroy
+ };
+
+ cib->call_timeout = PCMK__IPC_TIMEOUT;
+
+ if (type == cib_command) {
+ cib->state = cib_connected_command;
+ channel = PCMK__SERVER_BASED_RW;
+
+ } else if (type == cib_command_nonblocking) {
+ cib->state = cib_connected_command;
+ channel = PCMK__SERVER_BASED_SHM;
+
+ } else if (type == cib_query) {
+ cib->state = cib_connected_query;
+ channel = PCMK__SERVER_BASED_RO;
+
+ } else {
+ return -ENOTCONN;
+ }
+
+ crm_trace("Connecting %s channel", channel);
+
+ if (async_fd != NULL) {
+ native->ipc = crm_ipc_new(channel, 0);
+
+ if (native->ipc && crm_ipc_connect(native->ipc)) {
+ *async_fd = crm_ipc_get_fd(native->ipc);
+
+ } else if (native->ipc) {
+ rc = -ENOTCONN;
+ }
+
+ } else {
+ native->source =
+ mainloop_add_ipc_client(channel, G_PRIORITY_HIGH, 512 * 1024 /* 512k */ , cib,
+ &cib_callbacks);
+ native->ipc = mainloop_get_ipc_client(native->source);
+ }
+
+ if (rc != pcmk_ok || native->ipc == NULL || !crm_ipc_connected(native->ipc)) {
+ crm_info("Could not connect to CIB manager for %s", name);
+ rc = -ENOTCONN;
+ }
+
+ if (rc == pcmk_ok) {
+ xmlNode *reply = NULL;
+ xmlNode *hello = create_xml_node(NULL, "cib_command");
+
+ crm_xml_add(hello, F_TYPE, T_CIB);
+ crm_xml_add(hello, F_CIB_OPERATION, CRM_OP_REGISTER);
+ crm_xml_add(hello, F_CIB_CLIENTNAME, name);
+ crm_xml_add_int(hello, F_CIB_CALLOPTS, cib_sync_call);
+
+ if (crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply) > 0) {
+ const char *msg_type = crm_element_value(reply, F_CIB_OPERATION);
+
+ rc = pcmk_ok;
+ crm_log_xml_trace(reply, "reg-reply");
+
+ if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
+ crm_info("Reply to CIB registration message has "
+ "unknown type '%s'", msg_type);
+ rc = -EPROTO;
+
+ } else {
+ native->token = crm_element_value_copy(reply, F_CIB_CLIENTID);
+ if (native->token == NULL) {
+ rc = -EPROTO;
+ }
+ }
+ free_xml(reply);
+
+ } else {
+ rc = -ECOMM;
+ }
+
+ free_xml(hello);
+ }
+
+ if (rc == pcmk_ok) {
+ crm_info("Successfully connected to CIB manager for %s", name);
+ return pcmk_ok;
+ }
+
+ crm_info("Connection to CIB manager for %s failed: %s",
+ name, pcmk_strerror(rc));
+ cib_native_signoff(cib);
+ return rc;
+}
+
+static int
+cib_native_signon(cib_t *cib, const char *name, enum cib_conn_type type)
+{
+ return cib_native_signon_raw(cib, name, type, NULL);
+}
+
+static int
+cib_native_free(cib_t *cib)
+{
+ int rc = pcmk_ok;
+
+ if (cib->state != cib_disconnected) {
+ rc = cib_native_signoff(cib);
+ }
+
+ if (cib->state == cib_disconnected) {
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ free(native->token);
+ free(cib->variant_opaque);
+ free(cib->cmds);
+ free(cib);
+ }
+
+ return rc;
+}
+
+static int
+cib_native_register_notification(cib_t *cib, const char *callback, int enabled)
+{
+ int rc = pcmk_ok;
+ xmlNode *notify_msg = create_xml_node(NULL, "cib-callback");
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ if (cib->state != cib_disconnected) {
+ crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY);
+ crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback);
+ crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled);
+ rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response,
+ 1000 * cib->call_timeout, NULL);
+ if (rc <= 0) {
+ crm_trace("Notification not registered: %d", rc);
+ rc = -ECOMM;
+ }
+ }
+
+ free_xml(notify_msg);
+ return rc;
+}
+
+static int
+cib_native_set_connection_dnotify(cib_t *cib,
+ void (*dnotify) (gpointer user_data))
+{
+ cib_native_opaque_t *native = NULL;
+
+ if (cib == NULL) {
+ crm_err("No CIB!");
+ return FALSE;
+ }
+
+ native = cib->variant_opaque;
+ native->dnotify_fn = dnotify;
+
+ return pcmk_ok;
+}
+
+/*!
+ * \internal
+ * \brief Get the given CIB connection's unique client identifier
+ *
+ * These can be used to check whether this client requested the action that
+ * triggered a CIB notification.
+ *
+ * \param[in] cib CIB connection
+ * \param[out] async_id If not \p NULL, where to store asynchronous client ID
+ * \param[out] sync_id If not \p NULL, where to store synchronous client ID
+ *
+ * \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
+ *
+ * \note This is the \p cib_native variant implementation of
+ * \p cib_api_operations_t:client_id().
+ * \note For \p cib_native objects, \p async_id and \p sync_id are the same.
+ * \note The client ID is assigned during CIB sign-on.
+ */
+static int
+cib_native_client_id(const cib_t *cib, const char **async_id,
+ const char **sync_id)
+{
+ cib_native_opaque_t *native = cib->variant_opaque;
+
+ if (async_id != NULL) {
+ *async_id = native->token;
+ }
+ if (sync_id != NULL) {
+ *sync_id = native->token;
+ }
+ return pcmk_ok;
+}
+
+cib_t *
+cib_native_new(void)
+{
+ cib_native_opaque_t *native = NULL;
+ cib_t *cib = cib_new_variant();
+
+ if (cib == NULL) {
+ return NULL;
+ }
+
+ native = calloc(1, sizeof(cib_native_opaque_t));
+
+ if (native == NULL) {
+ free(cib);
+ return NULL;
+ }
+
+ cib->variant = cib_native;
+ cib->variant_opaque = native;
+
+ native->ipc = NULL;
+ native->source = NULL;
+ native->dnotify_fn = NULL;
+
+ /* assign variant specific ops */
+ cib->delegate_fn = cib_native_perform_op_delegate;
+ cib->cmds->signon = cib_native_signon;
+ cib->cmds->signon_raw = cib_native_signon_raw;
+ cib->cmds->signoff = cib_native_signoff;
+ cib->cmds->free = cib_native_free;
+
+ cib->cmds->register_notification = cib_native_register_notification;
+ cib->cmds->set_connection_dnotify = cib_native_set_connection_dnotify;
+
+ cib->cmds->client_id = cib_native_client_id;
+
+ return cib;
+}
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
new file mode 100644
index 0000000..d3293c4
--- /dev/null
+++ b/lib/cib/cib_ops.c
@@ -0,0 +1,869 @@
+/*
+ * 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.
+ */
+
+#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/xml_internal.h>
+
+int
+cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ xmlNode *obj_root = NULL;
+ int result = pcmk_ok;
+
+ crm_trace("Processing %s for %s section",
+ op, pcmk__s(section, "unspecified"));
+
+ if (options & cib_xpath) {
+ return cib_process_xpath(op, options, section, req, input,
+ existing_cib, result_cib, answer);
+ }
+
+ CRM_CHECK(*answer == NULL, free_xml(*answer));
+ *answer = NULL;
+
+ if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
+ section = NULL;
+ }
+
+ obj_root = pcmk_find_cib_element(existing_cib, section);
+
+ if (obj_root == NULL) {
+ result = -ENXIO;
+
+ } else if (options & cib_no_children) {
+ const char *tag = TYPE(obj_root);
+ xmlNode *shallow = create_xml_node(*answer, tag);
+
+ copy_in_properties(shallow, obj_root);
+ *answer = shallow;
+
+ } else {
+ *answer = obj_root;
+ }
+
+ if (result == pcmk_ok && *answer == NULL) {
+ crm_err("Error creating query response");
+ result = -ENOMSG;
+ }
+
+ return result;
+}
+
+static int
+update_counter(xmlNode *xml_obj, const char *field, bool reset)
+{
+ char *new_value = NULL;
+ char *old_value = NULL;
+ int int_value = -1;
+
+ if (!reset && crm_element_value(xml_obj, field) != NULL) {
+ old_value = crm_element_value_copy(xml_obj, field);
+ }
+ if (old_value != NULL) {
+ int_value = atoi(old_value);
+ new_value = pcmk__itoa(++int_value);
+ } else {
+ new_value = strdup("1");
+ CRM_ASSERT(new_value != NULL);
+ }
+
+ crm_trace("Update %s from %s to %s",
+ field, pcmk__s(old_value, "unset"), new_value);
+ crm_xml_add(xml_obj, field, new_value);
+
+ free(new_value);
+ free(old_value);
+
+ return pcmk_ok;
+}
+
+int
+cib_process_erase(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;
+ free_xml(*result_cib);
+ *result_cib = createEmptyCib(0);
+
+ copy_in_properties(*result_cib, existing_cib);
+ update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, false);
+
+ return result;
+}
+
+int
+cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ int rc = 0;
+ int new_version = 0;
+ int current_version = 0;
+ int max_version = 0;
+ const char *max = crm_element_value(req, F_CIB_SCHEMA_MAX);
+ const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION);
+
+ *answer = NULL;
+ crm_trace("Processing \"%s\" event with max=%s", op, max);
+
+ if (value != NULL) {
+ current_version = get_schema_version(value);
+ }
+
+ if (max) {
+ max_version = get_schema_version(max);
+ }
+
+ rc = update_validation(result_cib, &new_version, max_version, TRUE,
+ !(options & cib_verbose));
+ if (new_version > current_version) {
+ update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, false);
+ update_counter(*result_cib, XML_ATTR_GENERATION, true);
+ update_counter(*result_cib, XML_ATTR_NUMUPDATES, true);
+ return pcmk_ok;
+ }
+
+ return rc;
+}
+
+int
+cib_process_bump(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 for epoch='%s'", op,
+ pcmk__s(crm_element_value(existing_cib, XML_ATTR_GENERATION), ""));
+
+ *answer = NULL;
+ update_counter(*result_cib, XML_ATTR_GENERATION, false);
+
+ return result;
+}
+
+int
+cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer)
+{
+ const char *tag = NULL;
+ int result = pcmk_ok;
+
+ crm_trace("Processing %s for %s section",
+ op, pcmk__s(section, "unspecified"));
+
+ if (options & cib_xpath) {
+ return cib_process_xpath(op, options, section, req, input,
+ existing_cib, result_cib, answer);
+ }
+
+ *answer = NULL;
+
+ if (input == NULL) {
+ return -EINVAL;
+ }
+
+ tag = crm_element_name(input);
+
+ if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
+ section = NULL;
+
+ } else if (pcmk__str_eq(tag, section, pcmk__str_casei)) {
+ section = NULL;
+ }
+
+ if (pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) {
+ int updates = 0;
+ int epoch = 0;
+ int admin_epoch = 0;
+
+ int replace_updates = 0;
+ int replace_epoch = 0;
+ int replace_admin_epoch = 0;
+
+ const char *reason = NULL;
+ const char *peer = crm_element_value(req, F_ORIG);
+ const char *digest = crm_element_value(req, XML_ATTR_DIGEST);
+
+ if (digest) {
+ const char *version = crm_element_value(req, XML_ATTR_CRM_VERSION);
+ char *digest_verify = calculate_xml_versioned_digest(input, FALSE, TRUE,
+ version ? version :
+ CRM_FEATURE_SET);
+
+ if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) {
+ crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer,
+ digest_verify, digest);
+ reason = "digest mismatch";
+
+ } else {
+ crm_info("Digest matched on replace from %s: %s", peer, digest);
+ }
+ free(digest_verify);
+
+ } else {
+ crm_trace("No digest to verify");
+ }
+
+ cib_version_details(existing_cib, &admin_epoch, &epoch, &updates);
+ cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates);
+
+ if (replace_admin_epoch < admin_epoch) {
+ reason = XML_ATTR_GENERATION_ADMIN;
+
+ } else if (replace_admin_epoch > admin_epoch) {
+ /* no more checks */
+
+ } else if (replace_epoch < epoch) {
+ reason = XML_ATTR_GENERATION;
+
+ } else if (replace_epoch > epoch) {
+ /* no more checks */
+
+ } else if (replace_updates < updates) {
+ reason = XML_ATTR_NUMUPDATES;
+ }
+
+ if (reason != NULL) {
+ crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:"
+ " current %s is greater than the replacement",
+ replace_admin_epoch, replace_epoch,
+ replace_updates, peer, admin_epoch, epoch, updates, reason);
+ result = -pcmk_err_old_data;
+ } else {
+ crm_info("Replaced %d.%d.%d with %d.%d.%d from %s",
+ admin_epoch, epoch, updates,
+ replace_admin_epoch, replace_epoch, replace_updates, peer);
+ }
+
+ free_xml(*result_cib);
+ *result_cib = copy_xml(input);
+
+ } else {
+ xmlNode *obj_root = NULL;
+ gboolean ok = TRUE;
+
+ obj_root = pcmk_find_cib_element(*result_cib, section);
+ ok = replace_xml_child(NULL, obj_root, input, FALSE);
+ if (ok == FALSE) {
+ crm_trace("No matching object to replace");
+ result = -ENXIO;
+ }
+ }
+
+ return result;
+}
+
+int
+cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ xmlNode *obj_root = NULL;
+
+ crm_trace("Processing \"%s\" event", op);
+
+ if (options & cib_xpath) {
+ return cib_process_xpath(op, options, section, req, input,
+ existing_cib, result_cib, answer);
+ }
+
+ if (input == NULL) {
+ crm_err("Cannot perform modification with no data");
+ return -EINVAL;
+ }
+
+ obj_root = pcmk_find_cib_element(*result_cib, section);
+ if(pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
+ xmlNode *child = NULL;
+ for (child = pcmk__xml_first_child(input); child;
+ child = pcmk__xml_next(child)) {
+ if (replace_xml_child(NULL, obj_root, child, TRUE) == FALSE) {
+ crm_trace("No matching object to delete: %s=%s", child->name, ID(child));
+ }
+ }
+
+ } else if (replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) {
+ crm_trace("No matching object to delete: %s=%s", input->name, ID(input));
+ }
+
+ return pcmk_ok;
+}
+
+int
+cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ xmlNode *obj_root = NULL;
+
+ crm_trace("Processing \"%s\" event", op);
+
+ if (options & cib_xpath) {
+ return cib_process_xpath(op, options, section, req, input,
+ existing_cib, result_cib, answer);
+ }
+
+ if (input == NULL) {
+ crm_err("Cannot perform modification with no data");
+ return -EINVAL;
+ }
+
+ obj_root = pcmk_find_cib_element(*result_cib, section);
+ if (obj_root == NULL) {
+ xmlNode *tmp_section = NULL;
+ const char *path = pcmk_cib_parent_name_for(section);
+
+ if (path == NULL) {
+ return -EINVAL;
+ }
+
+ tmp_section = create_xml_node(NULL, section);
+ cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section,
+ NULL, result_cib, answer);
+ free_xml(tmp_section);
+
+ obj_root = pcmk_find_cib_element(*result_cib, section);
+ }
+
+ CRM_CHECK(obj_root != NULL, return -EINVAL);
+
+ if (update_xml_child(obj_root, input) == FALSE) {
+ if (options & cib_can_create) {
+ add_node_copy(obj_root, input);
+ } else {
+ return -ENXIO;
+ }
+ }
+
+ if(options & cib_mixed_update) {
+ int max = 0, lpc;
+ xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__");
+
+ if (xpathObj) {
+ max = numXpathResults(xpathObj);
+ crm_log_xml_trace(*result_cib, "Mixed result");
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+ xmlChar *match_path = xmlGetNodePath(match);
+
+ crm_debug("Destroying %s", match_path);
+ free(match_path);
+ free_xml(match);
+ }
+
+ freeXpathObject(xpathObj);
+ }
+ return pcmk_ok;
+}
+
+static int
+update_cib_object(xmlNode * parent, xmlNode * update)
+{
+ int result = pcmk_ok;
+ xmlNode *target = NULL;
+ xmlNode *a_child = NULL;
+ const char *replace = NULL;
+ const char *object_id = NULL;
+ const char *object_name = NULL;
+
+ CRM_CHECK(update != NULL, return -EINVAL);
+ CRM_CHECK(parent != NULL, return -EINVAL);
+
+ object_name = crm_element_name(update);
+ CRM_CHECK(object_name != NULL, return -EINVAL);
+
+ object_id = ID(update);
+ crm_trace("Processing update for <%s%s%s%s>", object_name,
+ ((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(object_id, ""),
+ ((object_id == NULL)? "" : "'"));
+
+ if (object_id == NULL) {
+ /* placeholder object */
+ target = find_xml_node(parent, object_name, FALSE);
+
+ } else {
+ target = pcmk__xe_match(parent, object_name, XML_ATTR_ID, object_id);
+ }
+
+ if (target == NULL) {
+ target = create_xml_node(parent, object_name);
+ }
+
+ crm_trace("Found node <%s%s%s%s> to update", object_name,
+ ((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(object_id, ""),
+ ((object_id == NULL)? "" : "'"));
+
+ // @COMPAT: XML_CIB_ATTR_REPLACE is unused internally. Remove at break.
+ replace = crm_element_value(update, XML_CIB_ATTR_REPLACE);
+ if (replace != NULL) {
+ xmlNode *remove = NULL;
+ int last = 0, lpc = 0, len = 0;
+
+ len = strlen(replace);
+ while (lpc <= len) {
+ if (replace[lpc] == ',' || replace[lpc] == 0) {
+ char *replace_item = NULL;
+
+ if (last == lpc) {
+ /* nothing to do */
+ last = lpc + 1;
+ goto incr;
+ }
+
+ replace_item = strndup(replace + last, lpc - last);
+ remove = find_xml_node(target, replace_item, FALSE);
+ if (remove != NULL) {
+ crm_trace("Replacing node <%s> in <%s>",
+ replace_item, crm_element_name(target));
+ free_xml(remove);
+ remove = NULL;
+ }
+ free(replace_item);
+ last = lpc + 1;
+ }
+ incr:
+ lpc++;
+ }
+ xml_remove_prop(update, XML_CIB_ATTR_REPLACE);
+ xml_remove_prop(target, XML_CIB_ATTR_REPLACE);
+ }
+
+ copy_in_properties(target, update);
+
+ if (xml_acl_denied(target)) {
+ crm_notice("Cannot update <%s " XML_ATTR_ID "=%s>",
+ pcmk__s(object_name, "<null>"),
+ pcmk__s(object_id, "<null>"));
+ return -EACCES;
+ }
+
+ crm_trace("Processing children of <%s%s%s%s>", object_name,
+ ((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(object_id, ""),
+ ((object_id == NULL)? "" : "'"));
+
+ for (a_child = pcmk__xml_first_child(update); a_child != NULL;
+ a_child = pcmk__xml_next(a_child)) {
+ int tmp_result = 0;
+
+ crm_trace("Updating child <%s%s%s%s>", crm_element_name(a_child),
+ ((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'"));
+
+ tmp_result = update_cib_object(target, a_child);
+
+ /* only the first error is likely to be interesting */
+ if (tmp_result != pcmk_ok) {
+ crm_err("Error updating child <%s%s%s%s>",
+ crm_element_name(a_child),
+ ((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(ID(a_child), ""),
+ ((ID(a_child) == NULL)? "" : "'"));
+
+ if (result == pcmk_ok) {
+ result = tmp_result;
+ }
+ }
+ }
+
+ crm_trace("Finished handling update for <%s%s%s%s>", object_name,
+ ((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(object_id, ""),
+ ((object_id == NULL)? "" : "'"));
+
+ return result;
+}
+
+static int
+add_cib_object(xmlNode * parent, xmlNode * new_obj)
+{
+ const char *object_name = NULL;
+ const char *object_id = NULL;
+ xmlNode *equiv_node = NULL;
+
+ if ((parent == NULL) || (new_obj == NULL)) {
+ return -EINVAL;
+ }
+
+ object_name = crm_element_name(new_obj);
+ if (object_name == NULL) {
+ return -EINVAL;
+ }
+
+ object_id = ID(new_obj);
+
+ crm_trace("Processing creation of <%s%s%s%s>", object_name,
+ ((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
+ pcmk__s(object_id, ""),
+ ((object_id == NULL)? "" : "'"));
+
+ if (object_id == NULL) {
+ equiv_node = find_xml_node(parent, object_name, FALSE);
+ } else {
+ equiv_node = pcmk__xe_match(parent, object_name, XML_ATTR_ID,
+ object_id);
+ }
+ if (equiv_node != NULL) {
+ return -EEXIST;
+ }
+
+ return update_cib_object(parent, new_obj);
+}
+
+static bool
+update_results(xmlNode *failed, xmlNode *target, const char *operation,
+ int return_code)
+{
+ xmlNode *xml_node = NULL;
+ bool was_error = false;
+ const char *error_msg = NULL;
+
+ if (return_code != pcmk_ok) {
+ error_msg = pcmk_strerror(return_code);
+
+ was_error = true;
+ xml_node = create_xml_node(failed, XML_FAIL_TAG_CIB);
+ add_node_copy(xml_node, target);
+
+ crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID, ID(target));
+ crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, TYPE(target));
+ crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP, operation);
+ crm_xml_add(xml_node, XML_FAILCIB_ATTR_REASON, error_msg);
+
+ crm_warn("Action %s failed: %s (cde=%d)",
+ operation, error_msg, return_code);
+ }
+
+ return was_error;
+}
+
+int
+cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ xmlNode *failed = NULL;
+ int result = pcmk_ok;
+ xmlNode *update_section = NULL;
+
+ crm_trace("Processing %s for %s section",
+ op, pcmk__s(section, "unspecified"));
+ if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
+ section = NULL;
+
+ } else if (pcmk__str_eq(XML_TAG_CIB, section, pcmk__str_casei)) {
+ section = NULL;
+
+ } else if (pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) {
+ section = NULL;
+ }
+
+ CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL);
+
+ if (input == NULL) {
+ crm_err("Cannot perform modification with no data");
+ return -EINVAL;
+ }
+
+ if (section == NULL) {
+ return cib_process_modify(op, options, section, req, input, existing_cib, result_cib,
+ answer);
+ }
+
+ failed = create_xml_node(NULL, XML_TAG_FAILED);
+
+ update_section = pcmk_find_cib_element(*result_cib, section);
+ if (pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
+ xmlNode *a_child = NULL;
+
+ for (a_child = pcmk__xml_first_child(input); a_child != NULL;
+ a_child = pcmk__xml_next(a_child)) {
+ result = add_cib_object(update_section, a_child);
+ if (update_results(failed, a_child, op, result)) {
+ break;
+ }
+ }
+
+ } else {
+ result = add_cib_object(update_section, input);
+ update_results(failed, input, op, result);
+ }
+
+ if ((result == pcmk_ok) && xml_has_children(failed)) {
+ result = -EINVAL;
+ }
+
+ if (result != pcmk_ok) {
+ crm_log_xml_err(failed, "CIB Update failures");
+ *answer = failed;
+
+ } else {
+ free_xml(failed);
+ }
+
+ return result;
+}
+
+int
+cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
+ xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
+{
+ const char *originator = NULL;
+
+ if (req != NULL) {
+ originator = crm_element_value(req, F_ORIG);
+ }
+
+ crm_trace("Processing \"%s\" event from %s%s",
+ op, originator,
+ (pcmk_is_set(options, cib_force_diff)? " (global update)" : ""));
+
+ free_xml(*result_cib);
+ *result_cib = copy_xml(existing_cib);
+ return xml_apply_patchset(*result_cib, input, TRUE);
+}
+
+// @COMPAT: v1-only
+bool
+cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff)
+{
+ int lpc = 0, max = 0;
+ bool config_changes = false;
+ xmlXPathObject *xpathObj = NULL;
+ int format = 1;
+
+ CRM_ASSERT(diff != NULL);
+
+ if (*diff == NULL && last != NULL && next != NULL) {
+ *diff = diff_xml_object(last, next, FALSE);
+ }
+
+ if (*diff == NULL) {
+ goto done;
+ }
+
+ crm_element_value_int(*diff, "format", &format);
+ CRM_LOG_ASSERT(format == 1);
+
+ xpathObj = xpath_search(*diff, "//" XML_CIB_TAG_CONFIGURATION);
+ if (numXpathResults(xpathObj) > 0) {
+ config_changes = true;
+ goto done;
+ }
+ freeXpathObject(xpathObj);
+
+ /*
+ * Do not check XML_TAG_DIFF_ADDED "//" XML_TAG_CIB
+ * This always contains every field and would produce a false positive
+ * every time if the checked value existed
+ */
+ xpathObj = xpath_search(*diff, "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_CIB);
+ max = numXpathResults(xpathObj);
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *top = getXpathResult(xpathObj, lpc);
+
+ if (crm_element_value(top, XML_ATTR_GENERATION) != NULL) {
+ config_changes = true;
+ goto done;
+ }
+ if (crm_element_value(top, XML_ATTR_GENERATION_ADMIN) != NULL) {
+ config_changes = true;
+ goto done;
+ }
+
+ if (crm_element_value(top, XML_ATTR_VALIDATION) != NULL) {
+ config_changes = true;
+ goto done;
+ }
+ if (crm_element_value(top, XML_ATTR_CRM_VERSION) != NULL) {
+ config_changes = true;
+ goto done;
+ }
+ if (crm_element_value(top, "remote-clear-port") != NULL) {
+ config_changes = true;
+ goto done;
+ }
+ if (crm_element_value(top, "remote-tls-port") != NULL) {
+ config_changes = true;
+ goto done;
+ }
+ }
+
+ done:
+ freeXpathObject(xpathObj);
+ return config_changes;
+}
+
+int
+cib_process_xpath(const char *op, int options, const char *section,
+ const xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode **answer)
+{
+ int lpc = 0;
+ int max = 0;
+ int rc = pcmk_ok;
+ bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none);
+
+ xmlXPathObjectPtr xpathObj = NULL;
+
+ crm_trace("Processing \"%s\" event", op);
+
+ if (is_query) {
+ xpathObj = xpath_search(existing_cib, section);
+ } else {
+ xpathObj = xpath_search(*result_cib, section);
+ }
+
+ max = numXpathResults(xpathObj);
+
+ if ((max < 1)
+ && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
+ crm_debug("%s was already removed", section);
+
+ } else if (max < 1) {
+ crm_debug("%s: %s does not exist", op, section);
+ rc = -ENXIO;
+
+ } else if (is_query) {
+ if (max > 1) {
+ *answer = create_xml_node(NULL, "xpath-query");
+ }
+ }
+
+ if (pcmk_is_set(options, cib_multiple)
+ && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
+ dedupXpathResults(xpathObj);
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlChar *path = NULL;
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+
+ if (match == NULL) {
+ continue;
+ }
+
+ path = xmlGetNodePath(match);
+ crm_debug("Processing %s op for %s with %s", op, section, path);
+ free(path);
+
+ if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
+ if (match == *result_cib) {
+ /* Attempting to delete the whole "/cib" */
+ crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section);
+ rc = -EINVAL;
+ break;
+ }
+
+ free_xml(match);
+ if ((options & cib_multiple) == 0) {
+ break;
+ }
+
+ } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) {
+ if (update_xml_child(match, input) == FALSE) {
+ rc = -ENXIO;
+ } else if ((options & cib_multiple) == 0) {
+ break;
+ }
+
+ } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) {
+ add_node_copy(match, input);
+ break;
+
+ } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
+
+ if (options & cib_no_children) {
+ const char *tag = TYPE(match);
+ xmlNode *shallow = create_xml_node(*answer, tag);
+
+ copy_in_properties(shallow, match);
+
+ if (*answer == NULL) {
+ *answer = shallow;
+ }
+
+ } else if (options & cib_xpath_address) {
+ char *path = NULL;
+ xmlNode *parent = match;
+
+ while (parent && parent->type == XML_ELEMENT_NODE) {
+ const char *id = crm_element_value(parent, XML_ATTR_ID);
+ char *new_path = NULL;
+
+ if (id) {
+ new_path = crm_strdup_printf("/%s[@" XML_ATTR_ID "='%s']"
+ "%s",
+ parent->name, id,
+ pcmk__s(path, ""));
+ } else {
+ new_path = crm_strdup_printf("/%s%s", parent->name,
+ pcmk__s(path, ""));
+ }
+ free(path);
+ path = new_path;
+ parent = parent->parent;
+ }
+ crm_trace("Got: %s", path);
+
+ if (*answer == NULL) {
+ *answer = create_xml_node(NULL, "xpath-query");
+ }
+ parent = create_xml_node(*answer, "xpath-query-path");
+ crm_xml_add(parent, XML_ATTR_ID, path);
+ free(path);
+
+ } else if (*answer) {
+ add_node_copy(*answer, match);
+
+ } else {
+ *answer = match;
+ }
+
+ } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE,
+ pcmk__str_none)) {
+ xmlNode *parent = match->parent;
+
+ free_xml(match);
+ if (input != NULL) {
+ add_node_copy(parent, input);
+ }
+
+ if ((options & cib_multiple) == 0) {
+ break;
+ }
+ }
+ }
+
+ freeXpathObject(xpathObj);
+ return rc;
+}
diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c
new file mode 100644
index 0000000..28095b3
--- /dev/null
+++ b/lib/cib/cib_remote.c
@@ -0,0 +1,638 @@
+/*
+ * 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 Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <netdb.h>
+#include <termios.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/mainloop.h>
+#include <crm/common/remote_internal.h>
+#include <crm/common/output_internal.h>
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+
+# include <gnutls/gnutls.h>
+
+# define TLS_HANDSHAKE_TIMEOUT_MS 5000
+
+static gnutls_anon_client_credentials_t anon_cred_c;
+static gboolean remote_gnutls_credentials_init = FALSE;
+
+#endif // HAVE_GNUTLS_GNUTLS_H
+
+#include <arpa/inet.h>
+
+typedef struct cib_remote_opaque_s {
+ int port;
+ char *server;
+ char *user;
+ char *passwd;
+ gboolean encrypted;
+ pcmk__remote_t command;
+ pcmk__remote_t callback;
+ pcmk__output_t *out;
+} cib_remote_opaque_t;
+
+static int
+cib_remote_perform_op(cib_t *cib, const char *op, const char *host,
+ const char *section, xmlNode *data,
+ xmlNode **output_data, int call_options, const char *name)
+{
+ int rc;
+ int remaining_time = 0;
+ time_t start_time;
+
+ xmlNode *op_msg = NULL;
+ xmlNode *op_reply = NULL;
+
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ if (cib->state == cib_disconnected) {
+ return -ENOTCONN;
+ }
+
+ if (output_data != NULL) {
+ *output_data = NULL;
+ }
+
+ if (op == NULL) {
+ crm_err("No operation specified");
+ return -EINVAL;
+ }
+
+ cib->call_id++;
+ if (cib->call_id < 1) {
+ cib->call_id = 1;
+ }
+
+ op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options,
+ NULL);
+ if (op_msg == NULL) {
+ return -EPROTO;
+ }
+
+ crm_trace("Sending %s message to the CIB manager", op);
+ if (!(call_options & cib_sync_call)) {
+ pcmk__remote_send_xml(&private->callback, op_msg);
+ } else {
+ pcmk__remote_send_xml(&private->command, op_msg);
+ }
+ free_xml(op_msg);
+
+ if ((call_options & cib_discard_reply)) {
+ crm_trace("Discarding reply");
+ return pcmk_ok;
+
+ } else if (!(call_options & cib_sync_call)) {
+ return cib->call_id;
+ }
+
+ crm_trace("Waiting for a synchronous reply");
+
+ start_time = time(NULL);
+ remaining_time = cib->call_timeout ? cib->call_timeout : 60;
+
+ rc = pcmk_rc_ok;
+ while (remaining_time > 0 && (rc != ENOTCONN)) {
+ int reply_id = -1;
+ int msg_id = cib->call_id;
+
+ rc = pcmk__read_remote_message(&private->command,
+ remaining_time * 1000);
+ op_reply = pcmk__remote_message_xml(&private->command);
+
+ if (!op_reply) {
+ break;
+ }
+
+ crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id);
+
+ if (reply_id == msg_id) {
+ break;
+
+ } else if (reply_id < msg_id) {
+ crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
+ crm_log_xml_trace(op_reply, "Old reply");
+
+ } else if ((reply_id - 10000) > msg_id) {
+ /* wrap-around case */
+ crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
+ crm_log_xml_trace(op_reply, "Old reply");
+ } else {
+ crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id);
+ }
+
+ free_xml(op_reply);
+ op_reply = NULL;
+
+ /* wasn't the right reply, try and read some more */
+ remaining_time = time(NULL) - start_time;
+ }
+
+ /* if(IPC_ISRCONN(native->command_channel) == FALSE) { */
+ /* crm_err("The CIB manager disconnected: %d", */
+ /* native->command_channel->ch_status); */
+ /* cib->state = cib_disconnected; */
+ /* } */
+
+ if (rc == ENOTCONN) {
+ crm_err("Disconnected while waiting for reply.");
+ return -ENOTCONN;
+ } else if (op_reply == NULL) {
+ crm_err("No reply message - empty");
+ return -ENOMSG;
+ }
+
+ crm_trace("Synchronous reply received");
+
+ /* Start processing the reply... */
+ if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) {
+ rc = -EPROTO;
+ }
+
+ if (rc == -pcmk_err_diff_resync) {
+ /* This is an internal value that clients do not and should not care about */
+ rc = pcmk_ok;
+ }
+
+ if (rc == pcmk_ok || rc == -EPERM) {
+ crm_log_xml_debug(op_reply, "passed");
+
+ } else {
+/* } else if(rc == -ETIME) { */
+ crm_err("Call failed: %s", pcmk_strerror(rc));
+ crm_log_xml_warn(op_reply, "failed");
+ }
+
+ if (output_data == NULL) {
+ /* do nothing more */
+
+ } else if (!(call_options & cib_discard_reply)) {
+ xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA);
+
+ if (tmp == NULL) {
+ crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1);
+ } else {
+ *output_data = copy_xml(tmp);
+ }
+ }
+
+ free_xml(op_reply);
+
+ return rc;
+}
+
+static int
+cib_remote_callback_dispatch(gpointer user_data)
+{
+ int rc;
+ cib_t *cib = user_data;
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ xmlNode *msg = NULL;
+
+ crm_info("Message on callback channel");
+
+ rc = pcmk__read_remote_message(&private->callback, -1);
+
+ msg = pcmk__remote_message_xml(&private->callback);
+ while (msg) {
+ const char *type = crm_element_value(msg, F_TYPE);
+
+ crm_trace("Activating %s callbacks...", type);
+
+ if (pcmk__str_eq(type, T_CIB, pcmk__str_casei)) {
+ cib_native_callback(cib, msg, 0, 0);
+
+ } else if (pcmk__str_eq(type, T_CIB_NOTIFY, pcmk__str_casei)) {
+ g_list_foreach(cib->notify_list, cib_native_notify, msg);
+
+ } else {
+ crm_err("Unknown message type: %s", type);
+ }
+
+ free_xml(msg);
+ msg = pcmk__remote_message_xml(&private->callback);
+ }
+
+ if (rc == ENOTCONN) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+cib_remote_command_dispatch(gpointer user_data)
+{
+ int rc;
+ cib_t *cib = user_data;
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ rc = pcmk__read_remote_message(&private->command, -1);
+
+ free(private->command.buffer);
+ private->command.buffer = NULL;
+ crm_err("received late reply for remote cib connection, discarding");
+
+ if (rc == ENOTCONN) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+cib_tls_close(cib_t *cib)
+{
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ if (private->encrypted) {
+ if (private->command.tls_session) {
+ gnutls_bye(*(private->command.tls_session), GNUTLS_SHUT_RDWR);
+ gnutls_deinit(*(private->command.tls_session));
+ gnutls_free(private->command.tls_session);
+ }
+
+ if (private->callback.tls_session) {
+ gnutls_bye(*(private->callback.tls_session), GNUTLS_SHUT_RDWR);
+ gnutls_deinit(*(private->callback.tls_session));
+ gnutls_free(private->callback.tls_session);
+ }
+ private->command.tls_session = NULL;
+ private->callback.tls_session = NULL;
+ if (remote_gnutls_credentials_init) {
+ gnutls_anon_free_client_credentials(anon_cred_c);
+ gnutls_global_deinit();
+ remote_gnutls_credentials_init = FALSE;
+ }
+ }
+#endif
+
+ if (private->command.tcp_socket) {
+ shutdown(private->command.tcp_socket, SHUT_RDWR); /* no more receptions */
+ close(private->command.tcp_socket);
+ }
+ if (private->callback.tcp_socket) {
+ shutdown(private->callback.tcp_socket, SHUT_RDWR); /* no more receptions */
+ close(private->callback.tcp_socket);
+ }
+ private->command.tcp_socket = 0;
+ private->callback.tcp_socket = 0;
+
+ free(private->command.buffer);
+ free(private->callback.buffer);
+ private->command.buffer = NULL;
+ private->callback.buffer = NULL;
+
+ return 0;
+}
+
+static void
+cib_remote_connection_destroy(gpointer user_data)
+{
+ crm_err("Connection destroyed");
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ cib_tls_close(user_data);
+#endif
+}
+
+static int
+cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
+{
+ cib_remote_opaque_t *private = cib->variant_opaque;
+ int rc;
+
+ xmlNode *answer = NULL;
+ xmlNode *login = NULL;
+
+ static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, };
+
+ cib_fd_callbacks.dispatch =
+ event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch;
+ cib_fd_callbacks.destroy = cib_remote_connection_destroy;
+
+ connection->tcp_socket = -1;
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ connection->tls_session = NULL;
+#endif
+ rc = pcmk__connect_remote(private->server, private->port, 0, NULL,
+ &(connection->tcp_socket), NULL, NULL);
+ if (rc != pcmk_rc_ok) {
+ crm_info("Remote connection to %s:%d failed: %s " CRM_XS " rc=%d",
+ private->server, private->port, pcmk_rc_str(rc), rc);
+ return -ENOTCONN;
+ }
+
+ if (private->encrypted) {
+ /* initialize GnuTls lib */
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ if (remote_gnutls_credentials_init == FALSE) {
+ crm_gnutls_global_init();
+ gnutls_anon_allocate_client_credentials(&anon_cred_c);
+ remote_gnutls_credentials_init = TRUE;
+ }
+
+ /* bind the socket to GnuTls lib */
+ connection->tls_session = pcmk__new_tls_session(connection->tcp_socket,
+ GNUTLS_CLIENT,
+ GNUTLS_CRD_ANON,
+ anon_cred_c);
+ if (connection->tls_session == NULL) {
+ cib_tls_close(cib);
+ return -1;
+ }
+
+ if (pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT_MS)
+ != pcmk_rc_ok) {
+ crm_err("Session creation for %s:%d failed", private->server, private->port);
+
+ gnutls_deinit(*connection->tls_session);
+ gnutls_free(connection->tls_session);
+ connection->tls_session = NULL;
+ cib_tls_close(cib);
+ return -1;
+ }
+#else
+ return -EPROTONOSUPPORT;
+#endif
+ }
+
+ /* login to server */
+ login = create_xml_node(NULL, "cib_command");
+ crm_xml_add(login, "op", "authenticate");
+ crm_xml_add(login, "user", private->user);
+ crm_xml_add(login, "password", private->passwd);
+ crm_xml_add(login, "hidden", "password");
+
+ pcmk__remote_send_xml(connection, login);
+ free_xml(login);
+
+ rc = pcmk_ok;
+ if (pcmk__read_remote_message(connection, -1) == ENOTCONN) {
+ rc = -ENOTCONN;
+ }
+
+ answer = pcmk__remote_message_xml(connection);
+
+ crm_log_xml_trace(answer, "Reply");
+ if (answer == NULL) {
+ rc = -EPROTO;
+
+ } else {
+ /* grab the token */
+ const char *msg_type = crm_element_value(answer, F_CIB_OPERATION);
+ const char *tmp_ticket = crm_element_value(answer, F_CIB_CLIENTID);
+
+ if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
+ crm_err("Invalid registration message: %s", msg_type);
+ rc = -EPROTO;
+
+ } else if (tmp_ticket == NULL) {
+ rc = -EPROTO;
+
+ } else {
+ connection->token = strdup(tmp_ticket);
+ }
+ }
+ free_xml(answer);
+ answer = NULL;
+
+ if (rc != 0) {
+ cib_tls_close(cib);
+ return rc;
+ }
+
+ crm_trace("remote client connection established");
+ connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH,
+ connection->tcp_socket, cib,
+ &cib_fd_callbacks);
+ return rc;
+}
+
+static int
+cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type)
+{
+ int rc = pcmk_ok;
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ if (private->passwd == NULL) {
+ if (private->out == NULL) {
+ /* If no pcmk__output_t is set, just assume that a text prompt
+ * is good enough.
+ */
+ pcmk__text_prompt("Password", false, &(private->passwd));
+ } else {
+ private->out->prompt("Password", false, &(private->passwd));
+ }
+ }
+
+ if (private->server == NULL || private->user == NULL) {
+ rc = -EINVAL;
+ }
+
+ if (rc == pcmk_ok) {
+ rc = cib_tls_signon(cib, &(private->command), FALSE);
+ }
+
+ if (rc == pcmk_ok) {
+ rc = cib_tls_signon(cib, &(private->callback), TRUE);
+ }
+
+ if (rc == pcmk_ok) {
+ xmlNode *hello = cib_create_op(0, CRM_OP_REGISTER, NULL, NULL, NULL, 0,
+ NULL);
+ crm_xml_add(hello, F_CIB_CLIENTNAME, name);
+ pcmk__remote_send_xml(&private->command, hello);
+ free_xml(hello);
+ }
+
+ if (rc == pcmk_ok) {
+ crm_info("Opened connection to %s:%d for %s",
+ private->server, private->port, name);
+ cib->state = cib_connected_command;
+ cib->type = cib_command;
+
+ } else {
+ crm_info("Connection to %s:%d for %s failed: %s\n",
+ private->server, private->port, name, pcmk_strerror(rc));
+ }
+
+ return rc;
+}
+
+static int
+cib_remote_signoff(cib_t *cib)
+{
+ int rc = pcmk_ok;
+
+ crm_debug("Disconnecting from the CIB manager");
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ cib_tls_close(cib);
+#endif
+
+ cib->state = cib_disconnected;
+ cib->type = cib_no_connection;
+
+ return rc;
+}
+
+static int
+cib_remote_free(cib_t *cib)
+{
+ int rc = pcmk_ok;
+
+ crm_warn("Freeing CIB");
+ if (cib->state != cib_disconnected) {
+ rc = cib_remote_signoff(cib);
+ if (rc == pcmk_ok) {
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ free(private->server);
+ free(private->user);
+ free(private->passwd);
+ free(cib->cmds);
+ free(private);
+ free(cib);
+ }
+ }
+
+ return rc;
+}
+
+static int
+cib_remote_inputfd(cib_t * cib)
+{
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ return private->callback.tcp_socket;
+}
+
+static int
+cib_remote_register_notification(cib_t * cib, const char *callback, int enabled)
+{
+ xmlNode *notify_msg = create_xml_node(NULL, "cib_command");
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY);
+ crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback);
+ crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled);
+ pcmk__remote_send_xml(&private->callback, notify_msg);
+ free_xml(notify_msg);
+ return pcmk_ok;
+}
+
+static int
+cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
+{
+ return -EPROTONOSUPPORT;
+}
+
+/*!
+ * \internal
+ * \brief Get the given CIB connection's unique client identifiers
+ *
+ * These can be used to check whether this client requested the action that
+ * triggered a CIB notification.
+ *
+ * \param[in] cib CIB connection
+ * \param[out] async_id If not \p NULL, where to store asynchronous client ID
+ * \param[out] sync_id If not \p NULL, where to store synchronous client ID
+ *
+ * \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
+ *
+ * \note This is the \p cib_remote variant implementation of
+ * \p cib_api_operations_t:client_id().
+ * \note The client IDs are assigned during CIB sign-on.
+ */
+static int
+cib_remote_client_id(const cib_t *cib, const char **async_id,
+ const char **sync_id)
+{
+ cib_remote_opaque_t *private = cib->variant_opaque;
+
+ if (async_id != NULL) {
+ // private->callback is the channel for async requests
+ *async_id = private->callback.token;
+ }
+ if (sync_id != NULL) {
+ // private->command is the channel for sync requests
+ *sync_id = private->command.token;
+ }
+ return pcmk_ok;
+}
+
+cib_t *
+cib_remote_new(const char *server, const char *user, const char *passwd, int port,
+ gboolean encrypted)
+{
+ cib_remote_opaque_t *private = NULL;
+ cib_t *cib = cib_new_variant();
+
+ if (cib == NULL) {
+ return NULL;
+ }
+
+ private = calloc(1, sizeof(cib_remote_opaque_t));
+
+ if (private == NULL) {
+ free(cib);
+ return NULL;
+ }
+
+ cib->variant = cib_remote;
+ cib->variant_opaque = private;
+
+ pcmk__str_update(&private->server, server);
+ pcmk__str_update(&private->user, user);
+ pcmk__str_update(&private->passwd, passwd);
+
+ private->port = port;
+ private->encrypted = encrypted;
+
+ /* assign variant specific ops */
+ cib->delegate_fn = cib_remote_perform_op;
+ cib->cmds->signon = cib_remote_signon;
+ cib->cmds->signoff = cib_remote_signoff;
+ cib->cmds->free = cib_remote_free;
+ cib->cmds->inputfd = cib_remote_inputfd;
+
+ cib->cmds->register_notification = cib_remote_register_notification;
+ cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify;
+
+ cib->cmds->client_id = cib_remote_client_id;
+
+ return cib;
+}
+
+void
+cib__set_output(cib_t *cib, pcmk__output_t *out)
+{
+ cib_remote_opaque_t *private;
+
+ if (cib->variant != cib_remote) {
+ return;
+ }
+
+ private = cib->variant_opaque;
+ private->out = out;
+}
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
new file mode 100644
index 0000000..c75d844
--- /dev/null
+++ b/lib/cib/cib_utils.c
@@ -0,0 +1,837 @@
+/*
+ * Original copyright 2004 International Business Machines
+ * Later changes 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 Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+#include <crm_internal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/utsname.h>
+
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/cib/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+#include <crm/pengine/rules.h>
+
+xmlNode *
+cib_get_generation(cib_t * cib)
+{
+ xmlNode *the_cib = NULL;
+ xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
+
+ cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
+ if (the_cib != NULL) {
+ copy_in_properties(generation, the_cib);
+ free_xml(the_cib);
+ }
+
+ return generation;
+}
+
+gboolean
+cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
+{
+ *epoch = -1;
+ *updates = -1;
+ *admin_epoch = -1;
+
+ if (cib == NULL) {
+ return FALSE;
+
+ } else {
+ crm_element_value_int(cib, XML_ATTR_GENERATION, epoch);
+ crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates);
+ crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch);
+ }
+ return TRUE;
+}
+
+gboolean
+cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
+ int *_admin_epoch, int *_epoch, int *_updates)
+{
+ int add[] = { 0, 0, 0 };
+ int del[] = { 0, 0, 0 };
+
+ xml_patch_versions(diff, add, del);
+
+ *admin_epoch = add[0];
+ *epoch = add[1];
+ *updates = add[2];
+
+ *_admin_epoch = del[0];
+ *_epoch = del[1];
+ *_updates = del[2];
+
+ return TRUE;
+}
+
+/*!
+ * \brief Create XML for a new (empty) CIB
+ *
+ * \param[in] cib_epoch What to use as "epoch" CIB property
+ *
+ * \return Newly created XML for empty CIB
+ * \note It is the caller's responsibility to free the result with free_xml().
+ */
+xmlNode *
+createEmptyCib(int cib_epoch)
+{
+ xmlNode *cib_root = NULL, *config = NULL;
+
+ cib_root = create_xml_node(NULL, XML_TAG_CIB);
+ crm_xml_add(cib_root, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
+ crm_xml_add(cib_root, XML_ATTR_VALIDATION, xml_latest_schema());
+
+ crm_xml_add_int(cib_root, XML_ATTR_GENERATION, cib_epoch);
+ crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0);
+ crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0);
+
+ config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION);
+ create_xml_node(cib_root, XML_CIB_TAG_STATUS);
+
+ create_xml_node(config, XML_CIB_TAG_CRMCONFIG);
+ create_xml_node(config, XML_CIB_TAG_NODES);
+ create_xml_node(config, XML_CIB_TAG_RESOURCES);
+ create_xml_node(config, XML_CIB_TAG_CONSTRAINTS);
+
+#if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
+ {
+ xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG);
+ xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS);
+ xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR);
+
+ crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults");
+ crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS);
+ crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS);
+ crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE,
+ PCMK__RESOURCE_STICKINESS_DEFAULT);
+ }
+#endif
+ return cib_root;
+}
+
+static bool
+cib_acl_enabled(xmlNode *xml, const char *user)
+{
+ bool rc = FALSE;
+
+ if(pcmk_acl_required(user)) {
+ const char *value = NULL;
+ GHashTable *options = pcmk__strkey_table(free, free);
+
+ cib_read_config(options, xml);
+ value = cib_pref(options, "enable-acl");
+ rc = crm_is_true(value);
+ g_hash_table_destroy(options);
+ }
+
+ crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
+ return rc;
+}
+
+int
+cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query,
+ const char *section, xmlNode * req, xmlNode * input,
+ gboolean manage_counters, gboolean * config_changed,
+ xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output)
+{
+ int rc = pcmk_ok;
+ gboolean check_schema = TRUE;
+ xmlNode *top = NULL;
+ xmlNode *scratch = NULL;
+ xmlNode *local_diff = NULL;
+
+ const char *new_version = NULL;
+ const char *user = crm_element_value(req, F_CIB_USER);
+ bool with_digest = FALSE;
+
+ pcmk__output_t *out = NULL;
+ int out_rc = pcmk_rc_no_output;
+
+ crm_trace("Begin %s%s%s op",
+ (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
+ (is_query? "read-only " : ""), op);
+
+ CRM_CHECK(output != NULL, return -ENOMSG);
+ CRM_CHECK(result_cib != NULL, return -ENOMSG);
+ CRM_CHECK(config_changed != NULL, return -ENOMSG);
+
+ if(output) {
+ *output = NULL;
+ }
+
+ *result_cib = NULL;
+ *config_changed = FALSE;
+
+ if (fn == NULL) {
+ return -EINVAL;
+ }
+
+ if (is_query) {
+ xmlNode *cib_ro = current_cib;
+ xmlNode *cib_filtered = NULL;
+
+ if(cib_acl_enabled(cib_ro, user)) {
+ if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) {
+ if (cib_filtered == NULL) {
+ crm_debug("Pre-filtered the entire cib");
+ return -EACCES;
+ }
+ cib_ro = cib_filtered;
+ crm_log_xml_trace(cib_ro, "filtered");
+ }
+ }
+
+ rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
+
+ if(output == NULL || *output == NULL) {
+ /* nothing */
+
+ } else if(cib_filtered == *output) {
+ cib_filtered = NULL; /* Let them have this copy */
+
+ } else if(*output == current_cib) {
+ /* They already know not to free it */
+
+ } else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
+ /* We're about to free the document of which *output is a part */
+ *output = copy_xml(*output);
+
+ } else if((*output)->doc == current_cib->doc) {
+ /* Give them a copy they can free */
+ *output = copy_xml(*output);
+ }
+
+ free_xml(cib_filtered);
+ return rc;
+ }
+
+
+ if (pcmk_is_set(call_options, cib_zero_copy)) {
+ /* Conditional on v2 patch style */
+
+ scratch = current_cib;
+
+ /* Create a shallow copy of current_cib for the version details */
+ current_cib = create_xml_node(NULL, (const char *)scratch->name);
+ copy_in_properties(current_cib, scratch);
+ top = current_cib;
+
+ xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
+ rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
+
+ } else {
+ scratch = copy_xml(current_cib);
+ xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
+ rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output);
+
+ if(scratch && xml_tracking_changes(scratch) == FALSE) {
+ crm_trace("Inferring changes after %s op", op);
+ xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user));
+ xml_calculate_changes(current_cib, scratch);
+ }
+ CRM_CHECK(current_cib != scratch, return -EINVAL);
+ }
+
+ xml_acl_disable(scratch); /* Allow the system to make any additional changes */
+
+ if (rc == pcmk_ok && scratch == NULL) {
+ rc = -EINVAL;
+ goto done;
+
+ } else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
+ crm_trace("ACL rejected part or all of the proposed changes");
+ rc = -EACCES;
+ goto done;
+
+ } else if (rc != pcmk_ok) {
+ goto done;
+ }
+
+ if (scratch) {
+ new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION);
+
+ if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) {
+ crm_err("Discarding update with feature set '%s' greater than our own '%s'",
+ new_version, CRM_FEATURE_SET);
+ rc = -EPROTONOSUPPORT;
+ goto done;
+ }
+ }
+
+ if (current_cib) {
+ int old = 0;
+ int new = 0;
+
+ crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new);
+ crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old);
+
+ if (old > new) {
+ crm_err("%s went backwards: %d -> %d (Opts: %#x)",
+ XML_ATTR_GENERATION_ADMIN, old, new, call_options);
+ crm_log_xml_warn(req, "Bad Op");
+ crm_log_xml_warn(input, "Bad Data");
+ rc = -pcmk_err_old_data;
+
+ } else if (old == new) {
+ crm_element_value_int(scratch, XML_ATTR_GENERATION, &new);
+ crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old);
+ if (old > new) {
+ crm_err("%s went backwards: %d -> %d (Opts: %#x)",
+ XML_ATTR_GENERATION, old, new, call_options);
+ crm_log_xml_warn(req, "Bad Op");
+ crm_log_xml_warn(input, "Bad Data");
+ rc = -pcmk_err_old_data;
+ }
+ }
+ }
+
+ crm_trace("Massaging CIB contents");
+ pcmk__strip_xml_text(scratch);
+ fix_plus_plus_recursive(scratch);
+
+ if (pcmk_is_set(call_options, cib_zero_copy)) {
+ /* At this point, current_cib is just the 'cib' tag and its properties,
+ *
+ * The v1 format would barf on this, but we know the v2 patch
+ * format only needs it for the top-level version fields
+ */
+ local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters);
+
+ } else {
+ static time_t expires = 0;
+ time_t tm_now = time(NULL);
+
+ if (expires < tm_now) {
+ expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
+ with_digest = TRUE;
+ }
+
+ local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters);
+ }
+
+ // Create a log output object only if we're going to use it
+ pcmk__if_tracing(
+ {
+ rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
+ CRM_CHECK(rc == pcmk_ok, goto done);
+
+ pcmk__output_set_log_level(out, LOG_TRACE);
+ out_rc = pcmk__xml_show_changes(out, scratch);
+ },
+ {}
+ );
+ xml_accept_changes(scratch);
+
+ if(local_diff) {
+ int temp_rc = pcmk_rc_no_output;
+
+ patchset_process_digest(local_diff, current_cib, scratch, with_digest);
+
+ if (out == NULL) {
+ rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
+ CRM_CHECK(rc == pcmk_ok, goto done);
+ }
+ pcmk__output_set_log_level(out, LOG_INFO);
+ temp_rc = out->message(out, "xml-patchset", local_diff);
+ out_rc = pcmk__output_select_rc(rc, temp_rc);
+
+ crm_log_xml_trace(local_diff, "raw patch");
+ }
+
+ if (out != NULL) {
+ out->finish(out, pcmk_rc2exitc(out_rc), true, NULL);
+ pcmk__output_free(out);
+ out = NULL;
+ }
+
+ if (!pcmk_is_set(call_options, cib_zero_copy) && (local_diff != NULL)) {
+ // Original to compare against doesn't exist
+ pcmk__if_tracing(
+ {
+ // Validate the calculated patch set
+ int test_rc = pcmk_ok;
+ int format = 1;
+ xmlNode *cib_copy = copy_xml(current_cib);
+
+ crm_element_value_int(local_diff, "format", &format);
+ test_rc = xml_apply_patchset(cib_copy, local_diff,
+ manage_counters);
+
+ if (test_rc != pcmk_ok) {
+ save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
+ save_xml_to_file(current_cib, "PatchApply:input", NULL);
+ save_xml_to_file(scratch, "PatchApply:actual", NULL);
+ save_xml_to_file(local_diff, "PatchApply:diff", NULL);
+ crm_err("v%d patchset error, patch failed to apply: %s "
+ "(%d)",
+ format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
+ test_rc);
+ }
+ free_xml(cib_copy);
+ },
+ {}
+ );
+ }
+
+ if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
+ /* Throttle the amount of costly validation we perform due to status updates
+ * a) we don't really care whats in the status section
+ * b) we don't validate any of its contents at the moment anyway
+ */
+ check_schema = FALSE;
+ }
+
+ /* === scratch must not be modified after this point ===
+ * Exceptions, anything in:
+
+ static filter_t filter[] = {
+ { 0, XML_ATTR_ORIGIN },
+ { 0, XML_CIB_ATTR_WRITTEN },
+ { 0, XML_ATTR_UPDATE_ORIG },
+ { 0, XML_ATTR_UPDATE_CLIENT },
+ { 0, XML_ATTR_UPDATE_USER },
+ };
+ */
+
+ if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
+ const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION);
+
+ pcmk__xe_add_last_written(scratch);
+ if (schema) {
+ static int minimum_schema = 0;
+ int current_schema = get_schema_version(schema);
+
+ if (minimum_schema == 0) {
+ minimum_schema = get_schema_version("pacemaker-1.2");
+ }
+
+ /* Does the CIB support the "update-*" attributes... */
+ if (current_schema >= minimum_schema) {
+ const char *origin = crm_element_value(req, F_ORIG);
+
+ CRM_LOG_ASSERT(origin != NULL);
+ crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin);
+ crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT,
+ crm_element_value(req, F_CIB_CLIENTNAME));
+ crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER));
+ }
+ }
+ }
+
+ crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
+ if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) {
+ const char *current_schema = crm_element_value(scratch,
+ XML_ATTR_VALIDATION);
+
+ crm_warn("Updated CIB does not validate against %s schema",
+ pcmk__s(current_schema, "unspecified"));
+ rc = -pcmk_err_schema_validation;
+ }
+
+ done:
+
+ *result_cib = scratch;
+ if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) {
+ if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) {
+ if (*result_cib == NULL) {
+ crm_debug("Pre-filtered the entire cib result");
+ }
+ free_xml(scratch);
+ }
+ }
+
+ if(diff) {
+ *diff = local_diff;
+ } else {
+ free_xml(local_diff);
+ }
+
+ free_xml(top);
+ crm_trace("Done");
+ return rc;
+}
+
+xmlNode *
+cib_create_op(int call_id, const char *op, const char *host,
+ const char *section, xmlNode *data, int call_options,
+ const char *user_name)
+{
+ xmlNode *op_msg = create_xml_node(NULL, "cib_command");
+
+ CRM_CHECK(op_msg != NULL, return NULL);
+
+ crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command");
+
+ crm_xml_add(op_msg, F_TYPE, T_CIB);
+ crm_xml_add(op_msg, F_CIB_OPERATION, op);
+ crm_xml_add(op_msg, F_CIB_HOST, host);
+ crm_xml_add(op_msg, F_CIB_SECTION, section);
+ crm_xml_add_int(op_msg, F_CIB_CALLID, call_id);
+ if (user_name) {
+ crm_xml_add(op_msg, F_CIB_USER, user_name);
+ }
+ crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
+ crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options);
+
+ if (data != NULL) {
+ add_message_xml(op_msg, F_CIB_CALLDATA, data);
+ }
+
+ if (call_options & cib_inhibit_bcast) {
+ CRM_CHECK((call_options & cib_scope_local), return NULL);
+ }
+ return op_msg;
+}
+
+void
+cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
+{
+ xmlNode *output = NULL;
+ cib_callback_client_t *blob = NULL;
+
+ if (msg != NULL) {
+ crm_element_value_int(msg, F_CIB_RC, &rc);
+ crm_element_value_int(msg, F_CIB_CALLID, &call_id);
+ output = get_message_xml(msg, F_CIB_CALLDATA);
+ }
+
+ blob = cib__lookup_id(call_id);
+
+ if (blob == NULL) {
+ crm_trace("No callback found for call %d", call_id);
+ }
+
+ if (cib == NULL) {
+ crm_debug("No cib object supplied");
+ }
+
+ if (rc == -pcmk_err_diff_resync) {
+ /* This is an internal value that clients do not and should not care about */
+ rc = pcmk_ok;
+ }
+
+ if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
+ crm_trace("Invoking callback %s for call %d",
+ pcmk__s(blob->id, "without ID"), call_id);
+ blob->callback(msg, call_id, rc, output, blob->user_data);
+
+ } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
+ crm_warn("CIB command failed: %s", pcmk_strerror(rc));
+ crm_log_xml_debug(msg, "Failed CIB Update");
+ }
+
+ /* This may free user_data, so do it after the callback */
+ if (blob) {
+ remove_cib_op_callback(call_id, FALSE);
+ }
+
+ if (cib && cib->op_callback != NULL) {
+ crm_trace("Invoking global callback for call %d", call_id);
+ cib->op_callback(msg, call_id, rc, output);
+ }
+ crm_trace("OP callback activated for %d", call_id);
+}
+
+void
+cib_native_notify(gpointer data, gpointer user_data)
+{
+ xmlNode *msg = user_data;
+ cib_notify_client_t *entry = data;
+ const char *event = NULL;
+
+ if (msg == NULL) {
+ crm_warn("Skipping callback - NULL message");
+ return;
+ }
+
+ event = crm_element_value(msg, F_SUBTYPE);
+
+ if (entry == NULL) {
+ crm_warn("Skipping callback - NULL callback client");
+ return;
+
+ } else if (entry->callback == NULL) {
+ crm_warn("Skipping callback - NULL callback");
+ return;
+
+ } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
+ crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
+ return;
+ }
+
+ crm_trace("Invoking callback for %p/%s event...", entry, event);
+ entry->callback(event, msg);
+ crm_trace("Callback invoked...");
+}
+
+static pcmk__cluster_option_t cib_opts[] = {
+ /* name, legacy name, type, allowed values,
+ * default value, validator,
+ * short description,
+ * long description
+ */
+ {
+ "enable-acl", NULL, "boolean", NULL,
+ "false", pcmk__valid_boolean,
+ N_("Enable Access Control Lists (ACLs) for the CIB"),
+ NULL
+ },
+ {
+ "cluster-ipc-limit", NULL, "integer", NULL,
+ "500", pcmk__valid_positive_number,
+ N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
+ N_("Raise this if log has \"Evicting client\" messages for cluster daemon"
+ " PIDs (a good value is the number of resources in the cluster"
+ " multiplied by the number of nodes).")
+ },
+};
+
+void
+cib_metadata(void)
+{
+ const char *desc_short = "Cluster Information Base manager options";
+ const char *desc_long = "Cluster options used by Pacemaker's Cluster "
+ "Information Base manager";
+
+ gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short,
+ desc_long, cib_opts,
+ PCMK__NELEM(cib_opts));
+ printf("%s", s);
+ g_free(s);
+}
+
+static void
+verify_cib_options(GHashTable *options)
+{
+ pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts));
+}
+
+const char *
+cib_pref(GHashTable * options, const char *name)
+{
+ return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts),
+ name);
+}
+
+gboolean
+cib_read_config(GHashTable * options, xmlNode * current_cib)
+{
+ xmlNode *config = NULL;
+ crm_time_t *now = NULL;
+
+ if (options == NULL || current_cib == NULL) {
+ return FALSE;
+ }
+
+ now = crm_time_new(NULL);
+
+ g_hash_table_remove_all(options);
+
+ config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG);
+ if (config) {
+ pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL,
+ options, CIB_OPTIONS_FIRST, TRUE, now, NULL);
+ }
+
+ verify_cib_options(options);
+
+ crm_time_free(now);
+
+ return TRUE;
+}
+
+int
+cib_internal_op(cib_t * cib, const char *op, const char *host,
+ const char *section, xmlNode * data,
+ xmlNode ** output_data, int call_options, const char *user_name)
+{
+ int (*delegate) (cib_t * cib, const char *op, const char *host,
+ const char *section, xmlNode * data,
+ xmlNode ** output_data, int call_options, const char *user_name) =
+ cib->delegate_fn;
+
+ if(user_name == NULL) {
+ user_name = getenv("CIB_user");
+ }
+
+ return delegate(cib, op, host, section, data, output_data, call_options, user_name);
+}
+
+/*!
+ * \brief Apply a CIB update patch to a given CIB
+ *
+ * \param[in] event CIB update patch
+ * \param[in] input CIB to patch
+ * \param[out] output Resulting CIB after patch
+ * \param[in] level Log the patch at this log level (unless LOG_CRIT)
+ *
+ * \return Legacy Pacemaker return code
+ * \note sbd calls this function
+ */
+int
+cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
+ int level)
+{
+ int rc = pcmk_err_generic;
+
+ xmlNode *diff = NULL;
+
+ CRM_ASSERT(event);
+ CRM_ASSERT(input);
+ CRM_ASSERT(output);
+
+ crm_element_value_int(event, F_CIB_RC, &rc);
+ diff = get_message_xml(event, F_CIB_UPDATE_RESULT);
+
+ if (rc < pcmk_ok || diff == NULL) {
+ return rc;
+ }
+
+ if (level > LOG_CRIT) {
+ pcmk__output_t *out = NULL;
+
+ rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
+ CRM_CHECK(rc == pcmk_ok, return rc);
+
+ pcmk__output_set_log_level(out, level);
+ rc = out->message(out, "xml-patchset", diff);
+ out->finish(out, pcmk_rc2exitc(rc), true, NULL);
+ pcmk__output_free(out);
+ rc = pcmk_ok;
+ }
+
+ if (input != NULL) {
+ rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
+ NULL);
+
+ if (rc != pcmk_ok) {
+ crm_debug("Update didn't apply: %s (%d) %p",
+ pcmk_strerror(rc), rc, *output);
+
+ if (rc == -pcmk_err_old_data) {
+ crm_trace("Masking error, we already have the supplied update");
+ return pcmk_ok;
+ }
+ free_xml(*output);
+ *output = NULL;
+ return rc;
+ }
+ }
+ return rc;
+}
+
+#define log_signon_query_err(out, fmt, args...) do { \
+ if (out != NULL) { \
+ out->err(out, fmt, ##args); \
+ } else { \
+ crm_err(fmt, ##args); \
+ } \
+ } while (0)
+
+int
+cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
+{
+ int rc = pcmk_rc_ok;
+ cib_t *cib_conn = NULL;
+
+ CRM_ASSERT(cib_object != NULL);
+
+ if (cib == NULL) {
+ cib_conn = cib_new();
+ } else {
+ if (*cib == NULL) {
+ *cib = cib_new();
+ }
+ cib_conn = *cib;
+ }
+
+ if (cib_conn == NULL) {
+ return ENOMEM;
+ }
+
+ if (cib_conn->state == cib_disconnected) {
+ rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
+ rc = pcmk_legacy2rc(rc);
+ }
+
+ if (rc != pcmk_rc_ok) {
+ log_signon_query_err(out, "Could not connect to the CIB: %s",
+ pcmk_rc_str(rc));
+ goto done;
+ }
+
+ if (out != NULL) {
+ out->transient(out, "Querying CIB...");
+ }
+ rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
+ cib_scope_local|cib_sync_call);
+ rc = pcmk_legacy2rc(rc);
+
+ if (rc != pcmk_rc_ok) {
+ log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
+ }
+
+done:
+ if (cib == NULL) {
+ cib__clean_up_connection(&cib_conn);
+ }
+
+ if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
+ return pcmk_rc_no_input;
+ }
+ return rc;
+}
+
+int
+cib__clean_up_connection(cib_t **cib)
+{
+ int rc;
+
+ if (*cib == NULL) {
+ return pcmk_rc_ok;
+ }
+
+ rc = (*cib)->cmds->signoff(*cib);
+ cib_delete(*cib);
+ *cib = NULL;
+ return pcmk_legacy2rc(rc);
+}
+
+// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
+
+#include <crm/cib/util_compat.h>
+
+const char *
+get_object_path(const char *object_type)
+{
+ return pcmk_cib_xpath_for(object_type);
+}
+
+const char *
+get_object_parent(const char *object_type)
+{
+ return pcmk_cib_parent_name_for(object_type);
+}
+
+xmlNode *
+get_object_root(const char *object_type, xmlNode *the_root)
+{
+ return pcmk_find_cib_element(the_root, object_type);
+}
+
+// LCOV_EXCL_STOP
+// End deprecated API