root/lib/pengine/rules.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pe_evaluate_rules
  2. pe_test_rule
  3. pe_test_expression
  4. find_expression_type
  5. phase_of_the_moon
  6. check_one
  7. check_passes
  8. pe_cron_range_satisfied
  9. update_field
  10. parse_xml_duration
  11. crm_time_set_if_earlier
  12. sort_pairs
  13. populate_hash
  14. unpack_attr_set
  15. make_pairs
  16. pe_eval_nvpairs
  17. pe_unpack_nvpairs
  18. pe_expand_re_matches
  19. pe_eval_rules
  20. pe_eval_expr
  21. pe_eval_subexpr
  22. compare_attr_expr_vals
  23. accept_attr_expr
  24. expand_value_source
  25. pe__eval_attr_expr
  26. pe__eval_date_expr
  27. pe__eval_op_expr
  28. pe__eval_role_expr
  29. pe__eval_rsc_expr
  30. test_ruleset
  31. test_rule
  32. pe_test_rule_re
  33. pe_test_rule_full
  34. test_expression
  35. pe_test_expression_re
  36. pe_test_expression_full
  37. unpack_instance_attributes

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

/* [previous][next][first][last][top][bottom][index][help] */