1 /*
2 * Copyright 2004-2021 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 #include <crm/crm.h>
12 #include <crm/msg_xml.h>
13 #include <crm/common/xml.h>
14 #include <crm/common/xml_internal.h>
15
16 #include <glib.h>
17
18 #include <crm/pengine/rules.h>
19 #include <crm/pengine/rules_internal.h>
20 #include <crm/pengine/internal.h>
21
22 #include <sys/types.h>
23 #include <regex.h>
24 #include <ctype.h>
25
26 CRM_TRACE_INIT_DATA(pe_rules);
27
28 /*!
29 * \brief Evaluate any rules contained by given XML element
30 *
31 * \param[in] xml XML element to check for rules
32 * \param[in] node_hash Node attributes to use when evaluating expressions
33 * \param[in] now Time to use when evaluating expressions
34 * \param[out] next_change If not NULL, set to when evaluation will change
35 *
36 * \return TRUE if no rules, or any of rules present is in effect, else FALSE
37 */
38 gboolean
pe_evaluate_rules(xmlNode * ruleset,GHashTable * node_hash,crm_time_t * now,crm_time_t * next_change)39 pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now,
40 crm_time_t *next_change)
41 {
42 pe_rule_eval_data_t rule_data = {
43 .node_hash = node_hash,
44 .role = RSC_ROLE_UNKNOWN,
45 .now = now,
46 .match_data = NULL,
47 .rsc_data = NULL,
48 .op_data = NULL
49 };
50
51 return pe_eval_rules(ruleset, &rule_data, next_change);
52 }
53
54 gboolean
pe_test_rule(xmlNode * rule,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now,crm_time_t * next_change,pe_match_data_t * match_data)55 pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
56 crm_time_t *now, crm_time_t *next_change,
57 pe_match_data_t *match_data)
58 {
59 pe_rule_eval_data_t rule_data = {
60 .node_hash = node_hash,
61 .role = role,
62 .now = now,
63 .match_data = match_data,
64 .rsc_data = NULL,
65 .op_data = NULL
66 };
67
68 return pe_eval_expr(rule, &rule_data, next_change);
69 }
70
71 /*!
72 * \brief Evaluate one rule subelement (pass/fail)
73 *
74 * A rule element may contain another rule, a node attribute expression, or a
75 * date expression. Given any one of those, evaluate it and return whether it
76 * passed.
77 *
78 * \param[in] expr Rule subelement XML
79 * \param[in] node_hash Node attributes to use when evaluating expression
80 * \param[in] role Resource role to use when evaluating expression
81 * \param[in] now Time to use when evaluating expression
82 * \param[out] next_change If not NULL, set to when evaluation will change
83 * \param[in] match_data If not NULL, resource back-references and params
84 *
85 * \return TRUE if expression is in effect under given conditions, else FALSE
86 */
87 gboolean
pe_test_expression(xmlNode * expr,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now,crm_time_t * next_change,pe_match_data_t * match_data)88 pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role,
89 crm_time_t *now, crm_time_t *next_change,
90 pe_match_data_t *match_data)
91 {
92 pe_rule_eval_data_t rule_data = {
93 .node_hash = node_hash,
94 .role = role,
95 .now = now,
96 .match_data = match_data,
97 .rsc_data = NULL,
98 .op_data = NULL
99 };
100
101 return pe_eval_subexpr(expr, &rule_data, next_change);
102 }
103
104 enum expression_type
find_expression_type(xmlNode * expr)105 find_expression_type(xmlNode * expr)
106 {
107 const char *tag = NULL;
108 const char *attr = NULL;
109
110 attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
111 tag = crm_element_name(expr);
112
113 if (pcmk__str_eq(tag, "date_expression", pcmk__str_casei)) {
114 return time_expr;
115
116 } else if (pcmk__str_eq(tag, "rsc_expression", pcmk__str_casei)) {
117 return rsc_expr;
118
119 } else if (pcmk__str_eq(tag, "op_expression", pcmk__str_casei)) {
120 return op_expr;
121
122 } else if (pcmk__str_eq(tag, XML_TAG_RULE, pcmk__str_casei)) {
123 return nested_rule;
124
125 } else if (!pcmk__str_eq(tag, "expression", pcmk__str_casei)) {
126 return not_expr;
127
128 } else if (pcmk__strcase_any_of(attr, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID, NULL)) {
129 return loc_expr;
130
131 } else if (pcmk__str_eq(attr, CRM_ATTR_ROLE, pcmk__str_casei)) {
132 return role_expr;
133
134 #if ENABLE_VERSIONED_ATTRS
135 } else if (pcmk__str_eq(attr, CRM_ATTR_RA_VERSION, pcmk__str_casei)) {
136 return version_expr;
137 #endif
138 }
139
140 return attr_expr;
141 }
142
143 /* As per the nethack rules:
144 *
145 * moon period = 29.53058 days ~= 30, year = 365.2422 days
146 * days moon phase advances on first day of year compared to preceding year
147 * = 365.2422 - 12*29.53058 ~= 11
148 * years in Metonic cycle (time until same phases fall on the same days of
149 * the month) = 18.6 ~= 19
150 * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30
151 * (29 as initial condition)
152 * current phase in days = first day phase + days elapsed in year
153 * 6 moons ~= 177 days
154 * 177 ~= 8 reported phases * 22
155 * + 11/22 for rounding
156 *
157 * 0-7, with 0: new, 4: full
158 */
159
160 static int
phase_of_the_moon(crm_time_t * now)161 phase_of_the_moon(crm_time_t * now)
162 {
163 uint32_t epact, diy, goldn;
164 uint32_t y;
165
166 crm_time_get_ordinal(now, &y, &diy);
167
168 goldn = (y % 19) + 1;
169 epact = (11 * goldn + 18) % 30;
170 if ((epact == 25 && goldn > 11) || epact == 24)
171 epact++;
172
173 return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7);
174 }
175
176 static int
check_one(xmlNode * cron_spec,const char * xml_field,uint32_t time_field)177 check_one(xmlNode *cron_spec, const char *xml_field, uint32_t time_field) {
178 int rc = pcmk_rc_undetermined;
179 const char *value = crm_element_value(cron_spec, xml_field);
180 long long low, high;
181
182 if (value == NULL) {
183 /* Return pe_date_result_undetermined if the field is missing. */
184 goto bail;
185 }
186
187 if (pcmk__parse_ll_range(value, &low, &high) == pcmk_rc_unknown_format) {
188 goto bail;
189 } else if (low == high) {
190 /* A single number was given, not a range. */
191 if (time_field < low) {
192 rc = pcmk_rc_before_range;
193 } else if (time_field > high) {
194 rc = pcmk_rc_after_range;
195 } else {
196 rc = pcmk_rc_within_range;
197 }
198 } else if (low != -1 && high != -1) {
199 /* This is a range with both bounds. */
200 if (time_field < low) {
201 rc = pcmk_rc_before_range;
202 } else if (time_field > high) {
203 rc = pcmk_rc_after_range;
204 } else {
205 rc = pcmk_rc_within_range;
206 }
207 } else if (low == -1) {
208 /* This is a range with no starting value. */
209 rc = time_field <= high ? pcmk_rc_within_range : pcmk_rc_after_range;
210 } else if (high == -1) {
211 /* This is a range with no ending value. */
212 rc = time_field >= low ? pcmk_rc_within_range : pcmk_rc_before_range;
213 }
214
215 bail:
216 if (rc == pcmk_rc_within_range) {
217 crm_debug("Condition '%s' in %s: passed", value, xml_field);
218 } else {
219 crm_debug("Condition '%s' in %s: failed", value, xml_field);
220 }
221
222 return rc;
223 }
224
225 static gboolean
check_passes(int rc)226 check_passes(int rc) {
227 /* _within_range is obvious. _undetermined is a pass because
228 * this is the return value if a field is not given. In this
229 * case, we just want to ignore it and check other fields to
230 * see if they place some restriction on what can pass.
231 */
232 return rc == pcmk_rc_within_range || rc == pcmk_rc_undetermined;
233 }
234
235 #define CHECK_ONE(spec, name, var) do { \
236 int subpart_rc = check_one(spec, name, var); \
237 if (check_passes(subpart_rc) == FALSE) { \
238 return subpart_rc; \
239 } \
240 } while (0)
241
242 int
pe_cron_range_satisfied(crm_time_t * now,xmlNode * cron_spec)243 pe_cron_range_satisfied(crm_time_t * now, xmlNode * cron_spec)
244 {
245 uint32_t h, m, s, y, d, w;
246
247 CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied);
248
249 crm_time_get_gregorian(now, &y, &m, &d);
250 CHECK_ONE(cron_spec, "years", y);
251 CHECK_ONE(cron_spec, "months", m);
252 CHECK_ONE(cron_spec, "monthdays", d);
253
254 crm_time_get_timeofday(now, &h, &m, &s);
255 CHECK_ONE(cron_spec, "hours", h);
256 CHECK_ONE(cron_spec, "minutes", m);
257 CHECK_ONE(cron_spec, "seconds", s);
258
259 crm_time_get_ordinal(now, &y, &d);
260 CHECK_ONE(cron_spec, "yeardays", d);
261
262 crm_time_get_isoweek(now, &y, &w, &d);
263 CHECK_ONE(cron_spec, "weekyears", y);
264 CHECK_ONE(cron_spec, "weeks", w);
265 CHECK_ONE(cron_spec, "weekdays", d);
266
267 CHECK_ONE(cron_spec, "moon", phase_of_the_moon(now));
268
269 /* If we get here, either no fields were specified (which is success), or all
270 * the fields that were specified had their conditions met (which is also a
271 * success). Thus, the result is success.
272 */
273 return pcmk_rc_ok;
274 }
275
276 static void
update_field(crm_time_t * t,xmlNode * xml,const char * attr,void (* time_fn)(crm_time_t *,int))277 update_field(crm_time_t *t, xmlNode *xml, const char *attr,
278 void (*time_fn)(crm_time_t *, int))
279 {
280 long long value;
281
282 if ((pcmk__scan_ll(crm_element_value(xml, attr), &value, 0LL) == pcmk_rc_ok)
283 && (value != 0LL) && (value >= INT_MIN) && (value <= INT_MAX)) {
284 time_fn(t, (int) value);
285 }
286 }
287
288 crm_time_t *
pe_parse_xml_duration(crm_time_t * start,xmlNode * duration_spec)289 pe_parse_xml_duration(crm_time_t * start, xmlNode * duration_spec)
290 {
291 crm_time_t *end = crm_time_new_undefined();
292
293 crm_time_set(end, start);
294
295 update_field(end, duration_spec, "years", crm_time_add_years);
296 update_field(end, duration_spec, "months", crm_time_add_months);
297 update_field(end, duration_spec, "weeks", crm_time_add_weeks);
298 update_field(end, duration_spec, "days", crm_time_add_days);
299 update_field(end, duration_spec, "hours", crm_time_add_hours);
300 update_field(end, duration_spec, "minutes", crm_time_add_minutes);
301 update_field(end, duration_spec, "seconds", crm_time_add_seconds);
302
303 return end;
304 }
305
306 // Set next_change to t if t is earlier
307 static void
crm_time_set_if_earlier(crm_time_t * next_change,crm_time_t * t)308 crm_time_set_if_earlier(crm_time_t *next_change, crm_time_t *t)
309 {
310 if ((next_change != NULL) && (t != NULL)) {
311 if (!crm_time_is_defined(next_change)
312 || (crm_time_compare(t, next_change) < 0)) {
313 crm_time_set(next_change, t);
314 }
315 }
316 }
317
318 // Information about a block of nvpair elements
319 typedef struct sorted_set_s {
320 int score; // This block's score for sorting
321 const char *name; // This block's ID
322 const char *special_name; // ID that should sort first
323 xmlNode *attr_set; // This block
324 } sorted_set_t;
325
326 static gint
sort_pairs(gconstpointer a,gconstpointer b)327 sort_pairs(gconstpointer a, gconstpointer b)
328 {
329 const sorted_set_t *pair_a = a;
330 const sorted_set_t *pair_b = b;
331
332 if (a == NULL && b == NULL) {
333 return 0;
334 } else if (a == NULL) {
335 return 1;
336 } else if (b == NULL) {
337 return -1;
338 }
339
340 if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) {
341 return -1;
342
343 } else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) {
344 return 1;
345 }
346
347 if (pair_a->score < pair_b->score) {
348 return 1;
349 } else if (pair_a->score > pair_b->score) {
350 return -1;
351 }
352 return 0;
353 }
354
355 static void
populate_hash(xmlNode * nvpair_list,GHashTable * hash,gboolean overwrite,xmlNode * top)356 populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top)
357 {
358 const char *name = NULL;
359 const char *value = NULL;
360 const char *old_value = NULL;
361 xmlNode *list = nvpair_list;
362 xmlNode *an_attr = NULL;
363
364 name = crm_element_name(list->children);
365 if (pcmk__str_eq(XML_TAG_ATTRS, name, pcmk__str_casei)) {
366 list = list->children;
367 }
368
369 for (an_attr = pcmk__xe_first_child(list); an_attr != NULL;
370 an_attr = pcmk__xe_next(an_attr)) {
371
372 if (pcmk__str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, pcmk__str_none)) {
373 xmlNode *ref_nvpair = expand_idref(an_attr, top);
374
375 name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME);
376 if (name == NULL) {
377 name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME);
378 }
379
380 value = crm_element_value(an_attr, XML_NVPAIR_ATTR_VALUE);
381 if (value == NULL) {
382 value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE);
383 }
384
385 if (name == NULL || value == NULL) {
386 continue;
387 }
388
389 old_value = g_hash_table_lookup(hash, name);
390
391 if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
392 if (old_value) {
393 crm_trace("Letting %s default (removing explicit value \"%s\")",
394 name, value);
395 g_hash_table_remove(hash, name);
396 }
397 continue;
398
399 } else if (old_value == NULL) {
400 crm_trace("Setting %s=\"%s\"", name, value);
401 g_hash_table_insert(hash, strdup(name), strdup(value));
402
403 } else if (overwrite) {
404 crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")",
405 name, value, old_value);
406 g_hash_table_replace(hash, strdup(name), strdup(value));
407 }
408 }
409 }
410 }
411
412 #if ENABLE_VERSIONED_ATTRS
413 static xmlNode*
get_versioned_rule(xmlNode * attr_set)414 get_versioned_rule(xmlNode * attr_set)
415 {
416 xmlNode * rule = NULL;
417 xmlNode * expr = NULL;
418
419 for (rule = pcmk__xe_first_child(attr_set); rule != NULL;
420 rule = pcmk__xe_next(rule)) {
421
422 if (pcmk__str_eq((const char *)rule->name, XML_TAG_RULE,
423 pcmk__str_none)) {
424 for (expr = pcmk__xe_first_child(rule); expr != NULL;
425 expr = pcmk__xe_next(expr)) {
426
427 if (find_expression_type(expr) == version_expr) {
428 return rule;
429 }
430 }
431 }
432 }
433
434 return NULL;
435 }
436
437 static void
add_versioned_attributes(xmlNode * attr_set,xmlNode * versioned_attrs)438 add_versioned_attributes(xmlNode * attr_set, xmlNode * versioned_attrs)
439 {
440 xmlNode *attr_set_copy = NULL;
441 xmlNode *rule = NULL;
442 xmlNode *expr = NULL;
443
444 if (!attr_set || !versioned_attrs) {
445 return;
446 }
447
448 attr_set_copy = copy_xml(attr_set);
449
450 rule = get_versioned_rule(attr_set_copy);
451 if (!rule) {
452 free_xml(attr_set_copy);
453 return;
454 }
455
456 expr = pcmk__xe_first_child(rule);
457 while (expr != NULL) {
458 if (find_expression_type(expr) != version_expr) {
459 xmlNode *node = expr;
460
461 expr = pcmk__xe_next(expr);
462 free_xml(node);
463 } else {
464 expr = pcmk__xe_next(expr);
465 }
466 }
467
468 add_node_nocopy(versioned_attrs, NULL, attr_set_copy);
469 }
470 #endif
471
472 typedef struct unpack_data_s {
473 gboolean overwrite;
474 void *hash;
475 crm_time_t *next_change;
476 pe_rule_eval_data_t *rule_data;
477 xmlNode *top;
478 } unpack_data_t;
479
480 static void
unpack_attr_set(gpointer data,gpointer user_data)481 unpack_attr_set(gpointer data, gpointer user_data)
482 {
483 sorted_set_t *pair = data;
484 unpack_data_t *unpack_data = user_data;
485
486 if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data,
487 unpack_data->next_change)) {
488 return;
489 }
490
491 #if ENABLE_VERSIONED_ATTRS
492 if (get_versioned_rule(pair->attr_set) && !(unpack_data->rule_data->node_hash &&
493 g_hash_table_lookup_extended(unpack_data->rule_data->node_hash,
494 CRM_ATTR_RA_VERSION, NULL, NULL))) {
495 // we haven't actually tested versioned expressions yet
496 return;
497 }
498 #endif
499
500 crm_trace("Adding attributes from %s (score %d) %s overwrite",
501 pair->name, pair->score,
502 (unpack_data->overwrite? "with" : "without"));
503 populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top);
504 }
505
506 #if ENABLE_VERSIONED_ATTRS
507 static void
unpack_versioned_attr_set(gpointer data,gpointer user_data)508 unpack_versioned_attr_set(gpointer data, gpointer user_data)
509 {
510 sorted_set_t *pair = data;
511 unpack_data_t *unpack_data = user_data;
512
513 if (pe_eval_rules(pair->attr_set, unpack_data->rule_data,
514 unpack_data->next_change)) {
515 add_versioned_attributes(pair->attr_set, unpack_data->hash);
516 }
517 }
518 #endif
519
520 /*!
521 * \internal
522 * \brief Create a sorted list of nvpair blocks
523 *
524 * \param[in] top XML document root (used to expand id-ref's)
525 * \param[in] xml_obj XML element containing blocks of nvpair elements
526 * \param[in] set_name If not NULL, only get blocks of this element type
527 * \param[in] always_first If not NULL, sort block with this ID as first
528 *
529 * \return List of sorted_set_t entries for nvpair blocks
530 */
531 static GList *
make_pairs(xmlNode * top,xmlNode * xml_obj,const char * set_name,const char * always_first)532 make_pairs(xmlNode *top, xmlNode *xml_obj, const char *set_name,
533 const char *always_first)
534 {
535 GList *unsorted = NULL;
536
537 if (xml_obj == NULL) {
538 return NULL;
539 }
540 for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
541 attr_set = pcmk__xe_next(attr_set)) {
542
543 if (pcmk__str_eq(set_name, (const char *) attr_set->name,
544 pcmk__str_null_matches)) {
545 const char *score = NULL;
546 sorted_set_t *pair = NULL;
547 xmlNode *expanded_attr_set = expand_idref(attr_set, top);
548
549 if (expanded_attr_set == NULL) {
550 // Schema (if not "none") prevents this
551 continue;
552 }
553
554 pair = calloc(1, sizeof(sorted_set_t));
555 pair->name = ID(expanded_attr_set);
556 pair->special_name = always_first;
557 pair->attr_set = expanded_attr_set;
558
559 score = crm_element_value(expanded_attr_set, XML_RULE_ATTR_SCORE);
560 pair->score = char2score(score);
561
562 unsorted = g_list_prepend(unsorted, pair);
563 }
564 }
565 return g_list_sort(unsorted, sort_pairs);
566 }
567
568 /*!
569 * \internal
570 * \brief Extract nvpair blocks contained by an XML element into a hash table
571 *
572 * \param[in] top XML document root (used to expand id-ref's)
573 * \param[in] xml_obj XML element containing blocks of nvpair elements
574 * \param[in] set_name If not NULL, only use blocks of this element type
575 * \param[out] hash Where to store extracted name/value pairs
576 * \param[in] always_first If not NULL, process block with this ID first
577 * \param[in] overwrite Whether to replace existing values with same name
578 * \param[in] rule_data Matching parameters to use when unpacking
579 * \param[out] next_change If not NULL, set to when rule evaluation will change
580 * \param[in] unpack_func Function to call to unpack each block
581 */
582 static void
unpack_nvpair_blocks(xmlNode * top,xmlNode * xml_obj,const char * set_name,void * hash,const char * always_first,gboolean overwrite,pe_rule_eval_data_t * rule_data,crm_time_t * next_change,GFunc unpack_func)583 unpack_nvpair_blocks(xmlNode *top, xmlNode *xml_obj, const char *set_name,
584 void *hash, const char *always_first, gboolean overwrite,
585 pe_rule_eval_data_t *rule_data, crm_time_t *next_change,
586 GFunc unpack_func)
587 {
588 GList *pairs = make_pairs(top, xml_obj, set_name, always_first);
589
590 if (pairs) {
591 unpack_data_t data = {
592 .hash = hash,
593 .overwrite = overwrite,
594 .next_change = next_change,
595 .top = top,
596 .rule_data = rule_data
597 };
598
599 g_list_foreach(pairs, unpack_func, &data);
600 g_list_free_full(pairs, free);
601 }
602 }
603
604 void
pe_eval_nvpairs(xmlNode * top,xmlNode * xml_obj,const char * set_name,pe_rule_eval_data_t * rule_data,GHashTable * hash,const char * always_first,gboolean overwrite,crm_time_t * next_change)605 pe_eval_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name,
606 pe_rule_eval_data_t *rule_data, GHashTable *hash,
607 const char *always_first, gboolean overwrite,
608 crm_time_t *next_change)
609 {
610 unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first,
611 overwrite, rule_data, next_change, unpack_attr_set);
612 }
613
614 /*!
615 * \brief Extract nvpair blocks contained by an XML element into a hash table
616 *
617 * \param[in] top XML document root (used to expand id-ref's)
618 * \param[in] xml_obj XML element containing blocks of nvpair elements
619 * \param[in] set_name Element name to identify nvpair blocks
620 * \param[in] node_hash Node attributes to use when evaluating rules
621 * \param[out] hash Where to store extracted name/value pairs
622 * \param[in] always_first If not NULL, process block with this ID first
623 * \param[in] overwrite Whether to replace existing values with same name
624 * \param[in] now Time to use when evaluating rules
625 * \param[out] next_change If not NULL, set to when rule evaluation will change
626 */
627 void
pe_unpack_nvpairs(xmlNode * top,xmlNode * xml_obj,const char * set_name,GHashTable * node_hash,GHashTable * hash,const char * always_first,gboolean overwrite,crm_time_t * now,crm_time_t * next_change)628 pe_unpack_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name,
629 GHashTable *node_hash, GHashTable *hash,
630 const char *always_first, gboolean overwrite,
631 crm_time_t *now, crm_time_t *next_change)
632 {
633 pe_rule_eval_data_t rule_data = {
634 .node_hash = node_hash,
635 .role = RSC_ROLE_UNKNOWN,
636 .now = now,
637 .match_data = NULL,
638 .rsc_data = NULL,
639 .op_data = NULL
640 };
641
642 pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash,
643 always_first, overwrite, next_change);
644 }
645
646 #if ENABLE_VERSIONED_ATTRS
647 void
pe_eval_versioned_attributes(xmlNode * top,xmlNode * xml_obj,const char * set_name,pe_rule_eval_data_t * rule_data,xmlNode * hash,crm_time_t * next_change)648 pe_eval_versioned_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name,
649 pe_rule_eval_data_t *rule_data, xmlNode *hash,
650 crm_time_t *next_change)
651 {
652 unpack_nvpair_blocks(top, xml_obj, set_name, hash, NULL, FALSE, rule_data,
653 next_change, unpack_versioned_attr_set);
654 }
655 #endif
656
657 char *
pe_expand_re_matches(const char * string,pe_re_match_data_t * match_data)658 pe_expand_re_matches(const char *string, pe_re_match_data_t *match_data)
659 {
660 size_t len = 0;
661 int i;
662 const char *p, *last_match_index;
663 char *p_dst, *result = NULL;
664
665 if (pcmk__str_empty(string) || !match_data) {
666 return NULL;
667 }
668
669 p = last_match_index = string;
670
671 while (*p) {
672 if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
673 i = *(p + 1) - '0';
674 if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
675 match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
676 len += p - last_match_index + (match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so);
677 last_match_index = p + 2;
678 }
679 p++;
680 }
681 p++;
682 }
683 len += p - last_match_index + 1;
684
685 /* FIXME: Excessive? */
686 if (len - 1 <= 0) {
687 return NULL;
688 }
689
690 p_dst = result = calloc(1, len);
691 p = string;
692
693 while (*p) {
694 if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
695 i = *(p + 1) - '0';
696 if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
697 match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
698 /* rm_eo can be equal to rm_so, but then there is nothing to do */
699 int match_len = match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so;
700 memcpy(p_dst, match_data->string + match_data->pmatch[i].rm_so, match_len);
701 p_dst += match_len;
702 }
703 p++;
704 } else {
705 *(p_dst) = *(p);
706 p_dst++;
707 }
708 p++;
709 }
710
711 return result;
712 }
713
714 #if ENABLE_VERSIONED_ATTRS
715 GHashTable*
pe_unpack_versioned_parameters(xmlNode * versioned_params,const char * ra_version)716 pe_unpack_versioned_parameters(xmlNode *versioned_params, const char *ra_version)
717 {
718 GHashTable *hash = pcmk__strkey_table(free, free);
719
720 if (versioned_params && ra_version) {
721 GHashTable *node_hash = pcmk__strkey_table(free, free);
722 xmlNode *attr_set = pcmk__xe_first_child(versioned_params);
723
724 if (attr_set) {
725 g_hash_table_insert(node_hash, strdup(CRM_ATTR_RA_VERSION),
726 strdup(ra_version));
727 pe_unpack_nvpairs(NULL, versioned_params,
728 crm_element_name(attr_set), node_hash, hash, NULL,
729 FALSE, NULL, NULL);
730 }
731
732 g_hash_table_destroy(node_hash);
733 }
734
735 return hash;
736 }
737 #endif
738
739 gboolean
pe_eval_rules(xmlNode * ruleset,pe_rule_eval_data_t * rule_data,crm_time_t * next_change)740 pe_eval_rules(xmlNode *ruleset, pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
741 {
742 // If there are no rules, pass by default
743 gboolean ruleset_default = TRUE;
744
745 for (xmlNode *rule = first_named_child(ruleset, XML_TAG_RULE);
746 rule != NULL; rule = crm_next_same_xml(rule)) {
747
748 ruleset_default = FALSE;
749 if (pe_eval_expr(rule, rule_data, next_change)) {
750 /* Only the deprecated "lifetime" element of location constraints
751 * may contain more than one rule at the top level -- the schema
752 * limits a block of nvpairs to a single top-level rule. So, this
753 * effectively means that a lifetime is active if any rule it
754 * contains is active.
755 */
756 return TRUE;
757 }
758 }
759
760 return ruleset_default;
761 }
762
763 gboolean
pe_eval_expr(xmlNode * rule,pe_rule_eval_data_t * rule_data,crm_time_t * next_change)764 pe_eval_expr(xmlNode *rule, pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
765 {
766 xmlNode *expr = NULL;
767 gboolean test = TRUE;
768 gboolean empty = TRUE;
769 gboolean passed = TRUE;
770 gboolean do_and = TRUE;
771 const char *value = NULL;
772
773 rule = expand_idref(rule, NULL);
774 value = crm_element_value(rule, XML_RULE_ATTR_BOOLEAN_OP);
775 if (pcmk__str_eq(value, "or", pcmk__str_casei)) {
776 do_and = FALSE;
777 passed = FALSE;
778 }
779
780 crm_trace("Testing rule %s", ID(rule));
781 for (expr = pcmk__xe_first_child(rule); expr != NULL;
782 expr = pcmk__xe_next(expr)) {
783
784 test = pe_eval_subexpr(expr, rule_data, next_change);
785 empty = FALSE;
786
787 if (test && do_and == FALSE) {
788 crm_trace("Expression %s/%s passed", ID(rule), ID(expr));
789 return TRUE;
790
791 } else if (test == FALSE && do_and) {
792 crm_trace("Expression %s/%s failed", ID(rule), ID(expr));
793 return FALSE;
794 }
795 }
796
797 if (empty) {
798 crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule));
799 }
800
801 crm_trace("Rule %s %s", ID(rule), passed ? "passed" : "failed");
802 return passed;
803 }
804
805 gboolean
pe_eval_subexpr(xmlNode * expr,pe_rule_eval_data_t * rule_data,crm_time_t * next_change)806 pe_eval_subexpr(xmlNode *expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
807 {
808 gboolean accept = FALSE;
809 const char *uname = NULL;
810
811 switch (find_expression_type(expr)) {
812 case nested_rule:
813 accept = pe_eval_expr(expr, rule_data, next_change);
814 break;
815 case attr_expr:
816 case loc_expr:
817 /* these expressions can never succeed if there is
818 * no node to compare with
819 */
820 if (rule_data->node_hash != NULL) {
821 accept = pe__eval_attr_expr(expr, rule_data);
822 }
823 break;
824
825 case time_expr:
826 switch (pe__eval_date_expr(expr, rule_data, next_change)) {
827 case pcmk_rc_within_range:
828 case pcmk_rc_ok:
829 accept = TRUE;
830 break;
831
832 default:
833 accept = FALSE;
834 break;
835 }
836 break;
837
838 case role_expr:
839 accept = pe__eval_role_expr(expr, rule_data);
840 break;
841
842 case rsc_expr:
843 accept = pe__eval_rsc_expr(expr, rule_data);
844 break;
845
846 case op_expr:
847 accept = pe__eval_op_expr(expr, rule_data);
848 break;
849
850 #if ENABLE_VERSIONED_ATTRS
851 case version_expr:
852 if (rule_data->node_hash &&
853 g_hash_table_lookup_extended(rule_data->node_hash,
854 CRM_ATTR_RA_VERSION, NULL, NULL)) {
855 accept = pe__eval_attr_expr(expr, rule_data);
856 } else {
857 // we are going to test it when we have ra-version
858 accept = TRUE;
859 }
860 break;
861 #endif
862
863 default:
864 CRM_CHECK(FALSE /* bad type */ , return FALSE);
865 accept = FALSE;
866 }
867 if (rule_data->node_hash) {
868 uname = g_hash_table_lookup(rule_data->node_hash, CRM_ATTR_UNAME);
869 }
870
871 crm_trace("Expression %s %s on %s",
872 ID(expr), accept ? "passed" : "failed", uname ? uname : "all nodes");
873 return accept;
874 }
875
876 /*!
877 * \internal
878 * \brief Compare two values in a rule's node attribute expression
879 *
880 * \param[in] l_val Value on left-hand side of comparison
881 * \param[in] r_val Value on right-hand side of comparison
882 * \param[in] type How to interpret the values (allowed values:
883 * \c "string", \c "integer", \c "number",
884 * \c "version", \c NULL)
885 * \param[in] op Type of comparison
886 *
887 * \return -1 if <tt>(l_val < r_val)</tt>,
888 * 0 if <tt>(l_val == r_val)</tt>,
889 * 1 if <tt>(l_val > r_val)</tt>
890 */
891 static int
compare_attr_expr_vals(const char * l_val,const char * r_val,const char * type,const char * op)892 compare_attr_expr_vals(const char *l_val, const char *r_val, const char *type,
893 const char *op)
894 {
895 int cmp = 0;
896
897 if (l_val != NULL && r_val != NULL) {
898 if (type == NULL) {
899 if (pcmk__strcase_any_of(op, "lt", "lte", "gt", "gte", NULL)) {
900 if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) {
901 type = "number";
902 } else {
903 type = "integer";
904 }
905
906 } else {
907 type = "string";
908 }
909 crm_trace("Defaulting to %s based comparison for '%s' op", type, op);
910 }
911
912 if (pcmk__str_eq(type, "string", pcmk__str_casei)) {
913 cmp = strcasecmp(l_val, r_val);
914
915 } else if (pcmk__str_eq(type, "integer", pcmk__str_casei)) {
916 long long l_val_num;
917 int rc1 = pcmk__scan_ll(l_val, &l_val_num, 0LL);
918
919 long long r_val_num;
920 int rc2 = pcmk__scan_ll(r_val, &r_val_num, 0LL);
921
922 if ((rc1 == pcmk_rc_ok) && (rc2 == pcmk_rc_ok)) {
923 if (l_val_num < r_val_num) {
924 cmp = -1;
925 } else if (l_val_num > r_val_num) {
926 cmp = 1;
927 } else {
928 cmp = 0;
929 }
930
931 } else {
932 crm_debug("Integer parse error. Comparing %s and %s as strings",
933 l_val, r_val);
934 cmp = compare_attr_expr_vals(l_val, r_val, "string", op);
935 }
936
937 } else if (pcmk__str_eq(type, "number", pcmk__str_casei)) {
938 double l_val_num;
939 double r_val_num;
940
941 int rc1 = pcmk__scan_double(l_val, &l_val_num, NULL, NULL);
942 int rc2 = pcmk__scan_double(r_val, &r_val_num, NULL, NULL);
943
944 if (rc1 == pcmk_rc_ok && rc2 == pcmk_rc_ok) {
945 if (l_val_num < r_val_num) {
946 cmp = -1;
947 } else if (l_val_num > r_val_num) {
948 cmp = 1;
949 } else {
950 cmp = 0;
951 }
952
953 } else {
954 crm_debug("Floating-point parse error. Comparing %s and %s as "
955 "strings", l_val, r_val);
956 cmp = compare_attr_expr_vals(l_val, r_val, "string", op);
957 }
958
959 } else if (pcmk__str_eq(type, "version", pcmk__str_casei)) {
960 cmp = compare_version(l_val, r_val);
961
962 }
963
964 } else if (l_val == NULL && r_val == NULL) {
965 cmp = 0;
966 } else if (r_val == NULL) {
967 cmp = 1;
968 } else { // l_val == NULL && r_val != NULL
969 cmp = -1;
970 }
971
972 return cmp;
973 }
974
975 /*!
976 * \internal
977 * \brief Check whether an attribute expression evaluates to \c true
978 *
979 * \param[in] l_val Value on left-hand side of comparison
980 * \param[in] r_val Value on right-hand side of comparison
981 * \param[in] type How to interpret the values (allowed values:
982 * \c "string", \c "integer", \c "number",
983 * \c "version", \c NULL)
984 * \param[in] op Type of comparison.
985 *
986 * \return \c true if expression evaluates to \c true, \c false
987 * otherwise
988 */
989 static bool
accept_attr_expr(const char * l_val,const char * r_val,const char * type,const char * op)990 accept_attr_expr(const char *l_val, const char *r_val, const char *type,
991 const char *op)
992 {
993 int cmp;
994
995 if (pcmk__str_eq(op, "defined", pcmk__str_casei)) {
996 return (l_val != NULL);
997
998 } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) {
999 return (l_val == NULL);
1000
1001 }
1002
1003 cmp = compare_attr_expr_vals(l_val, r_val, type, op);
1004
1005 if (pcmk__str_eq(op, "eq", pcmk__str_casei)) {
1006 return (cmp == 0);
1007
1008 } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) {
1009 return (cmp != 0);
1010
1011 } else if (l_val == NULL || r_val == NULL) {
1012 // The comparison is meaningless from this point on
1013 return false;
1014
1015 } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) {
1016 return (cmp < 0);
1017
1018 } else if (pcmk__str_eq(op, "lte", pcmk__str_casei)) {
1019 return (cmp <= 0);
1020
1021 } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) {
1022 return (cmp > 0);
1023
1024 } else if (pcmk__str_eq(op, "gte", pcmk__str_casei)) {
1025 return (cmp >= 0);
1026 }
1027
1028 return false; // Should never reach this point
1029 }
1030
1031 /*!
1032 * \internal
1033 * \brief Get correct value according to value-source
1034 *
1035 * \param[in] value value given in rule expression
1036 * \param[in] value_source value-source given in rule expressions
1037 * \param[in] match_data If not NULL, resource back-references and params
1038 */
1039 static const char *
expand_value_source(const char * value,const char * value_source,pe_match_data_t * match_data)1040 expand_value_source(const char *value, const char *value_source,
1041 pe_match_data_t *match_data)
1042 {
1043 GHashTable *table = NULL;
1044
1045 if (pcmk__str_empty(value)) {
1046 return NULL; // value_source is irrelevant
1047
1048 } else if (pcmk__str_eq(value_source, "param", pcmk__str_casei)) {
1049 table = match_data->params;
1050
1051 } else if (pcmk__str_eq(value_source, "meta", pcmk__str_casei)) {
1052 table = match_data->meta;
1053
1054 } else { // literal
1055 return value;
1056 }
1057
1058 if (table == NULL) {
1059 return NULL;
1060 }
1061 return (const char *) g_hash_table_lookup(table, value);
1062 }
1063
1064 /*!
1065 * \internal
1066 * \brief Evaluate a node attribute expression based on #uname, #id, #kind,
1067 * or a generic node attribute
1068 *
1069 * \param[in] expr XML of rule expression
1070 * \param[in] rule_data The match_data and node_hash members are used
1071 *
1072 * \return TRUE if rule_data satisfies the expression, FALSE otherwise
1073 */
1074 gboolean
pe__eval_attr_expr(xmlNodePtr expr,pe_rule_eval_data_t * rule_data)1075 pe__eval_attr_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data)
1076 {
1077 gboolean attr_allocated = FALSE;
1078 const char *h_val = NULL;
1079
1080 const char *op = NULL;
1081 const char *type = NULL;
1082 const char *attr = NULL;
1083 const char *value = NULL;
1084 const char *value_source = NULL;
1085
1086 attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
1087 op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
1088 value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
1089 type = crm_element_value(expr, XML_EXPR_ATTR_TYPE);
1090 value_source = crm_element_value(expr, XML_EXPR_ATTR_VALUE_SOURCE);
1091
1092 if (attr == NULL || op == NULL) {
1093 pe_err("Invalid attribute or operation in expression"
1094 " (\'%s\' \'%s\' \'%s\')", crm_str(attr), crm_str(op), crm_str(value));
1095 return FALSE;
1096 }
1097
1098 if (rule_data->match_data != NULL) {
1099 // Expand any regular expression submatches (%0-%9) in attribute name
1100 if (rule_data->match_data->re != NULL) {
1101 char *resolved_attr = pe_expand_re_matches(attr, rule_data->match_data->re);
1102
1103 if (resolved_attr != NULL) {
1104 attr = (const char *) resolved_attr;
1105 attr_allocated = TRUE;
1106 }
1107 }
1108
1109 // Get value appropriate to value-source
1110 value = expand_value_source(value, value_source, rule_data->match_data);
1111 }
1112
1113 if (rule_data->node_hash != NULL) {
1114 h_val = (const char *)g_hash_table_lookup(rule_data->node_hash, attr);
1115 }
1116
1117 if (attr_allocated) {
1118 free((char *)attr);
1119 attr = NULL;
1120 }
1121
1122 return accept_attr_expr(h_val, value, type, op);
1123 }
1124
1125 /*!
1126 * \internal
1127 * \brief Evaluate a date_expression
1128 *
1129 * \param[in] expr XML of rule expression
1130 * \param[in] rule_data Only the now member is used
1131 * \param[out] next_change If not NULL, set to when evaluation will change
1132 *
1133 * \return Standard Pacemaker return code
1134 */
1135 int
pe__eval_date_expr(xmlNodePtr expr,pe_rule_eval_data_t * rule_data,crm_time_t * next_change)1136 pe__eval_date_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change)
1137 {
1138 crm_time_t *start = NULL;
1139 crm_time_t *end = NULL;
1140 const char *value = NULL;
1141 const char *op = crm_element_value(expr, "operation");
1142
1143 xmlNode *duration_spec = NULL;
1144 xmlNode *date_spec = NULL;
1145
1146 // "undetermined" will also be returned for parsing errors
1147 int rc = pcmk_rc_undetermined;
1148
1149 crm_trace("Testing expression: %s", ID(expr));
1150
1151 duration_spec = first_named_child(expr, "duration");
1152 date_spec = first_named_child(expr, "date_spec");
1153
1154 value = crm_element_value(expr, "start");
1155 if (value != NULL) {
1156 start = crm_time_new(value);
1157 }
1158 value = crm_element_value(expr, "end");
1159 if (value != NULL) {
1160 end = crm_time_new(value);
1161 }
1162
1163 if (start != NULL && end == NULL && duration_spec != NULL) {
1164 end = pe_parse_xml_duration(start, duration_spec);
1165 }
1166
1167 if (pcmk__str_eq(op, "in_range", pcmk__str_null_matches | pcmk__str_casei)) {
1168 if ((start == NULL) && (end == NULL)) {
1169 // in_range requires at least one of start or end
1170 } else if ((start != NULL) && (crm_time_compare(rule_data->now, start) < 0)) {
1171 rc = pcmk_rc_before_range;
1172 crm_time_set_if_earlier(next_change, start);
1173 } else if ((end != NULL) && (crm_time_compare(rule_data->now, end) > 0)) {
1174 rc = pcmk_rc_after_range;
1175 } else {
1176 rc = pcmk_rc_within_range;
1177 if (end && next_change) {
1178 // Evaluation doesn't change until second after end
1179 crm_time_add_seconds(end, 1);
1180 crm_time_set_if_earlier(next_change, end);
1181 }
1182 }
1183
1184 } else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) {
1185 rc = pe_cron_range_satisfied(rule_data->now, date_spec);
1186 // @TODO set next_change appropriately
1187
1188 } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) {
1189 if (start == NULL) {
1190 // gt requires start
1191 } else if (crm_time_compare(rule_data->now, start) > 0) {
1192 rc = pcmk_rc_within_range;
1193 } else {
1194 rc = pcmk_rc_before_range;
1195
1196 // Evaluation doesn't change until second after start
1197 crm_time_add_seconds(start, 1);
1198 crm_time_set_if_earlier(next_change, start);
1199 }
1200
1201 } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) {
1202 if (end == NULL) {
1203 // lt requires end
1204 } else if (crm_time_compare(rule_data->now, end) < 0) {
1205 rc = pcmk_rc_within_range;
1206 crm_time_set_if_earlier(next_change, end);
1207 } else {
1208 rc = pcmk_rc_after_range;
1209 }
1210 }
1211
1212 crm_time_free(start);
1213 crm_time_free(end);
1214 return rc;
1215 }
1216
1217 gboolean
pe__eval_op_expr(xmlNodePtr expr,pe_rule_eval_data_t * rule_data)1218 pe__eval_op_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) {
1219 const char *name = crm_element_value(expr, XML_NVPAIR_ATTR_NAME);
1220 const char *interval_s = crm_element_value(expr, XML_LRM_ATTR_INTERVAL);
1221 guint interval;
1222
1223 crm_trace("Testing op_defaults expression: %s", ID(expr));
1224
1225 if (rule_data->op_data == NULL) {
1226 crm_trace("No operations data provided");
1227 return FALSE;
1228 }
1229
1230 interval = crm_parse_interval_spec(interval_s);
1231 if (interval == 0 && errno != 0) {
1232 crm_trace("Could not parse interval: %s", interval_s);
1233 return FALSE;
1234 }
1235
1236 if (interval_s != NULL && interval != rule_data->op_data->interval) {
1237 crm_trace("Interval doesn't match: %d != %d", interval, rule_data->op_data->interval);
1238 return FALSE;
1239 }
1240
1241 if (!pcmk__str_eq(name, rule_data->op_data->op_name, pcmk__str_none)) {
1242 crm_trace("Name doesn't match: %s != %s", name, rule_data->op_data->op_name);
1243 return FALSE;
1244 }
1245
1246 return TRUE;
1247 }
1248
1249 /*!
1250 * \internal
1251 * \brief Evaluate a node attribute expression based on #role
1252 *
1253 * \param[in] expr XML of rule expression
1254 * \param[in] rule_data Only the role member is used
1255 *
1256 * \return TRUE if rule_data->role satisfies the expression, FALSE otherwise
1257 */
1258 gboolean
pe__eval_role_expr(xmlNodePtr expr,pe_rule_eval_data_t * rule_data)1259 pe__eval_role_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data)
1260 {
1261 gboolean accept = FALSE;
1262 const char *op = NULL;
1263 const char *value = NULL;
1264
1265 if (rule_data->role == RSC_ROLE_UNKNOWN) {
1266 return accept;
1267 }
1268
1269 value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
1270 op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
1271
1272 if (pcmk__str_eq(op, "defined", pcmk__str_casei)) {
1273 if (rule_data->role > RSC_ROLE_STARTED) {
1274 accept = TRUE;
1275 }
1276
1277 } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) {
1278 if ((rule_data->role > RSC_ROLE_UNKNOWN)
1279 && (rule_data->role < RSC_ROLE_UNPROMOTED)) {
1280 accept = TRUE;
1281 }
1282
1283 } else if (pcmk__str_eq(op, "eq", pcmk__str_casei)) {
1284 if (text2role(value) == rule_data->role) {
1285 accept = TRUE;
1286 }
1287
1288 } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) {
1289 // Test "ne" only with promotable clone roles
1290 if ((rule_data->role > RSC_ROLE_UNKNOWN)
1291 && (rule_data->role < RSC_ROLE_UNPROMOTED)) {
1292 accept = FALSE;
1293
1294 } else if (text2role(value) != rule_data->role) {
1295 accept = TRUE;
1296 }
1297 }
1298 return accept;
1299 }
1300
1301 gboolean
pe__eval_rsc_expr(xmlNodePtr expr,pe_rule_eval_data_t * rule_data)1302 pe__eval_rsc_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data)
1303 {
1304 const char *class = crm_element_value(expr, XML_AGENT_ATTR_CLASS);
1305 const char *provider = crm_element_value(expr, XML_AGENT_ATTR_PROVIDER);
1306 const char *type = crm_element_value(expr, XML_EXPR_ATTR_TYPE);
1307
1308 crm_trace("Testing rsc_defaults expression: %s", ID(expr));
1309
1310 if (rule_data->rsc_data == NULL) {
1311 crm_trace("No resource data provided");
1312 return FALSE;
1313 }
1314
1315 if (class != NULL &&
1316 !pcmk__str_eq(class, rule_data->rsc_data->standard, pcmk__str_none)) {
1317 crm_trace("Class doesn't match: %s != %s", class, rule_data->rsc_data->standard);
1318 return FALSE;
1319 }
1320
1321 if ((provider == NULL && rule_data->rsc_data->provider != NULL) ||
1322 (provider != NULL && rule_data->rsc_data->provider == NULL) ||
1323 !pcmk__str_eq(provider, rule_data->rsc_data->provider, pcmk__str_none)) {
1324 crm_trace("Provider doesn't match: %s != %s", provider, rule_data->rsc_data->provider);
1325 return FALSE;
1326 }
1327
1328 if (type != NULL &&
1329 !pcmk__str_eq(type, rule_data->rsc_data->agent, pcmk__str_none)) {
1330 crm_trace("Agent doesn't match: %s != %s", type, rule_data->rsc_data->agent);
1331 return FALSE;
1332 }
1333
1334 return TRUE;
1335 }
1336
1337 // Deprecated functions kept only for backward API compatibility
1338
1339 #include <crm/pengine/rules_compat.h>
1340
1341 gboolean
test_ruleset(xmlNode * ruleset,GHashTable * node_hash,crm_time_t * now)1342 test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now)
1343 {
1344 return pe_evaluate_rules(ruleset, node_hash, now, NULL);
1345 }
1346
1347 gboolean
test_rule(xmlNode * rule,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now)1348 test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
1349 {
1350 return pe_test_rule(rule, node_hash, role, now, NULL, NULL);
1351 }
1352
1353 gboolean
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)1354 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)
1355 {
1356 pe_match_data_t match_data = {
1357 .re = re_match_data,
1358 .params = NULL,
1359 .meta = NULL,
1360 };
1361 return pe_test_rule(rule, node_hash, role, now, NULL, &match_data);
1362 }
1363
1364 gboolean
pe_test_rule_full(xmlNode * rule,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now,pe_match_data_t * match_data)1365 pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
1366 crm_time_t *now, pe_match_data_t *match_data)
1367 {
1368 return pe_test_rule(rule, node_hash, role, now, NULL, match_data);
1369 }
1370
1371 gboolean
test_expression(xmlNode * expr,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now)1372 test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
1373 {
1374 return pe_test_expression(expr, node_hash, role, now, NULL, NULL);
1375 }
1376
1377 gboolean
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)1378 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)
1379 {
1380 pe_match_data_t match_data = {
1381 .re = re_match_data,
1382 .params = NULL,
1383 .meta = NULL,
1384 };
1385 return pe_test_expression(expr, node_hash, role, now, NULL, &match_data);
1386 }
1387
1388 gboolean
pe_test_expression_full(xmlNode * expr,GHashTable * node_hash,enum rsc_role_e role,crm_time_t * now,pe_match_data_t * match_data)1389 pe_test_expression_full(xmlNode *expr, GHashTable *node_hash,
1390 enum rsc_role_e role, crm_time_t *now,
1391 pe_match_data_t *match_data)
1392 {
1393 return pe_test_expression(expr, node_hash, role, now, NULL, match_data);
1394 }
1395
1396 void
unpack_instance_attributes(xmlNode * top,xmlNode * xml_obj,const char * set_name,GHashTable * node_hash,GHashTable * hash,const char * always_first,gboolean overwrite,crm_time_t * now)1397 unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name,
1398 GHashTable *node_hash, GHashTable *hash,
1399 const char *always_first, gboolean overwrite,
1400 crm_time_t *now)
1401 {
1402 pe_rule_eval_data_t rule_data = {
1403 .node_hash = node_hash,
1404 .role = RSC_ROLE_UNKNOWN,
1405 .now = now,
1406 .match_data = NULL,
1407 .rsc_data = NULL,
1408 .op_data = NULL
1409 };
1410
1411 unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first,
1412 overwrite, &rule_data, NULL, unpack_attr_set);
1413 }
1414
1415 // End deprecated API
1416