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 <stdlib.h>
15   	#include <glib.h>
16   	
17   	#include <crm/cib/internal.h>       // cib__*
18   	#include <crm/common/logging.h>
19   	#include <crm/common/results.h>
20   	#include <crm/common/xml.h>
21   	#include <crm/cluster/internal.h>   // pcmk__get_node()
22   	
23   	#include "pacemaker-attrd.h"
24   	
25   	static int last_cib_op_done = 0;
26   	
27   	static void write_attribute(attribute_t *a, bool ignore_delay);
28   	
29   	static void
30   	attrd_cib_destroy_cb(gpointer user_data)
31   	{
32   	    cib_t *cib = user_data;
33   	
34   	    cib->cmds->signoff(cib);
35   	
36   	    if (attrd_shutting_down()) {
37   	        pcmk__info("Disconnected from the CIB manager");
38   	
39   	    } else {
40   	        // @TODO This should trigger a reconnect, not a shutdown
41   	        pcmk__crit("Lost connection to the CIB manager, shutting down");
42   	        attrd_exit_status = CRM_EX_DISCONNECT;
43   	        attrd_shutdown(0);
44   	    }
45   	}
46   	
47   	static void
48   	attrd_cib_updated_cb(const char *event, xmlNode *msg)
49   	{
50   	    const xmlNode *patchset = NULL;
51   	    const char *client_name = NULL;
52   	    bool status_changed = false;
53   	
54   	    if (cib__get_notify_patchset(msg, &patchset) != pcmk_rc_ok) {
55   	        return;
56   	    }
57   	
58   	    if (pcmk__cib_element_in_patchset(patchset, PCMK_XE_ALERTS)) {
59   	        if (attrd_shutting_down()) {
60   	            pcmk__debug("Ignoring alerts change in CIB during shutdown");
61   	        } else {
62   	            mainloop_set_trigger(attrd_config_read);
63   	        }
64   	    }
65   	
66   	    status_changed = pcmk__cib_element_in_patchset(patchset, PCMK_XE_STATUS);
67   	
68   	    client_name = pcmk__xe_get(msg, PCMK__XA_CIB_CLIENTNAME);
69   	    if (!cib__client_triggers_refresh(client_name)) {
70   	        /* This change came from a source that ensured the CIB is consistent
71   	         * with our attributes table, so we don't need to write anything out.
72   	         */
73   	        return;
74   	    }
75   	
76   	    if (!attrd_election_won()) {
77   	        // Don't write attributes if we're not the writer
78   	        return;
79   	    }
80   	
81   	    if (status_changed
82   	        || pcmk__cib_element_in_patchset(patchset, PCMK_XE_NODES)) {
83   	
84   	        if (attrd_shutting_down()) {
85   	            pcmk__debug("Ignoring node change in CIB during shutdown");
86   	            return;
87   	        }
88   	
89   	        /* An unsafe client modified the PCMK_XE_NODES or PCMK_XE_STATUS
90   	         * section. Write transient attributes to ensure they're up-to-date in
91   	         * the CIB.
92   	         */
93   	        if (client_name == NULL) {
94   	            client_name = pcmk__xe_get(msg, PCMK__XA_CIB_CLIENTID);
95   	        }
96   	        pcmk__notice("Updating all attributes after %s event triggered by %s",
97   	                     event, pcmk__s(client_name, "unidentified client"));
98   	
99   	        attrd_write_attributes(attrd_write_all);
100  	    }
101  	}
102  	
103  	int
104  	attrd_cib_connect(int max_retry)
105  	{
106  	    static int attempts = 0;
107  	
108  	    int rc = -ENOTCONN;
109  	
110  	    the_cib = cib_new();
111  	    if (the_cib == NULL) {
112  	        return -ENOTCONN;
113  	    }
114  	
115  	    do {
116  	        if (attempts > 0) {
117  	            sleep(attempts);
118  	        }
119  	        attempts++;
120  	        pcmk__debug("Connection attempt %d to the CIB manager", attempts);
121  	        rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
122  	
123  	    } while ((rc != pcmk_ok) && (attempts < max_retry));
124  	
125  	    if (rc != pcmk_ok) {
126  	        pcmk__err("Connection to the CIB manager failed: %s " QB_XS " rc=%d",
127  	                  pcmk_strerror(rc), rc);
128  	        goto cleanup;
129  	    }
130  	
131  	    pcmk__debug("Connected to the CIB manager after %d attempts", attempts);
132  	
133  	    rc = the_cib->cmds->set_connection_dnotify(the_cib, attrd_cib_destroy_cb);
134  	    if (rc != pcmk_ok) {
135  	        pcmk__err("Could not set disconnection callback");
136  	        goto cleanup;
137  	    }
138  	
139  	    rc = the_cib->cmds->add_notify_callback(the_cib,
140  	                                            PCMK__VALUE_CIB_DIFF_NOTIFY,
141  	                                            attrd_cib_updated_cb);
142  	    if (rc != pcmk_ok) {
143  	        pcmk__err("Could not set CIB notification callback");
144  	        goto cleanup;
145  	    }
146  	
147  	    return pcmk_ok;
148  	
149  	cleanup:
150  	    cib__clean_up_connection(&the_cib);
151  	    return -ENOTCONN;
152  	}
153  	
154  	void
155  	attrd_cib_disconnect(void)
156  	{
157  	    CRM_CHECK(the_cib != NULL, return);
158  	    the_cib->cmds->del_notify_callback(the_cib, PCMK__VALUE_CIB_DIFF_NOTIFY,
159  	                                       attrd_cib_updated_cb);
160  	    cib__clean_up_connection(&the_cib);
161  	    mainloop_destroy_trigger(attrd_config_read);
162  	}
163  	
164  	static void
165  	attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output,
166  	               void *user_data)
167  	{
168  	    const char *node = pcmk__s((const char *) user_data, "a node");
169  	
170  	    if (rc == pcmk_ok) {
171  	        pcmk__info("Cleared transient node attributes for %s from CIB", node);
172  	    } else {
173  	        pcmk__err("Unable to clear transient node attributes for %s from CIB: "
174  	                  "%s",
175  	                  node, pcmk_strerror(rc));
176  	    }
177  	}
178  	
179  	#define XPATH_TRANSIENT "//" PCMK__XE_NODE_STATE    \
180  	                        "[@" PCMK_XA_UNAME "='%s']" \
181  	                        "/" PCMK__XE_TRANSIENT_ATTRIBUTES
182  	
183  	/*!
184  	 * \internal
185  	 * \brief Wipe all transient node attributes for a node from the CIB
186  	 *
187  	 * \param[in] node  Node to clear attributes for
188  	 */
189  	void
190  	attrd_cib_erase_transient_attrs(const char *node)
191  	{
192  	    int call_id = 0;
193  	    char *xpath = NULL;
194  	
195  	    CRM_CHECK(node != NULL, return);
196  	
197  	    xpath = pcmk__assert_asprintf(XPATH_TRANSIENT, node);
198  	
199  	    pcmk__debug("Clearing transient node attributes for %s from CIB using %s",
200  	                node, xpath);
201  	
202  	    call_id = the_cib->cmds->remove(the_cib, xpath, NULL, cib_xpath);
203  	    free(xpath);
204  	
205  	    the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE,
206  	                                          pcmk__str_copy(node),
207  	                                          "attrd_erase_cb", attrd_erase_cb,
208  	                                          free);
209  	}
210  	
211  	/*!
212  	 * \internal
213  	 * \brief Prepare the CIB after cluster is connected
214  	 */
215  	void
216  	attrd_cib_init(void)
217  	{
218  	    /* We have no attribute values in memory, so wipe the CIB to match. This is
219  	     * normally done by the DC's controller when this node leaves the cluster, but
220  	     * this handles the case where the node restarted so quickly that the
221  	     * cluster layer didn't notice.
222  	     *
223  	     * \todo If the attribute manager respawns after crashing (see
224  	     *       PCMK_ENV_RESPAWNED), ideally we'd skip this and sync our attributes
225  	     *       from the writer. However, currently we reject any values for us
226  	     *       that the writer has, in attrd_peer_update().
227  	     */
228  	    attrd_cib_erase_transient_attrs(attrd_cluster->priv->node_name);
229  	
230  	    // Set a trigger for reading the CIB (for the alerts section)
231  	    attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
232  	
233  	    // Always read the CIB at start-up
234  	    mainloop_set_trigger(attrd_config_read);
235  	}
236  	
237  	static gboolean
238  	attribute_timer_cb(gpointer data)
239  	{
240  	    attribute_t *a = data;
241  	    pcmk__trace("Dampen interval expired for %s", a->id);
242  	    attrd_write_or_elect_attribute(a);
243  	    return FALSE;
244  	}
245  	
246  	static void
247  	attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data)
248  	{
249  	    int level = LOG_ERR;
250  	    GHashTableIter iter;
251  	    const char *peer = NULL;
252  	    attribute_value_t *v = NULL;
253  	
254  	    char *name = user_data;
255  	    attribute_t *a = g_hash_table_lookup(attributes, name);
256  	
(1) Event path: Condition "a == NULL", taking false branch.
257  	    if(a == NULL) {
258  	        pcmk__info("Attribute %s no longer exists", name);
259  	        return;
260  	    }
261  	
262  	    a->update = 0;
(2) Event path: Condition "rc == 0", taking true branch.
(3) Event path: Condition "call_id < 0", taking false branch.
263  	    if (rc == pcmk_ok && call_id < 0) {
264  	        rc = call_id;
265  	    }
266  	
(4) Event path: Switch case value "0".
267  	    switch (rc) {
268  	        case pcmk_ok:
269  	            level = LOG_INFO;
270  	            last_cib_op_done = call_id;
(5) Event path: Condition "a->timer", taking true branch.
(6) Event path: Condition "!a->timeout_ms", taking true branch.
271  	            if (a->timer && !a->timeout_ms) {
272  	                // Remove temporary dampening for failed writes
(7) Event path: Condition "_p", taking true branch.
273  	                g_clear_pointer(&a->timer, mainloop_timer_del);
274  	            }
(8) Event path: Breaking from switch.
275  	            break;
276  	
277  	        case -pcmk_err_diff_failed:    /* When an attr changes while the CIB is syncing */
278  	        case -ETIME:           /* When an attr changes while there is a DC election */
279  	        case -ENXIO:           /* When an attr changes while the CIB is syncing a
280  	                                *   newer config from a node that just came up
281  	                                */
282  	            level = LOG_WARNING;
283  	            break;
284  	    }
285  	
(9) Event path: Switch case default.
(10) Event path: Breaking from switch.
286  	    do_crm_log(level, "CIB update %d result for %s: %s " QB_XS " rc=%d",
287  	               call_id, a->id, pcmk_strerror(rc), rc);
288  	
289  	    g_hash_table_iter_init(&iter, a->values);
(11) Event path: Condition "g_hash_table_iter_next(&iter, (gpointer *)&peer, (gpointer *)&v)", taking true branch.
290  	    while (g_hash_table_iter_next(&iter, (gpointer *) & peer, (gpointer *) & v)) {
(12) Event path: Condition "rc == 0", taking true branch.
291  	        if (rc == pcmk_ok) {
292  	            pcmk__info("* Wrote %s[%s]=%s", a->id, peer,
293  	                       pcmk__s(v->requested, "(unset)"));
CID (unavailable; MK=14470e4b64f1db75ac29fca86937b391) (#2 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(13) Event assign_union_field: The union field "in" of "_pp" is written.
(14) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
294  	            g_clear_pointer(&v->requested, free);
295  	        } else {
296  	            do_crm_log(level, "* Could not write %s[%s]=%s",
297  	                       a->id, peer, pcmk__s(v->requested, "(unset)"));
298  	            /* Reattempt write below if we are still the writer */
299  	            attrd_set_attr_flags(a, attrd_attr_changed);
300  	        }
301  	    }
302  	
303  	    if (pcmk__is_set(a->flags, attrd_attr_changed) && attrd_election_won()) {
304  	        if (rc == pcmk_ok) {
305  	            /* We deferred a write of a new update because this update was in
306  	             * progress. Write out the new value without additional delay.
307  	             */
308  	            pcmk__debug("Pending update for %s can be written now", a->id);
309  	            write_attribute(a, false);
310  	
311  	        /* We're re-attempting a write because the original failed; delay
312  	         * the next attempt so we don't potentially flood the CIB manager
313  	         * and logs with a zillion attempts per second.
314  	         *
315  	         * @TODO We could elect a new writer instead. However, we'd have to
316  	         * somehow downgrade our vote, and we'd still need something like this
317  	         * if all peers similarly fail to write this attribute (which may
318  	         * indicate a corrupted attribute entry rather than a CIB issue).
319  	         */
320  	        } else if (a->timer) {
321  	            // Attribute has a dampening value, so use that as delay
322  	            if (!mainloop_timer_running(a->timer)) {
323  	                pcmk__trace("Delayed re-attempted write for %s by %s",
324  	                            name, pcmk__readable_interval(a->timeout_ms));
325  	                mainloop_timer_start(a->timer);
326  	            }
327  	        } else {
328  	            /* Set a temporary dampening of 2 seconds (timer will continue
329  	             * to exist until the attribute's dampening gets set or the
330  	             * write succeeds).
331  	             */
332  	            a->timer = attrd_add_timer(a->id, 2000, a);
333  	            mainloop_timer_start(a->timer);
334  	        }
335  	    }
336  	}
337  	
338  	/*!
339  	 * \internal
340  	 * \brief Add a set-attribute update request to the current CIB transaction
341  	 *
342  	 * \param[in] attr     Attribute to update
343  	 * \param[in] attr_id  ID of attribute to update
344  	 * \param[in] node_id  ID of node for which to update attribute value
345  	 * \param[in] set_id   ID of attribute set
346  	 * \param[in] value    New value for attribute
347  	 *
348  	 * \return Standard Pacemaker return code
349  	 */
350  	static int
351  	add_set_attr_update(const attribute_t *attr, const char *attr_id,
352  	                    const char *node_id, const char *set_id, const char *value)
353  	{
354  	    xmlNode *update = pcmk__xe_create(NULL, PCMK__XE_NODE_STATE);
355  	    xmlNode *child = update;
356  	    int rc = ENOMEM;
357  	
358  	    pcmk__xe_set(child, PCMK_XA_ID, node_id);
359  	
360  	    child = pcmk__xe_create(child, PCMK__XE_TRANSIENT_ATTRIBUTES);
361  	    pcmk__xe_set(child, PCMK_XA_ID, node_id);
362  	
363  	    child = pcmk__xe_create(child, attr->set_type);
364  	    pcmk__xe_set(child, PCMK_XA_ID, set_id);
365  	
366  	    child = pcmk__xe_create(child, PCMK_XE_NVPAIR);
367  	    pcmk__xe_set(child, PCMK_XA_ID, attr_id);
368  	    pcmk__xe_set(child, PCMK_XA_NAME, attr->id);
369  	    pcmk__xe_set(child, PCMK_XA_VALUE, value);
370  	
371  	    rc = the_cib->cmds->modify(the_cib, PCMK_XE_STATUS, update,
372  	                               cib_can_create|cib_transaction);
373  	    rc = pcmk_legacy2rc(rc);
374  	
375  	    pcmk__xml_free(update);
376  	    return rc;
377  	}
378  	
379  	/*!
380  	 * \internal
381  	 * \brief Add an unset-attribute update request to the current CIB transaction
382  	 *
383  	 * \param[in] attr     Attribute to update
384  	 * \param[in] attr_id  ID of attribute to update
385  	 * \param[in] node_id  ID of node for which to update attribute value
386  	 * \param[in] set_id   ID of attribute set
387  	 *
388  	 * \return Standard Pacemaker return code
389  	 */
390  	static int
391  	add_unset_attr_update(const attribute_t *attr, const char *attr_id,
392  	                      const char *node_id, const char *set_id)
393  	{
394  	    char *xpath = pcmk__assert_asprintf("/" PCMK_XE_CIB
395  	                                        "/" PCMK_XE_STATUS
396  	                                        "/" PCMK__XE_NODE_STATE
397  	                                            "[@" PCMK_XA_ID "='%s']"
398  	                                        "/" PCMK__XE_TRANSIENT_ATTRIBUTES
399  	                                            "[@" PCMK_XA_ID "='%s']"
400  	                                        "/%s[@" PCMK_XA_ID "='%s']"
401  	                                        "/" PCMK_XE_NVPAIR
402  	                                            "[@" PCMK_XA_ID "='%s' "
403  	                                             "and @" PCMK_XA_NAME "='%s']",
404  	                                        node_id, node_id, attr->set_type,
405  	                                        set_id, attr_id, attr->id);
406  	
407  	    int rc = the_cib->cmds->remove(the_cib, xpath, NULL,
408  	                                   cib_xpath|cib_transaction);
409  	
410  	    free(xpath);
411  	    return pcmk_legacy2rc(rc);
412  	}
413  	
414  	/*!
415  	 * \internal
416  	 * \brief Add an attribute update request to the current CIB transaction
417  	 *
418  	 * \param[in] attr      Attribute to update
419  	 * \param[in] value     New value for attribute
420  	 * \param[in] node_id   ID of node for which to update attribute value
421  	 *
422  	 * \return Standard Pacemaker return code
423  	 */
424  	static int
425  	add_attr_update(const attribute_t *attr, const char *value, const char *node_id)
426  	{
427  	    char *set_id = attrd_set_id(attr, node_id);
428  	    char *nvpair_id = attrd_nvpair_id(attr, node_id);
429  	    int rc = pcmk_rc_ok;
430  	
431  	    if (value == NULL) {
432  	        rc = add_unset_attr_update(attr, nvpair_id, node_id, set_id);
433  	    } else {
434  	        rc = add_set_attr_update(attr, nvpair_id, node_id, set_id, value);
435  	    }
436  	    free(set_id);
437  	    free(nvpair_id);
438  	    return rc;
439  	}
440  	
441  	static void
442  	send_alert_attributes_value(attribute_t *a, GHashTable *t)
443  	{
444  	    int rc = 0;
445  	    attribute_value_t *at = NULL;
446  	    GHashTableIter vIter;
447  	
448  	    g_hash_table_iter_init(&vIter, t);
449  	
450  	    while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & at)) {
451  	        const char *node_xml_id = attrd_get_node_xml_id(at->nodename);
452  	        const char *failed_s = NULL;
453  	
454  	        rc = attrd_send_attribute_alert(at->nodename, node_xml_id,
455  	                                        a->id, at->current);
456  	
457  	        switch (rc) {
458  	            case pcmk_ok:
459  	                failed_s = "no agents failed";
460  	                break;
461  	
462  	            case -1:
463  	                failed_s = "some agents failed";
464  	                break;
465  	
466  	            case -2:
467  	                failed_s = "all agents failed";
468  	                break;
469  	
470  	            default:
471  	                failed_s = "bug: unexpected return code";
472  	                break;
473  	        }
474  	
475  	        pcmk__trace("Sent alerts for %s[%s]=%s with node XML ID %s (%s, rc=%d)",
476  	                    a->id, at->nodename, at->current,
477  	                    pcmk__s(node_xml_id, "<unknown>"), failed_s, rc);
478  	    }
479  	}
480  	
481  	static void
482  	set_alert_attribute_value(GHashTable *t, attribute_value_t *v)
483  	{
484  	    attribute_value_t *a_v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
485  	
486  	    a_v->nodename = pcmk__str_copy(v->nodename);
487  	    a_v->current = pcmk__str_copy(v->current);
488  	
489  	    g_hash_table_replace(t, a_v->nodename, a_v);
490  	}
491  	
492  	mainloop_timer_t *
493  	attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr)
494  	{
495  	   return mainloop_timer_add(id, timeout_ms, FALSE, attribute_timer_cb, attr);
496  	}
497  	
498  	/*!
499  	 * \internal
500  	 * \brief Write an attribute's values to the CIB if appropriate
501  	 *
502  	 * \param[in,out] a             Attribute to write
503  	 * \param[in]     ignore_delay  If true, write attribute now regardless of any
504  	 *                              configured delay
505  	 */
506  	static void
507  	write_attribute(attribute_t *a, bool ignore_delay)
508  	{
509  	    int private_updates = 0, cib_updates = 0;
510  	    attribute_value_t *v = NULL;
511  	    GHashTableIter iter;
512  	    GHashTable *alert_attribute_value = NULL;
513  	    int rc = pcmk_ok;
514  	    bool should_write = true;
515  	
516  	    if (a == NULL) {
517  	        return;
518  	    }
519  	
520  	    // Private attributes (or any in standalone mode) are not written to the CIB
521  	    if (stand_alone || pcmk__is_set(a->flags, attrd_attr_is_private)) {
522  	        should_write = false;
523  	    }
524  	
525  	    /* If this attribute will be written to the CIB ... */
526  	    if (should_write) {
527  	        /* Defer the write if now's not a good time */
528  	        if (a->update && (a->update < last_cib_op_done)) {
529  	            pcmk__info("Write out of '%s' continuing: update %d considered "
530  	                       "lost",
531  	                       a->id, a->update);
532  	            a->update = 0; // Don't log this message again
533  	
534  	        } else if (a->update) {
535  	            pcmk__info("Write out of '%s' delayed: update %d in progress",
536  	                       a->id, a->update);
537  	            goto done;
538  	
539  	        } else if (mainloop_timer_running(a->timer)) {
540  	            if (ignore_delay) {
541  	                mainloop_timer_stop(a->timer);
542  	                pcmk__debug("Overriding '%s' write delay", a->id);
543  	            } else {
544  	                pcmk__info("Delaying write of '%s'", a->id);
545  	                goto done;
546  	            }
547  	        }
548  	
549  	        // Initiate a transaction for all the peer value updates
550  	        CRM_CHECK(the_cib != NULL, goto done);
551  	        the_cib->cmds->set_user(the_cib, a->user);
552  	        rc = the_cib->cmds->init_transaction(the_cib);
553  	        if (rc != pcmk_ok) {
554  	            pcmk__err("Failed to write %s (set %s): Could not initiate "
555  	                      "CIB transaction",
556  	                      a->id, pcmk__s(a->set_id, "unspecified"));
557  	            goto done;
558  	        }
559  	    }
560  	
561  	    /* The changed and force-write flags apply only to the next write,
562  	     * which this is, so clear them now. Also clear the "node unknown" flag
563  	     * because we will check whether it is known below and reset if appopriate.
564  	     */
565  	    attrd_clear_attr_flags(a, attrd_attr_changed
566  	                              |attrd_attr_force_write
567  	                              |attrd_attr_node_unknown);
568  	
569  	    /* Make the table for the attribute trap */
570  	    alert_attribute_value = pcmk__strikey_table(NULL,
571  	                                                attrd_free_attribute_value);
572  	
573  	    /* Iterate over each peer value of this attribute */
574  	    g_hash_table_iter_init(&iter, a->values);
575  	    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
576  	        const char *node_xml_id = NULL;
577  	        const char *prev_xml_id = NULL;
578  	
579  	        if (!should_write) {
580  	            private_updates++;
581  	            continue;
582  	        }
583  	
584  	        /* We need the node's CIB XML ID to write out its attributes, so look
585  	         * for it now. Check the node caches first, even if the ID was
586  	         * previously known (in case it changed), but use any previous value as
587  	         * a fallback.
588  	         */
589  	
590  	        prev_xml_id = attrd_get_node_xml_id(v->nodename);
591  	
592  	        if (pcmk__is_set(v->flags, attrd_value_remote)) {
593  	            // A Pacemaker Remote node's XML ID is the same as its name
594  	            node_xml_id = v->nodename;
595  	
596  	        } else {
597  	            // This creates a cluster node cache entry if none exists
598  	            pcmk__node_status_t *peer = pcmk__get_node(0, v->nodename,
599  	                                                       prev_xml_id,
600  	                                                       pcmk__node_search_any);
601  	
602  	            node_xml_id = pcmk__cluster_get_xml_id(peer);
603  	            if (node_xml_id == NULL) {
604  	                node_xml_id = prev_xml_id;
605  	            }
606  	        }
607  	
608  	        // Defer write if this is a cluster node that's never been seen
609  	        if (node_xml_id == NULL) {
610  	            attrd_set_attr_flags(a, attrd_attr_node_unknown);
611  	            pcmk__notice("Cannot write %s[%s]='%s' to CIB because node's XML "
612  	                         "ID is unknown (will retry if learned)",
613  	                         a->id, v->nodename, v->current);
614  	            continue;
615  	        }
616  	
617  	        if (!pcmk__str_eq(prev_xml_id, node_xml_id, pcmk__str_none)) {
618  	            pcmk__trace("Setting %s[%s] node XML ID to %s (was %s)", a->id,
619  	                        v->nodename, node_xml_id,
620  	                        pcmk__s(prev_xml_id, "unknown"));
621  	            attrd_set_node_xml_id(v->nodename, node_xml_id);
622  	        }
623  	
624  	        // Update this value as part of the CIB transaction we're building
625  	        rc = add_attr_update(a, v->current, node_xml_id);
626  	        if (rc != pcmk_rc_ok) {
627  	            pcmk__err("Couldn't add %s[%s]='%s' to CIB transaction: %s "
628  	                      QB_XS " node XML ID %s",
629  	                      a->id, v->nodename, v->current, pcmk_rc_str(rc),
630  	                      node_xml_id);
631  	            continue;
632  	        }
633  	
634  	        pcmk__debug("Added %s[%s]=%s to CIB transaction (node XML ID %s)",
635  	                    a->id, v->nodename, pcmk__s(v->current, "(unset)"),
636  	                    node_xml_id);
637  	        cib_updates++;
638  	
639  	        /* Preservation of the attribute to transmit alert */
640  	        set_alert_attribute_value(alert_attribute_value, v);
641  	
642  	        // Save this value so we can log it when write completes
643  	        pcmk__str_update(&(v->requested), v->current);
644  	    }
645  	
646  	    if (private_updates) {
647  	        pcmk__info("Processed %d private change%s for %s (set %s)",
648  	                   private_updates, pcmk__plural_s(private_updates),
649  	                   a->id, pcmk__s(a->set_id, "unspecified"));
650  	    }
651  	    if (cib_updates > 0) {
652  	        char *id = pcmk__str_copy(a->id);
653  	
654  	        // Commit transaction
655  	        a->update = the_cib->cmds->end_transaction(the_cib, true, cib_none);
656  	
657  	        pcmk__info("Sent CIB request %d with %d change%s for %s (set %s)",
658  	                   a->update, cib_updates, pcmk__plural_s(cib_updates),
659  	                   a->id, pcmk__s(a->set_id, "unspecified"));
660  	
661  	        if (the_cib->cmds->register_callback_full(the_cib, a->update,
662  	                                                  CIB_OP_TIMEOUT_S, FALSE, id,
663  	                                                  "attrd_cib_callback",
664  	                                                  attrd_cib_callback, free)) {
665  	            // Transmit alert of the attribute
666  	            send_alert_attributes_value(a, alert_attribute_value);
667  	        }
668  	    }
669  	
670  	done:
671  	    // Discard transaction (if any)
672  	    if (the_cib != NULL) {
673  	        the_cib->cmds->end_transaction(the_cib, false, cib_none);
674  	        the_cib->cmds->set_user(the_cib, NULL);
675  	    }
676  	
677  	    g_clear_pointer(&alert_attribute_value, g_hash_table_destroy);
678  	}
679  	
680  	/*!
681  	 * \internal
682  	 * \brief Write out attributes
683  	 *
684  	 * \param[in] options  Group of enum attrd_write_options
685  	 */
686  	void
687  	attrd_write_attributes(uint32_t options)
688  	{
689  	    GHashTableIter iter;
690  	    attribute_t *a = NULL;
691  	
692  	    pcmk__debug("Writing out %s attributes",
693  	                pcmk__is_set(options, attrd_write_all)? "all" : "changed");
694  	    g_hash_table_iter_init(&iter, attributes);
695  	    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & a)) {
696  	        if (!pcmk__is_set(options, attrd_write_all)
697  	            && pcmk__is_set(a->flags, attrd_attr_node_unknown)) {
698  	            // Try writing this attribute again, in case peer ID was learned
699  	            attrd_set_attr_flags(a, attrd_attr_changed);
700  	        } else if (pcmk__is_set(a->flags, attrd_attr_force_write)) {
701  	            /* If the force_write flag is set, write the attribute. */
702  	            attrd_set_attr_flags(a, attrd_attr_changed);
703  	        }
704  	
705  	        if (pcmk__is_set(options, attrd_write_all)
706  	            || pcmk__is_set(a->flags, attrd_attr_changed)) {
707  	
708  	            bool ignore_delay = pcmk__is_set(options, attrd_write_no_delay);
709  	
710  	            if (pcmk__is_set(a->flags, attrd_attr_force_write)) {
711  	                // Always ignore delay when forced write flag is set
712  	                ignore_delay = true;
713  	            }
714  	            write_attribute(a, ignore_delay);
715  	        } else {
716  	            pcmk__trace("Skipping unchanged attribute %s", a->id);
717  	        }
718  	    }
719  	}
720  	
721  	void
722  	attrd_write_or_elect_attribute(attribute_t *a)
723  	{
724  	    if (attrd_election_won()) {
725  	        write_attribute(a, false);
726  	    } else {
727  	        attrd_start_election_if_needed();
728  	    }
729  	}
730