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. pe_test_role_expression
  6. pe_test_attr_expression
  7. phase_of_the_moon
  8. check_one
  9. check_passes
  10. pe_cron_range_satisfied
  11. pe_parse_xml_duration
  12. pe_test_date_expression
  13. crm_time_set_if_earlier
  14. pe_eval_date_expression
  15. sort_pairs
  16. populate_hash
  17. get_versioned_rule
  18. add_versioned_attributes
  19. unpack_attr_set
  20. unpack_versioned_attr_set
  21. make_pairs
  22. unpack_nvpair_blocks
  23. pe_eval_nvpairs
  24. pe_unpack_nvpairs
  25. pe_eval_versioned_attributes
  26. pe_expand_re_matches
  27. pe_unpack_versioned_parameters
  28. pe_eval_rules
  29. pe_eval_expr
  30. pe_eval_subexpr
  31. compare_attr_expr_vals
  32. accept_attr_expr
  33. pe__eval_attr_expr
  34. pe__eval_date_expr
  35. pe__eval_op_expr
  36. pe__eval_role_expr
  37. pe__eval_rsc_expr
  38. test_ruleset
  39. test_rule
  40. pe_test_rule_re
  41. pe_test_rule_full
  42. test_expression
  43. pe_test_expression_re
  44. pe_test_expression_full
  45. unpack_instance_attributes

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

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