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 General Public License version 2
7    	 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <sys/param.h>
13   	#include <sys/types.h>
14   	#include <stdbool.h>
15   	#include <regex.h>
16   	#include <glib.h>
17   	
18   	#include <crm/crm.h>
19   	#include <crm/cib.h>
20   	#include <crm/common/scheduler.h>
21   	#include <crm/common/xml.h>
22   	#include <crm/common/iso8601.h>
23   	#include <crm/pengine/status.h>
24   	#include <crm/pengine/internal.h>
25   	#include <pacemaker-internal.h>
26   	#include "libpacemaker_private.h"
27   	
28   	/*!
29   	 * \internal
30   	 * \brief Unpack constraints from XML
31   	 *
32   	 * Given scheduler data, unpack all constraints from its input XML into
33   	 * data structures.
34   	 *
35   	 * \param[in,out] scheduler  Scheduler data
36   	 */
37   	void
38   	pcmk__unpack_constraints(pcmk_scheduler_t *scheduler)
39   	{
40   	    xmlNode *xml_constraints = pcmk_find_cib_element(scheduler->input,
41   	                                                     PCMK_XE_CONSTRAINTS);
42   	
43   	    for (xmlNode *xml_obj = pcmk__xe_first_child(xml_constraints, NULL, NULL,
44   	                                                 NULL);
45   	         xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj, NULL)) {
46   	
47   	        const char *id = pcmk__xe_get(xml_obj, PCMK_XA_ID);
48   	        const char *tag = (const char *) xml_obj->name;
49   	
50   	        if (id == NULL) {
51   	            pcmk__config_err("Ignoring <%s> constraint without "
52   	                             PCMK_XA_ID, tag);
53   	            continue;
54   	        }
55   	
56   	        pcmk__trace("Unpacking %s constraint '%s'", tag, id);
57   	
58   	        if (pcmk__str_eq(PCMK_XE_RSC_ORDER, tag, pcmk__str_none)) {
59   	            pcmk__unpack_ordering(xml_obj, scheduler);
60   	
61   	        } else if (pcmk__str_eq(PCMK_XE_RSC_COLOCATION, tag, pcmk__str_none)) {
62   	            pcmk__unpack_colocation(xml_obj, scheduler);
63   	
64   	        } else if (pcmk__str_eq(PCMK_XE_RSC_LOCATION, tag, pcmk__str_none)) {
65   	            pcmk__unpack_location(xml_obj, scheduler);
66   	
67   	        } else if (pcmk__str_eq(PCMK_XE_RSC_TICKET, tag, pcmk__str_none)) {
68   	            pcmk__unpack_rsc_ticket(xml_obj, scheduler);
69   	
70   	        } else {
71   	            pcmk__config_err("Unsupported constraint type: %s", tag);
72   	        }
73   	    }
74   	}
75   	
76   	pcmk_resource_t *
77   	pcmk__find_constraint_resource(GList *rsc_list, const char *id)
78   	{
79   	    if (id == NULL) {
80   	        return NULL;
81   	    }
82   	    for (GList *iter = rsc_list; iter != NULL; iter = iter->next) {
83   	        pcmk_resource_t *parent = iter->data;
84   	        pcmk_resource_t *match = NULL;
85   	
86   	        match = parent->priv->fns->find_rsc(parent, id, NULL,
87   	                                            pcmk_rsc_match_history);
88   	        if (match != NULL) {
89   	            if (!pcmk__str_eq(match->id, id, pcmk__str_none)) {
90   	                /* We found an instance of a clone instead */
91   	                match = uber_parent(match);
92   	                pcmk__debug("Found %s for %s", match->id, id);
93   	            }
94   	            return match;
95   	        }
96   	    }
97   	    pcmk__trace("No match for %s", id);
98   	    return NULL;
99   	}
100  	
101  	/*!
102  	 * \internal
103  	 * \brief Check whether an ID references a resource tag
104  	 *
105  	 * \param[in]  scheduler  Scheduler data
106  	 * \param[in]  id         Tag ID to search for
107  	 * \param[out] tag        Where to store tag, if found
108  	 *
109  	 * \return true if ID refers to a tagged resource or resource set template,
110  	 *         otherwise false
111  	 */
112  	static bool
113  	find_constraint_tag(const pcmk_scheduler_t *scheduler, const char *id,
114  	                    pcmk__idref_t **tag)
115  	{
116  	    *tag = NULL;
117  	
118  	    // Check whether id refers to a resource set template
119  	    if (g_hash_table_lookup_extended(scheduler->priv->templates, id,
120  	                                     NULL, (gpointer *) tag)) {
121  	        if (*tag == NULL) {
122  	            pcmk__notice("No resource is derived from template '%s'", id);
123  	            return false;
124  	        }
125  	        return true;
126  	    }
127  	
128  	    // If not, check whether id refers to a tag
129  	    if (g_hash_table_lookup_extended(scheduler->priv->tags, id,
130  	                                     NULL, (gpointer *) tag)) {
131  	        if (*tag == NULL) {
132  	            pcmk__notice("No resource is tagged with '%s'", id);
133  	            return false;
134  	        }
135  	        return true;
136  	    }
137  	
138  	    pcmk__config_warn("No resource, template, or tag named '%s'", id);
139  	    return false;
140  	}
141  	
142  	/*!
143  	 * \internal
144  	 * \brief Parse a role attribute from a constraint
145  	 *
146  	 * This is like pcmk_parse_role() except that started is treated as
147  	 * pcmk_role_unknown (indicating any role), and the return value is
148  	 * pcmk_rc_unpack_error for invalid specifications.
149  	 *
150  	 * \param[in] id         ID of constraint being parsed (for logging only)
151  	 * \param[in] role_spec  Role specification
152  	 * \param[in] role       Where to store parsed role
153  	 *
154  	 * \return Standard Pacemaker return code
155  	 */
156  	int
157  	pcmk__parse_constraint_role(const char *id, const char *role_spec,
158  	                            enum rsc_role_e *role)
159  	{
160  	    *role = pcmk_parse_role(role_spec);
161  	    switch (*role) {
162  	        case pcmk_role_unknown:
163  	            if (role_spec != NULL) {
164  	                pcmk__config_err("Ignoring constraint %s: Invalid role '%s'",
165  	                                 id, role_spec);
166  	                return pcmk_rc_unpack_error;
167  	            }
168  	            break;
169  	
170  	        case pcmk_role_started:
171  	            *role = pcmk_role_unknown;
172  	            break;
173  	
174  	        default:
175  	            break;
176  	    }
177  	    return pcmk_rc_ok;
178  	}
179  	
180  	/*!
181  	 * \brief
182  	 * \internal Check whether an ID refers to a valid resource or tag
183  	 *
184  	 * \param[in]  scheduler  Scheduler data
185  	 * \param[in]  id         ID to search for
186  	 * \param[out] rsc        Where to store resource, if found
187  	 *                        (or NULL to skip searching resources)
188  	 * \param[out] tag        Where to store tag, if found
189  	 *                        (or NULL to skip searching tags)
190  	 *
191  	 * \return true if id refers to a resource (possibly indirectly via a tag)
192  	 */
193  	bool
194  	pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler, const char *id,
195  	                            pcmk_resource_t **rsc, pcmk__idref_t **tag)
196  	{
197  	    if (rsc != NULL) {
198  	        *rsc = pcmk__find_constraint_resource(scheduler->priv->resources, id);
199  	        if (*rsc != NULL) {
200  	            return true;
201  	        }
202  	    }
203  	
204  	    if ((tag != NULL) && find_constraint_tag(scheduler, id, tag)) {
205  	        return true;
206  	    }
207  	
208  	    return false;
209  	}
210  	
211  	/*!
212  	 * \internal
213  	 * \brief Replace any resource tags with equivalent \C PCMK_XE_RESOURCE_REF
214  	 *        entries
215  	 *
216  	 * If a given constraint has resource sets, check each set for
217  	 * \c PCMK_XE_RESOURCE_REF entries that list tags rather than resource IDs, and
218  	 * replace any found with \c PCMK_XE_RESOURCE_REF entries for the corresponding
219  	 * resource IDs.
220  	 *
221  	 * \param[in,out] xml_obj    Constraint XML
222  	 * \param[in]     scheduler  Scheduler data
223  	 *
224  	 * \return Equivalent XML with resource tags replaced (or NULL if none)
225  	 * \note It is the caller's responsibility to free the return value with
226  	 *       \c pcmk__xml_free().
227  	 */
228  	xmlNode *
229  	pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pcmk_scheduler_t *scheduler)
230  	{
231  	    xmlNode *new_xml = NULL;
232  	    bool any_refs = false;
233  	
234  	    // Short-circuit if there are no sets
(1) Event path: Condition "pcmk__xe_first_child(xml_obj, "resource_set", NULL, NULL) == NULL", taking false branch.
235  	    if (pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL,
236  	                             NULL) == NULL) {
237  	        return NULL;
238  	    }
239  	
240  	    new_xml = pcmk__xml_copy(NULL, xml_obj);
241  	
(2) Event path: Condition "set != NULL", taking true branch.
(10) Event path: Condition "set != NULL", taking false branch.
242  	    for (xmlNode *set = pcmk__xe_first_child(new_xml, PCMK_XE_RESOURCE_SET,
243  	                                             NULL, NULL);
244  	         set != NULL; set = pcmk__xe_next(set, PCMK_XE_RESOURCE_SET)) {
245  	
246  	        GList *tag_refs = NULL;
247  	        GList *iter = NULL;
248  	
(3) Event path: Condition "xml_rsc != NULL", taking true branch.
(7) Event path: Condition "xml_rsc != NULL", taking false branch.
249  	        for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
250  	                                                     NULL, NULL);
251  	             xml_rsc != NULL;
252  	             xml_rsc = pcmk__xe_next(xml_rsc, PCMK_XE_RESOURCE_REF)) {
253  	
254  	            pcmk_resource_t *rsc = NULL;
255  	            pcmk__idref_t *tag = NULL;
256  	
(4) Event path: Condition "!pcmk__valid_resource_or_tag(scheduler, pcmk__xe_id(xml_rsc), &rsc, &tag)", taking false branch.
257  	            if (!pcmk__valid_resource_or_tag(scheduler, pcmk__xe_id(xml_rsc),
258  	                                             &rsc, &tag)) {
259  	                pcmk__config_err("Ignoring resource sets for constraint '%s' "
260  	                                 "because '%s' is not a valid resource or tag",
261  	                                 pcmk__xe_id(xml_obj), pcmk__xe_id(xml_rsc));
262  	                pcmk__xml_free(new_xml);
263  	                return NULL;
264  	
(5) Event path: Condition "rsc", taking true branch.
265  	            } else if (rsc) {
(6) Event path: Continuing loop.
266  	                continue;
267  	
268  	            } else if (tag) {
269  	                /* PCMK_XE_RESOURCE_REF under PCMK_XE_RESOURCE_SET references
270  	                 * template or tag
271  	                 */
272  	                xmlNode *last_ref = xml_rsc;
273  	
274  	                /* For example, given the original XML:
275  	                 *
276  	                 *   <resource_set id="tag1-colocation-0" sequential="true">
277  	                 *     <resource_ref id="rsc1"/>
278  	                 *     <resource_ref id="tag1"/>
279  	                 *     <resource_ref id="rsc4"/>
280  	                 *   </resource_set>
281  	                 *
282  	                 * If rsc2 and rsc3 are tagged with tag1, we add them after it:
283  	                 *
284  	                 *   <resource_set id="tag1-colocation-0" sequential="true">
285  	                 *     <resource_ref id="rsc1"/>
286  	                 *     <resource_ref id="tag1"/>
287  	                 *     <resource_ref id="rsc2"/>
288  	                 *     <resource_ref id="rsc3"/>
289  	                 *     <resource_ref id="rsc4"/>
290  	                 *   </resource_set>
291  	                 */
292  	
293  	                for (iter = tag->refs; iter != NULL; iter = iter->next) {
294  	                    const char *ref_id = iter->data;
295  	                    xmlNode *new_ref = pcmk__xe_create(set,
296  	                                                       PCMK_XE_RESOURCE_REF);
297  	
298  	                    pcmk__xe_set(new_ref, PCMK_XA_ID, ref_id);
299  	                    xmlAddNextSibling(last_ref, new_ref);
300  	
301  	                    last_ref = new_ref;
302  	                }
303  	
304  	                any_refs = true;
305  	
306  	                /* Freeing the resource_ref now would break the XML child
307  	                 * iteration, so just remember it for freeing later.
308  	                 */
309  	                tag_refs = g_list_append(tag_refs, xml_rsc);
310  	            }
311  	        }
312  	
313  	        /* Now free '<resource_ref id="tag1"/>', and finally get:
314  	
315  	           <resource_set id="tag1-colocation-0" sequential="true">
316  	             <resource_ref id="rsc1"/>
317  	             <resource_ref id="rsc2"/>
318  	             <resource_ref id="rsc3"/>
319  	             <resource_ref id="rsc4"/>
320  	           </resource_set>
321  	
322  	         */
(8) Event path: Condition "iter != NULL", taking false branch.
323  	        for (iter = tag_refs; iter != NULL; iter = iter->next) {
324  	            xmlNode *tag_ref = iter->data;
325  	
326  	            pcmk__xml_free(tag_ref);
327  	        }
328  	        g_list_free(tag_refs);
(9) Event path: Jumping back to the beginning of the loop.
329  	    }
330  	
(11) Event path: Condition "!any_refs", taking true branch.
331  	    if (!any_refs) {
CID (unavailable; MK=d87191d8ef1350a91c240942edc91724) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(12) Event assign_union_field: The union field "in" of "_pp" is written.
(13) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
332  	        g_clear_pointer(&new_xml, pcmk__xml_free);
333  	    }
334  	
335  	    return new_xml;
336  	}
337  	
338  	/*!
339  	 * \internal
340  	 * \brief Convert a tag into a resource set of tagged resources
341  	 *
342  	 * \param[in,out] xml_obj      Constraint XML
343  	 * \param[out]    rsc_set      Where to store resource set XML
344  	 * \param[in]     attr         Name of XML attribute with resource or tag ID
345  	 * \param[in]     convert_rsc  If true, convert to set even if \p attr
346  	 *                             references a resource
347  	 * \param[in]     scheduler    Scheduler data
348  	 */
349  	bool
350  	pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
351  	                 bool convert_rsc, const pcmk_scheduler_t *scheduler)
352  	{
353  	    const char *cons_id = NULL;
354  	    const char *id = NULL;
355  	
356  	    pcmk_resource_t *rsc = NULL;
357  	    pcmk__idref_t *tag = NULL;
358  	
359  	    *rsc_set = NULL;
360  	
361  	    CRM_CHECK((xml_obj != NULL) && (attr != NULL), return false);
362  	
363  	    cons_id = pcmk__xe_id(xml_obj);
364  	    if (cons_id == NULL) {
365  	        pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
366  	                         xml_obj->name);
367  	        return false;
368  	    }
369  	
370  	    id = pcmk__xe_get(xml_obj, attr);
371  	    if (id == NULL) {
372  	        return true;
373  	    }
374  	
375  	    if (!pcmk__valid_resource_or_tag(scheduler, id, &rsc, &tag)) {
376  	        pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
377  	                         "valid resource or tag", cons_id, id);
378  	        return false;
379  	
380  	    } else if (tag) {
381  	        /* The "attr" attribute (for a resource in a constraint) specifies a
382  	         * template or tag. Add the corresponding PCMK_XE_RESOURCE_SET
383  	         * containing the resources derived from or tagged with it.
384  	         */
385  	        *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
386  	        pcmk__xe_set(*rsc_set, PCMK_XA_ID, id);
387  	
388  	        for (GList *iter = tag->refs; iter != NULL; iter = iter->next) {
389  	            const char *obj_ref = iter->data;
390  	            xmlNode *rsc_ref = NULL;
391  	
392  	            rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
393  	            pcmk__xe_set(rsc_ref, PCMK_XA_ID, obj_ref);
394  	        }
395  	
396  	        // Set PCMK_XA_SEQUENTIAL=PCMK_VALUE_FALSE for the PCMK_XE_RESOURCE_SET
397  	        pcmk__xe_set_bool(*rsc_set, PCMK_XA_SEQUENTIAL, false);
398  	
399  	    } else if ((rsc != NULL) && convert_rsc) {
400  	        /* Even if a regular resource is referenced by "attr", convert it into a
401  	         * PCMK_XE_RESOURCE_SET, because the other resource reference in the
402  	         * constraint could be a template or tag.
403  	         */
404  	        xmlNode *rsc_ref = NULL;
405  	
406  	        *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
407  	        pcmk__xe_set(*rsc_set, PCMK_XA_ID, id);
408  	
409  	        rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
410  	        pcmk__xe_set(rsc_ref, PCMK_XA_ID, id);
411  	
412  	    } else {
413  	        return true;
414  	    }
415  	
416  	    /* Remove the "attr" attribute referencing the template/tag */
417  	    if (*rsc_set != NULL) {
418  	        pcmk__xe_remove_attr(xml_obj, attr);
419  	    }
420  	
421  	    return true;
422  	}
423  	
424  	/*!
425  	 * \internal
426  	 * \brief Create constraints inherent to resource types
427  	 *
428  	 * \param[in,out] scheduler  Scheduler data
429  	 */
430  	void
431  	pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler)
432  	{
433  	    pcmk__trace("Create internal constraints");
434  	    for (GList *iter = scheduler->priv->resources;
435  	         iter != NULL; iter = iter->next) {
436  	
437  	        pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
438  	
439  	        rsc->priv->cmds->internal_constraints(rsc);
440  	    }
441  	}
442