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

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