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 <crm/common/xml.h>
18   	#include <crm/common/output.h>
19   	#include <pe_status_private.h>
20   	
21   	typedef struct {
22   	    pcmk_resource_t *last_child;    // Last group member
23   	    uint32_t flags;                 // Group of enum pcmk__group_flags
24   	} group_variant_data_t;
25   	
26   	/*!
27   	 * \internal
28   	 * \brief Get a group's last member
29   	 *
30   	 * \param[in] group  Group resource to check
31   	 *
32   	 * \return Last member of \p group if any, otherwise NULL
33   	 */
34   	pcmk_resource_t *
35   	pe__last_group_member(const pcmk_resource_t *group)
36   	{
37   	    if (group != NULL) {
38   	        const group_variant_data_t *group_data = NULL;
39   	
40   	        CRM_CHECK(pcmk__is_group(group), return NULL);
41   	        group_data = group->priv->variant_opaque;
42   	        return group_data->last_child;
43   	    }
44   	    return NULL;
45   	}
46   	
47   	/*!
48   	 * \internal
49   	 * \brief Check whether a group flag is set
50   	 *
51   	 * \param[in] group  Group resource to check
52   	 * \param[in] flags  Flag or flags to check
53   	 *
54   	 * \return true if all \p flags are set for \p group, otherwise false
55   	 */
56   	bool
57   	pe__group_flag_is_set(const pcmk_resource_t *group, uint32_t flags)
58   	{
59   	    const group_variant_data_t *group_data = NULL;
60   	
61   	    CRM_CHECK(pcmk__is_group(group), return false);
62   	    group_data = group->priv->variant_opaque;
63   	    return pcmk__all_flags_set(group_data->flags, flags);
64   	}
65   	
66   	/*!
67   	 * \internal
68   	 * \brief Set a (deprecated) group flag
69   	 *
70   	 * \param[in,out] group   Group resource to check
71   	 * \param[in]     option  Name of boolean configuration option
72   	 * \param[in]     flag    Flag to set if \p option is true (which is default)
73   	 * \param[in]     wo_bit  "Warn once" flag to use for deprecation warning
74   	 */
75   	static void
76   	set_group_flag(pcmk_resource_t *group, const char *option, uint32_t flag,
77   	               uint32_t wo_bit)
78   	{
79   	    const char *value_s = g_hash_table_lookup(group->priv->meta, option);
80   	    bool value = false;
81   	
82   	    if ((value_s == NULL) || (pcmk__parse_bool(value_s, &value) != pcmk_rc_ok)
83   	        || value) {
84   	        // Set flag if value is unset, invalid, or true
85   	        group_variant_data_t *group_data = group->priv->variant_opaque;
86   	
87   	        group_data->flags |= flag;
88   	
89   	    } else {
90   	        pcmk__warn_once(wo_bit,
91   	                        "Support for the '%s' group meta-attribute is "
92   	                        "deprecated and will be removed in a future release "
93   	                        "(use a resource set instead)", option);
94   	    }
95   	}
96   	
97   	static int
98   	inactive_resources(pcmk_resource_t *rsc)
99   	{
100  	    int retval = 0;
101  	
102  	    for (GList *gIter = rsc->priv->children;
103  	         gIter != NULL; gIter = gIter->next) {
104  	
105  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
106  	
107  	        if (!child_rsc->priv->fns->active(child_rsc, true)) {
108  	            retval++;
109  	        }
110  	    }
111  	
112  	    return retval;
113  	}
114  	
115  	static void
116  	group_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
117  	             int n_inactive, bool show_inactive, const char *desc)
118  	{
119  	    GString *attrs = NULL;
120  	
121  	    if (n_inactive > 0 && !show_inactive) {
122  	        attrs = g_string_sized_new(64);
123  	        g_string_append_printf(attrs, "%d member%s inactive", n_inactive,
124  	                               pcmk__plural_s(n_inactive));
125  	    }
126  	
127  	    if (pe__resource_is_disabled(rsc)) {
128  	        pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
129  	    }
130  	
131  	    if (pcmk__is_set(rsc->flags, pcmk__rsc_maintenance)) {
132  	        pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
133  	
134  	    } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_managed)) {
135  	        pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
136  	    }
137  	
138  	    if (attrs != NULL) {
139  	        PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s (%s)%s%s%s",
140  	                                 rsc->id,
141  	                                 (const char *) attrs->str, desc ? " (" : "",
142  	                                 desc ? desc : "", desc ? ")" : "");
143  	        g_string_free(attrs, TRUE);
144  	    } else {
145  	        PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s%s%s%s",
146  	                                 rsc->id,
147  	                                 desc ? " (" : "", desc ? desc : "",
148  	                                 desc ? ")" : "");
149  	    }
150  	}
151  	
152  	static bool
153  	skip_child_rsc(pcmk_resource_t *rsc, pcmk_resource_t *child, bool parent_passes,
154  	               GList *only_rsc, uint32_t show_opts)
155  	{
156  	    const bool star_list = pcmk__list_of_1(only_rsc)
157  	                           && pcmk__str_eq("*", g_list_first(only_rsc)->data,
158  	                                           pcmk__str_none);
159  	    const bool child_filtered = child->priv->fns->is_filtered(child, only_rsc,
160  	                                                              false);
161  	    const bool child_active = child->priv->fns->active(child, false);
162  	    const bool show_inactive = pcmk__is_set(show_opts, pcmk_show_inactive_rscs);
163  	
164  	    /* If the resource is in only_rsc by name (so, ignoring "*") then allow
165  	     * it regardless of if it's active or not.
166  	     */
167  	    if (!star_list && !child_filtered) {
168  	        return false;
169  	
170  	    } else if (!child_filtered && (child_active || show_inactive)) {
171  	        return false;
172  	
173  	    } else if (parent_passes && (child_active || show_inactive)) {
174  	        return false;
175  	
176  	    }
177  	
178  	    return true;
179  	}
180  	
181  	bool
182  	group_unpack(pcmk_resource_t *rsc)
183  	{
184  	    xmlNode *xml_obj = rsc->priv->xml;
185  	    xmlNode *xml_native_rsc = NULL;
186  	    group_variant_data_t *group_data = NULL;
187  	    const char *clone_id = NULL;
188  	
(1) Event path: Switch case default.
(2) Event path: Condition "trace_tag_cs == NULL", taking true branch.
(3) Event path: Condition "crm_is_callsite_active(trace_tag_cs, _level, converted_tag)", taking false branch.
189  	    pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
190  	
191  	    group_data = pcmk__assert_alloc(1, sizeof(group_variant_data_t));
192  	    group_data->last_child = NULL;
193  	    rsc->priv->variant_opaque = group_data;
194  	
195  	    // @COMPAT These are deprecated since 2.1.5
196  	    set_group_flag(rsc, PCMK_META_ORDERED, pcmk__group_ordered,
197  	                   pcmk__wo_group_order);
198  	    set_group_flag(rsc, "collocated", pcmk__group_colocated,
199  	                   pcmk__wo_group_coloc);
200  	
201  	    clone_id = pcmk__xe_get(rsc->priv->xml, PCMK__META_CLONE);
202  	
(4) Event path: Condition "xml_native_rsc != NULL", taking true branch.
(10) Event path: Condition "xml_native_rsc != NULL", taking true branch.
(13) Event path: Condition "xml_native_rsc != NULL", taking false branch.
203  	    for (xml_native_rsc = pcmk__xe_first_child(xml_obj, PCMK_XE_PRIMITIVE,
204  	                                               NULL, NULL);
205  	         xml_native_rsc != NULL;
206  	         xml_native_rsc = pcmk__xe_next(xml_native_rsc, PCMK_XE_PRIMITIVE)) {
207  	
208  	        pcmk_resource_t *new_rsc = NULL;
209  	
210  	        pcmk__xe_set(xml_native_rsc, PCMK__META_CLONE, clone_id);
(5) Event path: Condition "pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, rsc->priv->scheduler) != pcmk_rc_ok", taking false branch.
(11) Event path: Condition "pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, rsc->priv->scheduler) != pcmk_rc_ok", taking true branch.
211  	        if (pe__unpack_resource(xml_native_rsc, &new_rsc, rsc,
212  	                                rsc->priv->scheduler) != pcmk_rc_ok) {
(12) Event path: Continuing loop.
213  	            continue;
214  	        }
215  	
216  	        rsc->priv->children = g_list_append(rsc->priv->children, new_rsc);
217  	        group_data->last_child = new_rsc;
(6) Event path: Switch case default.
(7) Event path: Condition "trace_tag_cs == NULL", taking true branch.
(8) Event path: Condition "crm_is_callsite_active(trace_tag_cs, _level, converted_tag)", taking false branch.
218  	        pcmk__rsc_trace(rsc, "Added %s member %s", rsc->id, new_rsc->id);
(9) Event path: Jumping back to the beginning of the loop.
219  	    }
220  	
(14) Event path: Condition "rsc->priv->children == NULL", taking true branch.
221  	    if (rsc->priv->children == NULL) {
222  	        // Not possible with schema validation enabled
CID (unavailable; MK=7793849108895c5ffa254533e4da0326) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(15) Event assign_union_field: The union field "in" of "_pp" is written.
(16) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
223  	        g_clear_pointer(&rsc->priv->variant_opaque, free);
224  	        pcmk__config_err("Group %s has no members", rsc->id);
225  	        return FALSE;
226  	    }
227  	
228  	    return TRUE;
229  	}
230  	
231  	bool
232  	group_active(const pcmk_resource_t *rsc, bool all)
233  	{
234  	    gboolean c_all = TRUE;
235  	    gboolean c_any = FALSE;
236  	
237  	    for (GList *gIter = rsc->priv->children;
238  	         gIter != NULL; gIter = gIter->next) {
239  	
240  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
241  	
242  	        if (child_rsc->priv->fns->active(child_rsc, all)) {
243  	            c_any = TRUE;
244  	        } else {
245  	            c_all = FALSE;
246  	        }
247  	    }
248  	
249  	    if (c_any == FALSE) {
250  	        return FALSE;
251  	    } else if (all && c_all == FALSE) {
252  	        return FALSE;
253  	    }
254  	    return TRUE;
255  	}
256  	
257  	PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
258  	                  "GList *")
259  	int
260  	pe__group_xml(pcmk__output_t *out, va_list args)
261  	{
262  	    uint32_t show_opts = va_arg(args, uint32_t);
263  	    pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
264  	    GList *only_node = va_arg(args, GList *);
265  	    GList *only_rsc = va_arg(args, GList *);
266  	
267  	    const char *desc = NULL;
268  	
269  	    int rc = pcmk_rc_no_output;
270  	
271  	    bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
272  	                                           pcmk__str_star_matches)
273  	                         || ((strchr(rsc->id, ':') != NULL)
274  	                             && pcmk__str_in_list(rsc->id, only_rsc,
275  	                                                  pcmk__str_star_matches));
276  	
277  	    desc = pe__resource_description(rsc, show_opts);
278  	
279  	    if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
280  	        return rc;
281  	    }
282  	
283  	    for (GList *gIter = rsc->priv->children;
284  	         gIter != NULL; gIter = gIter->next) {
285  	
286  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
287  	
288  	        if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
289  	            continue;
290  	        }
291  	
292  	        if (rc == pcmk_rc_no_output) {
293  	            char *count = pcmk__itoa(g_list_length(gIter));
294  	            const char *maintenance = pcmk__flag_text(rsc->flags,
295  	                                                      pcmk__rsc_maintenance);
296  	            const char *managed = pcmk__flag_text(rsc->flags,
297  	                                                  pcmk__rsc_managed);
298  	            const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
299  	
300  	            rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_GROUP,
301  	                                          PCMK_XA_ID, rsc->id,
302  	                                          PCMK_XA_NUMBER_RESOURCES, count,
303  	                                          PCMK_XA_MAINTENANCE, maintenance,
304  	                                          PCMK_XA_MANAGED, managed,
305  	                                          PCMK_XA_DISABLED, disabled,
306  	                                          PCMK_XA_DESCRIPTION, desc,
307  	                                          NULL);
308  	            free(count);
309  	            pcmk__assert(rc == pcmk_rc_ok);
310  	        }
311  	
312  	        out->message(out, (const char *) child_rsc->priv->xml->name,
313  	                     show_opts, child_rsc, only_node, only_rsc);
314  	    }
315  	
316  	    if (rc == pcmk_rc_ok) {
317  	        pcmk__output_xml_pop_parent(out);
318  	    }
319  	
320  	    return rc;
321  	}
322  	
323  	PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
324  	                  "GList *")
325  	int
326  	pe__group_default(pcmk__output_t *out, va_list args)
327  	{
328  	    uint32_t show_opts = va_arg(args, uint32_t);
329  	    pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
330  	    GList *only_node = va_arg(args, GList *);
331  	    GList *only_rsc = va_arg(args, GList *);
332  	
333  	    const char *desc = NULL;
334  	    int rc = pcmk_rc_no_output;
335  	
336  	    bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
337  	                                           pcmk__str_star_matches)
338  	                         || ((strchr(rsc->id, ':') != NULL)
339  	                             && pcmk__str_in_list(rsc->id, only_rsc,
340  	                                                  pcmk__str_star_matches));
341  	
342  	    const bool active = rsc->priv->fns->active(rsc, true);
343  	    const bool partially_active = rsc->priv->fns->active(rsc, false);
344  	    const bool count_inactive = !active && partially_active;
345  	
346  	    desc = pe__resource_description(rsc, show_opts);
347  	
348  	    if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
349  	        return rc;
350  	    }
351  	
352  	    if (pcmk__is_set(show_opts, pcmk_show_brief)) {
353  	        GList *rscs = pe__filter_rsc_list(rsc->priv->children, only_rsc);
354  	
355  	        if (rscs != NULL) {
356  	            group_header(out, &rc, rsc,
357  	                         (count_inactive? inactive_resources(rsc) : 0),
358  	                         pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
359  	                         desc);
360  	            pe__rscs_brief_output(out, rscs, show_opts | pcmk_show_inactive_rscs);
361  	
362  	            rc = pcmk_rc_ok;
363  	            g_list_free(rscs);
364  	        }
365  	
366  	    } else {
367  	        for (GList *gIter = rsc->priv->children;
368  	             gIter != NULL; gIter = gIter->next) {
369  	            pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
370  	
371  	            if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
372  	                continue;
373  	            }
374  	
375  	            group_header(out, &rc, rsc,
376  	                         (count_inactive? inactive_resources(rsc) : 0),
377  	                         pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
378  	                         desc);
379  	            out->message(out, (const char *) child_rsc->priv->xml->name,
380  	                         show_opts, child_rsc, only_node, only_rsc);
381  	        }
382  	    }
383  	
384  		PCMK__OUTPUT_LIST_FOOTER(out, rc);
385  	
386  	    return rc;
387  	}
388  	
389  	void
390  	group_free(pcmk_resource_t * rsc)
391  	{
392  	    CRM_CHECK(rsc != NULL, return);
393  	
394  	    pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
395  	
396  	    for (GList *gIter = rsc->priv->children;
397  	         gIter != NULL; gIter = gIter->next) {
398  	
399  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
400  	
401  	        pcmk__assert(child_rsc != NULL);
402  	        pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
403  	        pcmk__free_resource(child_rsc);
404  	    }
405  	
406  	    pcmk__rsc_trace(rsc, "Freeing child list");
407  	    g_list_free(rsc->priv->children);
408  	
409  	    common_free(rsc);
410  	}
411  	
412  	enum rsc_role_e
413  	group_resource_state(const pcmk_resource_t *rsc, bool current)
414  	{
415  	    enum rsc_role_e group_role = pcmk_role_unknown;
416  	
417  	    for (GList *gIter = rsc->priv->children;
418  	         gIter != NULL; gIter = gIter->next) {
419  	
420  	        pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
421  	        enum rsc_role_e role = child_rsc->priv->fns->state(child_rsc,
422  	                                                           current);
423  	
424  	        if (role > group_role) {
425  	            group_role = role;
426  	        }
427  	    }
428  	
429  	    pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(group_role));
430  	    return group_role;
431  	}
432  	
433  	bool
434  	pe__group_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
435  	                      bool check_parent)
436  	{
437  	    if (check_parent) {
438  	        const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
439  	
440  	        if (pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
441  	                              pcmk__str_star_matches)) {
442  	            return false;
443  	        }
444  	    }
445  	
446  	    if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
447  	                          pcmk__str_star_matches)) {
448  	        return false;
449  	    }
450  	
451  	    if ((strchr(rsc->id, ':') != NULL)
452  	        && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) {
453  	        return false;
454  	    }
455  	
456  	    for (const GList *iter = rsc->priv->children; iter != NULL;
457  	         iter = iter->next) {
458  	
459  	        const pcmk_resource_t *child_rsc = iter->data;
460  	
461  	        if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, false)) {
462  	            return false;
463  	        }
464  	    }
465  	
466  	    return true;
467  	}
468  	
469  	/*!
470  	 * \internal
471  	 * \brief Get maximum group resource instances per node
472  	 *
473  	 * \param[in] rsc  Group resource to check
474  	 *
475  	 * \return Maximum number of \p rsc instances that can be active on one node
476  	 */
477  	unsigned int
478  	pe__group_max_per_node(const pcmk_resource_t *rsc)
479  	{
480  	    pcmk__assert(pcmk__is_group(rsc));
481  	    return 1U;
482  	}
483