1    	/*
2    	 * Copyright 2004-2026 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 <stdbool.h>
14   	#include <stdint.h>
15   	#include <stdlib.h>
16   	#include <inttypes.h>   // PRIu32
17   	#include <sys/types.h>
18   	
19   	#include <crm/cluster.h>
20   	#include <crm/cluster/internal.h>
21   	#include <crm/common/logging.h>
22   	#include <crm/common/results.h>
23   	#include <crm/common/util.h>
24   	#include <crm/common/xml.h>
25   	
26   	#include "pacemaker-attrd.h"
27   	
28   	static qb_ipcs_service_t *ipcs = NULL;
29   	
30   	/*!
31   	 * \internal
32   	 * \brief Build the XML reply to a client query
33   	 *
34   	 * \param[in] attr Name of requested attribute
35   	 * \param[in] host Name of requested host (or NULL for all hosts)
36   	 *
37   	 * \return New XML reply
38   	 * \note Caller is responsible for freeing the resulting XML
39   	 */
40   	static xmlNode *build_query_reply(const char *attr, const char *host)
41   	{
42   	    xmlNode *reply = pcmk__xe_create(NULL, __func__);
43   	    attribute_t *a;
44   	
45   	    pcmk__xe_set(reply, PCMK__XA_T, PCMK__VALUE_ATTRD);
46   	    pcmk__xe_set(reply, PCMK__XA_SUBT, PCMK__ATTRD_CMD_QUERY);
47   	    pcmk__xe_set(reply, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
48   	
49   	    /* If desired attribute exists, add its value(s) to the reply */
50   	    a = g_hash_table_lookup(attributes, attr);
51   	    if (a) {
52   	        attribute_value_t *v;
53   	        xmlNode *host_value;
54   	
55   	        pcmk__xe_set(reply, PCMK__XA_ATTR_NAME, attr);
56   	
57   	        /* Allow caller to use "localhost" to refer to local node */
58   	        if (pcmk__str_eq(host, "localhost", pcmk__str_casei)) {
59   	            host = attrd_cluster->priv->node_name;
60   	            pcmk__trace("Mapped localhost to %s", host);
61   	        }
62   	
63   	        /* If a specific node was requested, add its value */
64   	        if (host) {
65   	            v = g_hash_table_lookup(a->values, host);
66   	            host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
67   	            pcmk__xe_set(host_value, PCMK__XA_ATTR_HOST, host);
68   	            pcmk__xe_set(host_value, PCMK__XA_ATTR_VALUE,
69   	                         ((v != NULL)? v->current : NULL));
70   	
71   	        /* Otherwise, add all nodes' values */
72   	        } else {
73   	            GHashTableIter iter;
74   	
75   	            g_hash_table_iter_init(&iter, a->values);
76   	            while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
77   	                host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
78   	                pcmk__xe_set(host_value, PCMK__XA_ATTR_HOST, v->nodename);
79   	                pcmk__xe_set(host_value, PCMK__XA_ATTR_VALUE, v->current);
80   	            }
81   	        }
82   	    }
83   	    return reply;
84   	}
85   	
86   	void
87   	attrd_client_clear_failure(pcmk__request_t *request)
88   	{
89   	    xmlNode *xml = request->xml;
90   	    const char *rsc, *op, *interval_spec;
91   	
92   	    if (minimum_protocol_version >= 2) {
93   	        /* Propagate to all peers (including ourselves).
94   	         * This ends up at attrd_peer_message().
95   	         */
96   	        attrd_send_message(NULL, xml, false);
97   	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
98   	        return;
99   	    }
100  	
101  	    rsc = pcmk__xe_get(xml, PCMK__XA_ATTR_RESOURCE);
102  	    op = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
103  	    interval_spec = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_INTERVAL);
104  	
105  	    /* Map this to an update */
106  	    pcmk__xe_set(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
107  	
108  	    /* Add regular expression matching desired attributes */
109  	
110  	    if (rsc) {
111  	        char *pattern;
112  	
113  	        if (op == NULL) {
114  	            pattern = pcmk__assert_asprintf(ATTRD_RE_CLEAR_ONE, rsc);
115  	
116  	        } else {
117  	            guint interval_ms = 0U;
118  	
119  	            pcmk_parse_interval_spec(interval_spec, &interval_ms);
120  	            pattern = pcmk__assert_asprintf(ATTRD_RE_CLEAR_OP, rsc, op,
121  	                                            interval_ms);
122  	        }
123  	
124  	        pcmk__xe_set(xml, PCMK__XA_ATTR_REGEX, pattern);
125  	        free(pattern);
126  	
127  	    } else {
128  	        pcmk__xe_set(xml, PCMK__XA_ATTR_REGEX, ATTRD_RE_CLEAR_ALL);
129  	    }
130  	
131  	    /* Make sure attribute and value are not set, so we delete via regex */
132  	    pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_NAME);
133  	    pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
134  	
135  	    attrd_client_update(request);
136  	}
137  	
138  	void
139  	attrd_client_peer_remove(pcmk__request_t *request)
140  	{
141  	    xmlNode *xml = request->xml;
142  	
143  	    // Host and ID are not used in combination, rather host has precedence
144  	    const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
145  	    char *host_alloc = NULL;
146  	
147  	    attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
148  	
149  	    if (host == NULL) {
150  	        int nodeid = 0;
151  	
152  	        pcmk__xe_get_int(xml, PCMK__XA_ATTR_HOST_ID, &nodeid);
153  	        if (nodeid > 0) {
154  	            pcmk__node_status_t *node = NULL;
155  	            char *host_alloc = NULL;
156  	
157  	            node = pcmk__search_node_caches(nodeid, NULL, NULL,
158  	                                            pcmk__node_search_cluster_member);
159  	            if ((node != NULL) && (node->name != NULL)) {
160  	                // Use cached name if available
161  	                host = node->name;
162  	            } else {
163  	                // Otherwise ask cluster layer
164  	                host_alloc = pcmk__cluster_node_name(nodeid);
165  	                host = host_alloc;
166  	            }
167  	            pcmk__xe_set(xml, PCMK__XA_ATTR_HOST, host);
168  	        }
169  	    }
170  	
171  	    if (host) {
172  	        pcmk__info("Client %s is requesting all values for %s be removed",
173  	                   pcmk__client_name(request->ipc_client), host);
174  	        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
175  	        free(host_alloc);
176  	    } else {
177  	        pcmk__info("Ignoring request by client %s to remove all peer values "
178  	                   "without specifying peer",
179  	                   pcmk__client_name(request->ipc_client));
180  	    }
181  	
182  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
183  	}
184  	
185  	xmlNode *
186  	attrd_client_query(pcmk__request_t *request)
187  	{
188  	    xmlNode *query = request->xml;
189  	    xmlNode *reply = NULL;
190  	    const char *attr = NULL;
191  	
192  	    pcmk__debug("Query arrived from %s",
193  	                pcmk__client_name(request->ipc_client));
194  	
195  	    /* Request must specify attribute name to query */
196  	    attr = pcmk__xe_get(query, PCMK__XA_ATTR_NAME);
197  	    if (attr == NULL) {
198  	        pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
199  	                            "Ignoring malformed query from %s (no attribute name given)",
200  	                            pcmk__client_name(request->ipc_client));
201  	        return NULL;
202  	    }
203  	
204  	    /* Build the XML reply */
205  	    reply = build_query_reply(attr, pcmk__xe_get(query, PCMK__XA_ATTR_HOST));
206  	    if (reply == NULL) {
207  	        pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
208  	                            "Could not respond to query from %s: could not create XML reply",
209  	                            pcmk__client_name(request->ipc_client));
210  	        return NULL;
211  	    } else {
212  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
213  	    }
214  	
215  	    request->ipc_client->request_id = 0;
216  	    return reply;
217  	}
218  	
219  	void
220  	attrd_client_refresh(pcmk__request_t *request)
221  	{
222  	    pcmk__info("Updating all attributes");
223  	
224  	    attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
225  	    attrd_write_attributes(attrd_write_all|attrd_write_no_delay);
226  	
227  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
228  	}
229  	
230  	static void
231  	handle_missing_host(xmlNode *xml)
232  	{
233  	    if (pcmk__xe_get(xml, PCMK__XA_ATTR_HOST) == NULL) {
234  	        pcmk__trace("Inferring local node %s with XML ID %s",
235  	                    attrd_cluster->priv->node_name,
236  	                    attrd_cluster->priv->node_xml_id);
237  	        pcmk__xe_set(xml, PCMK__XA_ATTR_HOST, attrd_cluster->priv->node_name);
238  	        pcmk__xe_set(xml, PCMK__XA_ATTR_HOST_ID,
239  	                     attrd_cluster->priv->node_xml_id);
240  	    }
241  	}
242  	
243  	/* Convert a single IPC message with a regex into one with multiple children, one
244  	 * for each regex match.
245  	 */
246  	static int
247  	expand_regexes(xmlNode *xml, const char *attr, const char *value, const char *regex)
248  	{
249  	    if (attr == NULL && regex) {
250  	        bool matched = false;
251  	        GHashTableIter aIter;
252  	        regex_t r_patt;
253  	
254  	        pcmk__debug("Setting %s to %s", regex, value);
255  	        if (regcomp(&r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
256  	            return EINVAL;
257  	        }
258  	
259  	        g_hash_table_iter_init(&aIter, attributes);
260  	        while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
261  	            int status = regexec(&r_patt, attr, 0, NULL, 0);
262  	
263  	            if (status == 0) {
264  	                xmlNode *child = pcmk__xe_create(xml, PCMK_XE_OP);
265  	
266  	                pcmk__trace("Matched %s with %s", attr, regex);
267  	                matched = true;
268  	
269  	                /* Copy all the non-conflicting attributes from the parent over,
270  	                 * but remove the regex and replace it with the name.
271  	                 */
272  	                pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
273  	                pcmk__xe_remove_attr(child, PCMK__XA_ATTR_REGEX);
274  	                pcmk__xe_set(child, PCMK__XA_ATTR_NAME, attr);
275  	            }
276  	        }
277  	
278  	        regfree(&r_patt);
279  	
280  	        /* Return a code if we never matched anything.  This should not be treated
281  	         * as an error.  It indicates there was a regex, and it was a valid regex,
282  	         * but simply did not match anything and the caller should not continue
283  	         * doing any regex-related processing.
284  	         */
285  	        if (!matched) {
286  	            return pcmk_rc_op_unsatisfied;
287  	        }
288  	
289  	    } else if (attr == NULL) {
290  	        return pcmk_rc_bad_nvpair;
291  	    }
292  	
293  	    return pcmk_rc_ok;
294  	}
295  	
296  	static int
297  	handle_regexes(pcmk__request_t *request)
298  	{
299  	    xmlNode *xml = request->xml;
300  	    int rc = pcmk_rc_ok;
301  	
302  	    const char *attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
303  	    const char *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
304  	    const char *regex = pcmk__xe_get(xml, PCMK__XA_ATTR_REGEX);
305  	
306  	    rc = expand_regexes(xml, attr, value, regex);
307  	
308  	    if (rc == EINVAL) {
309  	        pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
310  	                            "Bad regex '%s' for update from client %s", regex,
311  	                            pcmk__client_name(request->ipc_client));
312  	
313  	    } else if (rc == pcmk_rc_bad_nvpair) {
314  	        pcmk__err("Update request did not specify attribute or regular "
315  	                  "expression");
316  	        pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
317  	                            "Client %s update request did not specify attribute or regular expression",
318  	                            pcmk__client_name(request->ipc_client));
319  	    }
320  	
321  	    return rc;
322  	}
323  	
324  	static int
325  	handle_value_expansion(const char **value, xmlNode *xml, const char *op,
326  	                       const char *attr)
327  	{
328  	    attribute_t *a = g_hash_table_lookup(attributes, attr);
329  	
330  	    if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
331  	        return EINVAL;
332  	    }
333  	
334  	    if (*value && attrd_value_needs_expansion(*value)) {
335  	        int int_value;
336  	        attribute_value_t *v = NULL;
337  	
338  	        if (a) {
339  	            const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
340  	            v = g_hash_table_lookup(a->values, host);
341  	        }
342  	
343  	        int_value = attrd_expand_value(*value, (v? v->current : NULL));
344  	
345  	        pcmk__info("Expanded %s=%s to %d", attr, *value, int_value);
346  	        pcmk__xe_set_int(xml, PCMK__XA_ATTR_VALUE, int_value);
347  	
348  	        /* Replacing the value frees the previous memory, so re-query it */
349  	        *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
350  	    }
351  	
352  	    return pcmk_rc_ok;
353  	}
354  	
355  	static void
356  	send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
357  	{
358  	    if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
359  	        /* The client is waiting on the cluster-wide sync point.  In this case,
360  	         * the response ACK is not sent until this attrd broadcasts the update
361  	         * and receives its own confirmation back from all peers.
362  	         */
363  	        attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
364  	        attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
365  	
366  	    } else {
367  	        /* The client is either waiting on the local sync point or was not
368  	         * waiting on any sync point at all.  For the local sync point, the
369  	         * response ACK is sent in attrd_peer_update.  For clients not
370  	         * waiting on any sync point, the response ACK is sent in
371  	         * handle_update_request immediately before this function was called.
372  	         */
373  	        attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
374  	    }
375  	}
376  	
377  	static int
378  	send_child_update(xmlNode *child, void *data)
379  	{
380  	    pcmk__request_t *request = (pcmk__request_t *) data;
381  	
382  	    /* Calling pcmk__set_result is handled by one of these calls to
383  	     * attrd_client_update, so no need to do it again here.
384  	     */
385  	    request->xml = child;
386  	    attrd_client_update(request);
387  	    return pcmk_rc_ok;
388  	}
389  	
390  	void
391  	attrd_client_update(pcmk__request_t *request)
392  	{
393  	    xmlNode *xml = NULL;
394  	    const char *attr, *value, *regex;
395  	
396  	    CRM_CHECK((request != NULL) && (request->xml != NULL), return);
397  	
398  	    xml = request->xml;
399  	
400  	    /* If the message has children, that means it is a message from a newer
401  	     * client that supports sending multiple operations at a time.  There are
402  	     * two ways we can handle that.
403  	     */
404  	    if (xml->children != NULL) {
405  	        if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
406  	            /* First, if all peers support a certain protocol version, we can
407  	             * just broadcast the big message and they'll handle it.  However,
408  	             * we also need to apply all the transformations in this function
409  	             * to the children since they don't happen anywhere else.
410  	             */
411  	            for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL,
412  	                                                       NULL);
413  	                 child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
414  	
415  	                attr = pcmk__xe_get(child, PCMK__XA_ATTR_NAME);
416  	                value = pcmk__xe_get(child, PCMK__XA_ATTR_VALUE);
417  	
418  	                handle_missing_host(child);
419  	
420  	                if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
421  	                    pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
422  	                                        "Attribute %s does not exist", attr);
423  	                    return;
424  	                }
425  	            }
426  	
427  	            send_update_msg_to_cluster(request, xml);
428  	            pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
429  	
430  	        } else {
431  	            /* Save the original xml node pointer so it can be restored after iterating
432  	             * over all the children.
433  	             */
434  	            xmlNode *orig_xml = request->xml;
435  	
436  	            /* Second, if they do not support that protocol version, split it
437  	             * up into individual messages and call attrd_client_update on
438  	             * each one.
439  	             */
440  	            pcmk__xe_foreach_child(xml, PCMK_XE_OP, send_child_update, request);
441  	            request->xml = orig_xml;
442  	        }
443  	
444  	        return;
445  	    }
446  	
447  	    attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
448  	    value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
449  	    regex = pcmk__xe_get(xml, PCMK__XA_ATTR_REGEX);
450  	
451  	    if (handle_regexes(request) != pcmk_rc_ok) {
452  	        /* Error handling was already dealt with in handle_regexes, so just return. */
453  	        return;
454  	    }
455  	
456  	    if (regex != NULL) {
457  	        /* Recursively call attrd_client_update on the new message with regexes
458  	         * expanded.  If supported by the attribute daemon, this means that all
459  	         * matches can also be handled atomically.
460  	         */
461  	        attrd_client_update(request);
462  	        return;
463  	    }
464  	
465  	    handle_missing_host(xml);
466  	
467  	    if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
468  	        pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
469  	                            "Attribute %s does not exist", attr);
470  	        return;
471  	    }
472  	
473  	    pcmk__debug("Broadcasting %s[%s]=%s%s", attr,
474  	                pcmk__xe_get(xml, PCMK__XA_ATTR_HOST), value,
475  	                (attrd_election_won()? " (writer)" : ""));
476  	
477  	    send_update_msg_to_cluster(request, xml);
478  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
479  	}
480  	
481  	/*!
482  	 * \internal
483  	 * \brief Accept a new client IPC connection
484  	 *
485  	 * \param[in,out] c    New connection
486  	 * \param[in]     uid  Client user id
487  	 * \param[in]     gid  Client group id
488  	 *
489  	 * \return pcmk_ok on success, -errno otherwise
490  	 */
491  	static int32_t
492  	attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
493  	{
494  	    pcmk__trace("New client connection %p", c);
495  	    if (attrd_shutting_down()) {
496  	        pcmk__info("Ignoring new connection from pid %d during shutdown",
497  	                   pcmk__client_pid(c));
498  	        return -ECONNREFUSED;
499  	    }
500  	
501  	    if (pcmk__new_client(c, uid, gid) == NULL) {
502  	        return -ENOMEM;
503  	    }
504  	    return pcmk_ok;
505  	}
506  	
507  	/*!
508  	 * \internal
509  	 * \brief Destroy a client IPC connection
510  	 *
511  	 * \param[in] c  Connection to destroy
512  	 *
513  	 * \return 0 (do not re-run this callback)
514  	 */
515  	static int32_t
516  	attrd_ipc_closed(qb_ipcs_connection_t *c)
517  	{
518  	    pcmk__client_t *client = pcmk__find_client(c);
519  	
520  	    if (client == NULL) {
521  	        pcmk__trace("Ignoring request to clean up unknown connection %p", c);
522  	    } else {
523  	        pcmk__trace("Cleaning up closed client connection %p", c);
524  	
525  	        /* Remove the client from the sync point waitlist if it's present. */
526  	        attrd_remove_client_from_waitlist(client);
527  	
528  	        /* And no longer wait for confirmations from any peers. */
529  	        attrd_do_not_wait_for_client(client);
530  	
531  	        pcmk__free_client(client);
532  	    }
533  	
534  	    return 0;
535  	}
536  	
537  	/*!
538  	 * \internal
539  	 * \brief Destroy a client IPC connection
540  	 *
541  	 * \param[in,out] c  Connection to destroy
542  	 *
543  	 * \note We handle a destroyed connection the same as a closed one,
544  	 *       but we need a separate handler because the return type is different.
545  	 */
546  	static void
547  	attrd_ipc_destroy(qb_ipcs_connection_t *c)
548  	{
549  	    pcmk__trace("Destroying client connection %p", c);
550  	    attrd_ipc_closed(c);
551  	}
552  	
553  	static int32_t
554  	attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
555  	{
556  	    int rc = pcmk_rc_ok;
557  	    uint32_t id = 0;
558  	    uint32_t flags = 0;
559  	    pcmk__client_t *client = pcmk__find_client(c);
560  	    xmlNode *xml = NULL;
561  	
562  	    // Sanity-check, and parse XML from IPC data
563  	    CRM_CHECK(client != NULL, return 0);
564  	    if (data == NULL) {
565  	        pcmk__debug("No IPC data from PID %d", pcmk__client_pid(c));
566  	        return 0;
567  	    }
568  	
569  	    rc = pcmk__ipc_msg_append(&client->buffer, data);
570  	
571  	    if (rc == pcmk_rc_ipc_more) {
572  	        /* We haven't read the complete message yet, so just return. */
573  	        return 0;
574  	
575  	    } else if (rc == pcmk_rc_ok) {
576  	        /* We've read the complete message and there's already a header on
577  	         * the front.  Pass it off for processing.
578  	         */
579  	        xml = pcmk__client_data2xml(client, &id, &flags);
580  	        g_byte_array_free(client->buffer, TRUE);
581  	        client->buffer = NULL;
582  	
583  	    } else {
584  	        /* Some sort of error occurred reassembling the message.  All we can
585  	         * do is clean up, log an error and return.
586  	         */
587  	        pcmk__err("Error when reading IPC message: %s", pcmk_rc_str(rc));
588  	
589  	        if (client->buffer != NULL) {
590  	            g_byte_array_free(client->buffer, TRUE);
591  	            client->buffer = NULL;
592  	        }
593  	
594  	        return 0;
595  	    }
596  	
597  	    if (xml == NULL) {
598  	        pcmk__debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
599  	        pcmk__ipc_send_ack(client, id, flags, NULL, CRM_EX_PROTOCOL);
600  	        return 0;
601  	
602  	    } else {
603  	        pcmk__request_t request = {
604  	            .ipc_client     = client,
605  	            .ipc_id         = id,
606  	            .ipc_flags      = flags,
607  	            .peer           = NULL,
608  	            .xml            = xml,
609  	            .call_options   = 0,
610  	            .result         = PCMK__UNKNOWN_RESULT,
611  	        };
612  	
613  	        pcmk__assert(client->user != NULL);
614  	        pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
615  	
616  	        request.op = pcmk__xe_get_copy(request.xml, PCMK_XA_TASK);
617  	        CRM_CHECK(request.op != NULL, goto done);
618  	
619  	        attrd_handle_request(&request);
620  	    }
621  	
622  	done:
623  	    pcmk__xml_free(xml);
624  	    return 0;
625  	}
626  	
627  	static struct qb_ipcs_service_handlers ipc_callbacks = {
628  	    .connection_accept = attrd_ipc_accept,
629  	    .connection_created = NULL,
630  	    .msg_process = attrd_ipc_dispatch,
631  	    .connection_closed = attrd_ipc_closed,
632  	    .connection_destroyed = attrd_ipc_destroy
633  	};
634  	
635  	/*!
636  	 * \internal
637  	 * \brief Clean up attrd IPC communication
638  	 */
639  	void
640  	attrd_ipc_cleanup(void)
641  	{
(1) Event path: Condition "ipcs != NULL", taking true branch.
642  	    if (ipcs != NULL) {
643  	        pcmk__drop_all_clients(ipcs);
CID (unavailable; MK=19397ca4ee716844aa7198cf2c3e2bef) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(2) Event assign_union_field: The union field "in" of "_pp" is written.
(3) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
644  	        g_clear_pointer(&ipcs, qb_ipcs_destroy);
645  	    }
646  	
647  	    pcmk__client_cleanup();
648  	}
649  	
650  	/*!
651  	 * \internal
652  	 * \brief Set up attrd IPC communication
653  	 */
654  	void
655  	attrd_ipc_init(void)
656  	{
657  	    pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
658  	}
659