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