Line data Source code
1 : /*
2 : * Copyright 2004-2024 the Pacemaker project contributors
3 : *
4 : * The version control history for this file may have further details.
5 : *
6 : * This source code is licensed under the GNU Lesser General Public License
7 : * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <glib.h>
13 :
14 : #include <crm/crm.h>
15 : #include <crm/common/xml.h>
16 : #include <crm/pengine/rules.h>
17 :
18 : #include <crm/common/iso8601_internal.h>
19 : #include <crm/common/nvpair_internal.h>
20 : #include <crm/common/rules_internal.h>
21 : #include <crm/common/xml_internal.h>
22 : #include <crm/pengine/internal.h>
23 : #include <crm/pengine/rules_internal.h>
24 :
25 : #include <sys/types.h>
26 : #include <regex.h>
27 :
28 : CRM_TRACE_INIT_DATA(pe_rules);
29 :
30 : /*!
31 : * \internal
32 : * \brief Map pe_rule_eval_data_t to pcmk_rule_input_t
33 : *
34 : * \param[out] new New data struct
35 : * \param[in] old Old data struct
36 : */
37 : static void
38 0 : map_rule_input(pcmk_rule_input_t *new, const pe_rule_eval_data_t *old)
39 : {
40 0 : if (old == NULL) {
41 0 : return;
42 : }
43 0 : new->now = old->now;
44 0 : new->node_attrs = old->node_hash;
45 0 : if (old->rsc_data != NULL) {
46 0 : new->rsc_standard = old->rsc_data->standard;
47 0 : new->rsc_provider = old->rsc_data->provider;
48 0 : new->rsc_agent = old->rsc_data->agent;
49 : }
50 0 : if (old->match_data != NULL) {
51 0 : new->rsc_params = old->match_data->params;
52 0 : new->rsc_meta = old->match_data->meta;
53 0 : if (old->match_data->re != NULL) {
54 0 : new->rsc_id = old->match_data->re->string;
55 0 : new->rsc_id_submatches = old->match_data->re->pmatch;
56 0 : new->rsc_id_nmatches = old->match_data->re->nregs;
57 : }
58 : }
59 0 : if (old->op_data != NULL) {
60 0 : new->op_name = old->op_data->op_name;
61 0 : new->op_interval_ms = old->op_data->interval;
62 : }
63 : }
64 :
65 : static gint
66 0 : sort_pairs(gconstpointer a, gconstpointer b, gpointer user_data)
67 : {
68 0 : const xmlNode *pair_a = a;
69 0 : const xmlNode *pair_b = b;
70 0 : pcmk__nvpair_unpack_t *unpack_data = user_data;
71 :
72 0 : const char *score = NULL;
73 0 : int score_a = 0;
74 0 : int score_b = 0;
75 :
76 0 : if (a == NULL && b == NULL) {
77 0 : return 0;
78 0 : } else if (a == NULL) {
79 0 : return 1;
80 0 : } else if (b == NULL) {
81 0 : return -1;
82 : }
83 :
84 0 : if (pcmk__str_eq(pcmk__xe_id(pair_a), unpack_data->first_id,
85 : pcmk__str_none)) {
86 0 : return -1;
87 :
88 0 : } else if (pcmk__str_eq(pcmk__xe_id(pair_b), unpack_data->first_id,
89 : pcmk__str_none)) {
90 0 : return 1;
91 : }
92 :
93 0 : score = crm_element_value(pair_a, PCMK_XA_SCORE);
94 0 : score_a = char2score(score);
95 :
96 0 : score = crm_element_value(pair_b, PCMK_XA_SCORE);
97 0 : score_b = char2score(score);
98 :
99 : /* If we're overwriting values, we want lowest score first, so the highest
100 : * score is processed last; if we're not overwriting values, we want highest
101 : * score first, so nothing else overwrites it.
102 : */
103 0 : if (score_a < score_b) {
104 0 : return unpack_data->overwrite? -1 : 1;
105 0 : } else if (score_a > score_b) {
106 0 : return unpack_data->overwrite? 1 : -1;
107 : }
108 0 : return 0;
109 : }
110 :
111 : static void
112 0 : populate_hash(xmlNode *nvpair_list, GHashTable *hash, bool overwrite)
113 : {
114 0 : const char *name = NULL;
115 0 : const char *value = NULL;
116 0 : const char *old_value = NULL;
117 0 : xmlNode *list = nvpair_list;
118 0 : xmlNode *an_attr = NULL;
119 :
120 0 : if (pcmk__xe_is(list->children, PCMK__XE_ATTRIBUTES)) {
121 0 : list = list->children;
122 : }
123 :
124 0 : for (an_attr = pcmk__xe_first_child(list, NULL, NULL, NULL);
125 0 : an_attr != NULL; an_attr = pcmk__xe_next(an_attr)) {
126 :
127 0 : if (pcmk__xe_is(an_attr, PCMK_XE_NVPAIR)) {
128 0 : xmlNode *ref_nvpair = expand_idref(an_attr, NULL);
129 :
130 0 : name = crm_element_value(an_attr, PCMK_XA_NAME);
131 0 : if ((name == NULL) && (ref_nvpair != NULL)) {
132 0 : name = crm_element_value(ref_nvpair, PCMK_XA_NAME);
133 : }
134 :
135 0 : value = crm_element_value(an_attr, PCMK_XA_VALUE);
136 0 : if ((value == NULL) && (ref_nvpair != NULL)) {
137 0 : value = crm_element_value(ref_nvpair, PCMK_XA_VALUE);
138 : }
139 :
140 0 : if (name == NULL || value == NULL) {
141 0 : continue;
142 : }
143 :
144 0 : old_value = g_hash_table_lookup(hash, name);
145 :
146 0 : if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
147 : // @COMPAT Deprecated since 2.1.8
148 0 : pcmk__config_warn("Support for setting meta-attributes (such "
149 : "as %s) to the explicit value '#default' is "
150 : "deprecated and will be removed in a future "
151 : "release", name);
152 0 : if (old_value) {
153 0 : crm_trace("Letting %s default (removing explicit value \"%s\")",
154 : name, value);
155 0 : g_hash_table_remove(hash, name);
156 : }
157 0 : continue;
158 :
159 0 : } else if (old_value == NULL) {
160 0 : crm_trace("Setting %s=\"%s\"", name, value);
161 0 : pcmk__insert_dup(hash, name, value);
162 :
163 0 : } else if (overwrite) {
164 0 : crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")",
165 : name, value, old_value);
166 0 : pcmk__insert_dup(hash, name, value);
167 : }
168 : }
169 : }
170 0 : }
171 :
172 : static void
173 0 : unpack_attr_set(gpointer data, gpointer user_data)
174 : {
175 0 : xmlNode *pair = data;
176 0 : pcmk__nvpair_unpack_t *unpack_data = user_data;
177 :
178 0 : if (pcmk__evaluate_rules(pair, &(unpack_data->rule_input),
179 : unpack_data->next_change) != pcmk_rc_ok) {
180 0 : return;
181 : }
182 :
183 0 : crm_trace("Adding name/value pairs from %s %s overwrite",
184 : pcmk__xe_id(pair), (unpack_data->overwrite? "with" : "without"));
185 0 : populate_hash(pair, unpack_data->values, unpack_data->overwrite);
186 : }
187 :
188 : /*!
189 : * \internal
190 : * \brief Create a sorted list of nvpair blocks
191 : *
192 : * \param[in] xml_obj XML element containing blocks of nvpair elements
193 : * \param[in] set_name If not NULL, only get blocks of this element
194 : *
195 : * \return List of XML blocks of name/value pairs
196 : */
197 : static GList *
198 0 : make_pairs(const xmlNode *xml_obj, const char *set_name)
199 : {
200 0 : GList *unsorted = NULL;
201 :
202 0 : if (xml_obj == NULL) {
203 0 : return NULL;
204 : }
205 0 : for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
206 0 : attr_set != NULL; attr_set = pcmk__xe_next(attr_set)) {
207 :
208 0 : if ((set_name == NULL) || pcmk__xe_is(attr_set, set_name)) {
209 0 : xmlNode *expanded_attr_set = expand_idref(attr_set, NULL);
210 :
211 0 : if (expanded_attr_set == NULL) {
212 0 : continue; // Not possible with schema validation enabled
213 : }
214 0 : unsorted = g_list_prepend(unsorted, expanded_attr_set);
215 : }
216 : }
217 0 : return unsorted;
218 : }
219 :
220 : /*!
221 : * \brief Extract nvpair blocks contained by an XML element into a hash table
222 : *
223 : * \param[in,out] top Ignored
224 : * \param[in] xml_obj XML element containing blocks of nvpair elements
225 : * \param[in] set_name If not NULL, only use blocks of this element
226 : * \param[in] rule_data Matching parameters to use when unpacking
227 : * \param[out] hash Where to store extracted name/value pairs
228 : * \param[in] always_first If not NULL, process block with this ID first
229 : * \param[in] overwrite Whether to replace existing values with same name
230 : * \param[out] next_change If not NULL, set to when evaluation will change
231 : */
232 : void
233 0 : pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
234 : const pe_rule_eval_data_t *rule_data, GHashTable *hash,
235 : const char *always_first, gboolean overwrite,
236 : crm_time_t *next_change)
237 : {
238 0 : GList *pairs = make_pairs(xml_obj, set_name);
239 :
240 0 : if (pairs) {
241 0 : pcmk__nvpair_unpack_t data = {
242 : .values = hash,
243 : .first_id = always_first,
244 : .overwrite = overwrite,
245 : .next_change = next_change,
246 : };
247 :
248 0 : map_rule_input(&(data.rule_input), rule_data);
249 :
250 0 : pairs = g_list_sort_with_data(pairs, sort_pairs, &data);
251 0 : g_list_foreach(pairs, unpack_attr_set, &data);
252 0 : g_list_free(pairs);
253 : }
254 0 : }
255 :
256 : /*!
257 : * \brief Extract nvpair blocks contained by an XML element into a hash table
258 : *
259 : * \param[in,out] top Ignored
260 : * \param[in] xml_obj XML element containing blocks of nvpair elements
261 : * \param[in] set_name Element name to identify nvpair blocks
262 : * \param[in] node_hash Node attributes to use when evaluating rules
263 : * \param[out] hash Where to store extracted name/value pairs
264 : * \param[in] always_first If not NULL, process block with this ID first
265 : * \param[in] overwrite Whether to replace existing values with same name
266 : * \param[in] now Time to use when evaluating rules
267 : * \param[out] next_change If not NULL, set to when evaluation will change
268 : */
269 : void
270 0 : pe_unpack_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
271 : GHashTable *node_hash, GHashTable *hash,
272 : const char *always_first, gboolean overwrite,
273 : crm_time_t *now, crm_time_t *next_change)
274 : {
275 0 : pe_rule_eval_data_t rule_data = {
276 : .node_hash = node_hash,
277 : .now = now,
278 : .match_data = NULL,
279 : .rsc_data = NULL,
280 : .op_data = NULL
281 : };
282 :
283 0 : pe_eval_nvpairs(NULL, xml_obj, set_name, &rule_data, hash,
284 : always_first, overwrite, next_change);
285 0 : }
286 :
287 : // Deprecated functions kept only for backward API compatibility
288 : // LCOV_EXCL_START
289 :
290 : #include <crm/pengine/rules_compat.h>
291 :
292 : gboolean
293 : pe_eval_rules(xmlNode *ruleset, const pe_rule_eval_data_t *rule_data,
294 : crm_time_t *next_change)
295 : {
296 : pcmk_rule_input_t rule_input = { NULL, };
297 :
298 : map_rule_input(&rule_input, rule_data);
299 : return pcmk__evaluate_rules(ruleset, &rule_input,
300 : next_change) == pcmk_rc_ok;
301 : }
302 :
303 : gboolean
304 : pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now,
305 : crm_time_t *next_change)
306 : {
307 : pcmk_rule_input_t rule_input = {
308 : .node_attrs = node_hash,
309 : .now = now,
310 : };
311 :
312 : return pcmk__evaluate_rules(ruleset, &rule_input, next_change);
313 : }
314 :
315 : gboolean
316 : pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
317 : crm_time_t *now, crm_time_t *next_change,
318 : pe_match_data_t *match_data)
319 : {
320 : pcmk_rule_input_t rule_input = {
321 : .node_attrs = node_hash,
322 : .now = now,
323 : };
324 :
325 : if (match_data != NULL) {
326 : rule_input.rsc_params = match_data->params;
327 : rule_input.rsc_meta = match_data->meta;
328 : if (match_data->re != NULL) {
329 : rule_input.rsc_id = match_data->re->string;
330 : rule_input.rsc_id_submatches = match_data->re->pmatch;
331 : rule_input.rsc_id_nmatches = match_data->re->nregs;
332 : }
333 : }
334 : return pcmk_evaluate_rule(rule, &rule_input, next_change) == pcmk_rc_ok;
335 : }
336 :
337 : gboolean
338 : test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now)
339 : {
340 : return pe_evaluate_rules(ruleset, node_hash, now, NULL);
341 : }
342 :
343 : gboolean
344 : test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
345 : {
346 : return pe_test_rule(rule, node_hash, role, now, NULL, NULL);
347 : }
348 :
349 : gboolean
350 : pe_test_rule_re(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
351 : {
352 : pe_match_data_t match_data = {
353 : .re = re_match_data,
354 : .params = NULL,
355 : .meta = NULL,
356 : };
357 : return pe_test_rule(rule, node_hash, role, now, NULL, &match_data);
358 : }
359 :
360 : gboolean
361 : pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
362 : crm_time_t *now, pe_match_data_t *match_data)
363 : {
364 : return pe_test_rule(rule, node_hash, role, now, NULL, match_data);
365 : }
366 :
367 : gboolean
368 : pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role,
369 : crm_time_t *now, crm_time_t *next_change,
370 : pe_match_data_t *match_data)
371 : {
372 : pcmk_rule_input_t rule_input = {
373 : .now = now,
374 : .node_attrs = node_hash,
375 : };
376 :
377 : if (match_data != NULL) {
378 : rule_input.rsc_params = match_data->params;
379 : rule_input.rsc_meta = match_data->meta;
380 : if (match_data->re != NULL) {
381 : rule_input.rsc_id = match_data->re->string;
382 : rule_input.rsc_id_submatches = match_data->re->pmatch;
383 : rule_input.rsc_id_nmatches = match_data->re->nregs;
384 : }
385 : }
386 : return pcmk__evaluate_condition(expr, &rule_input,
387 : next_change) == pcmk_rc_ok;
388 : }
389 :
390 : gboolean
391 : test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
392 : {
393 : return pe_test_expression(expr, node_hash, role, now, NULL, NULL);
394 : }
395 :
396 : gboolean
397 : pe_test_expression_re(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
398 : {
399 : pe_match_data_t match_data = {
400 : .re = re_match_data,
401 : .params = NULL,
402 : .meta = NULL,
403 : };
404 : return pe_test_expression(expr, node_hash, role, now, NULL, &match_data);
405 : }
406 :
407 : gboolean
408 : pe_test_expression_full(xmlNode *expr, GHashTable *node_hash,
409 : enum rsc_role_e role, crm_time_t *now,
410 : pe_match_data_t *match_data)
411 : {
412 : return pe_test_expression(expr, node_hash, role, now, NULL, match_data);
413 : }
414 :
415 : gboolean
416 : pe_eval_expr(xmlNode *rule, const pe_rule_eval_data_t *rule_data,
417 : crm_time_t *next_change)
418 : {
419 : pcmk_rule_input_t rule_input = { NULL, };
420 :
421 : map_rule_input(&rule_input, rule_data);
422 : return pcmk_evaluate_rule(rule, &rule_input, next_change) == pcmk_rc_ok;
423 : }
424 :
425 : gboolean
426 : pe_eval_subexpr(xmlNode *expr, const pe_rule_eval_data_t *rule_data,
427 : crm_time_t *next_change)
428 : {
429 : pcmk_rule_input_t rule_input = { NULL, };
430 :
431 : map_rule_input(&rule_input, rule_data);
432 : return pcmk__evaluate_condition(expr, &rule_input,
433 : next_change) == pcmk_rc_ok;
434 : }
435 :
436 : void
437 : unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name,
438 : GHashTable *node_hash, GHashTable *hash,
439 : const char *always_first, gboolean overwrite,
440 : crm_time_t *now)
441 : {
442 : pe_rule_eval_data_t rule_data = {
443 : .node_hash = node_hash,
444 : .now = now,
445 : .match_data = NULL,
446 : .rsc_data = NULL,
447 : .op_data = NULL
448 : };
449 :
450 : pe_eval_nvpairs(NULL, xml_obj, set_name, &rule_data, hash, always_first,
451 : overwrite, NULL);
452 : }
453 :
454 : enum expression_type
455 : find_expression_type(xmlNode *expr)
456 : {
457 : return pcmk__condition_type(expr);
458 : }
459 :
460 : char *
461 : pe_expand_re_matches(const char *string, const pe_re_match_data_t *match_data)
462 : {
463 : if (match_data == NULL) {
464 : return NULL;
465 : }
466 : return pcmk__replace_submatches(string, match_data->string,
467 : match_data->pmatch, match_data->nregs);
468 : }
469 :
470 : // LCOV_EXCL_STOP
471 : // End deprecated API
|