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 <stdbool.h>                        // bool, true, false
13   	#include <stdint.h>
14   	
15   	#include <crm/pengine/status.h>
16   	#include <crm/pengine/internal.h>
17   	#include <pe_status_private.h>
18   	#include <crm/common/xml.h>
19   	#include <crm/common/output.h>
20   	
21   	typedef struct {
22   	    int clone_max;
23   	    int clone_node_max;
24   	
25   	    int promoted_max;
26   	    int promoted_node_max;
27   	
28   	    int total_clones;
29   	
30   	    uint32_t flags; // Group of enum pcmk__clone_flags
31   	
32   	    notify_data_t *stop_notify;
33   	    notify_data_t *start_notify;
34   	    notify_data_t *demote_notify;
35   	    notify_data_t *promote_notify;
36   	
37   	    xmlNode *xml_obj_child;
38   	} clone_variant_data_t;
39   	
40   	#define get_clone_variant_data(data, rsc) do {  \
41   	        pcmk__assert(pcmk__is_clone(rsc));      \
42   	        data = rsc->priv->variant_opaque;       \
43   	    } while (0)
44   	
45   	/*!
46   	 * \internal
47   	 * \brief Return the maximum number of clone instances allowed to be run
48   	 *
49   	 * \param[in] clone  Clone or clone instance to check
50   	 *
51   	 * \return Maximum instances for \p clone
52   	 */
53   	int
54   	pe__clone_max(const pcmk_resource_t *clone)
55   	{
56   	    const clone_variant_data_t *clone_data = NULL;
57   	
58   	    get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
59   	    return clone_data->clone_max;
60   	}
61   	
62   	/*!
63   	 * \internal
64   	 * \brief Return the maximum number of clone instances allowed per node
65   	 *
66   	 * \param[in] clone  Promotable clone or clone instance to check
67   	 *
68   	 * \return Maximum allowed instances per node for \p clone
69   	 */
70   	int
71   	pe__clone_node_max(const pcmk_resource_t *clone)
72   	{
73   	    const clone_variant_data_t *clone_data = NULL;
74   	
75   	    get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
76   	    return clone_data->clone_node_max;
77   	}
78   	
79   	/*!
80   	 * \internal
81   	 * \brief Return the maximum number of clone instances allowed to be promoted
82   	 *
83   	 * \param[in] clone  Promotable clone or clone instance to check
84   	 *
85   	 * \return Maximum promoted instances for \p clone
86   	 */
87   	int
88   	pe__clone_promoted_max(const pcmk_resource_t *clone)
89   	{
90   	    clone_variant_data_t *clone_data = NULL;
91   	
92   	    get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
93   	    return clone_data->promoted_max;
94   	}
95   	
96   	/*!
97   	 * \internal
98   	 * \brief Return the maximum number of clone instances allowed to be promoted
99   	 *
100  	 * \param[in] clone  Promotable clone or clone instance to check
101  	 *
102  	 * \return Maximum promoted instances for \p clone
103  	 */
104  	int
105  	pe__clone_promoted_node_max(const pcmk_resource_t *clone)
106  	{
107  	    clone_variant_data_t *clone_data = NULL;
108  	
109  	    get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
110  	    return clone_data->promoted_node_max;
111  	}
112  	
113  	static GList *
114  	sorted_hash_table_values(GHashTable *table)
115  	{
116  	    GList *retval = NULL;
117  	    GHashTableIter iter;
118  	    gpointer key, value;
119  	
120  	    g_hash_table_iter_init(&iter, table);
121  	    while (g_hash_table_iter_next(&iter, &key, &value)) {
122  	        if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
123  	            retval = g_list_prepend(retval, (char *) value);
124  	        }
125  	    }
126  	
127  	    retval = g_list_sort(retval, (GCompareFunc) strcmp);
128  	    return retval;
129  	}
130  	
131  	static GList *
132  	nodes_with_status(GHashTable *table, const char *status)
133  	{
134  	    GList *retval = NULL;
135  	    GHashTableIter iter;
136  	    gpointer key, value;
137  	
138  	    g_hash_table_iter_init(&iter, table);
139  	    while (g_hash_table_iter_next(&iter, &key, &value)) {
140  	        if (!strcmp((char *) value, status)) {
141  	            retval = g_list_prepend(retval, key);
142  	        }
143  	    }
144  	
145  	    retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
146  	    return retval;
147  	}
148  	
149  	static GString *
150  	node_list_to_str(const GList *list)
151  	{
152  	    GString *retval = NULL;
153  	
154  	    for (const GList *iter = list; iter != NULL; iter = iter->next) {
155  	        pcmk__add_word(&retval, 1024, (const char *) iter->data);
156  	    }
157  	
158  	    return retval;
159  	}
160  	
161  	static void
162  	clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
163  	             clone_variant_data_t *clone_data, const char *desc)
164  	{
165  	    GString *attrs = NULL;
166  	
167  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
168  	        pcmk__add_separated_word(&attrs, 64, "promotable", ", ");
169  	    }
170  	
171  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_unique)) {
172  	        pcmk__add_separated_word(&attrs, 64, "unique", ", ");
173  	    }
174  	
175  	    if (pe__resource_is_disabled(rsc)) {
176  	        pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
177  	    }
178  	
179  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_maintenance)) {
180  	        pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
181  	
182  	    } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_managed)) {
183  	        pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
184  	    }
185  	
186  	    if (attrs != NULL) {
187  	        PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s",
188  	                                 rsc->id,
189  	                                 pcmk__xe_id(clone_data->xml_obj_child),
190  	                                 (const char *) attrs->str, desc ? " (" : "",
191  	                                 desc ? desc : "", desc ? ")" : "");
192  	        g_string_free(attrs, TRUE);
193  	    } else {
194  	        PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s",
195  	                                 rsc->id,
196  	                                 pcmk__xe_id(clone_data->xml_obj_child),
197  	                                 desc ? " (" : "", desc ? desc : "",
198  	                                 desc ? ")" : "");
199  	    }
200  	}
201  	
202  	void
203  	pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid,
204  	               pcmk_scheduler_t *scheduler)
205  	{
206  	    if (pcmk__is_clone(rsc)) {
207  	        clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
208  	
209  	        pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s "
210  	                          "because %s resources such as %s can be used only as "
211  	                          "anonymous clones", rsc->id, standard, rid);
212  	
213  	        clone_data->clone_node_max = 1;
214  	        clone_data->clone_max = QB_MIN(clone_data->clone_max,
215  	                                       g_list_length(scheduler->nodes));
216  	    }
217  	}
218  	
219  	pcmk_resource_t *
220  	pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
221  	{
222  	    bool removed = false;
223  	    char *inc_num = NULL;
224  	    char *inc_max = NULL;
225  	    pcmk_resource_t *child_rsc = NULL;
226  	    xmlNode *child_copy = NULL;
227  	    clone_variant_data_t *clone_data = NULL;
228  	
229  	    get_clone_variant_data(clone_data, rsc);
230  	
231  	    CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
232  	
233  	    if (clone_data->total_clones >= clone_data->clone_max) {
234  	        /* If we've already used all available instances, this instance is
235  	         * treated as removed
236  	         */
237  	        removed = true;
238  	    }
239  	
240  	    // Allocate instance numbers in numerical order (starting at 0)
241  	    inc_num = pcmk__itoa(clone_data->total_clones);
242  	    inc_max = pcmk__itoa(clone_data->clone_max);
243  	
244  	    // Set PCMK__META_CLONE in a copy, not the original element
245  	    child_copy = pcmk__xml_copy(NULL, clone_data->xml_obj_child);
246  	    pcmk__xe_set(child_copy, PCMK__META_CLONE, inc_num);
247  	
248  	    if (pe__unpack_resource(child_copy, &child_rsc, rsc,
249  	                            scheduler) != pcmk_rc_ok) {
250  	        goto bail;
251  	    }
252  	/*  child_rsc->globally_unique = rsc->globally_unique; */
253  	
254  	    pcmk__assert(child_rsc != NULL);
255  	    clone_data->total_clones += 1;
256  	    pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s",
257  	                    child_rsc->id);
258  	    rsc->priv->children = g_list_append(rsc->priv->children, child_rsc);
259  	    if (removed) {
260  	        pe__set_resource_flags_recursive(child_rsc, pcmk__rsc_removed);
261  	    }
262  	
263  	    pcmk__insert_meta(child_rsc->priv, PCMK_META_CLONE_MAX, inc_max);
264  	    pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
265  	
266  	  bail:
267  	    free(inc_num);
268  	    free(inc_max);
269  	    pcmk__xml_free(child_copy);
270  	
271  	    return child_rsc;
272  	}
273  	
274  	/*!
275  	 * \internal
276  	 * \brief Unpack a nonnegative integer value from a resource meta-attribute
277  	 *
278  	 * \param[in]  rsc              Resource with meta-attribute
279  	 * \param[in]  meta_name        Name of meta-attribute to unpack
280  	 * \param[in]  deprecated_name  If not NULL, try unpacking this
281  	 *                              if \p meta_name is unset
282  	 * \param[in]  default_value    Value to use if unset
283  	 *
284  	 * \return Integer parsed from resource's specified meta-attribute if a valid
285  	 *         nonnegative integer, \p default_value if unset, or 0 if invalid
286  	 */
287  	static int
288  	unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name,
289  	                const char *deprecated_name, int default_value)
290  	{
291  	    int integer = default_value;
292  	    const char *value = g_hash_table_lookup(rsc->priv->meta, meta_name);
293  	
294  	    if ((value == NULL) && (deprecated_name != NULL)) {
295  	        value = g_hash_table_lookup(rsc->priv->meta, deprecated_name);
296  	
297  	        if (value != NULL) {
298  	            if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_MAX_LEGACY,
299  	                             pcmk__str_none)) {
300  	                pcmk__warn_once(pcmk__wo_clone_master_max,
301  	                                "Support for the " PCMK__META_PROMOTED_MAX_LEGACY
302  	                                " meta-attribute (such as in %s) is deprecated "
303  	                                "and will be removed in a future release. Use the "
304  	                                PCMK_META_PROMOTED_MAX " meta-attribute instead.",
305  	                                rsc->id);
306  	            } else if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_NODE_MAX_LEGACY,
307  	                                    pcmk__str_none)) {
308  	                pcmk__warn_once(pcmk__wo_clone_master_node_max,
309  	                                "Support for the " PCMK__META_PROMOTED_NODE_MAX_LEGACY
310  	                                " meta-attribute (such as in %s) is deprecated "
311  	                                "and will be removed in a future release. Use the "
312  	                                PCMK_META_PROMOTED_NODE_MAX " meta-attribute instead.",
313  	                                rsc->id);
314  	            }
315  	        }
316  	    }
317  	    if (value != NULL) {
318  	        pcmk__scan_min_int(value, &integer, 0);
319  	    }
320  	    return integer;
321  	}
322  	
323  	bool
324  	clone_unpack(pcmk_resource_t *rsc)
325  	{
326  	    int lpc = 0;
327  	    int num_nodes = g_list_length(rsc->priv->scheduler->nodes);
328  	    xmlNode *a_child = NULL;
329  	    xmlNode *xml_obj = rsc->priv->xml;
330  	    clone_variant_data_t *clone_data = NULL;
331  	
332  	    pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
333  	
334  	    clone_data = pcmk__assert_alloc(1, sizeof(clone_variant_data_t));
335  	    rsc->priv->variant_opaque = clone_data;
336  	
337  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
338  	        // Use 1 as default but 0 for minimum and invalid
339  	        // @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0
340  	        clone_data->promoted_max =
341  	            unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX,
342  	                            PCMK__META_PROMOTED_MAX_LEGACY, 1);
343  	
344  	        // Use 1 as default but 0 for minimum and invalid
345  	        // @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0
346  	        clone_data->promoted_node_max =
347  	            unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX,
348  	                            PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1);
349  	    }
350  	
351  	    // Use 1 as default but 0 for minimum and invalid
352  	    clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX,
353  	                                                 NULL, 1);
354  	
355  	    /* Use number of nodes (but always at least 1, which is handy for crm_verify
356  	     * for a CIB without nodes) as default, but 0 for minimum and invalid
357  	     *
358  	     * @TODO Exclude bundle nodes when counting
359  	     */
360  	    clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL,
361  	                                            QB_MAX(1, num_nodes));
362  	
363  	    if (pcmk__is_true(g_hash_table_lookup(rsc->priv->meta,
364  	                                          PCMK_META_ORDERED))) {
365  	        clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
366  	                                               "Clone", rsc->id,
367  	                                               clone_data->flags,
368  	                                               pcmk__clone_ordered,
369  	                                               "pcmk__clone_ordered");
370  	    }
371  	
372  	    if (!pcmk__is_set(rsc->flags, pcmk__rsc_unique)
373  	        && (clone_data->clone_node_max > 1)) {
374  	
375  	        pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s "
376  	                         "because anonymous clones support only one instance "
377  	                         "per node", clone_data->clone_node_max, rsc->id);
378  	        clone_data->clone_node_max = 1;
379  	    }
380  	
381  	    pcmk__rsc_trace(rsc, "Options for %s", rsc->id);
382  	    pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
383  	    pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
384  	    pcmk__rsc_trace(rsc, "\tClone is unique: %s",
385  	                    pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
386  	    pcmk__rsc_trace(rsc, "\tClone is promotable: %s",
387  	                    pcmk__flag_text(rsc->flags, pcmk__rsc_promotable));
388  	
389  	    // Clones may contain a single group or primitive
390  	    for (a_child = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
391  	         a_child != NULL; a_child = pcmk__xe_next(a_child, NULL)) {
392  	
393  	        if (pcmk__str_any_of((const char *) a_child->name,
394  	                             PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) {
395  	            clone_data->xml_obj_child = a_child;
396  	            break;
397  	        }
398  	    }
399  	
400  	    if (clone_data->xml_obj_child == NULL) {
401  	        pcmk__config_err("%s has nothing to clone", rsc->id);
402  	        return FALSE;
403  	    }
404  	
405  	    /*
406  	     * Make clones ever so slightly sticky by default
407  	     *
408  	     * This helps ensure clone instances are not shuffled around the cluster
409  	     * for no benefit in situations when pre-allocation is not appropriate
410  	     */
411  	    if (g_hash_table_lookup(rsc->priv->meta,
412  	                            PCMK_META_RESOURCE_STICKINESS) == NULL) {
413  	        pcmk__insert_meta(rsc->priv, PCMK_META_RESOURCE_STICKINESS, "1");
414  	    }
415  	
416  	    /* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for
417  	     * children to inherit when being unpacked, as well as in resource agents'
418  	     * environment.
419  	     */
420  	    pcmk__insert_meta(rsc->priv, PCMK_META_GLOBALLY_UNIQUE,
421  	                      pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
422  	
423  	    if (clone_data->clone_max <= 0) {
424  	        /* Create one child instance so that unpack_find_resource() will hook up
425  	         * any removed instances up to the parent correctly.
426  	         */
427  	        if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
428  	            return FALSE;
429  	        }
430  	
431  	    } else {
432  	        // Create a child instance for each available instance number
433  	        for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
434  	            if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
435  	                return FALSE;
436  	            }
437  	        }
438  	    }
439  	
440  	    pcmk__rsc_trace(rsc, "Added %d children to resource %s...",
441  	                    clone_data->clone_max, rsc->id);
442  	    return TRUE;
443  	}
444  	
445  	bool
446  	clone_active(const pcmk_resource_t *rsc, bool all)
447  	{
448  	    for (GList *gIter = rsc->priv->children;
449  	         gIter != NULL; gIter = gIter->next) {
450  	
451  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
452  	        bool child_active = child_rsc->priv->fns->active(child_rsc, all);
453  	
454  	        if (all == FALSE && child_active) {
455  	            return TRUE;
456  	        } else if (all && child_active == FALSE) {
457  	            return FALSE;
458  	        }
459  	    }
460  	
461  	    if (all) {
462  	        return TRUE;
463  	    } else {
464  	        return FALSE;
465  	    }
466  	}
467  	
468  	static const char *
469  	configured_role_str(pcmk_resource_t * rsc)
470  	{
471  	    const char *target_role = g_hash_table_lookup(rsc->priv->meta,
472  	                                                  PCMK_META_TARGET_ROLE);
473  	
474  	    if ((target_role == NULL) && (rsc->priv->children != NULL)) {
475  	        // Any instance will do
476  	        pcmk_resource_t *instance = rsc->priv->children->data;
477  	
478  	        target_role = g_hash_table_lookup(instance->priv->meta,
479  	                                          PCMK_META_TARGET_ROLE);
480  	    }
481  	    return target_role;
482  	}
483  	
484  	static enum rsc_role_e
485  	configured_role(pcmk_resource_t *rsc)
486  	{
487  	    enum rsc_role_e role = pcmk_role_unknown;
488  	    const char *target_role = configured_role_str(rsc);
489  	
490  	    if (target_role != NULL) {
491  	        role = pcmk_parse_role(target_role);
492  	        if (role == pcmk_role_unknown) {
493  	            pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
494  	                             " for resource %s", rsc->id);
495  	        }
496  	    }
497  	    return role;
498  	}
499  	
500  	bool
501  	is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any)
502  	{
503  	    bool all = !any;
504  	
505  	    if (pcmk__is_set(rsc->flags, flag)) {
506  	        if(any) {
507  	            return TRUE;
508  	        }
509  	    } else if(all) {
510  	        return FALSE;
511  	    }
512  	
513  	    for (GList *gIter = rsc->priv->children;
514  	         gIter != NULL; gIter = gIter->next) {
515  	
516  	        if(is_set_recursive(gIter->data, flag, any)) {
517  	            if(any) {
518  	                return TRUE;
519  	            }
520  	
521  	        } else if(all) {
522  	            return FALSE;
523  	        }
524  	    }
525  	
526  	    if(all) {
527  	        return TRUE;
528  	    }
529  	    return FALSE;
530  	}
531  	
532  	PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
533  	                  "GList *")
534  	int
535  	pe__clone_xml(pcmk__output_t *out, va_list args)
536  	{
537  	    uint32_t show_opts = va_arg(args, uint32_t);
538  	    pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
539  	    GList *only_node = va_arg(args, GList *);
540  	    GList *only_rsc = va_arg(args, GList *);
541  	
542  	    GList *all = NULL;
543  	    int rc = pcmk_rc_no_output;
544  	    gboolean printed_header = FALSE;
545  	    bool print_everything = true;
546  	
547  	    if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
548  	        return rc;
549  	    }
550  	
551  	    print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
552  	                                         pcmk__str_star_matches)
553  	                       || ((strchr(rsc->id, ':') != NULL)
554  	                           && pcmk__str_in_list(rsc->id, only_rsc,
555  	                                                pcmk__str_star_matches));
556  	
557  	    all = g_list_prepend(all, (gpointer) "*");
558  	
559  	    for (GList *gIter = rsc->priv->children;
560  	         gIter != NULL; gIter = gIter->next) {
561  	
562  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
563  	
564  	        if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
565  	            continue;
566  	        }
567  	
568  	        if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
569  	                                              print_everything)) {
570  	            continue;
571  	        }
572  	
573  	        if (!printed_header) {
574  	            const char *multi_state = pcmk__flag_text(rsc->flags,
575  	                                                      pcmk__rsc_promotable);
576  	            const char *unique = pcmk__flag_text(rsc->flags, pcmk__rsc_unique);
577  	            const char *maintenance = pcmk__flag_text(rsc->flags,
578  	                                                      pcmk__rsc_maintenance);
579  	            const char *managed = pcmk__flag_text(rsc->flags,
580  	                                                  pcmk__rsc_managed);
581  	            const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
582  	            const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed);
583  	            const char *ignored = pcmk__flag_text(rsc->flags,
584  	                                                  pcmk__rsc_ignore_failure);
585  	            const char *target_role = configured_role_str(rsc);
586  	            const char *desc = pe__resource_description(rsc, show_opts);
587  	
588  	            printed_header = TRUE;
589  	
590  	            rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE,
591  	                                          PCMK_XA_ID, rsc->id,
592  	                                          PCMK_XA_MULTI_STATE, multi_state,
593  	                                          PCMK_XA_UNIQUE, unique,
594  	                                          PCMK_XA_MAINTENANCE, maintenance,
595  	                                          PCMK_XA_MANAGED, managed,
596  	                                          PCMK_XA_DISABLED, disabled,
597  	                                          PCMK_XA_FAILED, failed,
598  	                                          PCMK_XA_FAILURE_IGNORED, ignored,
599  	                                          PCMK_XA_TARGET_ROLE, target_role,
600  	                                          PCMK_XA_DESCRIPTION, desc,
601  	                                          NULL);
602  	            pcmk__assert(rc == pcmk_rc_ok);
603  	        }
604  	
605  	        out->message(out, (const char *) child_rsc->priv->xml->name,
606  	                     show_opts, child_rsc, only_node, all);
607  	    }
608  	
609  	    if (printed_header) {
610  	        pcmk__output_xml_pop_parent(out);
611  	    }
612  	
613  	    g_list_free(all);
614  	    return rc;
615  	}
616  	
617  	PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
618  	                  "GList *")
619  	int
620  	pe__clone_default(pcmk__output_t *out, va_list args)
621  	{
622  	    uint32_t show_opts = va_arg(args, uint32_t);
623  	    pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
624  	    GList *only_node = va_arg(args, GList *);
625  	    GList *only_rsc = va_arg(args, GList *);
626  	
627  	    GHashTable *stopped = NULL;
628  	
629  	    GString *list_text = NULL;
630  	
631  	    GList *promoted_list = NULL;
632  	    GList *started_list = NULL;
633  	    GList *gIter = NULL;
634  	
635  	    const char *desc = NULL;
636  	
637  	    clone_variant_data_t *clone_data = NULL;
638  	    int active_instances = 0;
639  	    int rc = pcmk_rc_no_output;
640  	    gboolean print_everything = TRUE;
641  	
642  	    desc = pe__resource_description(rsc, show_opts);
643  	
(1) Event path: Condition "!pcmk__is_clone(rsc)", taking false branch.
644  	    get_clone_variant_data(clone_data, rsc);
645  	
(2) Event path: Condition "rsc->priv->fns->is_filtered(rsc, only_rsc, true /* 1 */)", taking false branch.
646  	    if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
647  	        return rc;
648  	    }
649  	
(3) Event path: Condition "pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)", taking true branch.
650  	    print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
651  	                                         pcmk__str_star_matches)
652  	                       || ((strchr(rsc->id, ':') != NULL)
653  	                           && pcmk__str_in_list(rsc->id, only_rsc,
654  	                                                pcmk__str_star_matches));
655  	
(4) Event path: Condition "gIter != NULL", taking true branch.
(9) Event path: Condition "gIter != NULL", taking true branch.
(19) Event path: Condition "gIter != NULL", taking true branch.
(36) Event path: Condition "gIter != NULL", taking false branch.
656  	    for (gIter = rsc->priv->children; gIter != NULL; gIter = gIter->next) {
657  	        gboolean print_full = FALSE;
658  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
659  	        bool partially_active = child_rsc->priv->fns->active(child_rsc, false);
660  	
(5) Event path: Condition "pcmk__rsc_filtered_by_node(child_rsc, only_node)", taking false branch.
(10) Event path: Condition "pcmk__rsc_filtered_by_node(child_rsc, only_node)", taking false branch.
(20) Event path: Condition "pcmk__rsc_filtered_by_node(child_rsc, only_node)", taking false branch.
661  	        if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
662  	            continue;
663  	        }
664  	
(6) Event path: Condition "print_everything", taking true branch.
(7) Event path: Condition "child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, print_everything)", taking true branch.
(11) Event path: Condition "print_everything", taking true branch.
(12) Event path: Condition "child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, print_everything)", taking false branch.
(21) Event path: Condition "print_everything", taking true branch.
(22) Event path: Condition "child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, print_everything)", taking false branch.
665  	        if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
666  	                                              print_everything)) {
(8) Event path: Continuing loop.
667  	            continue;
668  	        }
669  	
(13) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_clone_detail)", taking true branch.
(23) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_clone_detail)", taking false branch.
670  	        if (pcmk__is_set(show_opts, pcmk_show_clone_detail)) {
671  	            print_full = TRUE;
672  	        }
673  	
(14) Event path: Condition "pcmk__is_set(rsc->flags, pcmk__rsc_unique)", taking true branch.
(24) Event path: Condition "pcmk__is_set(rsc->flags, pcmk__rsc_unique)", taking false branch.
674  	        if (pcmk__is_set(rsc->flags, pcmk__rsc_unique)) {
675  	            // Print individual instance when unique, unless stopped and removed
(15) Event path: Condition "partially_active", taking true branch.
676  	            if (partially_active
677  	                || !pcmk__is_set(rsc->flags, pcmk__rsc_removed)) {
678  	                print_full = TRUE;
679  	            }
680  	
681  	        // Everything else in this block is for anonymous clones
682  	
(16) Event path: Falling through to end of if statement.
(25) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_pending)", taking true branch.
(26) Event path: Condition "child_rsc->priv->pending_action != NULL", taking true branch.
(27) Event path: Condition "strcmp(child_rsc->priv->pending_action, "probe") != 0", taking false branch.
683  	        } else if (pcmk__is_set(show_opts, pcmk_show_pending)
684  	                   && (child_rsc->priv->pending_action != NULL)
685  	                   && (strcmp(child_rsc->priv->pending_action,
686  	                              "probe") != 0)) {
687  	            // Print individual instance when non-probe action is pending
688  	            print_full = TRUE;
689  	
(28) Event path: Condition "partially_active == 0", taking true branch.
690  	        } else if (partially_active == FALSE) {
691  	            // List stopped instances when requested (except removed instances)
(29) Event path: Condition "!pcmk__is_set(child_rsc->flags, pcmk__rsc_removed)", taking true branch.
(30) Event path: Condition "!pcmk__is_set(show_opts, pcmk_show_clone_detail)", taking true branch.
(31) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_inactive_rscs)", taking true branch.
692  	            if (!pcmk__is_set(child_rsc->flags, pcmk__rsc_removed)
693  	                && !pcmk__is_set(show_opts, pcmk_show_clone_detail)
694  	                && pcmk__is_set(show_opts, pcmk_show_inactive_rscs)) {
(32) Event path: Condition "stopped == NULL", taking true branch.
695  	                if (stopped == NULL) {
696  	                    stopped = pcmk__strkey_table(free, free);
697  	                }
698  	                pcmk__insert_dup(stopped, child_rsc->id, "Stopped");
699  	            }
700  	
(33) Event path: Falling through to end of if statement.
701  	        } else if (is_set_recursive(child_rsc, pcmk__rsc_removed, TRUE)
702  	                   || !is_set_recursive(child_rsc, pcmk__rsc_managed, FALSE)
703  	                   || is_set_recursive(child_rsc, pcmk__rsc_failed, TRUE)) {
704  	
705  	            // Print individual instance when active removed/unmanaged/failed
706  	            print_full = TRUE;
707  	
708  	        } else if (child_rsc->priv->fns->active(child_rsc, true)) {
709  	            // Instance of fully active anonymous clone
710  	
711  	            pcmk_node_t *location = NULL;
712  	
713  	            location = child_rsc->priv->fns->location(child_rsc, NULL,
714  	                                                      pcmk__rsc_node_current);
715  	            if (location) {
716  	                // Instance is active on a single node
717  	
718  	                enum rsc_role_e a_role;
719  	
720  	                a_role = child_rsc->priv->fns->state(child_rsc, TRUE);
721  	
722  	                if (location->details->online == FALSE && location->details->unclean) {
723  	                    print_full = TRUE;
724  	
725  	                } else if (a_role > pcmk_role_unpromoted) {
726  	                    promoted_list = g_list_append(promoted_list, location);
727  	
728  	                } else {
729  	                    started_list = g_list_append(started_list, location);
730  	                }
731  	
732  	            } else {
733  	                /* uncolocated group - bleh */
734  	                print_full = TRUE;
735  	            }
736  	
737  	        } else {
738  	            // Instance of partially active anonymous clone
739  	            print_full = TRUE;
740  	        }
741  	
(17) Event path: Condition "print_full", taking true branch.
(34) Event path: Condition "print_full", taking false branch.
742  	        if (print_full) {
743  	            GList *all = NULL;
744  	
745  	            clone_header(out, &rc, rsc, clone_data, desc);
746  	
747  	            /* Print every resource that's a child of this clone. */
748  	            all = g_list_prepend(all, (gpointer) "*");
749  	            out->message(out, (const char *) child_rsc->priv->xml->name,
750  	                         show_opts, child_rsc, only_node, all);
751  	            g_list_free(all);
752  	        }
(18) Event path: Jumping back to the beginning of the loop.
(35) Event path: Jumping back to the beginning of the loop.
753  	    }
754  	
(37) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_clone_detail)", taking false branch.
755  	    if (pcmk__is_set(show_opts, pcmk_show_clone_detail)) {
756  	        PCMK__OUTPUT_LIST_FOOTER(out, rc);
757  	
758  	        g_list_free(promoted_list);
759  	        g_list_free(started_list);
760  	        return pcmk_rc_ok;
761  	    }
762  	
763  	    /* Promoted */
764  	    promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
(38) Event path: Condition "gIter", taking true branch.
(41) Event path: Condition "gIter", taking true branch.
(44) Event path: Condition "gIter", taking false branch.
765  	    for (gIter = promoted_list; gIter; gIter = gIter->next) {
766  	        pcmk_node_t *host = gIter->data;
767  	
(39) Event path: Condition "!pcmk__str_in_list(host->priv->name, only_node, 9U /* pcmk__str_star_matches | pcmk__str_casei */)", taking true branch.
(42) Event path: Condition "!pcmk__str_in_list(host->priv->name, only_node, 9U /* pcmk__str_star_matches | pcmk__str_casei */)", taking false branch.
768  	        if (!pcmk__str_in_list(host->priv->name, only_node,
769  	                               pcmk__str_star_matches|pcmk__str_casei)) {
(40) Event path: Continuing loop.
770  	            continue;
771  	        }
772  	
773  	        pcmk__add_word(&list_text, 1024, host->priv->name);
774  	        active_instances++;
(43) Event path: Jumping back to the beginning of the loop.
775  	    }
776  	    g_list_free(promoted_list);
777  	
(45) Event path: Condition "list_text != NULL", taking false branch.
778  	    if ((list_text != NULL) && (list_text->len > 0)) {
779  	        clone_header(out, &rc, rsc, clone_data, desc);
780  	
781  	        out->list_item(out, NULL, PCMK_ROLE_PROMOTED ": [ %s ]",
782  	                       (const char *) list_text->str);
783  	        g_string_truncate(list_text, 0);
784  	    }
785  	
786  	    /* Started/Unpromoted */
787  	    started_list = g_list_sort(started_list, pe__cmp_node_name);
(46) Event path: Condition "gIter", taking true branch.
(49) Event path: Condition "gIter", taking true branch.
(52) Event path: Condition "gIter", taking false branch.
788  	    for (gIter = started_list; gIter; gIter = gIter->next) {
789  	        pcmk_node_t *host = gIter->data;
790  	
(47) Event path: Condition "!pcmk__str_in_list(host->priv->name, only_node, 9U /* pcmk__str_star_matches | pcmk__str_casei */)", taking true branch.
(50) Event path: Condition "!pcmk__str_in_list(host->priv->name, only_node, 9U /* pcmk__str_star_matches | pcmk__str_casei */)", taking false branch.
791  	        if (!pcmk__str_in_list(host->priv->name, only_node,
792  	                               pcmk__str_star_matches|pcmk__str_casei)) {
(48) Event path: Continuing loop.
793  	            continue;
794  	        }
795  	
796  	        pcmk__add_word(&list_text, 1024, host->priv->name);
797  	        active_instances++;
(51) Event path: Jumping back to the beginning of the loop.
798  	    }
799  	    g_list_free(started_list);
800  	
(53) Event path: Condition "list_text != NULL", taking false branch.
801  	    if ((list_text != NULL) && (list_text->len > 0)) {
802  	        clone_header(out, &rc, rsc, clone_data, desc);
803  	
804  	        if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
805  	            enum rsc_role_e role = configured_role(rsc);
806  	
807  	            if (role == pcmk_role_unpromoted) {
808  	                out->list_item(out, NULL,
809  	                               PCMK_ROLE_UNPROMOTED
810  	                               " (" PCMK_META_TARGET_ROLE "): [ %s ]",
811  	                               (const char *) list_text->str);
812  	            } else {
813  	                out->list_item(out, NULL, PCMK_ROLE_UNPROMOTED ": [ %s ]",
814  	                               (const char *) list_text->str);
815  	            }
816  	
817  	        } else {
818  	            out->list_item(out, NULL, "Started: [ %s ]",
819  	                           (const char *) list_text->str);
820  	        }
821  	    }
822  	
(54) Event path: Condition "list_text != NULL", taking false branch.
823  	    if (list_text != NULL) {
824  	        g_string_free(list_text, TRUE);
825  	    }
826  	
(55) Event path: Condition "pcmk__is_set(show_opts, pcmk_show_inactive_rscs)", taking true branch.
827  	    if (pcmk__is_set(show_opts, pcmk_show_inactive_rscs)) {
(56) Event path: Condition "!pcmk__is_set(rsc->flags, pcmk__rsc_unique)", taking true branch.
(57) Event path: Condition "clone_data->clone_max > active_instances", taking true branch.
828  	        if (!pcmk__is_set(rsc->flags, pcmk__rsc_unique)
829  	            && (clone_data->clone_max > active_instances)) {
830  	
831  	            GList *nIter;
832  	            GList *list = g_hash_table_get_values(rsc->priv->allowed_nodes);
833  	
834  	            /* Custom stopped table for non-unique clones */
CID (unavailable; MK=343a98e554a45bb9ea744d4dd8966995) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(58) Event assign_union_field: The union field "in" of "_pp" is written.
(59) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
835  	            g_clear_pointer(&stopped, g_hash_table_destroy);
836  	
837  	            if (list == NULL) {
838  	                /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
839  	                 * calculated allowed nodes yet. If we've not probed for them
840  	                 * yet, the Stopped list will be empty.
841  	                 */
842  	                list = g_hash_table_get_values(rsc->priv->probed_nodes);
843  	            }
844  	
845  	            list = g_list_sort(list, pe__cmp_node_name);
846  	            for (nIter = list; nIter != NULL; nIter = nIter->next) {
847  	                pcmk_node_t *node = (pcmk_node_t *) nIter->data;
848  	
849  	                if ((pcmk__find_node_in_list(rsc->priv->active_nodes,
850  	                                             node->priv->name) == NULL)
851  	                    && pcmk__str_in_list(node->priv->name, only_node,
852  	                                         pcmk__str_star_matches|pcmk__str_casei)) {
853  	
854  	                    xmlNode *probe_op = NULL;
855  	                    const char *state = "Stopped";
856  	
857  	                    if (configured_role(rsc) == pcmk_role_stopped) {
858  	                        state = "Stopped (disabled)";
859  	                    }
860  	
861  	                    if (stopped == NULL) {
862  	                        stopped = pcmk__strkey_table(free, free);
863  	                    }
864  	
865  	                    probe_op = pe__failed_probe_for_rsc(rsc,
866  	                                                        node->priv->name);
867  	                    if (probe_op != NULL) {
868  	                        int rc = pcmk_rc_ok;
869  	                        char *key = pcmk__str_copy(node->priv->name);
870  	                        char *value = NULL;
871  	
872  	                        pcmk__scan_min_int(pcmk__xe_get(probe_op,
873  	                                                        PCMK__XA_RC_CODE),
874  	                                           &rc, 0);
875  	                        value = pcmk__assert_asprintf("Stopped (%s)",
876  	                                                      crm_exit_str(rc));
877  	                        g_hash_table_insert(stopped, key, value);
878  	
879  	                    } else {
880  	                        pcmk__insert_dup(stopped, node->priv->name, state);
881  	                    }
882  	                }
883  	            }
884  	            g_list_free(list);
885  	        }
886  	
887  	        if (stopped != NULL) {
888  	            GList *list = sorted_hash_table_values(stopped);
889  	
890  	            clone_header(out, &rc, rsc, clone_data, desc);
891  	
892  	            for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
893  	                const char *status = status_iter->data;
894  	                GList *nodes = nodes_with_status(stopped, status);
895  	                GString *nodes_str = node_list_to_str(nodes);
896  	
897  	                if (nodes_str != NULL) {
898  	                    if (nodes_str->len > 0) {
899  	                        out->list_item(out, NULL, "%s: [ %s ]", status,
900  	                                       (const char *) nodes_str->str);
901  	                    }
902  	                    g_string_free(nodes_str, TRUE);
903  	                }
904  	
905  	                g_list_free(nodes);
906  	            }
907  	
908  	            g_list_free(list);
909  	            g_hash_table_destroy(stopped);
910  	
911  	        /* If there are no instances of this clone (perhaps because there are no
912  	         * nodes configured), simply output the clone header by itself.  This can
913  	         * come up in PCS testing.
914  	         */
915  	        } else if (active_instances == 0) {
916  	            clone_header(out, &rc, rsc, clone_data, desc);
917  	            PCMK__OUTPUT_LIST_FOOTER(out, rc);
918  	            return rc;
919  	        }
920  	    }
921  	
922  	    PCMK__OUTPUT_LIST_FOOTER(out, rc);
923  	    return rc;
924  	}
925  	
926  	void
927  	clone_free(pcmk_resource_t * rsc)
928  	{
929  	    clone_variant_data_t *clone_data = NULL;
930  	
931  	    get_clone_variant_data(clone_data, rsc);
932  	
933  	    pcmk__rsc_trace(rsc, "Freeing clone %s, starting with child list", rsc->id);
934  	
935  	    g_list_free_full(rsc->priv->children, pcmk__free_resource);
936  	
937  	    if (clone_data) {
938  	        pcmk__assert((clone_data->demote_notify == NULL)
939  	                     && (clone_data->stop_notify == NULL)
940  	                     && (clone_data->start_notify == NULL)
941  	                     && (clone_data->promote_notify == NULL));
942  	    }
943  	
944  	    common_free(rsc);
945  	}
946  	
947  	enum rsc_role_e
948  	clone_resource_state(const pcmk_resource_t *rsc, bool current)
949  	{
950  	    enum rsc_role_e clone_role = pcmk_role_unknown;
951  	
952  	    for (GList *gIter = rsc->priv->children;
953  	         gIter != NULL; gIter = gIter->next) {
954  	
955  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
956  	        enum rsc_role_e a_role = child_rsc->priv->fns->state(child_rsc,
957  	                                                             current);
958  	
959  	        if (a_role > clone_role) {
960  	            clone_role = a_role;
961  	        }
962  	    }
963  	
964  	    pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role));
965  	    return clone_role;
966  	}
967  	
968  	/*!
969  	 * \internal
970  	 * \brief Check whether a clone has an instance for every node
971  	 *
972  	 * \param[in] rsc        Clone to check
973  	 * \param[in] scheduler  Scheduler data
974  	 */
975  	bool
976  	pe__is_universal_clone(const pcmk_resource_t *rsc,
977  	                       const pcmk_scheduler_t *scheduler)
978  	{
979  	    if (pcmk__is_clone(rsc)) {
980  	        clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
981  	
982  	        if (clone_data->clone_max == g_list_length(scheduler->nodes)) {
983  	            return TRUE;
984  	        }
985  	    }
986  	    return FALSE;
987  	}
988  	
989  	bool
990  	pe__clone_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
991  	                      bool check_parent)
992  	{
993  	    clone_variant_data_t *clone_data = NULL;
994  	
995  	    if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
996  	                          pcmk__str_star_matches)) {
997  	        return false;
998  	    }
999  	
1000 	    get_clone_variant_data(clone_data, rsc);
1001 	
1002 	    if (pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child), only_rsc,
1003 	                          pcmk__str_star_matches)) {
1004 	        return false;
1005 	    }
1006 	
1007 	    for (const GList *iter = rsc->priv->children; iter != NULL;
1008 	         iter = iter->next) {
1009 	
1010 	        const pcmk_resource_t *child_rsc = iter->data;
1011 	
1012 	        if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, false)) {
1013 	            return false;
1014 	        }
1015 	    }
1016 	
1017 	    return true;
1018 	}
1019 	
1020 	const char *
1021 	pe__clone_child_id(const pcmk_resource_t *rsc)
1022 	{
1023 	    clone_variant_data_t *clone_data = NULL;
1024 	    get_clone_variant_data(clone_data, rsc);
1025 	    return pcmk__xe_id(clone_data->xml_obj_child);
1026 	}
1027 	
1028 	/*!
1029 	 * \internal
1030 	 * \brief Check whether a clone is ordered
1031 	 *
1032 	 * \param[in] clone  Clone resource to check
1033 	 *
1034 	 * \return true if clone is ordered, otherwise false
1035 	 */
1036 	bool
1037 	pe__clone_is_ordered(const pcmk_resource_t *clone)
1038 	{
1039 	    clone_variant_data_t *clone_data = NULL;
1040 	
1041 	    get_clone_variant_data(clone_data, clone);
1042 	    return pcmk__is_set(clone_data->flags, pcmk__clone_ordered);
1043 	}
1044 	
1045 	/*!
1046 	 * \internal
1047 	 * \brief Set a clone flag
1048 	 *
1049 	 * \param[in,out] clone  Clone resource to set flag for
1050 	 * \param[in]     flag   Clone flag to set
1051 	 *
1052 	 * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
1053 	 *         already set or pcmk_rc_already if it was)
1054 	 */
1055 	int
1056 	pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag)
1057 	{
1058 	    clone_variant_data_t *clone_data = NULL;
1059 	
1060 	    get_clone_variant_data(clone_data, clone);
1061 	    if (pcmk__is_set(clone_data->flags, flag)) {
1062 	        return pcmk_rc_already;
1063 	    }
1064 	    clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
1065 	                                           "Clone", clone->id,
1066 	                                           clone_data->flags, flag, "flag");
1067 	    return pcmk_rc_ok;
1068 	}
1069 	
1070 	/*!
1071 	 * \internal
1072 	 * \brief Check whether a clone flag is set
1073 	 *
1074 	 * \param[in] group  Clone resource to check
1075 	 * \param[in] flags  Flag or flags to check
1076 	 *
1077 	 * \return \c true if all \p flags are set for \p clone, otherwise \c false
1078 	 */
1079 	bool
1080 	pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags)
1081 	{
1082 	    clone_variant_data_t *clone_data = NULL;
1083 	
1084 	    get_clone_variant_data(clone_data, clone);
1085 	    pcmk__assert(clone_data != NULL);
1086 	
1087 	    return pcmk__all_flags_set(clone_data->flags, flags);
1088 	}
1089 	
1090 	/*!
1091 	 * \internal
1092 	 * \brief Create pseudo-actions needed for promotable clones
1093 	 *
1094 	 * \param[in,out] clone          Promotable clone to create actions for
1095 	 * \param[in]     any_promoting  Whether any instances will be promoted
1096 	 * \param[in]     any_demoting   Whether any instance will be demoted
1097 	 */
1098 	void
1099 	pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting,
1100 	                                 bool any_demoting)
1101 	{
1102 	    pcmk_action_t *action = NULL;
1103 	    pcmk_action_t *action_complete = NULL;
1104 	    clone_variant_data_t *clone_data = NULL;
1105 	
1106 	    get_clone_variant_data(clone_data, clone);
1107 	
1108 	    // Create a "promote" action for the clone itself
1109 	    action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE,
1110 	                                       !any_promoting, true);
1111 	
1112 	    // Create a "promoted" action for when all promotions are done
1113 	    action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED,
1114 	                                                !any_promoting, true);
1115 	    action_complete->priority = PCMK_SCORE_INFINITY;
1116 	
1117 	    // Create notification pseudo-actions for promotion
1118 	    if (clone_data->promote_notify == NULL) {
1119 	        clone_data->promote_notify = pe__action_notif_pseudo_ops(clone,
1120 	                                                                 PCMK_ACTION_PROMOTE,
1121 	                                                                 action,
1122 	                                                                 action_complete);
1123 	    }
1124 	
1125 	    // Create a "demote" action for the clone itself
1126 	    action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE,
1127 	                                       !any_demoting, true);
1128 	
1129 	    // Create a "demoted" action for when all demotions are done
1130 	    action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED,
1131 	                                                !any_demoting, true);
1132 	    action_complete->priority = PCMK_SCORE_INFINITY;
1133 	
1134 	    // Create notification pseudo-actions for demotion
1135 	    if (clone_data->demote_notify == NULL) {
1136 	        clone_data->demote_notify = pe__action_notif_pseudo_ops(clone,
1137 	                                                                PCMK_ACTION_DEMOTE,
1138 	                                                                action,
1139 	                                                                action_complete);
1140 	
1141 	        if (clone_data->promote_notify != NULL) {
1142 	            order_actions(clone_data->stop_notify->post_done,
1143 	                          clone_data->promote_notify->pre, pcmk__ar_ordered);
1144 	            order_actions(clone_data->start_notify->post_done,
1145 	                          clone_data->promote_notify->pre, pcmk__ar_ordered);
1146 	            order_actions(clone_data->demote_notify->post_done,
1147 	                          clone_data->promote_notify->pre, pcmk__ar_ordered);
1148 	            order_actions(clone_data->demote_notify->post_done,
1149 	                          clone_data->start_notify->pre, pcmk__ar_ordered);
1150 	            order_actions(clone_data->demote_notify->post_done,
1151 	                          clone_data->stop_notify->pre, pcmk__ar_ordered);
1152 	        }
1153 	    }
1154 	}
1155 	
1156 	/*!
1157 	 * \internal
1158 	 * \brief Create all notification data and actions for a clone
1159 	 *
1160 	 * \param[in,out] clone  Clone to create notifications for
1161 	 */
1162 	void
1163 	pe__create_clone_notifications(pcmk_resource_t *clone)
1164 	{
1165 	    clone_variant_data_t *clone_data = NULL;
1166 	
1167 	    get_clone_variant_data(clone_data, clone);
1168 	
1169 	    pe__create_action_notifications(clone, clone_data->start_notify);
1170 	    pe__create_action_notifications(clone, clone_data->stop_notify);
1171 	    pe__create_action_notifications(clone, clone_data->promote_notify);
1172 	    pe__create_action_notifications(clone, clone_data->demote_notify);
1173 	}
1174 	
1175 	/*!
1176 	 * \internal
1177 	 * \brief Free all notification data for a clone
1178 	 *
1179 	 * \param[in,out] clone  Clone to free notification data for
1180 	 */
1181 	void
1182 	pe__free_clone_notification_data(pcmk_resource_t *clone)
1183 	{
1184 	    clone_variant_data_t *clone_data = NULL;
1185 	
1186 	    get_clone_variant_data(clone_data, clone);
1187 	
1188 	    g_clear_pointer(&clone_data->demote_notify,
1189 	                    pe__free_action_notification_data);
1190 	    g_clear_pointer(&clone_data->stop_notify,
1191 	                    pe__free_action_notification_data);
1192 	    g_clear_pointer(&clone_data->start_notify,
1193 	                    pe__free_action_notification_data);
1194 	    g_clear_pointer(&clone_data->promote_notify,
1195 	                    pe__free_action_notification_data);
1196 	}
1197 	
1198 	/*!
1199 	 * \internal
1200 	 * \brief Create pseudo-actions for clone start/stop notifications
1201 	 *
1202 	 * \param[in,out] clone    Clone to create pseudo-actions for
1203 	 * \param[in,out] start    Start action for \p clone
1204 	 * \param[in,out] stop     Stop action for \p clone
1205 	 * \param[in,out] started  Started action for \p clone
1206 	 * \param[in,out] stopped  Stopped action for \p clone
1207 	 */
1208 	void
1209 	pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
1210 	                                  pcmk_action_t *start, pcmk_action_t *started,
1211 	                                  pcmk_action_t *stop, pcmk_action_t *stopped)
1212 	{
1213 	    clone_variant_data_t *clone_data = NULL;
1214 	
1215 	    get_clone_variant_data(clone_data, clone);
1216 	
1217 	    if (clone_data->start_notify == NULL) {
1218 	        clone_data->start_notify = pe__action_notif_pseudo_ops(clone,
1219 	                                                               PCMK_ACTION_START,
1220 	                                                               start, started);
1221 	    }
1222 	
1223 	    if (clone_data->stop_notify == NULL) {
1224 	        clone_data->stop_notify = pe__action_notif_pseudo_ops(clone,
1225 	                                                              PCMK_ACTION_STOP,
1226 	                                                              stop, stopped);
1227 	        if ((clone_data->start_notify != NULL)
1228 	            && (clone_data->stop_notify != NULL)) {
1229 	            order_actions(clone_data->stop_notify->post_done,
1230 	                          clone_data->start_notify->pre, pcmk__ar_ordered);
1231 	        }
1232 	    }
1233 	}
1234 	
1235 	/*!
1236 	 * \internal
1237 	 * \brief Get maximum clone resource instances per node
1238 	 *
1239 	 * \param[in] rsc  Clone resource to check
1240 	 *
1241 	 * \return Maximum number of \p rsc instances that can be active on one node
1242 	 */
1243 	unsigned int
1244 	pe__clone_max_per_node(const pcmk_resource_t *rsc)
1245 	{
1246 	    const clone_variant_data_t *clone_data = NULL;
1247 	
1248 	    get_clone_variant_data(clone_data, rsc);
1249 	    return clone_data->clone_node_max;
1250 	}
1251