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

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