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. expand_regexes
  8. handle_regexes
  9. handle_value_expansion
  10. send_update_msg_to_cluster
  11. send_child_update
  12. attrd_client_update
  13. attrd_ipc_accept
  14. attrd_ipc_closed
  15. attrd_ipc_destroy
  16. attrd_ipc_dispatch
  17. attrd_ipc_fini
  18. attrd_init_ipc

   1 /*
   2  * Copyright 2004-2023 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     if (minimum_protocol_version >= 2) {
 105         /* Propagate to all peers (including ourselves).
 106          * This ends up at attrd_peer_message().
 107          */
 108         attrd_send_message(NULL, xml, false);
 109         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 110         return NULL;
 111     }
 112 
 113     rsc = crm_element_value(xml, PCMK__XA_ATTR_RESOURCE);
 114     op = crm_element_value(xml, PCMK__XA_ATTR_OPERATION);
 115     interval_spec = crm_element_value(xml, PCMK__XA_ATTR_INTERVAL);
 116 
 117     /* Map this to an update */
 118     crm_xml_add(xml, PCMK__XA_TASK, PCMK__ATTRD_CMD_UPDATE);
 119 
 120     /* Add regular expression matching desired attributes */
 121 
 122     if (rsc) {
 123         char *pattern;
 124 
 125         if (op == NULL) {
 126             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
 127 
 128         } else {
 129             guint interval_ms = crm_parse_interval_spec(interval_spec);
 130 
 131             pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP,
 132                                         rsc, op, interval_ms);
 133         }
 134 
 135         crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, pattern);
 136         free(pattern);
 137 
 138     } else {
 139         crm_xml_add(xml, PCMK__XA_ATTR_PATTERN, ATTRD_RE_CLEAR_ALL);
 140     }
 141 
 142     /* Make sure attribute and value are not set, so we delete via regex */
 143     if (crm_element_value(xml, PCMK__XA_ATTR_NAME)) {
 144         crm_xml_replace(xml, PCMK__XA_ATTR_NAME, NULL);
 145     }
 146     if (crm_element_value(xml, PCMK__XA_ATTR_VALUE)) {
 147         crm_xml_replace(xml, PCMK__XA_ATTR_VALUE, NULL);
 148     }
 149 
 150     return attrd_client_update(request);
 151 }
 152 
 153 xmlNode *
 154 attrd_client_peer_remove(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 155 {
 156     xmlNode *xml = request->xml;
 157 
 158     // Host and ID are not used in combination, rather host has precedence
 159     const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 160     char *host_alloc = NULL;
 161 
 162     attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
 163 
 164     if (host == NULL) {
 165         int nodeid = 0;
 166 
 167         crm_element_value_int(xml, PCMK__XA_ATTR_NODE_ID, &nodeid);
 168         if (nodeid > 0) {
 169             crm_node_t *node = pcmk__search_cluster_node_cache(nodeid, NULL);
 170             char *host_alloc = NULL;
 171 
 172             if (node && node->uname) {
 173                 // Use cached name if available
 174                 host = node->uname;
 175             } else {
 176                 // Otherwise ask cluster layer
 177                 host_alloc = get_node_name(nodeid);
 178                 host = host_alloc;
 179             }
 180             pcmk__xe_add_node(xml, host, 0);
 181         }
 182     }
 183 
 184     if (host) {
 185         crm_info("Client %s is requesting all values for %s be removed",
 186                  pcmk__client_name(request->ipc_client), host);
 187         attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
 188         free(host_alloc);
 189     } else {
 190         crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
 191                  pcmk__client_name(request->ipc_client));
 192     }
 193 
 194     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 195     return NULL;
 196 }
 197 
 198 xmlNode *
 199 attrd_client_query(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 200 {
 201     xmlNode *query = request->xml;
 202     xmlNode *reply = NULL;
 203     const char *attr = NULL;
 204 
 205     crm_debug("Query arrived from %s", pcmk__client_name(request->ipc_client));
 206 
 207     /* Request must specify attribute name to query */
 208     attr = crm_element_value(query, PCMK__XA_ATTR_NAME);
 209     if (attr == NULL) {
 210         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 211                             "Ignoring malformed query from %s (no attribute name given)",
 212                             pcmk__client_name(request->ipc_client));
 213         return NULL;
 214     }
 215 
 216     /* Build the XML reply */
 217     reply = build_query_reply(attr, crm_element_value(query,
 218                                                       PCMK__XA_ATTR_NODE_NAME));
 219     if (reply == NULL) {
 220         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 221                             "Could not respond to query from %s: could not create XML reply",
 222                             pcmk__client_name(request->ipc_client));
 223         return NULL;
 224     } else {
 225         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 226     }
 227 
 228     request->ipc_client->request_id = 0;
 229     return reply;
 230 }
 231 
 232 xmlNode *
 233 attrd_client_refresh(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 234 {
 235     crm_info("Updating all attributes");
 236 
 237     attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
 238     attrd_write_attributes(true, true);
 239 
 240     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 241     return NULL;
 242 }
 243 
 244 static void
 245 handle_missing_host(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 246 {
 247     const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 248 
 249     if (host == NULL) {
 250         crm_trace("Inferring host");
 251         pcmk__xe_add_node(xml, attrd_cluster->uname, attrd_cluster->nodeid);
 252     }
 253 }
 254 
 255 /* Convert a single IPC message with a regex into one with multiple children, one
 256  * for each regex match.
 257  */
 258 static int
 259 expand_regexes(xmlNode *xml, const char *attr, const char *value, const char *regex)
     /* [previous][next][first][last][top][bottom][index][help] */
 260 {
 261     if (attr == NULL && regex) {
 262         bool matched = false;
 263         GHashTableIter aIter;
 264         regex_t r_patt;
 265 
 266         crm_debug("Setting %s to %s", regex, value);
 267         if (regcomp(&r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
 268             return EINVAL;
 269         }
 270 
 271         g_hash_table_iter_init(&aIter, attributes);
 272         while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
 273             int status = regexec(&r_patt, attr, 0, NULL, 0);
 274 
 275             if (status == 0) {
 276                 xmlNode *child = create_xml_node(xml, XML_ATTR_OP);
 277 
 278                 crm_trace("Matched %s with %s", attr, regex);
 279                 matched = true;
 280 
 281                 /* Copy all the attributes from the parent over, but remove the
 282                  * regex and replace it with the name.
 283                  */
 284                 attrd_copy_xml_attributes(xml, child);
 285                 crm_xml_replace(child, PCMK__XA_ATTR_PATTERN, NULL);
 286                 crm_xml_add(child, PCMK__XA_ATTR_NAME, attr);
 287             }
 288         }
 289 
 290         regfree(&r_patt);
 291 
 292         /* Return a code if we never matched anything.  This should not be treated
 293          * as an error.  It indicates there was a regex, and it was a valid regex,
 294          * but simply did not match anything and the caller should not continue
 295          * doing any regex-related processing.
 296          */
 297         if (!matched) {
 298             return pcmk_rc_op_unsatisfied;
 299         }
 300 
 301     } else if (attr == NULL) {
 302         return pcmk_rc_bad_nvpair;
 303     }
 304 
 305     return pcmk_rc_ok;
 306 }
 307 
 308 static int
 309 handle_regexes(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 310 {
 311     xmlNode *xml = request->xml;
 312     int rc = pcmk_rc_ok;
 313 
 314     const char *attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
 315     const char *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
 316     const char *regex = crm_element_value(xml, PCMK__XA_ATTR_PATTERN);
 317 
 318     rc = expand_regexes(xml, attr, value, regex);
 319 
 320     if (rc == EINVAL) {
 321         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 322                             "Bad regex '%s' for update from client %s", regex,
 323                             pcmk__client_name(request->ipc_client));
 324 
 325     } else if (rc == pcmk_rc_bad_nvpair) {
 326         crm_err("Update request did not specify attribute or regular expression");
 327         pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
 328                             "Client %s update request did not specify attribute or regular expression",
 329                             pcmk__client_name(request->ipc_client));
 330     }
 331 
 332     return rc;
 333 }
 334 
 335 static int
 336 handle_value_expansion(const char **value, xmlNode *xml, const char *op,
     /* [previous][next][first][last][top][bottom][index][help] */
 337                        const char *attr)
 338 {
 339     attribute_t *a = g_hash_table_lookup(attributes, attr);
 340 
 341     if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
 342         return EINVAL;
 343     }
 344 
 345     if (*value && attrd_value_needs_expansion(*value)) {
 346         int int_value;
 347         attribute_value_t *v = NULL;
 348 
 349         if (a) {
 350             const char *host = crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME);
 351             v = g_hash_table_lookup(a->values, host);
 352         }
 353 
 354         int_value = attrd_expand_value(*value, (v? v->current : NULL));
 355 
 356         crm_info("Expanded %s=%s to %d", attr, *value, int_value);
 357         crm_xml_add_int(xml, PCMK__XA_ATTR_VALUE, int_value);
 358 
 359         /* Replacing the value frees the previous memory, so re-query it */
 360         *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
 361     }
 362 
 363     return pcmk_rc_ok;
 364 }
 365 
 366 static void
 367 send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 368 {
 369     if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
 370         /* The client is waiting on the cluster-wide sync point.  In this case,
 371          * the response ACK is not sent until this attrd broadcasts the update
 372          * and receives its own confirmation back from all peers.
 373          */
 374         attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
 375         attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
 376 
 377     } else {
 378         /* The client is either waiting on the local sync point or was not
 379          * waiting on any sync point at all.  For the local sync point, the
 380          * response ACK is sent in attrd_peer_update.  For clients not
 381          * waiting on any sync point, the response ACK is sent in
 382          * handle_update_request immediately before this function was called.
 383          */
 384         attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
 385     }
 386 }
 387 
 388 static int
 389 send_child_update(xmlNode *child, void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 390 {
 391     pcmk__request_t *request = (pcmk__request_t *) data;
 392 
 393     /* Calling pcmk__set_result is handled by one of these calls to
 394      * attrd_client_update, so no need to do it again here.
 395      */
 396     request->xml = child;
 397     attrd_client_update(request);
 398     return pcmk_rc_ok;
 399 }
 400 
 401 xmlNode *
 402 attrd_client_update(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
 403 {
 404     xmlNode *xml = request->xml;
 405     const char *attr, *value, *regex;
 406 
 407     /* If the message has children, that means it is a message from a newer
 408      * client that supports sending multiple operations at a time.  There are
 409      * two ways we can handle that.
 410      */
 411     if (xml_has_children(xml)) {
 412         if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
 413             /* First, if all peers support a certain protocol version, we can
 414              * just broadcast the big message and they'll handle it.  However,
 415              * we also need to apply all the transformations in this function
 416              * to the children since they don't happen anywhere else.
 417              */
 418             for (xmlNode *child = first_named_child(xml, XML_ATTR_OP); child != NULL;
 419                  child = crm_next_same_xml(child)) {
 420                 attr = crm_element_value(child, PCMK__XA_ATTR_NAME);
 421                 value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
 422 
 423                 handle_missing_host(child);
 424 
 425                 if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
 426                     pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
 427                                         "Attribute %s does not exist", attr);
 428                     return NULL;
 429                 }
 430             }
 431 
 432             send_update_msg_to_cluster(request, xml);
 433             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 434 
 435         } else {
 436             /* Save the original xml node pointer so it can be restored after iterating
 437              * over all the children.
 438              */
 439             xmlNode *orig_xml = request->xml;
 440 
 441             /* Second, if they do not support that protocol version, split it
 442              * up into individual messages and call attrd_client_update on
 443              * each one.
 444              */
 445             pcmk__xe_foreach_child(xml, XML_ATTR_OP, send_child_update, request);
 446             request->xml = orig_xml;
 447         }
 448 
 449         return NULL;
 450     }
 451 
 452     attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
 453     value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
 454     regex = crm_element_value(xml, PCMK__XA_ATTR_PATTERN);
 455 
 456     if (handle_regexes(request) != pcmk_rc_ok) {
 457         /* Error handling was already dealt with in handle_regexes, so just return. */
 458         return NULL;
 459     } else if (regex) {
 460         /* Recursively call attrd_client_update on the new message with regexes
 461          * expanded.  If supported by the attribute daemon, this means that all
 462          * matches can also be handled atomically.
 463          */
 464         return attrd_client_update(request);
 465     }
 466 
 467     handle_missing_host(xml);
 468 
 469     if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
 470         pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
 471                             "Attribute %s does not exist", attr);
 472         return NULL;
 473     }
 474 
 475     crm_debug("Broadcasting %s[%s]=%s%s", attr, crm_element_value(xml, PCMK__XA_ATTR_NODE_NAME),
 476               value, (attrd_election_won()? " (writer)" : ""));
 477 
 478     send_update_msg_to_cluster(request, xml);
 479     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 480     return NULL;
 481 }
 482 
 483 /*!
 484  * \internal
 485  * \brief Accept a new client IPC connection
 486  *
 487  * \param[in,out] c    New connection
 488  * \param[in]     uid  Client user id
 489  * \param[in]     gid  Client group id
 490  *
 491  * \return pcmk_ok on success, -errno otherwise
 492  */
 493 static int32_t
 494 attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 495 {
 496     crm_trace("New client connection %p", c);
 497     if (attrd_shutting_down()) {
 498         crm_info("Ignoring new connection from pid %d during shutdown",
 499                  pcmk__client_pid(c));
 500         return -EPERM;
 501     }
 502 
 503     if (pcmk__new_client(c, uid, gid) == NULL) {
 504         return -EIO;
 505     }
 506     return pcmk_ok;
 507 }
 508 
 509 /*!
 510  * \internal
 511  * \brief Destroy a client IPC connection
 512  *
 513  * \param[in] c  Connection to destroy
 514  *
 515  * \return FALSE (i.e. do not re-run this callback)
 516  */
 517 static int32_t
 518 attrd_ipc_closed(qb_ipcs_connection_t *c)
     /* [previous][next][first][last][top][bottom][index][help] */
 519 {
 520     pcmk__client_t *client = pcmk__find_client(c);
 521 
 522     if (client == NULL) {
 523         crm_trace("Ignoring request to clean up unknown connection %p", c);
 524     } else {
 525         crm_trace("Cleaning up closed client connection %p", c);
 526 
 527         /* Remove the client from the sync point waitlist if it's present. */
 528         attrd_remove_client_from_waitlist(client);
 529 
 530         /* And no longer wait for confirmations from any peers. */
 531         attrd_do_not_wait_for_client(client);
 532 
 533         pcmk__free_client(client);
 534     }
 535 
 536     return FALSE;
 537 }
 538 
 539 /*!
 540  * \internal
 541  * \brief Destroy a client IPC connection
 542  *
 543  * \param[in,out] c  Connection to destroy
 544  *
 545  * \note We handle a destroyed connection the same as a closed one,
 546  *       but we need a separate handler because the return type is different.
 547  */
 548 static void
 549 attrd_ipc_destroy(qb_ipcs_connection_t *c)
     /* [previous][next][first][last][top][bottom][index][help] */
 550 {
 551     crm_trace("Destroying client connection %p", c);
 552     attrd_ipc_closed(c);
 553 }
 554 
 555 static int32_t
 556 attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
     /* [previous][next][first][last][top][bottom][index][help] */
 557 {
 558     uint32_t id = 0;
 559     uint32_t flags = 0;
 560     pcmk__client_t *client = pcmk__find_client(c);
 561     xmlNode *xml = NULL;
 562 
 563     // Sanity-check, and parse XML from IPC data
 564     CRM_CHECK((c != NULL) && (client != NULL), return 0);
 565     if (data == NULL) {
 566         crm_debug("No IPC data from PID %d", pcmk__client_pid(c));
 567         return 0;
 568     }
 569 
 570     xml = pcmk__client_data2xml(client, data, &id, &flags);
 571 
 572     if (xml == NULL) {
 573         crm_debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
 574         pcmk__ipc_send_ack(client, id, flags, "ack", NULL, CRM_EX_PROTOCOL);
 575         return 0;
 576 
 577     } else {
 578         pcmk__request_t request = {
 579             .ipc_client     = client,
 580             .ipc_id         = id,
 581             .ipc_flags      = flags,
 582             .peer           = NULL,
 583             .xml            = xml,
 584             .call_options   = 0,
 585             .result         = PCMK__UNKNOWN_RESULT,
 586         };
 587 
 588         CRM_ASSERT(client->user != NULL);
 589         pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
 590 
 591         request.op = crm_element_value_copy(request.xml, PCMK__XA_TASK);
 592         CRM_CHECK(request.op != NULL, return 0);
 593 
 594         attrd_handle_request(&request);
 595         pcmk__reset_request(&request);
 596     }
 597 
 598     free_xml(xml);
 599     return 0;
 600 }
 601 
 602 static struct qb_ipcs_service_handlers ipc_callbacks = {
 603     .connection_accept = attrd_ipc_accept,
 604     .connection_created = NULL,
 605     .msg_process = attrd_ipc_dispatch,
 606     .connection_closed = attrd_ipc_closed,
 607     .connection_destroyed = attrd_ipc_destroy
 608 };
 609 
 610 void
 611 attrd_ipc_fini(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 612 {
 613     if (ipcs != NULL) {
 614         pcmk__drop_all_clients(ipcs);
 615         qb_ipcs_destroy(ipcs);
 616         ipcs = NULL;
 617     }
 618 }
 619 
 620 /*!
 621  * \internal
 622  * \brief Set up attrd IPC communication
 623  */
 624 void
 625 attrd_init_ipc(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 626 {
 627     pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
 628 }

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