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. get_versioned_rule
  15. add_versioned_attributes
  16. unpack_attr_set
  17. unpack_versioned_attr_set
  18. make_pairs
  19. unpack_nvpair_blocks
  20. pe_eval_nvpairs
  21. pe_unpack_nvpairs
  22. pe_eval_versioned_attributes
  23. pe_expand_re_matches
  24. pe_unpack_versioned_parameters
  25. pe_eval_rules
  26. pe_eval_expr
  27. pe_eval_subexpr
  28. compare_attr_expr_vals
  29. accept_attr_expr
  30. expand_value_source
  31. pe__eval_attr_expr
  32. pe__eval_date_expr
  33. pe__eval_op_expr
  34. pe__eval_role_expr
  35. pe__eval_rsc_expr
  36. test_ruleset
  37. test_rule
  38. pe_test_rule_re
  39. pe_test_rule_full
  40. test_expression
  41. pe_test_expression_re
  42. pe_test_expression_full
  43. unpack_instance_attributes

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

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