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. pe_parse_xml_duration
  11. crm_time_set_if_earlier
  12. sort_pairs
  13. populate_hash
  14. unpack_attr_set
  15. make_pairs
  16. unpack_nvpair_blocks
  17. pe_eval_nvpairs
  18. pe_unpack_nvpairs
  19. pe_expand_re_matches
  20. pe_eval_rules
  21. pe_eval_expr
  22. pe_eval_subexpr
  23. compare_attr_expr_vals
  24. accept_attr_expr
  25. expand_value_source
  26. pe__eval_attr_expr
  27. pe__eval_date_expr
  28. pe__eval_op_expr
  29. pe__eval_role_expr
  30. pe__eval_rsc_expr
  31. test_ruleset
  32. test_rule
  33. pe_test_rule_re
  34. pe_test_rule_full
  35. test_expression
  36. pe_test_expression_re
  37. pe_test_expression_full
  38. unpack_instance_attributes

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

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