summaryrefslogtreecommitdiffstats
path: root/daemons/attrd/attrd_ipc.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/attrd/attrd_ipc.c')
-rw-r--r--daemons/attrd/attrd_ipc.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
new file mode 100644
index 0000000..9d3dfff
--- /dev/null
+++ b/daemons/attrd/attrd_ipc.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <crm/cluster.h>
+#include <crm/cluster/internal.h>
+#include <crm/msg_xml.h>
+#include <crm/common/acl_internal.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/logging.h>
+#include <crm/common/results.h>
+#include <crm/common/strings_internal.h>
+#include <crm/common/util.h>
+
+#include "pacemaker-attrd.h"
+
+static qb_ipcs_service_t *ipcs = NULL;
+
+/*!
+ * \internal
+ * \brief Build the XML reply to a client query
+ *
+ * param[in] attr Name of requested attribute
+ * param[in] host Name of requested host (or NULL for all hosts)
+ *
+ * \return New XML reply
+ * \note Caller is responsible for freeing the resulting XML
+ */
+static xmlNode *build_query_reply(const char *attr, const char *host)
+{
+ xmlNode *reply = create_xml_node(NULL, __func__);
+ attribute_t *a;
+
+ if (reply == NULL) {
+ return NULL;
+ }
+ crm_xml_add(reply, F_TYPE, T_ATTRD);
+ crm_xml_add(reply, F_SUBTYPE, PCMK__ATTRD_CMD_QUERY);
+ crm_xml_add(reply, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
+
+ /* If desired attribute exists, add its value(s) to the reply */
+ a = g_hash_table_lookup(attributes, attr);
+ if (a) {
+ attribute_value_t *v;
+ xmlNode *host_value;
+
+ crm_xml_add(reply, PCMK__XA_ATTR_NAME, attr);
+
+ /* Allow caller to use "localhost" to refer to local node */
+ if (pcmk__str_eq(host, "localhost", pcmk__str_casei)) {
+ host = attrd_cluster->uname;
+ crm_trace("Mapped localhost to %s", host);
+ }
+
+ /* If a specific node was requested, add its value */
+ if (host) {
+ v = g_hash_table_lookup(a->values, host);
+ host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
+ if (host_value == NULL) {
+ free_xml(reply);
+ return NULL;
+ }
+ pcmk__xe_add_node(host_value, host, 0);
+ crm_xml_add(host_value, PCMK__XA_ATTR_VALUE,
+ (v? v->current : NULL));
+
+ /* Otherwise, add all nodes' values */
+ } else {
+ GHashTableIter iter;
+
+ g_hash_table_iter_init(&iter, a->values);
+ while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
+ host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
+ if (host_value == NULL) {
+ free_xml(reply);
+ return NULL;
+ }
+ pcmk__xe_add_node(host_value, v->nodename, 0);
+ crm_xml_add(host_value, PCMK__XA_ATTR_VALUE, v->current);
+ }
+ }
+ }
+ return reply;
+}
+
+xmlNode *
+attrd_client_clear_failure(pcmk__request_t *request)
+{
+ xmlNode *xml = request->xml;
+ const char *rsc, *op, *interval_spec;
+
+ if (minimum_protocol_version >= 2) {
+ /* Propagate to all peers (including ourselves).
+ * This ends up at attrd_peer_message().
+ */
+ attrd_send_message(NULL, xml, false);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+ }
+
+ rsc = crm_element_value(xml, PCMK__XA_ATTR_RESOURCE);
+ op = crm_element_value(xml, PCMK__XA_ATTR_OPERATION);
+ interval_spec = crm_element_value(xml, PCMK__XA_ATTR_INTERVAL);
+
+ /* Map this to an update */
+ crm_xml_add(xml, PCMK__XA_TASK, PCMK__ATTRD_CMD_UPDATE);
+
+ /* Add regular expression matching desired attributes */
+
+ if (rsc) {
+ char *pattern;
+
+ if (op == NULL) {
+ pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
+
+ } else {
+ guint interval_ms = crm_parse_interval_spec(interval_spec);
+
+ pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP,
+ rsc, op, interval_ms);
+ }
+
+ crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, pattern);
+ free(pattern);
+
+ } else {
+ crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, ATTRD_RE_CLEAR_ALL);
+ }
+
+ /* Make sure attribute and value are not set, so we delete via regex */
+ if (crm_element_value(xml, PCMK__XA_ATTR_NAME)) {
+ crm_xml_replace(xml, PCMK__XA_ATTR_NAME, NULL);
+ }
+ if (crm_element_value(xml, PCMK__XA_ATTR_VALUE)) {
+ crm_xml_replace(xml, PCMK__XA_ATTR_VALUE, NULL);
+ }
+
+ return attrd_client_update(request);
+}
+
+xmlNode *
+attrd_client_peer_remove(pcmk__request_t *request)
+{
+ xmlNode *xml = request->xml;
+
+ // Host and ID are not used in combination, rather host has precedence
+ const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
+ char *host_alloc = NULL;
+
+ attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
+
+ if (host == NULL) {
+ int nodeid = 0;
+
+ crm_element_value_int(xml, PCMK__XA_ATTR_NODE_ID, &nodeid);
+ if (nodeid > 0) {
+ crm_node_t *node = pcmk__search_cluster_node_cache(nodeid, NULL);
+ char *host_alloc = NULL;
+
+ if (node && node->uname) {
+ // Use cached name if available
+ host = node->uname;
+ } else {
+ // Otherwise ask cluster layer
+ host_alloc = get_node_name(nodeid);
+ host = host_alloc;
+ }
+ pcmk__xe_add_node(xml, host, 0);
+ }
+ }
+
+ if (host) {
+ crm_info("Client %s is requesting all values for %s be removed",
+ pcmk__client_name(request->ipc_client), host);
+ attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
+ free(host_alloc);
+ } else {
+ crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
+ pcmk__client_name(request->ipc_client));
+ }
+
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+}
+
+xmlNode *
+attrd_client_query(pcmk__request_t *request)
+{
+ xmlNode *query = request->xml;
+ xmlNode *reply = NULL;
+ const char *attr = NULL;
+
+ crm_debug("Query arrived from %s", pcmk__client_name(request->ipc_client));
+
+ /* Request must specify attribute name to query */
+ attr = crm_element_value(query, PCMK__XA_ATTR_NAME);
+ if (attr == NULL) {
+ pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ "Ignoring malformed query from %s (no attribute name given)",
+ pcmk__client_name(request->ipc_client));
+ return NULL;
+ }
+
+ /* Build the XML reply */
+ reply = build_query_reply(attr, crm_element_value(query,
+ PCMK__XA_ATTR_NODE_NAME));
+ if (reply == NULL) {
+ pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ "Could not respond to query from %s: could not create XML reply",
+ pcmk__client_name(request->ipc_client));
+ return NULL;
+ } else {
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ }
+
+ request->ipc_client->request_id = 0;
+ return reply;
+}
+
+xmlNode *
+attrd_client_refresh(pcmk__request_t *request)
+{
+ crm_info("Updating all attributes");
+
+ attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
+ attrd_write_attributes(true, true);
+
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+}
+
+static void
+handle_missing_host(xmlNode *xml)
+{
+ const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
+
+ if (host == NULL) {
+ crm_trace("Inferring host");
+ pcmk__xe_add_node(xml, attrd_cluster->uname, attrd_cluster->nodeid);
+ }
+}
+
+/* Convert a single IPC message with a regex into one with multiple children, one
+ * for each regex match.
+ */
+static int
+expand_regexes(xmlNode *xml, const char *attr, const char *value, const char *regex)
+{
+ if (attr == NULL && regex) {
+ bool matched = false;
+ GHashTableIter aIter;
+ regex_t r_patt;
+
+ crm_debug("Setting %s to %s", regex, value);
+ if (regcomp(&r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
+ return EINVAL;
+ }
+
+ g_hash_table_iter_init(&aIter, attributes);
+ while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
+ int status = regexec(&r_patt, attr, 0, NULL, 0);
+
+ if (status == 0) {
+ xmlNode *child = create_xml_node(xml, XML_ATTR_OP);
+
+ crm_trace("Matched %s with %s", attr, regex);
+ matched = true;
+
+ /* Copy all the attributes from the parent over, but remove the
+ * regex and replace it with the name.
+ */
+ attrd_copy_xml_attributes(xml, child);
+ crm_xml_replace(child, PCMK__XA_ATTR_PATTERN, NULL);
+ crm_xml_add(child, PCMK__XA_ATTR_NAME, attr);
+ }
+ }
+
+ regfree(&r_patt);
+
+ /* Return a code if we never matched anything. This should not be treated
+ * as an error. It indicates there was a regex, and it was a valid regex,
+ * but simply did not match anything and the caller should not continue
+ * doing any regex-related processing.
+ */
+ if (!matched) {
+ return pcmk_rc_op_unsatisfied;
+ }
+
+ } else if (attr == NULL) {
+ return pcmk_rc_bad_nvpair;
+ }
+
+ return pcmk_rc_ok;
+}
+
+static int
+handle_regexes(pcmk__request_t *request)
+{
+ xmlNode *xml = request->xml;
+ int rc = pcmk_rc_ok;
+
+ const char *attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
+ const char *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
+ const char *regex = crm_element_value(xml, PCMK__XA_ATTR_PATTERN);
+
+ rc = expand_regexes(xml, attr, value, regex);
+
+ if (rc == EINVAL) {
+ pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ "Bad regex '%s' for update from client %s", regex,
+ pcmk__client_name(request->ipc_client));
+
+ } else if (rc == pcmk_rc_bad_nvpair) {
+ crm_err("Update request did not specify attribute or regular expression");
+ pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ "Client %s update request did not specify attribute or regular expression",
+ pcmk__client_name(request->ipc_client));
+ }
+
+ return rc;
+}
+
+static int
+handle_value_expansion(const char **value, xmlNode *xml, const char *op,
+ const char *attr)
+{
+ attribute_t *a = g_hash_table_lookup(attributes, attr);
+
+ if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
+ return EINVAL;
+ }
+
+ if (*value && attrd_value_needs_expansion(*value)) {
+ int int_value;
+ attribute_value_t *v = NULL;
+
+ if (a) {
+ const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
+ v = g_hash_table_lookup(a->values, host);
+ }
+
+ int_value = attrd_expand_value(*value, (v? v->current : NULL));
+
+ crm_info("Expanded %s=%s to %d", attr, *value, int_value);
+ crm_xml_add_int(xml, PCMK__XA_ATTR_VALUE, int_value);
+
+ /* Replacing the value frees the previous memory, so re-query it */
+ *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
+ }
+
+ return pcmk_rc_ok;
+}
+
+static void
+send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
+{
+ if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
+ /* The client is waiting on the cluster-wide sync point. In this case,
+ * the response ACK is not sent until this attrd broadcasts the update
+ * and receives its own confirmation back from all peers.
+ */
+ attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
+ attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
+
+ } else {
+ /* The client is either waiting on the local sync point or was not
+ * waiting on any sync point at all. For the local sync point, the
+ * response ACK is sent in attrd_peer_update. For clients not
+ * waiting on any sync point, the response ACK is sent in
+ * handle_update_request immediately before this function was called.
+ */
+ attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
+ }
+}
+
+static int
+send_child_update(xmlNode *child, void *data)
+{
+ pcmk__request_t *request = (pcmk__request_t *) data;
+
+ /* Calling pcmk__set_result is handled by one of these calls to
+ * attrd_client_update, so no need to do it again here.
+ */
+ request->xml = child;
+ attrd_client_update(request);
+ return pcmk_rc_ok;
+}
+
+xmlNode *
+attrd_client_update(pcmk__request_t *request)
+{
+ xmlNode *xml = request->xml;
+ const char *attr, *value, *regex;
+
+ /* If the message has children, that means it is a message from a newer
+ * client that supports sending multiple operations at a time. There are
+ * two ways we can handle that.
+ */
+ if (xml_has_children(xml)) {
+ if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
+ /* First, if all peers support a certain protocol version, we can
+ * just broadcast the big message and they'll handle it. However,
+ * we also need to apply all the transformations in this function
+ * to the children since they don't happen anywhere else.
+ */
+ for (xmlNode *child = first_named_child(xml, XML_ATTR_OP); child != NULL;
+ child = crm_next_same_xml(child)) {
+ attr = crm_element_value(child, PCMK__XA_ATTR_NAME);
+ value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
+
+ handle_missing_host(child);
+
+ if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
+ pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
+ "Attribute %s does not exist", attr);
+ return NULL;
+ }
+ }
+
+ send_update_msg_to_cluster(request, xml);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+
+ } else {
+ /* Save the original xml node pointer so it can be restored after iterating
+ * over all the children.
+ */
+ xmlNode *orig_xml = request->xml;
+
+ /* Second, if they do not support that protocol version, split it
+ * up into individual messages and call attrd_client_update on
+ * each one.
+ */
+ pcmk__xe_foreach_child(xml, XML_ATTR_OP, send_child_update, request);
+ request->xml = orig_xml;
+ }
+
+ return NULL;
+ }
+
+ attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
+ value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
+ regex = crm_element_value(xml, PCMK__XA_ATTR_PATTERN);
+
+ if (handle_regexes(request) != pcmk_rc_ok) {
+ /* Error handling was already dealt with in handle_regexes, so just return. */
+ return NULL;
+ } else if (regex) {
+ /* Recursively call attrd_client_update on the new message with regexes
+ * expanded. If supported by the attribute daemon, this means that all
+ * matches can also be handled atomically.
+ */
+ return attrd_client_update(request);
+ }
+
+ handle_missing_host(xml);
+
+ if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
+ pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
+ "Attribute %s does not exist", attr);
+ return NULL;
+ }
+
+ crm_debug("Broadcasting %s[%s]=%s%s", attr, crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME),
+ value, (attrd_election_won()? " (writer)" : ""));
+
+ send_update_msg_to_cluster(request, xml);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Accept a new client IPC connection
+ *
+ * \param[in,out] c New connection
+ * \param[in] uid Client user id
+ * \param[in] gid Client group id
+ *
+ * \return pcmk_ok on success, -errno otherwise
+ */
+static int32_t
+attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
+{
+ crm_trace("New client connection %p", c);
+ if (attrd_shutting_down()) {
+ crm_info("Ignoring new connection from pid %d during shutdown",
+ pcmk__client_pid(c));
+ return -EPERM;
+ }
+
+ if (pcmk__new_client(c, uid, gid) == NULL) {
+ return -EIO;
+ }
+ return pcmk_ok;
+}
+
+/*!
+ * \internal
+ * \brief Destroy a client IPC connection
+ *
+ * \param[in] c Connection to destroy
+ *
+ * \return FALSE (i.e. do not re-run this callback)
+ */
+static int32_t
+attrd_ipc_closed(qb_ipcs_connection_t *c)
+{
+ pcmk__client_t *client = pcmk__find_client(c);
+
+ if (client == NULL) {
+ crm_trace("Ignoring request to clean up unknown connection %p", c);
+ } else {
+ crm_trace("Cleaning up closed client connection %p", c);
+
+ /* Remove the client from the sync point waitlist if it's present. */
+ attrd_remove_client_from_waitlist(client);
+
+ /* And no longer wait for confirmations from any peers. */
+ attrd_do_not_wait_for_client(client);
+
+ pcmk__free_client(client);
+ }
+
+ return FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Destroy a client IPC connection
+ *
+ * \param[in,out] c Connection to destroy
+ *
+ * \note We handle a destroyed connection the same as a closed one,
+ * but we need a separate handler because the return type is different.
+ */
+static void
+attrd_ipc_destroy(qb_ipcs_connection_t *c)
+{
+ crm_trace("Destroying client connection %p", c);
+ attrd_ipc_closed(c);
+}
+
+static int32_t
+attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
+{
+ uint32_t id = 0;
+ uint32_t flags = 0;
+ pcmk__client_t *client = pcmk__find_client(c);
+ xmlNode *xml = NULL;
+
+ // Sanity-check, and parse XML from IPC data
+ CRM_CHECK((c != NULL) && (client != NULL), return 0);
+ if (data == NULL) {
+ crm_debug("No IPC data from PID %d", pcmk__client_pid(c));
+ return 0;
+ }
+
+ xml = pcmk__client_data2xml(client, data, &id, &flags);
+
+ if (xml == NULL) {
+ crm_debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
+ pcmk__ipc_send_ack(client, id, flags, "ack", NULL, CRM_EX_PROTOCOL);
+ return 0;
+
+ } else {
+ pcmk__request_t request = {
+ .ipc_client = client,
+ .ipc_id = id,
+ .ipc_flags = flags,
+ .peer = NULL,
+ .xml = xml,
+ .call_options = 0,
+ .result = PCMK__UNKNOWN_RESULT,
+ };
+
+ CRM_ASSERT(client->user != NULL);
+ pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
+
+ request.op = crm_element_value_copy(request.xml, PCMK__XA_TASK);
+ CRM_CHECK(request.op != NULL, return 0);
+
+ attrd_handle_request(&request);
+ pcmk__reset_request(&request);
+ }
+
+ free_xml(xml);
+ return 0;
+}
+
+static struct qb_ipcs_service_handlers ipc_callbacks = {
+ .connection_accept = attrd_ipc_accept,
+ .connection_created = NULL,
+ .msg_process = attrd_ipc_dispatch,
+ .connection_closed = attrd_ipc_closed,
+ .connection_destroyed = attrd_ipc_destroy
+};
+
+void
+attrd_ipc_fini(void)
+{
+ if (ipcs != NULL) {
+ pcmk__drop_all_clients(ipcs);
+ qb_ipcs_destroy(ipcs);
+ ipcs = NULL;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Set up attrd IPC communication
+ */
+void
+attrd_init_ipc(void)
+{
+ pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
+}