diff options
Diffstat (limited to 'lib/cib')
-rw-r--r-- | lib/cib/Makefile.am | 28 | ||||
-rw-r--r-- | lib/cib/cib_attrs.c | 732 | ||||
-rw-r--r-- | lib/cib/cib_client.c | 750 | ||||
-rw-r--r-- | lib/cib/cib_file.c | 919 | ||||
-rw-r--r-- | lib/cib/cib_native.c | 502 | ||||
-rw-r--r-- | lib/cib/cib_ops.c | 869 | ||||
-rw-r--r-- | lib/cib/cib_remote.c | 638 | ||||
-rw-r--r-- | lib/cib/cib_utils.c | 837 |
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 |