1    	/*
2    	 * Copyright 2013-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   	
17   	#include <crm/cluster.h>
18   	#include <crm/cluster/internal.h>
19   	#include <crm/common/logging.h>
20   	#include <crm/common/results.h>
21   	#include <crm/common/xml.h>
22   	
23   	#include "pacemaker-attrd.h"
24   	
25   	pcmk_cluster_t *attrd_cluster = NULL;
26   	
27   	/*!
28   	 * \internal
29   	 * \brief Nodes removed by \c attrd_peer_remove()
30   	 *
31   	 * This table is to be used as a set. It contains nodes that have been removed
32   	 * by \c attrd_peer_remove() and whose transient attributes should be erased
33   	 * from the CIB.
34   	 *
35   	 * Setting an attribute value for a node via \c update_attr_on_host() removes
36   	 * the node from the table. At that point, we have transient attributes in
37   	 * memory for the node, so it should no longer be erased from the CIB.
38   	 *
39   	 * If another node erases a removed node's transient attributes from the CIB,
40   	 * the removed node remains in this table until an attribute value is set for
41   	 * it. This is for convenience: it avoids the need to monitor for CIB updates
42   	 * that erase a node's \c node_state or \c transient attributes element, just to
43   	 * remove the node from the table.
44   	 *
45   	 * Leaving a removed node in the table after erasure should be harmless. If a
46   	 * node is in this table, then we have no transient attributes for it in memory.
47   	 * If for some reason we erase its transient attributes from the CIB twice, its
48   	 * state in the CIB will still be correct.
49   	 */
50   	static GHashTable *removed_peers = NULL;
51   	
52   	/*!
53   	 * \internal
54   	 * \brief Free the removed nodes table
55   	 */
56   	void
57   	attrd_free_removed_peers(void)
58   	{
CID (unavailable; MK=3002fa9b2875eb96a573335d778576cf) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
59   	    g_clear_pointer(&removed_peers, g_hash_table_destroy);
60   	}
61   	
62   	static xmlNode *
63   	attrd_confirmation(int callid)
64   	{
65   	    xmlNode *node = pcmk__xe_create(NULL, __func__);
66   	
67   	    pcmk__xe_set(node, PCMK__XA_T, PCMK__VALUE_ATTRD);
68   	    pcmk__xe_set(node, PCMK__XA_SRC, pcmk__cluster_local_node_name());
69   	    pcmk__xe_set(node, PCMK_XA_TASK, PCMK__ATTRD_CMD_CONFIRM);
70   	    pcmk__xe_set_int(node, PCMK__XA_CALL_ID, callid);
71   	
72   	    return node;
73   	}
74   	
75   	static void
76   	attrd_peer_message(pcmk__node_status_t *peer, xmlNode *xml)
77   	{
78   	    const char *election_op = pcmk__xe_get(xml, PCMK__XA_CRM_TASK);
79   	
80   	    if (election_op) {
81   	        attrd_handle_election_op(peer, xml);
82   	        return;
83   	    }
84   	
85   	    if (attrd_shutting_down()) {
86   	        /* If we're shutting down, we want to continue responding to election
87   	         * ops as long as we're a cluster member (because our vote may be
88   	         * needed). Ignore all other messages.
89   	         */
90   	        return;
91   	
92   	    } else {
93   	        pcmk__request_t request = {
94   	            .ipc_client     = NULL,
95   	            .ipc_id         = 0,
96   	            .ipc_flags      = crm_ipc_flags_none,
97   	            .peer           = peer->name,
98   	            .xml            = xml,
99   	            .call_options   = 0,
100  	            .result         = PCMK__UNKNOWN_RESULT,
101  	        };
102  	
103  	        request.op = pcmk__xe_get_copy(request.xml, PCMK_XA_TASK);
104  	        CRM_CHECK(request.op != NULL, return);
105  	
106  	        attrd_handle_request(&request);
107  	
108  	        /* Having finished handling the request, check to see if the originating
109  	         * peer requested confirmation.  If so, send that confirmation back now.
110  	         */
111  	        if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM) &&
112  	            !pcmk__str_eq(request.op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
113  	            int callid = 0;
114  	            xmlNode *reply = NULL;
115  	
116  	            /* Add the confirmation ID for the message we are confirming to the
117  	             * response so the originating peer knows what they're a confirmation
118  	             * for.
119  	             */
120  	            pcmk__xe_get_int(xml, PCMK__XA_CALL_ID, &callid);
121  	            reply = attrd_confirmation(callid);
122  	
123  	            /* And then send the confirmation back to the originating peer.  This
124  	             * ends up right back in this same function (attrd_peer_message) on the
125  	             * peer where it will have to do something with a PCMK__XA_CONFIRM type
126  	             * message.
127  	             */
128  	            pcmk__debug("Sending %s a confirmation", peer->name);
129  	            attrd_send_message(peer, reply, false);
130  	            pcmk__xml_free(reply);
131  	        }
132  	    }
133  	}
134  	
135  	#if SUPPORT_COROSYNC
136  	/*!
137  	 * \internal
138  	 * \brief Callback for when a peer message is received
139  	 *
140  	 * \param[in]     handle     The cluster connection
141  	 * \param[in]     group_name The group that \p nodeid is a member of
142  	 * \param[in]     nodeid     Peer node that sent \p msg
143  	 * \param[in]     pid        Process that sent \p msg
144  	 * \param[in,out] msg        Received message
145  	 * \param[in]     msg_len    Length of \p msg
146  	 */
147  	static void
148  	attrd_cpg_dispatch(cpg_handle_t handle, const struct cpg_name *group_name,
149  	                   uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
150  	{
151  	    xmlNode *xml = NULL;
152  	    const char *from = NULL;
153  	    char *data = pcmk__cpg_message_data(handle, nodeid, pid, msg, &from);
154  	
155  	    if (data == NULL) {
156  	        return;
157  	    }
158  	
159  	    xml = pcmk__xml_parse(data);
160  	    if (xml == NULL) {
161  	        pcmk__err("Bad message received from %s[%" PRIu32 "]: '%.120s'", from,
162  	                  nodeid, data);
163  	    } else {
164  	        attrd_peer_message(pcmk__get_node(nodeid, from, NULL,
165  	                                          pcmk__node_search_cluster_member),
166  	                           xml);
167  	    }
168  	
169  	    pcmk__xml_free(xml);
170  	    free(data);
171  	}
172  	
173  	/*!
174  	 * \internal
175  	 * \brief Callback for when the cluster object is destroyed
176  	 *
177  	 * \param[in] unused Unused
178  	 */
179  	static void
180  	attrd_cpg_destroy(gpointer unused)
181  	{
182  	    if (attrd_shutting_down()) {
183  	        pcmk__info("Disconnected from Corosync process group");
184  	
185  	    } else {
186  	        pcmk__crit("Lost connection to Corosync process group, shutting down");
187  	        attrd_exit_status = CRM_EX_DISCONNECT;
188  	        attrd_shutdown(0);
189  	    }
190  	}
191  	#endif // SUPPORT_COROSYNC
192  	
193  	/*!
194  	 * \internal
195  	 * \brief Broadcast an update for a single attribute value
196  	 *
197  	 * \param[in] a  Attribute to broadcast
198  	 * \param[in] v  Attribute value to broadcast
199  	 */
200  	void
201  	attrd_broadcast_value(const attribute_t *a, const attribute_value_t *v)
202  	{
203  	    xmlNode *op = pcmk__xe_create(NULL, PCMK_XE_OP);
204  	
205  	    pcmk__xe_set(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
206  	    attrd_add_value_xml(op, a, v, false);
207  	    attrd_send_message(NULL, op, false);
208  	    pcmk__xml_free(op);
209  	}
210  	
211  	#define state_text(state) pcmk__s((state), "in unknown state")
212  	
213  	/*!
214  	 * \internal
215  	 * \brief Callback for peer status changes
216  	 *
217  	 * \param[in] type  What changed
218  	 * \param[in] node  What peer had the change
219  	 * \param[in] data  Previous value of what changed
220  	 */
221  	static void
222  	attrd_peer_change_cb(enum pcmk__node_update kind, pcmk__node_status_t *peer,
223  	                     const void *data)
224  	{
225  	    bool gone = false;
226  	    bool is_remote = pcmk__is_set(peer->flags, pcmk__node_status_remote);
227  	
228  	    switch (kind) {
229  	        case pcmk__node_update_name:
230  	            pcmk__debug("%s node %s[%" PRIu32 "] is now %s",
231  	                        (is_remote? "Remote" : "Cluster"),
232  	                        pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
233  	                        state_text(peer->state));
234  	            break;
235  	
236  	        case pcmk__node_update_processes:
237  	            if (!pcmk__is_set(peer->processes, crm_get_cluster_proc())) {
238  	                gone = true;
239  	            }
240  	            pcmk__debug("Node %s[%" PRIu32 "] is %s a peer",
241  	                        pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
242  	                        (gone? "no longer" : "now"));
243  	            break;
244  	
245  	        case pcmk__node_update_state:
246  	            pcmk__debug("%s node %s[%" PRIu32 "] is now %s (was %s)",
247  	                        (is_remote? "Remote" : "Cluster"),
248  	                        pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
249  	                        state_text(peer->state), state_text(data));
250  	
251  	            if (pcmk__str_eq(peer->state, PCMK_VALUE_MEMBER, pcmk__str_none)) {
252  	                /* If we're the writer, send new peers a list of all attributes
253  	                 * (unless it's a remote node, which doesn't run its own attrd)
254  	                 */
255  	                if (!is_remote) {
256  	                   if (attrd_election_won()) {
257  	                       attrd_peer_sync(peer);
258  	
259  	                   } else {
260  	                       // Anyway send a message so that the peer learns our name
261  	                       attrd_send_protocol(peer);
262  	                   }
263  	                }
264  	
265  	            } else {
266  	                // Remove all attribute values associated with lost nodes
267  	                if (peer->name != NULL) {
268  	                    attrd_peer_remove(peer->name, false, "loss");
269  	                }
270  	                gone = true;
271  	            }
272  	            break;
273  	    }
274  	
275  	    // Remove votes from cluster nodes that leave, in case election in progress
276  	    if (gone && !is_remote && peer->name != NULL) {
277  	        attrd_remove_voter(peer);
278  	        attrd_remove_peer_protocol_ver(peer->name);
279  	        attrd_do_not_expect_from_peer(peer->name);
280  	    }
281  	}
282  	
283  	#define readable_value(rv_v) pcmk__s((rv_v)->current, "(unset)")
284  	
285  	#define readable_peer(p)    \
286  	    (((p) == NULL)? "all peers" : pcmk__s((p)->name, "unknown peer"))
287  	
288  	static void
289  	update_attr_on_host(attribute_t *a, const pcmk__node_status_t *peer,
290  	                    const xmlNode *xml, const char *attr, const char *value,
291  	                    const char *host, bool filter)
292  	{
293  	    int is_remote = 0;
294  	    bool changed = false;
295  	    attribute_value_t *v = NULL;
296  	    const char *prev_xml_id = NULL;
297  	    const char *node_xml_id = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST_ID);
298  	
299  	    if (removed_peers != NULL) {
300  	        g_hash_table_remove(removed_peers, host);
301  	    }
302  	
303  	    // Create entry for value if not already existing
304  	    v = g_hash_table_lookup(a->values, host);
305  	    if (v == NULL) {
306  	        v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
307  	
308  	        v->nodename = pcmk__str_copy(host);
309  	        g_hash_table_replace(a->values, v->nodename, v);
310  	    }
311  	
312  	    /* If update doesn't contain the node XML ID, fall back to any previously
313  	     * known value (for logging)
314  	     */
315  	    prev_xml_id = attrd_get_node_xml_id(v->nodename);
316  	    if (node_xml_id == NULL) {
317  	        node_xml_id = prev_xml_id;
318  	    }
319  	
320  	    // If value is for a Pacemaker Remote node, remember that
321  	    pcmk__xe_get_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote);
322  	    if (is_remote) {
323  	        attrd_set_value_flags(v, attrd_value_remote);
324  	        pcmk__assert(pcmk__cluster_lookup_remote_node(host) != NULL);
325  	    }
326  	
327  	    // Check whether the value changed
328  	    changed = !pcmk__str_eq(v->current, value, pcmk__str_casei);
329  	
330  	    if (changed && filter
331  	        && pcmk__str_eq(host, attrd_cluster->priv->node_name,
332  	                        pcmk__str_casei)) {
333  	        /* Broadcast the local value for an attribute that differs from the
334  	         * value provided in a peer's attribute synchronization response. This
335  	         * ensures a node's values for itself take precedence and all peers are
336  	         * kept in sync.
337  	         */
338  	        v = g_hash_table_lookup(a->values, attrd_cluster->priv->node_name);
339  	        pcmk__notice("%s[%s]: local value '%s' takes priority over '%s' from "
340  	                     "%s",
341  	                     attr, host, readable_value(v), value, peer->name);
342  	        attrd_broadcast_value(a, v);
343  	
344  	    } else if (changed) {
345  	        const char *timeout_s = "no";
346  	
347  	        if (a->timeout_ms != 0) {
348  	            timeout_s = pcmk__readable_interval(a->timeout_ms);
349  	        }
350  	
351  	        pcmk__notice("Setting %s[%s]%s%s: %s -> %s "
352  	                     QB_XS " from %s with %s write delay and node XML ID %s",
353  	                     attr, host, ((a->set_type != NULL)? " in " : ""),
354  	                     pcmk__s(a->set_type, ""), readable_value(v),
355  	                     pcmk__s(value, "(unset)"), peer->name, timeout_s,
356  	                     pcmk__s(node_xml_id, "unknown"));
357  	        pcmk__str_update(&v->current, value);
358  	        attrd_set_attr_flags(a, attrd_attr_changed);
359  	
360  	        // Write out new value or start dampening timer
361  	        if (a->timeout_ms && a->timer) {
362  	            pcmk__trace("Delaying write of %s %s for dampening", attr,
363  	                        pcmk__readable_interval(a->timeout_ms));
364  	            mainloop_timer_start(a->timer);
365  	        } else {
366  	            attrd_write_or_elect_attribute(a);
367  	        }
368  	
369  	    } else {
370  	        int is_force_write = 0;
371  	
372  	        pcmk__xe_get_int(xml, PCMK__XA_ATTRD_IS_FORCE_WRITE, &is_force_write);
373  	
374  	        if (is_force_write == 1 && a->timeout_ms && a->timer) {
375  	            /* Save forced writing and set change flag. */
376  	            /* The actual attribute is written by Writer after election. */
377  	            pcmk__trace("%s[%s] from %s is unchanged (%s), forcing write", attr,
378  	                        host, peer->name, pcmk__s(value, "unset"));
379  	            attrd_set_attr_flags(a, attrd_attr_force_write);
380  	        } else {
381  	            pcmk__trace("%s[%s] from %s is unchanged (%s)", attr, host,
382  	                        peer->name, pcmk__s(value, "unset"));
383  	        }
384  	    }
385  	
386  	    // This allows us to later detect local values that peer doesn't know about
387  	    attrd_set_value_flags(v, attrd_value_from_peer);
388  	
389  	    // Remember node's XML ID if we're just learning it
390  	    if ((node_xml_id != NULL)
391  	        && !pcmk__str_eq(node_xml_id, prev_xml_id, pcmk__str_none)) {
392  	        // Remember node's name in case unknown in the membership cache
393  	        pcmk__node_status_t *known_peer =
394  	            pcmk__get_node(0, host, node_xml_id,
395  	                           pcmk__node_search_cluster_member);
396  	
397  	        pcmk__trace("Learned %s[%s] node XML ID is %s (was %s)", a->id,
398  	                    known_peer->name, node_xml_id,
399  	                    pcmk__s(prev_xml_id, "unknown"));
400  	
401  	        attrd_set_node_xml_id(v->nodename, node_xml_id);
402  	        if (attrd_election_won()) {
403  	            // In case we couldn't write a value missing the XML ID before
404  	            attrd_write_attributes(attrd_write_changed);
405  	        }
406  	    }
407  	}
408  	
409  	static void
410  	attrd_peer_update_one(const pcmk__node_status_t *peer, xmlNode *xml,
411  	                      bool filter)
412  	{
413  	    attribute_t *a = NULL;
414  	    const char *attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
415  	    const char *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
416  	    const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
417  	
418  	    if (attr == NULL) {
419  	        pcmk__warn("Could not update attribute: peer did not specify name");
420  	        return;
421  	    }
422  	
423  	    a = attrd_populate_attribute(xml, attr);
424  	    if (a == NULL) {
425  	        return;
426  	    }
427  	
428  	    if (host == NULL) {
429  	        // If no host was specified, update all hosts
430  	        GHashTableIter vIter;
431  	
432  	        pcmk__debug("Setting %s for all hosts to %s", attr, value);
433  	        pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_HOST_ID);
434  	        g_hash_table_iter_init(&vIter, a->values);
435  	
436  	        while (g_hash_table_iter_next(&vIter, (gpointer *) & host, NULL)) {
437  	            update_attr_on_host(a, peer, xml, attr, value, host, filter);
438  	        }
439  	
440  	    } else {
441  	        // Update attribute value for the given host
442  	        update_attr_on_host(a, peer, xml, attr, value, host, filter);
443  	    }
444  	
445  	    /* If this is a message from some attrd instance broadcasting its protocol
446  	     * version, check to see if it's a new minimum version.
447  	     */
448  	    if (pcmk__str_eq(attr, CRM_ATTR_PROTOCOL, pcmk__str_none)) {
449  	        attrd_update_minimum_protocol_ver(peer->name, value);
450  	    }
451  	}
452  	
453  	static void
454  	broadcast_unseen_local_values(void)
455  	{
456  	    GHashTableIter aIter;
457  	    GHashTableIter vIter;
458  	    attribute_t *a = NULL;
459  	    attribute_value_t *v = NULL;
460  	    xmlNode *sync = NULL;
461  	
462  	    g_hash_table_iter_init(&aIter, attributes);
463  	    while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
464  	
465  	        g_hash_table_iter_init(&vIter, a->values);
466  	        while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
467  	
468  	            if (!pcmk__is_set(v->flags, attrd_value_from_peer)
469  	                && pcmk__str_eq(v->nodename, attrd_cluster->priv->node_name,
470  	                                pcmk__str_casei)) {
471  	                pcmk__trace("* %s[%s]='%s' is local-only", a->id, v->nodename,
472  	                            readable_value(v));
473  	                if (sync == NULL) {
474  	                    sync = pcmk__xe_create(NULL, __func__);
475  	                    pcmk__xe_set(sync, PCMK_XA_TASK,
476  	                                 PCMK__ATTRD_CMD_SYNC_RESPONSE);
477  	                }
478  	                attrd_add_value_xml(sync, a, v, a->timeout_ms && a->timer);
479  	            }
480  	        }
481  	    }
482  	
483  	    if (sync != NULL) {
484  	        pcmk__debug("Broadcasting local-only values");
485  	        attrd_send_message(NULL, sync, false);
486  	        pcmk__xml_free(sync);
487  	    }
488  	}
489  	
490  	int
491  	attrd_cluster_connect(void)
492  	{
493  	    int rc = pcmk_rc_ok;
494  	
495  	    attrd_cluster = pcmk_cluster_new();
496  	
497  	#if SUPPORT_COROSYNC
498  	    if (pcmk_get_cluster_layer() == pcmk_cluster_layer_corosync) {
499  	        pcmk_cluster_set_destroy_fn(attrd_cluster, attrd_cpg_destroy);
500  	        pcmk_cpg_set_deliver_fn(attrd_cluster, attrd_cpg_dispatch);
501  	        pcmk_cpg_set_confchg_fn(attrd_cluster, pcmk__cpg_confchg_cb);
502  	    }
503  	#endif // SUPPORT_COROSYNC
504  	
505  	    pcmk__cluster_set_status_callback(&attrd_peer_change_cb);
506  	
507  	    rc = pcmk_cluster_connect(attrd_cluster);
508  	    if (rc != pcmk_rc_ok) {
509  	        pcmk__err("Cluster connection failed");
510  	    }
511  	
512  	    return rc;
513  	}
514  	
515  	void
516  	attrd_cluster_disconnect(void)
517  	{
518  	    pcmk_cluster_disconnect(attrd_cluster);
519  	    pcmk_cluster_free(attrd_cluster);
520  	}
521  	
522  	void
523  	attrd_peer_clear_failure(pcmk__request_t *request)
524  	{
525  	    xmlNode *xml = request->xml;
526  	    const char *rsc = pcmk__xe_get(xml, PCMK__XA_ATTR_RESOURCE);
527  	    const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
528  	    const char *op = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
529  	    const char *interval_spec = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_INTERVAL);
530  	    guint interval_ms = 0U;
531  	    char *attr = NULL;
532  	    GHashTableIter iter;
533  	    regex_t regex;
534  	
535  	    pcmk__node_status_t *peer =
536  	        pcmk__get_node(0, request->peer, NULL,
537  	                       pcmk__node_search_cluster_member);
538  	
539  	    pcmk_parse_interval_spec(interval_spec, &interval_ms);
540  	
541  	    if (attrd_failure_regex(&regex, rsc, op, interval_ms) != pcmk_ok) {
542  	        pcmk__info("Ignoring invalid request to clear failures for %s",
543  	                   pcmk__s(rsc, "all resources"));
544  	        return;
545  	    }
546  	
547  	    pcmk__xe_set(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
548  	
549  	    /* Make sure value is not set, so we delete */
550  	    pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
551  	
552  	    g_hash_table_iter_init(&iter, attributes);
553  	    while (g_hash_table_iter_next(&iter, (gpointer *) &attr, NULL)) {
554  	        if (regexec(&regex, attr, 0, NULL, 0) == 0) {
555  	            pcmk__trace("Matched %s when clearing %s", attr,
556  	                        pcmk__s(rsc, "all resources"));
557  	            pcmk__xe_set(xml, PCMK__XA_ATTR_NAME, attr);
558  	            attrd_peer_update(peer, xml, host, false);
559  	        }
560  	    }
561  	    regfree(&regex);
562  	}
563  	
564  	/*!
565  	 * \internal
566  	 * \brief Load attributes from a peer sync response
567  	 *
568  	 * \param[in]     peer      Peer that sent sync response
569  	 * \param[in]     peer_won  Whether peer is the attribute writer
570  	 * \param[in,out] xml       Request XML
571  	 */
572  	void
573  	attrd_peer_sync_response(const pcmk__node_status_t *peer, bool peer_won,
574  	                         xmlNode *xml)
575  	{
576  	    pcmk__info("Processing " PCMK__ATTRD_CMD_SYNC_RESPONSE " from %s",
577  	               peer->name);
578  	
579  	    if (peer_won) {
580  	        /* Initialize the "seen" flag for all attributes to cleared, so we can
581  	         * detect attributes that local node has but the writer doesn't.
582  	         */
583  	        attrd_clear_value_seen();
584  	    }
585  	
586  	    // Process each attribute update in the sync response
587  	    for (xmlNode *child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
588  	         child != NULL; child = pcmk__xe_next(child, NULL)) {
589  	
590  	        attrd_peer_update(peer, child, pcmk__xe_get(child, PCMK__XA_ATTR_HOST),
591  	                          true);
592  	    }
593  	
594  	    if (peer_won) {
595  	        /* If any attributes are still not marked as seen, the writer doesn't
596  	         * know about them, so send all peers an update with them.
597  	         */
598  	        broadcast_unseen_local_values();
599  	    }
600  	}
601  	
602  	/*!
603  	 * \internal
604  	 * \brief Erase all removed nodes' transient attributes from the CIB
605  	 *
606  	 * This should be called by a newly elected writer upon winning the election.
607  	 */
608  	void
609  	attrd_erase_removed_peer_attributes(void)
610  	{
611  	    const char *host = NULL;
612  	    GHashTableIter iter;
613  	
614  	    if (!attrd_election_won() || (removed_peers == NULL)) {
615  	        return;
616  	    }
617  	
618  	    g_hash_table_iter_init(&iter, removed_peers);
619  	    while (g_hash_table_iter_next(&iter, (gpointer *) &host, NULL)) {
620  	        attrd_cib_erase_transient_attrs(host);
621  	        g_hash_table_iter_remove(&iter);
622  	    }
623  	}
624  	
625  	/*!
626  	 * \internal
627  	 * \brief Remove all attributes and optionally peer cache entries for a node
628  	 *
629  	 * \param[in] host     Name of node to purge
630  	 * \param[in] uncache  If true, remove node from peer caches
631  	 * \param[in] source   Who requested removal (only used for logging)
632  	 */
633  	void
634  	attrd_peer_remove(const char *host, bool uncache, const char *source)
635  	{
636  	    attribute_t *a = NULL;
637  	    GHashTableIter aIter;
638  	
639  	    CRM_CHECK(host != NULL, return);
640  	    pcmk__notice("Removing all %s attributes for node %s "
641  	                 QB_XS " %s reaping node from cache",
642  	                 host, source, (uncache? "and" : "without"));
643  	
644  	    g_hash_table_iter_init(&aIter, attributes);
645  	    while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
646  	        if(g_hash_table_remove(a->values, host)) {
647  	            pcmk__debug("Removed %s[%s] for peer %s", a->id, host, source);
648  	        }
649  	    }
650  	
651  	    if (attrd_election_won()) {
652  	        // We are the writer. Wipe node's transient attributes from CIB now.
653  	        attrd_cib_erase_transient_attrs(host);
654  	
655  	    } else {
656  	        /* Make sure the attributes get erased from the CIB eventually.
657  	         * - If there's already a writer, it will call this function and enter
658  	         *   the "if" block above, requesting the erasure (unless it leaves
659  	         *   before sending the request -- see below).
660  	         *   attrd_start_election_if_needed() will do nothing here.
661  	         * - Otherwise, we ensure an election is happening (unless we're
662  	         *   shutting down). The winner will erase transient attributes from the
663  	         *   CIB for all removed nodes in attrd_election_cb().
664  	         *
665  	         * We add the node to the removed_peers table in case we win an election
666  	         * and need to request CIB erasures based on the table contents. This
667  	         * could happen for either of two reasons:
668  	         * - There is no current writer and we're not shutting down. An election
669  	         *   either is already in progress or will be triggered here.
670  	         * - The current writer leaves before sending the CIB update request. A
671  	         *   new election will be triggered.
672  	         */
673  	        if (removed_peers == NULL) {
674  	            removed_peers = pcmk__strikey_table(free, NULL);
675  	        }
676  	        g_hash_table_add(removed_peers, pcmk__str_copy(host));
677  	        attrd_start_election_if_needed();
678  	    }
679  	
680  	    if (uncache) {
681  	        pcmk__purge_node_from_cache(host, 0);
682  	        attrd_forget_node_xml_id(host);
683  	    }
684  	}
685  	
686  	/*!
687  	 * \internal
688  	 * \brief Send all known attributes and values to a peer
689  	 *
690  	 * \param[in] peer  Peer to send sync to (if NULL, broadcast to all peers)
691  	 */
692  	void
693  	attrd_peer_sync(pcmk__node_status_t *peer)
694  	{
695  	    GHashTableIter aIter;
696  	    GHashTableIter vIter;
697  	
698  	    attribute_t *a = NULL;
699  	    attribute_value_t *v = NULL;
700  	    xmlNode *sync = pcmk__xe_create(NULL, __func__);
701  	
702  	    pcmk__xe_set(sync, PCMK_XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE);
703  	
704  	    g_hash_table_iter_init(&aIter, attributes);
705  	    while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
706  	        g_hash_table_iter_init(&vIter, a->values);
707  	        while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
708  	            pcmk__debug("Syncing %s[%s]='%s' to %s", a->id, v->nodename,
709  	                        readable_value(v), readable_peer(peer));
710  	            attrd_add_value_xml(sync, a, v, false);
711  	        }
712  	    }
713  	
714  	    pcmk__debug("Syncing values to %s", readable_peer(peer));
715  	    attrd_send_message(peer, sync, false);
716  	    pcmk__xml_free(sync);
717  	}
718  	
719  	void
720  	attrd_peer_update(const pcmk__node_status_t *peer, xmlNode *xml,
721  	                  const char *host, bool filter)
722  	{
723  	    bool handle_sync_point = false;
724  	
725  	    CRM_CHECK((peer != NULL) && (xml != NULL), return);
726  	    if (xml->children != NULL) {
727  	        for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL, NULL);
728  	             child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
729  	
730  	            pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
731  	            attrd_peer_update_one(peer, child, filter);
732  	
733  	            if (attrd_request_has_sync_point(child)) {
734  	                handle_sync_point = true;
735  	            }
736  	        }
737  	
738  	    } else {
739  	        attrd_peer_update_one(peer, xml, filter);
740  	
741  	        if (attrd_request_has_sync_point(xml)) {
742  	            handle_sync_point = true;
743  	        }
744  	    }
745  	
746  	    /* If the update XML specified that the client wanted to wait for a sync
747  	     * point, process that now.
748  	     */
749  	    if (handle_sync_point) {
750  	        pcmk__trace("Hit local sync point for attribute update");
751  	        attrd_ack_waitlist_clients(attrd_sync_point_local, xml);
752  	    }
753  	}
754