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 Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <glib.h>
13   	#include <stdbool.h>
14   	
15   	#include <crm/crm.h>
16   	#include <crm/common/xml.h>
17   	#include <crm/pengine/internal.h>
18   	
19   	#include "pe_status_private.h"
20   	
21   	gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data);
22   	
23   	/*!
24   	 * \internal
25   	 * \brief Check whether we can fence a particular node
26   	 *
27   	 * \param[in] scheduler  Scheduler data
28   	 * \param[in] node       Name of node to check
29   	 *
30   	 * \return true if node can be fenced, false otherwise
31   	 */
32   	bool
33   	pe_can_fence(const pcmk_scheduler_t *scheduler, const pcmk_node_t *node)
34   	{
35   	    if (pcmk__is_guest_or_bundle_node(node)) {
36   	        /* A guest or bundle node is fenced by stopping its launcher, which is
37   	         * possible if the launcher's host is either online or fenceable.
38   	         */
39   	        pcmk_resource_t *rsc = node->priv->remote->priv->launcher;
40   	
41   	        for (GList *n = rsc->priv->active_nodes; n != NULL; n = n->next) {
42   	            pcmk_node_t *launcher_node = n->data;
43   	
44   	            if (!launcher_node->details->online
45   	                && !pe_can_fence(scheduler, launcher_node)) {
46   	                return false;
47   	            }
48   	        }
49   	        return true;
50   	
51   	    } else if (!pcmk__is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
52   	        return false; /* Turned off */
53   	
54   	    } else if (!pcmk__is_set(scheduler->flags, pcmk__sched_have_fencing)) {
55   	        return false; /* No devices */
56   	
57   	    } else if (pcmk__is_set(scheduler->flags, pcmk__sched_quorate)) {
58   	        return true;
59   	
60   	    } else if (scheduler->no_quorum_policy == pcmk_no_quorum_ignore) {
61   	        return true;
62   	
63   	    } else if (node == NULL) {
64   	        return false;
65   	
66   	    } else if (node->details->online) {
67   	        /* Remote nodes are marked online when we assign their resource to a
68   	         * node, not when they are actually started (see remote_connection_assigned)
69   	         * so the above test by itself isn't good enough.
70   	         */
71   	        if (pcmk__is_pacemaker_remote_node(node)
72   	            && !pcmk__is_set(scheduler->flags,
73   	                             pcmk__sched_fence_remote_no_quorum)) {
74   	            /* If we're on a system without quorum, it's entirely possible that
75   	             * the remote resource was automatically moved to a node on the
76   	             * partition with quorum.  We can't tell that from this node - the
77   	             * best we can do is check if it's possible for the resource to run
78   	             * on another node in the partition with quorum.  If so, it has
79   	             * likely been moved and we shouldn't fence it.
80   	             *
81   	             * NOTE:  This condition appears to only come up in very limited
82   	             * circumstances.  It at least requires some very lengthy fencing
83   	             * timeouts set, some way for fencing to still take place (a second
84   	             * NIC is how I've reproduced it in testing, but fence_scsi or
85   	             * sbd could work too), and a resource that runs on the remote node.
86   	             */
87   	            pcmk_resource_t *rsc = node->priv->remote;
88   	            pcmk_node_t *n = NULL;
89   	            GHashTableIter iter;
90   	
91   	            g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
92   	            while (g_hash_table_iter_next(&iter, NULL, (void **) &n)) {
93   	                /* A node that's not online according to this non-quorum node
94   	                 * is a node that's in another partition.
95   	                 */
96   	                if (!n->details->online) {
97   	                    return false;
98   	                }
99   	            }
100  	        }
101  	
102  	        pcmk__notice("We can fence %s without quorum because they're in our "
103  	                     "membership", pcmk__node_name(node));
104  	        return true;
105  	    }
106  	
107  	    pcmk__trace("Cannot fence %s", pcmk__node_name(node));
108  	    return false;
109  	}
110  	
111  	/*!
112  	 * \internal
113  	 * \brief Copy a node object
114  	 *
115  	 * \param[in] this_node  Node object to copy
116  	 *
117  	 * \return Newly allocated shallow copy of this_node
118  	 * \note This function asserts on errors and is guaranteed to return non-NULL.
119  	 *       The caller is responsible for freeing the result using
120  	 *       pcmk__free_node_copy().
121  	 */
122  	pcmk_node_t *
123  	pe__copy_node(const pcmk_node_t *this_node)
124  	{
125  	    pcmk_node_t *new_node = NULL;
126  	
127  	    pcmk__assert(this_node != NULL);
128  	
129  	    new_node = pcmk__assert_alloc(1, sizeof(pcmk_node_t));
130  	    new_node->assign = pcmk__assert_alloc(1,
131  	                                          sizeof(struct pcmk__node_assignment));
132  	
133  	    new_node->assign->probe_mode = this_node->assign->probe_mode;
134  	    new_node->assign->score = this_node->assign->score;
135  	    new_node->assign->count = this_node->assign->count;
136  	    new_node->details = this_node->details;
137  	    new_node->priv = this_node->priv;
138  	
139  	    return new_node;
140  	}
141  	
142  	/*!
143  	 * \internal
144  	 * \brief Create a hash table of node copies from a list of nodes
145  	 *
146  	 * \param[in] list  Node list
147  	 *
148  	 * \return Hash table equivalent of node list
149  	 */
150  	GHashTable *
151  	pe__node_list2table(const GList *list)
152  	{
153  	    GHashTable *result = NULL;
154  	
155  	    result = pcmk__strkey_table(NULL, pcmk__free_node_copy);
156  	    for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) {
157  	        pcmk_node_t *new_node = NULL;
158  	
159  	        new_node = pe__copy_node((const pcmk_node_t *) gIter->data);
160  	        g_hash_table_insert(result, (gpointer) new_node->priv->id, new_node);
161  	    }
162  	    return result;
163  	}
164  	
165  	/*!
166  	 * \internal
167  	 * \brief Compare two nodes by name, with numeric portions sorted numerically
168  	 *
169  	 * Sort two node names case-insensitively like strcasecmp(), but with any
170  	 * numeric portions of the name sorted numerically. For example, "node10" will
171  	 * sort higher than "node9" but lower than "remotenode9".
172  	 *
173  	 * \param[in] a  First node to compare (can be \c NULL)
174  	 * \param[in] b  Second node to compare (can be \c NULL)
175  	 *
176  	 * \retval -1 \c a comes before \c b (or \c a is \c NULL and \c b is not)
177  	 * \retval  0 \c a and \c b are equal (or both are \c NULL)
178  	 * \retval  1 \c a comes after \c b (or \c b is \c NULL and \c a is not)
179  	 */
180  	gint
181  	pe__cmp_node_name(gconstpointer a, gconstpointer b)
182  	{
183  	    const pcmk_node_t *node1 = (const pcmk_node_t *) a;
184  	    const pcmk_node_t *node2 = (const pcmk_node_t *) b;
185  	
186  	    if ((node1 == NULL) && (node2 == NULL)) {
187  	        return 0;
188  	    }
189  	
190  	    if (node1 == NULL) {
191  	        return -1;
192  	    }
193  	
194  	    if (node2 == NULL) {
195  	        return 1;
196  	    }
197  	
198  	    return pcmk__numeric_strcasecmp(node1->priv->name, node2->priv->name);
199  	}
200  	
201  	/*!
202  	 * \internal
203  	 * \brief Output node weights to stdout
204  	 *
205  	 * \param[in]     rsc        Use allowed nodes for this resource
206  	 * \param[in]     comment    Text description to prefix lines with
207  	 * \param[in]     nodes      If rsc is not specified, use these nodes
208  	 * \param[in,out] scheduler  Scheduler data
209  	 */
210  	static void
211  	pe__output_node_weights(const pcmk_resource_t *rsc, const char *comment,
212  	                        GHashTable *nodes, pcmk_scheduler_t *scheduler)
213  	{
214  	    pcmk__output_t *out = scheduler->priv->out;
215  	
216  	    // Sort the nodes so the output is consistent for regression tests
217  	    GList *list = g_list_sort(g_hash_table_get_values(nodes),
218  	                              pe__cmp_node_name);
219  	
220  	    for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) {
221  	        const pcmk_node_t *node = (const pcmk_node_t *) gIter->data;
222  	
223  	        out->message(out, "node-weight", rsc, comment, node->priv->name,
224  	                     pcmk_readable_score(node->assign->score));
225  	    }
226  	    g_list_free(list);
227  	}
228  	
229  	/*!
230  	 * \internal
231  	 * \brief Log node weights at trace level
232  	 *
233  	 * \param[in] file      Caller's filename
234  	 * \param[in] function  Caller's function name
235  	 * \param[in] line      Caller's line number
236  	 * \param[in] rsc       If not NULL, include this resource's ID in logs
237  	 * \param[in] comment   Text description to prefix lines with
238  	 * \param[in] nodes     Nodes whose scores should be logged
239  	 */
240  	static void
241  	pe__log_node_weights(const char *file, const char *function, int line,
242  	                     const pcmk_resource_t *rsc, const char *comment,
243  	                     GHashTable *nodes)
244  	{
245  	    GHashTableIter iter;
246  	    pcmk_node_t *node = NULL;
247  	
248  	    // Don't waste time if we're not tracing at this point
249  	    pcmk__if_tracing({}, return);
250  	
251  	    g_hash_table_iter_init(&iter, nodes);
252  	    while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
253  	        if (rsc) {
254  	            qb_log_from_external_source(function, file,
255  	                                        "%s: %s allocation score on %s: %s",
256  	                                        LOG_TRACE, line, 0,
257  	                                        comment, rsc->id,
258  	                                        pcmk__node_name(node),
259  	                                        pcmk_readable_score(node->assign->score));
260  	        } else {
261  	            qb_log_from_external_source(function, file, "%s: %s = %s",
262  	                                        LOG_TRACE, line, 0,
263  	                                        comment, pcmk__node_name(node),
264  	                                        pcmk_readable_score(node->assign->score));
265  	        }
266  	    }
267  	}
268  	
269  	/*!
270  	 * \internal
271  	 * \brief Log or output node weights
272  	 *
273  	 * \param[in]     file       Caller's filename
274  	 * \param[in]     function   Caller's function name
275  	 * \param[in]     line       Caller's line number
276  	 * \param[in]     to_log     Log if true, otherwise output
277  	 * \param[in]     rsc        If not NULL, use this resource's ID in logs,
278  	 *                           and show scores recursively for any children
279  	 * \param[in]     comment    Text description to prefix lines with
280  	 * \param[in]     nodes      Nodes whose scores should be shown
281  	 * \param[in,out] scheduler  Scheduler data
282  	 */
283  	void
284  	pe__show_node_scores_as(const char *file, const char *function, int line,
285  	                        bool to_log, const pcmk_resource_t *rsc,
286  	                        const char *comment, GHashTable *nodes,
287  	                        pcmk_scheduler_t *scheduler)
288  	{
289  	    if ((rsc != NULL) && pcmk__is_set(rsc->flags, pcmk__rsc_removed)) {
290  	        // Don't show allocation scores for removed resources
291  	        return;
292  	    }
293  	    if (nodes == NULL) {
294  	        // Nothing to show
295  	        return;
296  	    }
297  	
298  	    if (to_log) {
299  	        pe__log_node_weights(file, function, line, rsc, comment, nodes);
300  	    } else {
301  	        pe__output_node_weights(rsc, comment, nodes, scheduler);
302  	    }
303  	
304  	    if (rsc == NULL) {
305  	        return;
306  	    }
307  	
308  	    // If this resource has children, repeat recursively for each
309  	    for (GList *gIter = rsc->priv->children;
310  	         gIter != NULL; gIter = gIter->next) {
311  	
312  	        pcmk_resource_t *child = (pcmk_resource_t *) gIter->data;
313  	
314  	        pe__show_node_scores_as(file, function, line, to_log, child, comment,
315  	                                child->priv->allowed_nodes, scheduler);
316  	    }
317  	}
318  	
319  	/*!
320  	 * \internal
321  	 * \brief Compare two resources by priority
322  	 *
323  	 * \param[in] a  First resource to compare (can be \c NULL)
324  	 * \param[in] b  Second resource to compare (can be \c NULL)
325  	 *
326  	 * \retval -1 a's priority > b's priority (or \c b is \c NULL and \c a is not)
327  	 * \retval  0 a's priority == b's priority (or both \c a and \c b are \c NULL)
328  	 * \retval  1 a's priority < b's priority (or \c a is \c NULL and \c b is not)
329  	 */
330  	gint
331  	pe__cmp_rsc_priority(gconstpointer a, gconstpointer b)
332  	{
333  	    const pcmk_resource_t *resource1 = (const pcmk_resource_t *)a;
334  	    const pcmk_resource_t *resource2 = (const pcmk_resource_t *)b;
335  	
336  	    if (a == NULL && b == NULL) {
337  	        return 0;
338  	    }
339  	    if (a == NULL) {
340  	        return 1;
341  	    }
342  	    if (b == NULL) {
343  	        return -1;
344  	    }
345  	
346  	    if (resource1->priv->priority > resource2->priv->priority) {
347  	        return -1;
348  	    }
349  	
350  	    if (resource1->priv->priority < resource2->priv->priority) {
351  	        return 1;
352  	    }
353  	
354  	    return 0;
355  	}
356  	
357  	static void
358  	resource_node_score(pcmk_resource_t *rsc, const pcmk_node_t *node, int score,
359  	                    const char *tag)
360  	{
361  	    pcmk_node_t *match = NULL;
362  	
363  	    if ((pcmk__is_set(rsc->flags, pcmk__rsc_exclusive_probes)
364  	         || (node->assign->probe_mode == pcmk__probe_never))
365  	        && pcmk__str_eq(tag, "symmetric_default", pcmk__str_casei)) {
366  	        /* This string comparision may be fragile, but exclusive resources and
367  	         * exclusive nodes should not have the symmetric_default constraint
368  	         * applied to them.
369  	         */
370  	        return;
371  	
372  	    } else {
373  	        for (GList *gIter = rsc->priv->children;
374  	             gIter != NULL; gIter = gIter->next) {
375  	
376  	            pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
377  	
378  	            resource_node_score(child_rsc, node, score, tag);
379  	        }
380  	    }
381  	
382  	    match = g_hash_table_lookup(rsc->priv->allowed_nodes, node->priv->id);
383  	    if (match == NULL) {
384  	        match = pe__copy_node(node);
385  	        g_hash_table_insert(rsc->priv->allowed_nodes,
386  	                            (gpointer) match->priv->id, match);
387  	    }
388  	    match->assign->score = pcmk__add_scores(match->assign->score, score);
389  	    pcmk__rsc_trace(rsc,
390  	                    "Enabling %s preference (%s) for %s on %s (now %s)",
391  	                    tag, pcmk_readable_score(score), rsc->id,
392  	                    pcmk__node_name(node),
393  	                    pcmk_readable_score(match->assign->score));
394  	}
395  	
396  	void
397  	resource_location(pcmk_resource_t *rsc, const pcmk_node_t *node, int score,
398  	                  const char *tag, pcmk_scheduler_t *scheduler)
399  	{
400  	    if (node != NULL) {
401  	        resource_node_score(rsc, node, score, tag);
402  	
403  	    } else if (scheduler != NULL) {
404  	        GList *gIter = scheduler->nodes;
405  	
406  	        for (; gIter != NULL; gIter = gIter->next) {
407  	            pcmk_node_t *node_iter = (pcmk_node_t *) gIter->data;
408  	
409  	            resource_node_score(rsc, node_iter, score, tag);
410  	        }
411  	
412  	    } else {
413  	        GHashTableIter iter;
414  	        pcmk_node_t *node_iter = NULL;
415  	
416  	        g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
417  	        while (g_hash_table_iter_next(&iter, NULL, (void **)&node_iter)) {
418  	            resource_node_score(rsc, node_iter, score, tag);
419  	        }
420  	    }
421  	
422  	    if ((node == NULL) && (score == -PCMK_SCORE_INFINITY)
423  	        && (rsc->priv->assigned_node != NULL)) {
424  	
425  	        // @TODO Should this be more like pcmk__unassign_resource()?
426  	        pcmk__info("Unassigning %s from %s", rsc->id,
427  	                   pcmk__node_name(rsc->priv->assigned_node));
428  	        g_clear_pointer(&rsc->priv->assigned_node, pcmk__free_node_copy);
429  	    }
430  	}
431  	
432  	gboolean
433  	get_target_role(const pcmk_resource_t *rsc, enum rsc_role_e *role)
434  	{
435  	    enum rsc_role_e local_role = pcmk_role_unknown;
436  	    const char *value = g_hash_table_lookup(rsc->priv->meta,
437  	                                            PCMK_META_TARGET_ROLE);
438  	
439  	    CRM_CHECK(role != NULL, return FALSE);
440  	
441  	    if (pcmk__str_eq(value, PCMK_ROLE_STARTED,
442  	                     pcmk__str_null_matches|pcmk__str_casei)) {
443  	        return FALSE;
444  	    }
445  	    if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
446  	        // @COMPAT Deprecated since 2.1.8
447  	        pcmk__config_warn("Support for setting " PCMK_META_TARGET_ROLE
448  	                          " to the explicit value '" PCMK_VALUE_DEFAULT
449  	                          "' is deprecated and will be removed in a "
450  	                          "future release (just leave it unset)");
451  	        return FALSE;
452  	    }
453  	
454  	    local_role = pcmk_parse_role(value);
455  	    if (local_role == pcmk_role_unknown) {
456  	        pcmk__config_err("Ignoring '" PCMK_META_TARGET_ROLE "' for %s "
457  	                         "because '%s' is not valid", rsc->id, value);
458  	        return FALSE;
459  	
460  	    } else if (local_role > pcmk_role_started) {
461  	        if (pcmk__is_set(pe__const_top_resource(rsc, false)->flags,
462  	                         pcmk__rsc_promotable)) {
463  	            if (local_role > pcmk_role_unpromoted) {
464  	                /* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */
465  	                return FALSE;
466  	            }
467  	
468  	        } else {
469  	            pcmk__config_err("Ignoring '" PCMK_META_TARGET_ROLE "' for %s "
470  	                             "because '%s' only makes sense for promotable "
471  	                             "clones", rsc->id, value);
472  	            return FALSE;
473  	        }
474  	    }
475  	
476  	    *role = local_role;
477  	    return TRUE;
478  	}
479  	
480  	gboolean
481  	order_actions(pcmk_action_t *first, pcmk_action_t *then, uint32_t flags)
482  	{
483  	    GList *gIter = NULL;
484  	    pcmk__related_action_t *wrapper = NULL;
485  	    GList *list = NULL;
486  	
487  	    if (flags == pcmk__ar_none) {
488  	        return FALSE;
489  	    }
490  	
491  	    if ((first == NULL) || (then == NULL)) {
492  	        return FALSE;
493  	    }
494  	
495  	    pcmk__trace("Creating action wrappers for ordering: %s then %s",
496  	                first->uuid, then->uuid);
497  	
498  	    /* Ensure we never create a dependency on ourselves... it's happened */
499  	    pcmk__assert(first != then);
500  	
501  	    /* Filter dups, otherwise update_action_states() has too much work to do */
502  	    gIter = first->actions_after;
503  	    for (; gIter != NULL; gIter = gIter->next) {
504  	        pcmk__related_action_t *after = gIter->data;
505  	
506  	        if ((after->action == then)
507  	            && pcmk__any_flags_set(after->flags, flags)) {
508  	            return FALSE;
509  	        }
510  	    }
511  	
512  	    wrapper = pcmk__assert_alloc(1, sizeof(pcmk__related_action_t));
513  	    wrapper->action = then;
514  	    wrapper->flags = flags;
515  	    list = first->actions_after;
516  	    list = g_list_prepend(list, wrapper);
517  	    first->actions_after = list;
518  	
519  	    wrapper = pcmk__assert_alloc(1, sizeof(pcmk__related_action_t));
520  	    wrapper->action = first;
521  	    wrapper->flags = flags;
522  	    list = then->actions_before;
523  	    list = g_list_prepend(list, wrapper);
524  	    then->actions_before = list;
525  	    return TRUE;
526  	}
527  	
528  	void
529  	destroy_ticket(gpointer data)
530  	{
531  	    pcmk__ticket_t *ticket = data;
532  	
CID (unavailable; MK=be5e767e02575e76f83dce9fad4d2e10) (#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".
533  	    g_clear_pointer(&ticket->state, g_hash_table_destroy);
534  	    free(ticket->id);
535  	    free(ticket);
536  	}
537  	
538  	pcmk__ticket_t *
539  	ticket_new(const char *ticket_id, pcmk_scheduler_t *scheduler)
540  	{
541  	    pcmk__ticket_t *ticket = NULL;
542  	
543  	    if (pcmk__str_empty(ticket_id)) {
544  	        return NULL;
545  	    }
546  	
547  	    if (scheduler->priv->ticket_constraints == NULL) {
548  	        scheduler->priv->ticket_constraints =
549  	            pcmk__strkey_table(free, destroy_ticket);
550  	    }
551  	
552  	    ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints,
553  	                                 ticket_id);
554  	    if (ticket == NULL) {
555  	
556  	        ticket = calloc(1, sizeof(pcmk__ticket_t));
557  	        if (ticket == NULL) {
558  	            pcmk__sched_err(scheduler, "Cannot allocate ticket '%s'",
559  	                            ticket_id);
560  	            return NULL;
561  	        }
562  	
563  	        pcmk__trace("Creating ticket entry for %s", ticket_id);
564  	
565  	        ticket->id = strdup(ticket_id);
566  	        ticket->last_granted = -1;
567  	        ticket->state = pcmk__strkey_table(free, free);
568  	
569  	        g_hash_table_insert(scheduler->priv->ticket_constraints,
570  	                            pcmk__str_copy(ticket->id), ticket);
571  	    }
572  	
573  	    return ticket;
574  	}
575  	
576  	const char *
577  	rsc_printable_id(const pcmk_resource_t *rsc)
578  	{
579  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_unique)) {
580  	        return rsc->id;
581  	    }
582  	    return pcmk__xe_id(rsc->priv->xml);
583  	}
584  	
585  	void
586  	pe__clear_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags)
587  	{
588  	    pcmk__clear_rsc_flags(rsc, flags);
589  	
590  	    for (GList *gIter = rsc->priv->children;
591  	         gIter != NULL; gIter = gIter->next) {
592  	
593  	        pe__clear_resource_flags_recursive((pcmk_resource_t *) gIter->data,
594  	                                           flags);
595  	    }
596  	}
597  	
598  	void
599  	pe__clear_resource_flags_on_all(pcmk_scheduler_t *scheduler, uint64_t flag)
600  	{
601  	    for (GList *lpc = scheduler->priv->resources;
602  	         lpc != NULL; lpc = lpc->next) {
603  	
604  	        pcmk_resource_t *r = (pcmk_resource_t *) lpc->data;
605  	
606  	        pe__clear_resource_flags_recursive(r, flag);
607  	    }
608  	}
609  	
610  	void
611  	pe__set_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags)
612  	{
613  	    pcmk__set_rsc_flags(rsc, flags);
614  	
615  	    for (GList *gIter = rsc->priv->children;
616  	         gIter != NULL; gIter = gIter->next) {
617  	
618  	        pe__set_resource_flags_recursive((pcmk_resource_t *) gIter->data,
619  	                                         flags);
620  	    }
621  	}
622  	
623  	void
624  	trigger_unfencing(pcmk_resource_t *rsc, pcmk_node_t *node, const char *reason,
625  	                  pcmk_action_t *dependency, pcmk_scheduler_t *scheduler)
626  	{
627  	    if (!pcmk__is_set(scheduler->flags, pcmk__sched_enable_unfencing)) {
628  	        /* No resources require it */
629  	        return;
630  	
631  	    } else if ((rsc != NULL)
632  	               && !pcmk__is_set(rsc->flags, pcmk__rsc_fence_device)) {
633  	        // Wasn't a fencing device
634  	        return;
635  	
636  	    } else if(node
637  	              && node->details->online
638  	              && node->details->unclean == FALSE
639  	              && node->details->shutdown == FALSE) {
640  	        pcmk_action_t *unfence = pe_fence_op(node, PCMK_ACTION_ON, FALSE,
641  	                                             reason, FALSE, scheduler);
642  	
643  	        if(dependency) {
644  	            order_actions(unfence, dependency, pcmk__ar_ordered);
645  	        }
646  	
647  	    } else if(rsc) {
648  	        GHashTableIter iter;
649  	
650  	        g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
651  	        while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
652  	            if(node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) {
653  	                trigger_unfencing(rsc, node, reason, dependency, scheduler);
654  	            }
655  	        }
656  	    }
657  	}
658  	
659  	/*!
660  	 * \internal
661  	 * \brief Check whether shutdown has been requested for a node
662  	 *
663  	 * \param[in] node  Node to check
664  	 *
665  	 * \return TRUE if node has shutdown attribute set and nonzero, FALSE otherwise
666  	 * \note This differs from simply using node->details->shutdown in that it can
667  	 *       be used before that has been determined (and in fact to determine it),
668  	 *       and it can also be used to distinguish requested shutdown from implicit
669  	 *       shutdown of remote nodes by virtue of their connection stopping.
670  	 */
671  	bool
672  	pe__shutdown_requested(const pcmk_node_t *node)
673  	{
674  	    const char *shutdown = pcmk__node_attr(node, PCMK__NODE_ATTR_SHUTDOWN, NULL,
675  	                                           pcmk__rsc_node_current);
676  	
677  	    return !pcmk__str_eq(shutdown, "0", pcmk__str_null_matches);
678  	}
679  	
680  	/*!
681  	 * \internal
682  	 * \brief Extract nvpair blocks contained by a CIB XML element into a hash table
683  	 *
684  	 * \param[in]     xml_obj       XML element containing blocks of nvpair elements
685  	 * \param[in]     set_name      If not NULL, only use blocks of this element
686  	 * \param[in]     rule_input    Values used to evaluate rule criteria
687  	 *                              (node_attrs member must be NULL if \p set_name
688  	 *                              is PCMK_XE_META_ATTRIBUTES)
689  	 * \param[out]    hash          Where to store extracted name/value pairs
690  	 * \param[in]     always_first  If not NULL, process block with this ID first
691  	 * \param[in,out] scheduler     Scheduler data containing \p xml_obj
692  	 */
693  	void
694  	pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name,
695  	                           const pcmk_rule_input_t *rule_input,
696  	                           GHashTable *hash, const char *always_first,
697  	                           pcmk_scheduler_t *scheduler)
698  	{
699  	    crm_time_t *next_change = NULL;
700  	
701  	    CRM_CHECK((set_name != NULL) && (rule_input != NULL) && (hash != NULL)
702  	              && (scheduler != NULL), return);
703  	
704  	    // Node attribute expressions are not allowed for meta-attributes
705  	    CRM_CHECK((rule_input->node_attrs == NULL)
706  	              || (strcmp(set_name, PCMK_XE_META_ATTRIBUTES) != 0), return);
707  	
708  	    if (xml_obj == NULL) {
709  	        return;
710  	    }
711  	
712  	    next_change = crm_time_new_undefined();
713  	    pcmk__unpack_nvpair_blocks(xml_obj, set_name, always_first, rule_input,
714  	                               hash, next_change, scheduler->input->doc);
715  	
716  	    if (crm_time_is_defined(next_change)) {
717  	        time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change);
718  	
719  	        pcmk__update_recheck_time(recheck, scheduler, "rule evaluation");
720  	    }
721  	    crm_time_free(next_change);
722  	}
723  	
724  	bool
725  	pe__resource_is_disabled(const pcmk_resource_t *rsc)
726  	{
727  	    const char *target_role = NULL;
728  	
729  	    CRM_CHECK(rsc != NULL, return false);
730  	    target_role = g_hash_table_lookup(rsc->priv->meta,
731  	                                      PCMK_META_TARGET_ROLE);
732  	    if (target_role) {
733  	        // If invalid, we've already logged an error when unpacking
734  	        enum rsc_role_e target_role_e = pcmk_parse_role(target_role);
735  	
736  	        if ((target_role_e == pcmk_role_stopped)
737  	            || ((target_role_e == pcmk_role_unpromoted)
738  	                && pcmk__is_set(pe__const_top_resource(rsc, false)->flags,
739  	                                pcmk__rsc_promotable))) {
740  	            return true;
741  	        }
742  	    }
743  	    return false;
744  	}
745  	
746  	/*!
747  	 * \internal
748  	 * \brief Check whether a resource is running only on given node
749  	 *
750  	 * \param[in] rsc   Resource to check
751  	 * \param[in] node  Node to check
752  	 *
753  	 * \return true if \p rsc is running only on \p node, otherwise false
754  	 */
755  	bool
756  	pe__rsc_running_on_only(const pcmk_resource_t *rsc, const pcmk_node_t *node)
757  	{
758  	    return (rsc != NULL) && pcmk__list_of_1(rsc->priv->active_nodes)
759  	           && pcmk__same_node((const pcmk_node_t *)
760  	                              rsc->priv->active_nodes->data, node);
761  	}
762  	
763  	bool
764  	pe__rsc_running_on_any(pcmk_resource_t *rsc, GList *node_list)
765  	{
766  	    if (rsc != NULL) {
767  	        for (GList *ele = rsc->priv->active_nodes; ele; ele = ele->next) {
768  	            pcmk_node_t *node = (pcmk_node_t *) ele->data;
769  	            if (pcmk__str_in_list(node->priv->name, node_list,
770  	                                  pcmk__str_star_matches|pcmk__str_casei)) {
771  	                return true;
772  	            }
773  	        }
774  	    }
775  	    return false;
776  	}
777  	
778  	bool
779  	pcmk__rsc_filtered_by_node(pcmk_resource_t *rsc, GList *only_node)
780  	{
781  	    return rsc->priv->fns->active(rsc, false)
782  	           && !pe__rsc_running_on_any(rsc, only_node);
783  	}
784  	
785  	GList *
786  	pe__filter_rsc_list(GList *rscs, GList *filter)
787  	{
788  	    GList *retval = NULL;
789  	
790  	    for (GList *gIter = rscs; gIter; gIter = gIter->next) {
791  	        pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
792  	
793  	        /* I think the second condition is safe here for all callers of this
794  	         * function.  If not, it needs to move into pe__node_text.
795  	         */
796  	        if (pcmk__str_in_list(rsc_printable_id(rsc), filter, pcmk__str_star_matches) ||
797  	            ((rsc->priv->parent != NULL)
798  	             && pcmk__str_in_list(rsc_printable_id(rsc->priv->parent),
799  	                                  filter, pcmk__str_star_matches))) {
800  	            retval = g_list_prepend(retval, rsc);
801  	        }
802  	    }
803  	
804  	    return retval;
805  	}
806  	
807  	GList *
808  	pe__build_node_name_list(pcmk_scheduler_t *scheduler, const char *s)
809  	{
810  	    GList *nodes = NULL;
811  	
812  	    if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) {
813  	        /* Nothing was given so return a list of all node names.  Or, '*' was
814  	         * given.  This would normally fall into the pe__unames_with_tag branch
815  	         * where it will return an empty list.  Catch it here instead.
816  	         */
817  	        nodes = g_list_prepend(nodes, strdup("*"));
818  	    } else {
819  	        pcmk_node_t *node = pcmk_find_node(scheduler, s);
820  	
821  	        if (node) {
822  	            /* The given string was a valid uname for a node.  Return a
823  	             * singleton list containing just that uname.
824  	             */
825  	            nodes = g_list_prepend(nodes, strdup(s));
826  	        } else {
827  	            /* The given string was not a valid uname.  It's either a tag or
828  	             * it's a typo or something.  In the first case, we'll return a
829  	             * list of all the unames of the nodes with the given tag.  In the
830  	             * second case, we'll return a NULL pointer and nothing will
831  	             * get displayed.
832  	             */
833  	            nodes = pe__unames_with_tag(scheduler, s);
834  	        }
835  	    }
836  	
837  	    return nodes;
838  	}
839  	
840  	GList *
841  	pe__build_rsc_list(pcmk_scheduler_t *scheduler, const char *s)
842  	{
843  	    GList *resources = NULL;
844  	
845  	    if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) {
846  	        resources = g_list_prepend(resources, strdup("*"));
847  	    } else {
848  	        const uint32_t flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
849  	        pcmk_resource_t *rsc =
850  	            pe_find_resource_with_flags(scheduler->priv->resources, s, flags);
851  	
852  	        if (rsc) {
853  	            /* A colon in the name we were given means we're being asked to filter
854  	             * on a specific instance of a cloned resource.  Put that exact string
855  	             * into the filter list.  Otherwise, use the printable ID of whatever
856  	             * resource was found that matches what was asked for.
857  	             */
858  	            if (strchr(s, ':') != NULL) {
859  	                resources = g_list_prepend(resources, strdup(rsc->id));
860  	            } else {
861  	                resources = g_list_prepend(resources, strdup(rsc_printable_id(rsc)));
862  	            }
863  	        } else {
864  	            /* The given string was not a valid resource name. It's a tag or a
865  	             * typo or something. See pe__build_node_name_list() for more
866  	             * detail.
867  	             */
868  	            resources = pe__rscs_with_tag(scheduler, s);
869  	        }
870  	    }
871  	
872  	    return resources;
873  	}
874  	
875  	xmlNode *
876  	pe__failed_probe_for_rsc(const pcmk_resource_t *rsc, const char *name)
877  	{
878  	    const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
879  	    const char *rsc_id = rsc->id;
880  	    const pcmk_scheduler_t *scheduler = rsc->priv->scheduler;
881  	
882  	    if (pcmk__is_clone(parent)) {
883  	        rsc_id = pe__clone_child_id(parent);
884  	    }
885  	
886  	    for (xmlNode *xml_op = pcmk__xe_first_child(scheduler->priv->failed,
887  	                                                NULL, NULL, NULL);
888  	         xml_op != NULL; xml_op = pcmk__xe_next(xml_op, NULL)) {
889  	
890  	        const char *value = NULL;
891  	        char *op_id = NULL;
892  	
893  	        /* This resource operation is not a failed probe. */
894  	        if (!pcmk_xe_mask_probe_failure(xml_op)) {
895  	            continue;
896  	        }
897  	
898  	        /* This resource operation was not run on the given node.  Note that if name is
899  	         * NULL, this will always succeed.
900  	         */
901  	        value = pcmk__xe_get(xml_op, PCMK__META_ON_NODE);
902  	        if (value == NULL || !pcmk__str_eq(value, name, pcmk__str_casei|pcmk__str_null_matches)) {
903  	            continue;
904  	        }
905  	
906  	        if (!parse_op_key(pcmk__xe_history_key(xml_op), &op_id, NULL, NULL)) {
907  	            continue; // This history entry is missing an operation key
908  	        }
909  	
910  	        /* This resource operation's ID does not match the rsc_id we are looking for. */
911  	        if (!pcmk__str_eq(op_id, rsc_id, pcmk__str_none)) {
912  	            free(op_id);
913  	            continue;
914  	        }
915  	
916  	        free(op_id);
917  	        return xml_op;
918  	    }
919  	
920  	    return NULL;
921  	}
922