root/daemons/attrd/attrd_ipc.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. build_query_reply
  2. attrd_client_clear_failure
  3. attrd_client_peer_remove
  4. attrd_client_query
  5. attrd_client_refresh
  6. handle_missing_host
  7. handle_value_expansion
  8. attrd_client_update
  9. attrd_ipc_accept
  10. attrd_ipc_closed
  11. attrd_ipc_destroy
  12. attrd_ipc_dispatch
  13. attrd_ipc_fini
  14. attrd_init_ipc

   1 /*
   2  * Copyright 2004-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <errno.h>
  13 #include <stdint.h>
  14 #include <stdlib.h>
  15 #include <sys/types.h>
  16 
  17 #include <crm/cluster.h>
  18 #include <crm/cluster/internal.h>
  19 #include <crm/msg_xml.h>
  20 #include <crm/common/acl_internal.h>
  21 #include <crm/common/ipc_internal.h>
  22 #include <crm/common/logging.h>
  23 #include <crm/common/results.h>
  24 #include <crm/common/strings_internal.h>
  25 #include <crm/common/util.h>
  26 
  27 #include "pacemaker-attrd.h"
  28 
  29 static qb_ipcs_service_t *ipcs = NULL;
  30 
  31 /*!
  32  * \internal
  33  * \brief Build the XML reply to a client query
  34  *
  35  * param[in] attr Name of requested attribute
  36  * param[in] host Name of requested host (or NULL for all hosts)
  37  *
  38  * \return New XML reply
  39  * \note Caller is responsible for freeing the resulting XML
  40  */
  41 static xmlNode *build_query_reply(const char *attr, const char *host)
     /* [previous][next][first][last][top][bottom][index][help] */
  42 {
  43     xmlNode *reply = create_xml_node(NULL, __func__);
  44     attribute_t *a;
  45 
  46     if (reply == NULL) {
  47         return NULL;
  48     }
  49     crm_xml_add(reply, F_TYPE, T_ATTRD);
  50     crm_xml_add(reply, F_SUBTYPE, PCMK__ATTRD_CMD_QUERY);
  51     crm_xml_add(reply, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
  52 
  53     /* If desired attribute exists, add its value(s) to the reply */
  54     a = g_hash_table_lookup(attributes, attr);
  55     if (a) {
  56         attribute_value_t *v;
  57         xmlNode *host_value;
  58 
  59         crm_xml_add(reply, PCMK__XA_ATTR_NAME, attr);
  60 
  61         /* Allow caller to use "localhost" to refer to local node */
  62         if (pcmk__str_eq(host, "localhost", pcmk__str_casei)) {
  63             host = attrd_cluster->uname;
  64             crm_trace("Mapped localhost to %s", host);
  65         }
  66 
  67         /* If a specific node was requested, add its value */
  68         if (host) {
  69             v = g_hash_table_lookup(a->values, host);
  70             host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
  71             if (host_value == NULL) {
  72                 free_xml(reply);
  73                 return NULL;
  74             }
  75             pcmk__xe_add_node(host_value, host, 0);
  76             crm_xml_add(host_value, PCMK__XA_ATTR_VALUE,
  77                         (v? v->current : NULL));
  78 
  79         /* Otherwise, add all nodes' values */
  80         } else {
  81             GHashTableIter iter;
  82 
  83             g_hash_table_iter_init(&iter, a->values);
  84             while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
  85                 host_value = create_xml_node(reply, XML_CIB_TAG_NODE);
  86                 if (host_value == NULL) {
  87                     free_xml(reply);
  88                     return NULL;
  89                 }
  90                 pcmk__xe_add_node(host_value, v->nodename, 0);
  91                 crm_xml_add(host_value, PCMK__XA_ATTR_VALUE, v->current);
  92             }
  93         }
  94     }
  95     return reply;
  96 }
  97 
  98 xmlNode *
  99 attrd_client_clear_failure(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 100 {
 101     xmlNode *xml = request->xml;
 102     const char *rsc, *op, *interval_spec;
 103 
 104     attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
 105 
 106     if (minimum_protocol_version >= 2) {
 107         /* Propagate to all peers (including ourselves).
 108          * This ends up at attrd_peer_message().
 109          */
 110         attrd_send_message(NULL, xml);
 111         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 112         return NULL;
 113     }
 114 
 115     rsc = crm_element_value(xml, PCMK__XA_ATTR_RESOURCE);
 116     op = crm_element_value(xml, PCMK__XA_ATTR_OPERATION);
 117     interval_spec = crm_element_value(xml, PCMK__XA_ATTR_INTERVAL);
 118 
 119     /* Map this to an update */
 120     crm_xml_add(xml, PCMK__XA_TASK, PCMK__ATTRD_CMD_UPDATE);
 121 
 122     /* Add regular expression matching desired attributes */
 123 
 124     if (rsc) {
 125         char *pattern;
 126 
 127         if (op == NULL) {
 128             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
 129 
 130         } else {
 131             guint interval_ms = crm_parse_interval_spec(interval_spec);
 132 
 133             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP,
 134                                         rsc, op, interval_ms);
 135         }
 136 
 137         crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, pattern);
 138         free(pattern);
 139 
 140     } else {
 141         crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, ATTRD_RE_CLEAR_ALL);
 142     }
 143 
 144     /* Make sure attribute and value are not set, so we delete via regex */
 145     if (crm_element_value(xml, PCMK__XA_ATTR_NAME)) {
 146         crm_xml_replace(xml, PCMK__XA_ATTR_NAME, NULL);
 147     }
 148     if (crm_element_value(xml, PCMK__XA_ATTR_VALUE)) {
 149         crm_xml_replace(xml, PCMK__XA_ATTR_VALUE, NULL);
 150     }
 151 
 152     return attrd_client_update(request);
 153 }
 154 
 155 xmlNode *
 156 attrd_client_peer_remove(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 157 {
 158     xmlNode *xml = request->xml;
 159 
 160     // Host and ID are not used in combination, rather host has precedence
 161     const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 162     char *host_alloc = NULL;
 163 
 164     attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
 165 
 166     if (host == NULL) {
 167         int nodeid = 0;
 168 
 169         crm_element_value_int(xml, PCMK__XA_ATTR_NODE_ID, &nodeid);
 170         if (nodeid > 0) {
 171             crm_node_t *node = pcmk__search_cluster_node_cache(nodeid, NULL);
 172             char *host_alloc = NULL;
 173 
 174             if (node && node->uname) {
 175                 // Use cached name if available
 176                 host = node->uname;
 177             } else {
 178                 // Otherwise ask cluster layer
 179                 host_alloc = get_node_name(nodeid);
 180                 host = host_alloc;
 181             }
 182             pcmk__xe_add_node(xml, host, 0);
 183         }
 184     }
 185 
 186     if (host) {
 187         crm_info("Client %s is requesting all values for %s be removed",
 188                  pcmk__client_name(request->ipc_client), host);
 189         attrd_send_message(NULL, xml); /* ends up at attrd_peer_message() */
 190         free(host_alloc);
 191     } else {
 192         crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
 193                  pcmk__client_name(request->ipc_client));
 194     }
 195 
 196     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 197     return NULL;
 198 }
 199 
 200 xmlNode *
 201 attrd_client_query(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 202 {
 203     xmlNode *query = request->xml;
 204     xmlNode *reply = NULL;
 205     const char *attr = NULL;
 206 
 207     crm_debug("Query arrived from %s", pcmk__client_name(request->ipc_client));
 208 
 209     /* Request must specify attribute name to query */
 210     attr = crm_element_value(query, PCMK__XA_ATTR_NAME);
 211     if (attr == NULL) {
 212         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 213                             "Ignoring malformed query from %s (no attribute name given)",
 214                             pcmk__client_name(request->ipc_client));
 215         return NULL;
 216     }
 217 
 218     /* Build the XML reply */
 219     reply = build_query_reply(attr, crm_element_value(query,
 220                                                       PCMK__XA_ATTR_NODE_NAME));
 221     if (reply == NULL) {
 222         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 223                             "Could not respond to query from %s: could not create XML reply",
 224                             pcmk__client_name(request->ipc_client));
 225         return NULL;
 226     } else {
 227         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 228     }
 229 
 230     request->ipc_client->request_id = 0;
 231     return reply;
 232 }
 233 
 234 xmlNode *
 235 attrd_client_refresh(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 236 {
 237     crm_info("Updating all attributes");
 238 
 239     attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
 240     attrd_write_attributes(true, true);
 241 
 242     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 243     return NULL;
 244 }
 245 
 246 static void
 247 handle_missing_host(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 248 {
 249     const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 250 
 251     if (host == NULL) {
 252         crm_trace("Inferring host");
 253         pcmk__xe_add_node(xml, attrd_cluster->uname, attrd_cluster->nodeid);
 254     }
 255 }
 256 
 257 static int
 258 handle_value_expansion(const char **value, xmlNode *xml, const char *op,
     /* [previous][next][first][last][top][bottom][index][help] */
 259                        const char *attr)
 260 {
 261     attribute_t *a = g_hash_table_lookup(attributes, attr);
 262 
 263     if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
 264         return EINVAL;
 265     }
 266 
 267     if (*value && attrd_value_needs_expansion(*value)) {
 268         int int_value;
 269         attribute_value_t *v = NULL;
 270 
 271         if (a) {
 272             const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 273             v = g_hash_table_lookup(a->values, host);
 274         }
 275 
 276         int_value = attrd_expand_value(*value, (v? v->current : NULL));
 277 
 278         crm_info("Expanded %s=%s to %d", attr, *value, int_value);
 279         crm_xml_add_int(xml, PCMK__XA_ATTR_VALUE, int_value);
 280 
 281         /* Replacing the value frees the previous memory, so re-query it */
 282         *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
 283     }
 284 
 285     return pcmk_rc_ok;
 286 }
 287 
 288 xmlNode *
 289 attrd_client_update(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 290 {
 291     xmlNode *xml = request->xml;
 292     const char *attr, *value, *regex;
 293 
 294     /* If the message has children, that means it is a message from a newer
 295      * client that supports sending multiple operations at a time.  There are
 296      * two ways we can handle that.
 297      */
 298     if (xml_has_children(xml)) {
 299         if (minimum_protocol_version >= 4) {
 300             /* First, if all peers support a certain protocol version, we can
 301              * just broadcast the big message and they'll handle it.  However,
 302              * we also need to apply all the transformations in this function
 303              * to the children since they don't happen anywhere else.
 304              */
 305             for (xmlNode *child = first_named_child(xml, XML_ATTR_OP); child != NULL;
 306                  child = crm_next_same_xml(child)) {
 307                 attr = crm_element_value(child, PCMK__XA_ATTR_NAME);
 308                 value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
 309 
 310                 handle_missing_host(child);
 311 
 312                 if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
 313                     pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
 314                                         "Attribute %s does not exist", attr);
 315                     return NULL;
 316                 }
 317             }
 318 
 319             attrd_send_message(NULL, xml);
 320             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 321 
 322         } else {
 323             /* Second, if they do not support that protocol version, split it
 324              * up into individual messages and call attrd_client_update on
 325              * each one.
 326              */
 327             for (xmlNode *child = first_named_child(xml, XML_ATTR_OP); child != NULL;
 328                  child = crm_next_same_xml(child)) {
 329                 request->xml = child;
 330                 /* Calling pcmk__set_result is handled by one of these calls to
 331                  * attrd_client_update, so no need to do it again here.
 332                  */
 333                 attrd_client_update(request);
 334             }
 335         }
 336 
 337         return NULL;
 338     }
 339 
 340     attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
 341     value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
 342     regex = crm_element_value(xml, PCMK__XA_ATTR_PATTERN);
 343 
 344     /* If a regex was specified, broadcast a message for each match */
 345     if ((attr == NULL) && regex) {
 346         GHashTableIter aIter;
 347         regex_t *r_patt = calloc(1, sizeof(regex_t));
 348 
 349         crm_debug("Setting %s to %s", regex, value);
 350         if (regcomp(r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
 351             pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 352                                 "Bad regex '%s' for update from client %s", regex,
 353                                 pcmk__client_name(request->ipc_client));
 354 
 355         } else {
 356             g_hash_table_iter_init(&aIter, attributes);
 357             while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
 358                 int status = regexec(r_patt, attr, 0, NULL, 0);
 359 
 360                 if (status == 0) {
 361                     crm_trace("Matched %s with %s", attr, regex);
 362                     crm_xml_add(xml, PCMK__XA_ATTR_NAME, attr);
 363                     attrd_send_message(NULL, xml);
 364                 }
 365             }
 366 
 367             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 368         }
 369 
 370         regfree(r_patt);
 371         free(r_patt);
 372         return NULL;
 373 
 374     } else if (attr == NULL) {
 375         crm_err("Update request did not specify attribute or regular expression");
 376         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 377                             "Client %s update request did not specify attribute or regular expression",
 378                             pcmk__client_name(request->ipc_client));
 379         return NULL;
 380     }
 381 
 382     handle_missing_host(xml);
 383 
 384     if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
 385         pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
 386                             "Attribute %s does not exist", attr);
 387         return NULL;
 388     }
 389 
 390     crm_debug("Broadcasting %s[%s]=%s%s", attr, crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME),
 391               value, (attrd_election_won()? " (writer)" : ""));
 392 
 393     attrd_send_message(NULL, xml); /* ends up at attrd_peer_message() */
 394     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 395     return NULL;
 396 }
 397 
 398 /*!
 399  * \internal
 400  * \brief Accept a new client IPC connection
 401  *
 402  * \param[in,out] c    New connection
 403  * \param[in]     uid  Client user id
 404  * \param[in]     gid  Client group id
 405  *
 406  * \return pcmk_ok on success, -errno otherwise
 407  */
 408 static int32_t
 409 attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 410 {
 411     crm_trace("New client connection %p", c);
 412     if (attrd_shutting_down()) {
 413         crm_info("Ignoring new connection from pid %d during shutdown",
 414                  pcmk__client_pid(c));
 415         return -EPERM;
 416     }
 417 
 418     if (pcmk__new_client(c, uid, gid) == NULL) {
 419         return -EIO;
 420     }
 421     return pcmk_ok;
 422 }
 423 
 424 /*!
 425  * \internal
 426  * \brief Destroy a client IPC connection
 427  *
 428  * \param[in] c  Connection to destroy
 429  *
 430  * \return FALSE (i.e. do not re-run this callback)
 431  */
 432 static int32_t
 433 attrd_ipc_closed(qb_ipcs_connection_t *c)
     /* [previous][next][first][last][top][bottom][index][help] */
 434 {
 435     pcmk__client_t *client = pcmk__find_client(c);
 436 
 437     if (client == NULL) {
 438         crm_trace("Ignoring request to clean up unknown connection %p", c);
 439     } else {
 440         crm_trace("Cleaning up closed client connection %p", c);
 441         pcmk__free_client(client);
 442     }
 443     return FALSE;
 444 }
 445 
 446 /*!
 447  * \internal
 448  * \brief Destroy a client IPC connection
 449  *
 450  * \param[in,out] c  Connection to destroy
 451  *
 452  * \note We handle a destroyed connection the same as a closed one,
 453  *       but we need a separate handler because the return type is different.
 454  */
 455 static void
 456 attrd_ipc_destroy(qb_ipcs_connection_t *c)
     /* [previous][next][first][last][top][bottom][index][help] */
 457 {
 458     crm_trace("Destroying client connection %p", c);
 459     attrd_ipc_closed(c);
 460 }
 461 
 462 static int32_t
 463 attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
     /* [previous][next][first][last][top][bottom][index][help] */
 464 {
 465     uint32_t id = 0;
 466     uint32_t flags = 0;
 467     pcmk__client_t *client = pcmk__find_client(c);
 468     xmlNode *xml = NULL;
 469 
 470     // Sanity-check, and parse XML from IPC data
 471     CRM_CHECK((c != NULL) && (client != NULL), return 0);
 472     if (data == NULL) {
 473         crm_debug("No IPC data from PID %d", pcmk__client_pid(c));
 474         return 0;
 475     }
 476 
 477     xml = pcmk__client_data2xml(client, data, &id, &flags);
 478 
 479     if (xml == NULL) {
 480         crm_debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
 481         pcmk__ipc_send_ack(client, id, flags, "ack", NULL, CRM_EX_PROTOCOL);
 482         return 0;
 483 
 484     } else {
 485         pcmk__request_t request = {
 486             .ipc_client     = client,
 487             .ipc_id         = id,
 488             .ipc_flags      = flags,
 489             .peer           = NULL,
 490             .xml            = xml,
 491             .call_options   = 0,
 492             .result         = PCMK__UNKNOWN_RESULT,
 493         };
 494 
 495         CRM_ASSERT(client->user != NULL);
 496         pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
 497 
 498         request.op = crm_element_value_copy(request.xml, PCMK__XA_TASK);
 499         CRM_CHECK(request.op != NULL, return 0);
 500 
 501         attrd_handle_request(&request);
 502         pcmk__reset_request(&request);
 503     }
 504 
 505     free_xml(xml);
 506     return 0;
 507 }
 508 
 509 static struct qb_ipcs_service_handlers ipc_callbacks = {
 510     .connection_accept = attrd_ipc_accept,
 511     .connection_created = NULL,
 512     .msg_process = attrd_ipc_dispatch,
 513     .connection_closed = attrd_ipc_closed,
 514     .connection_destroyed = attrd_ipc_destroy
 515 };
 516 
 517 void
 518 attrd_ipc_fini(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 519 {
 520     if (ipcs != NULL) {
 521         pcmk__drop_all_clients(ipcs);
 522         qb_ipcs_destroy(ipcs);
 523         ipcs = NULL;
 524     }
 525 }
 526 
 527 /*!
 528  * \internal
 529  * \brief Set up attrd IPC communication
 530  */
 531 void
 532 attrd_init_ipc(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 533 {
 534     pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
 535 }

/* [previous][next][first][last][top][bottom][index][help] */