root/lib/common/rules.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__condition_type
  2. loggable_parent_id
  3. check_range
  4. pcmk__evaluate_date_spec
  5. pcmk__unpack_duration
  6. evaluate_in_range
  7. evaluate_gt
  8. evaluate_lt
  9. pcmk__evaluate_date_expression
  10. process_submatches
  11. pcmk__replace_submatches
  12. pcmk__parse_comparison
  13. pcmk__parse_type
  14. pcmk__cmp_by_type
  15. pcmk__parse_source
  16. pcmk__parse_combine
  17. evaluate_attr_comparison
  18. value_from_source
  19. pcmk__evaluate_attr_expression
  20. pcmk__evaluate_rsc_expression
  21. pcmk__evaluate_op_expression
  22. pcmk__evaluate_condition
  23. pcmk_evaluate_rule

   1 /*
   2  * Copyright 2004-2024 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 
  12 #include <stdio.h>                          // NULL, size_t
  13 #include <stdbool.h>                        // bool
  14 #include <ctype.h>                          // isdigit()
  15 #include <regex.h>                          // regmatch_t
  16 #include <stdint.h>                         // uint32_t
  17 #include <inttypes.h>                       // PRIu32
  18 #include <glib.h>                           // gboolean, FALSE
  19 #include <libxml/tree.h>                    // xmlNode
  20 
  21 #include <crm/common/scheduler.h>
  22 
  23 #include <crm/common/iso8601_internal.h>
  24 #include <crm/common/nvpair_internal.h>
  25 #include <crm/common/rules_internal.h>
  26 #include <crm/common/scheduler_internal.h>
  27 #include "crmcommon_private.h"
  28 
  29 /*!
  30  * \internal
  31  * \brief Get the condition type corresponding to given condition XML
  32  *
  33  * \param[in] condition  Rule condition XML
  34  *
  35  * \return Condition type corresponding to \p condition
  36  */
  37 enum expression_type
  38 pcmk__condition_type(const xmlNode *condition)
     /* [previous][next][first][last][top][bottom][index][help] */
  39 {
  40     const char *name = NULL;
  41 
  42     // Expression types based on element name
  43 
  44     if (pcmk__xe_is(condition, PCMK_XE_DATE_EXPRESSION)) {
  45         return pcmk__condition_datetime;
  46 
  47     } else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
  48         return pcmk__condition_resource;
  49 
  50     } else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
  51         return pcmk__condition_operation;
  52 
  53     } else if (pcmk__xe_is(condition, PCMK_XE_RULE)) {
  54         return pcmk__condition_rule;
  55 
  56     } else if (!pcmk__xe_is(condition, PCMK_XE_EXPRESSION)) {
  57         return pcmk__condition_unknown;
  58     }
  59 
  60     // Expression types based on node attribute name
  61 
  62     name = crm_element_value(condition, PCMK_XA_ATTRIBUTE);
  63 
  64     if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
  65                          NULL)) {
  66         return pcmk__condition_location;
  67     }
  68 
  69     return pcmk__condition_attribute;
  70 }
  71 
  72 /*!
  73  * \internal
  74  * \brief Get parent XML element's ID for logging purposes
  75  *
  76  * \param[in] xml  XML of a subelement
  77  *
  78  * \return ID of \p xml's parent for logging purposes (guaranteed non-NULL)
  79  */
  80 static const char *
  81 loggable_parent_id(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  82 {
  83     // Default if called without parent (likely for unit testing)
  84     const char *parent_id = "implied";
  85 
  86     if ((xml != NULL) && (xml->parent != NULL)) {
  87         parent_id = pcmk__xe_id(xml->parent);
  88         if (parent_id == NULL) { // Not possible with schema validation enabled
  89             parent_id = "without ID";
  90         }
  91     }
  92     return parent_id;
  93 }
  94 
  95 /*!
  96  * \internal
  97  * \brief Check an integer value against a range from a date specification
  98  *
  99  * \param[in] date_spec  XML of PCMK_XE_DATE_SPEC element to check
 100  * \param[in] id         XML ID of parent date expression for logging purposes
 101  * \param[in] attr       Name of XML attribute with range to check against
 102  * \param[in] value      Value to compare against range
 103  *
 104  * \return Standard Pacemaker return code (specifically, pcmk_rc_before_range,
 105  *         pcmk_rc_after_range, or pcmk_rc_ok to indicate that result is either
 106  *         within range or undetermined)
 107  * \note We return pcmk_rc_ok for an undetermined result so we can continue
 108  *       checking the next range attribute.
 109  */
 110 static int
 111 check_range(const xmlNode *date_spec, const char *id, const char *attr,
     /* [previous][next][first][last][top][bottom][index][help] */
 112             uint32_t value)
 113 {
 114     int rc = pcmk_rc_ok;
 115     const char *range = crm_element_value(date_spec, attr);
 116     long long low, high;
 117 
 118     if (range == NULL) {
 119         // Attribute not present
 120 
 121     } else if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
 122         // Invalid range
 123         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
 124                          "as not passing because '%s' is not a valid range "
 125                          "for " PCMK_XE_DATE_SPEC " attribute %s",
 126                          id, range, attr);
 127         rc = pcmk_rc_unpack_error;
 128 
 129     } else if ((low != -1) && (value < low)) {
 130         rc = pcmk_rc_before_range;
 131 
 132     } else if ((high != -1) && (value > high)) {
 133         rc = pcmk_rc_after_range;
 134     }
 135 
 136     crm_trace(PCMK_XE_DATE_EXPRESSION " %s: " PCMK_XE_DATE_SPEC
 137               " %s='%s' for %" PRIu32 ": %s",
 138               id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
 139     return rc;
 140 }
 141 
 142 /*!
 143  * \internal
 144  * \brief Evaluate a date specification for a given date/time
 145  *
 146  * \param[in] date_spec  XML of PCMK_XE_DATE_SPEC element to evaluate
 147  * \param[in] now        Time to check
 148  *
 149  * \return Standard Pacemaker return code (specifically, EINVAL for NULL
 150  *         arguments, pcmk_rc_unpack_error if the specification XML is invalid,
 151  *         \c pcmk_rc_ok if \p now is within the specification's ranges, or
 152  *         \c pcmk_rc_before_range or \c pcmk_rc_after_range as appropriate)
 153  */
 154 int
 155 pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
     /* [previous][next][first][last][top][bottom][index][help] */
 156 {
 157     const char *id = NULL;
 158     const char *parent_id = loggable_parent_id(date_spec);
 159 
 160     // Range attributes that can be specified for a PCMK_XE_DATE_SPEC element
 161     struct range {
 162         const char *attr;
 163         uint32_t value;
 164     } ranges[] = {
 165         { PCMK_XA_YEARS, 0U },
 166         { PCMK_XA_MONTHS, 0U },
 167         { PCMK_XA_MONTHDAYS, 0U },
 168         { PCMK_XA_HOURS, 0U },
 169         { PCMK_XA_MINUTES, 0U },
 170         { PCMK_XA_SECONDS, 0U },
 171         { PCMK_XA_YEARDAYS, 0U },
 172         { PCMK_XA_WEEKYEARS, 0U },
 173         { PCMK_XA_WEEKS, 0U },
 174         { PCMK_XA_WEEKDAYS, 0U },
 175     };
 176 
 177     if ((date_spec == NULL) || (now == NULL)) {
 178         return EINVAL;
 179     }
 180 
 181     // Get specification ID (for logging)
 182     id = pcmk__xe_id(date_spec);
 183     if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
 184         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 185                          "passing because " PCMK_XE_DATE_SPEC
 186                          " subelement has no " PCMK_XA_ID, parent_id);
 187         return pcmk_rc_unpack_error;
 188     }
 189 
 190     // Year, month, day
 191     crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
 192                            &(ranges[2].value));
 193 
 194     // Hour, minute, second
 195     crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
 196                            &(ranges[5].value));
 197 
 198     // Year (redundant) and day of year
 199     crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
 200 
 201     // Week year, week of week year, day of week
 202     crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
 203                          &(ranges[9].value));
 204 
 205     for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
 206         int rc = check_range(date_spec, parent_id, ranges[i].attr,
 207                              ranges[i].value);
 208 
 209         if (rc != pcmk_rc_ok) {
 210             return rc;
 211         }
 212     }
 213 
 214     // All specified ranges passed, or none were given (also considered a pass)
 215     return pcmk_rc_ok;
 216 }
 217 
 218 #define ADD_COMPONENT(component) do {                                       \
 219         int rc = pcmk__add_time_from_xml(*end, component, duration);        \
 220         if (rc != pcmk_rc_ok) {                                             \
 221             pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "     \
 222                              "as not passing because " PCMK_XE_DURATION     \
 223                              " %s attribute %s is invalid: %s",             \
 224                              parent_id, id,                                 \
 225                              pcmk__time_component_attr(component),          \
 226                              pcmk_rc_str(rc));                              \
 227             crm_time_free(*end);                                            \
 228             *end = NULL;                                                    \
 229             return rc;                                                      \
 230         }                                                                   \
 231     } while (0)
 232 
 233 /*!
 234  * \internal
 235  * \brief Given a duration and a start time, calculate the end time
 236  *
 237  * \param[in]  duration  XML of PCMK_XE_DURATION element
 238  * \param[in]  start     Start time
 239  * \param[out] end       Where to store end time (\p *end must be NULL
 240  *                       initially)
 241  *
 242  * \return Standard Pacemaker return code
 243  * \note The caller is responsible for freeing \p *end using crm_time_free().
 244  */
 245 int
 246 pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
     /* [previous][next][first][last][top][bottom][index][help] */
 247                       crm_time_t **end)
 248 {
 249     const char *id = NULL;
 250     const char *parent_id = loggable_parent_id(duration);
 251 
 252     if ((start == NULL) || (duration == NULL)
 253         || (end == NULL) || (*end != NULL)) {
 254         return EINVAL;
 255     }
 256 
 257     // Get duration ID (for logging)
 258     id = pcmk__xe_id(duration);
 259     if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
 260         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
 261                          "as not passing because " PCMK_XE_DURATION
 262                          " subelement has no " PCMK_XA_ID, parent_id);
 263         return pcmk_rc_unpack_error;
 264     }
 265 
 266     *end = pcmk_copy_time(start);
 267 
 268     ADD_COMPONENT(pcmk__time_years);
 269     ADD_COMPONENT(pcmk__time_months);
 270     ADD_COMPONENT(pcmk__time_weeks);
 271     ADD_COMPONENT(pcmk__time_days);
 272     ADD_COMPONENT(pcmk__time_hours);
 273     ADD_COMPONENT(pcmk__time_minutes);
 274     ADD_COMPONENT(pcmk__time_seconds);
 275 
 276     return pcmk_rc_ok;
 277 }
 278 
 279 /*!
 280  * \internal
 281  * \brief Evaluate a range check for a given date/time
 282  *
 283  * \param[in]     date_expression  XML of PCMK_XE_DATE_EXPRESSION element
 284  * \param[in]     id               Expression ID for logging purposes
 285  * \param[in]     now              Date/time to compare
 286  * \param[in,out] next_change      If not NULL, set this to when the evaluation
 287  *                                 will change, if known and earlier than the
 288  *                                 original value
 289  *
 290  * \return Standard Pacemaker return code
 291  */
 292 static int
 293 evaluate_in_range(const xmlNode *date_expression, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 294                   const crm_time_t *now, crm_time_t *next_change)
 295 {
 296     crm_time_t *start = NULL;
 297     crm_time_t *end = NULL;
 298 
 299     if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
 300                               &start) != pcmk_rc_ok) {
 301         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 302                          "passing because " PCMK_XA_START " is invalid", id);
 303         return pcmk_rc_unpack_error;
 304     }
 305 
 306     if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
 307                               &end) != pcmk_rc_ok) {
 308         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 309                          "passing because " PCMK_XA_END " is invalid", id);
 310         crm_time_free(start);
 311         return pcmk_rc_unpack_error;
 312     }
 313 
 314     if ((start == NULL) && (end == NULL)) {
 315         // Not possible with schema validation enabled
 316         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 317                          "passing because " PCMK_VALUE_IN_RANGE
 318                          " requires at least one of " PCMK_XA_START " or "
 319                          PCMK_XA_END, id);
 320         return pcmk_rc_unpack_error;
 321     }
 322 
 323     if (end == NULL) {
 324         xmlNode *duration = pcmk__xe_first_child(date_expression,
 325                                                  PCMK_XE_DURATION, NULL, NULL);
 326 
 327         if (duration != NULL) {
 328             int rc = pcmk__unpack_duration(duration, start, &end);
 329 
 330             if (rc != pcmk_rc_ok) {
 331                 pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION
 332                                  " %s as not passing because duration "
 333                                  "is invalid", id);
 334                 crm_time_free(start);
 335                 return rc;
 336             }
 337         }
 338     }
 339 
 340     if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
 341         pcmk__set_time_if_earlier(next_change, start);
 342         crm_time_free(start);
 343         crm_time_free(end);
 344         return pcmk_rc_before_range;
 345     }
 346 
 347     if (end != NULL) {
 348         if (crm_time_compare(now, end) > 0) {
 349             crm_time_free(start);
 350             crm_time_free(end);
 351             return pcmk_rc_after_range;
 352         }
 353 
 354         // Evaluation doesn't change until second after end
 355         if (next_change != NULL) {
 356             crm_time_add_seconds(end, 1);
 357             pcmk__set_time_if_earlier(next_change, end);
 358         }
 359     }
 360 
 361     crm_time_free(start);
 362     crm_time_free(end);
 363     return pcmk_rc_within_range;
 364 }
 365 
 366 /*!
 367  * \internal
 368  * \brief Evaluate a greater-than check for a given date/time
 369  *
 370  * \param[in]     date_expression  XML of PCMK_XE_DATE_EXPRESSION element
 371  * \param[in]     id               Expression ID for logging purposes
 372  * \param[in]     now              Date/time to compare
 373  * \param[in,out] next_change      If not NULL, set this to when the evaluation
 374  *                                 will change, if known and earlier than the
 375  *                                 original value
 376  *
 377  * \return Standard Pacemaker return code
 378  */
 379 static int
 380 evaluate_gt(const xmlNode *date_expression, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 381             const crm_time_t *now, crm_time_t *next_change)
 382 {
 383     crm_time_t *start = NULL;
 384 
 385     if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
 386                               &start) != pcmk_rc_ok) {
 387         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 388                          "passing because " PCMK_XA_START " is invalid",
 389                          id);
 390         return pcmk_rc_unpack_error;
 391     }
 392 
 393     if (start == NULL) { // Not possible with schema validation enabled
 394         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 395                          "passing because " PCMK_VALUE_GT " requires "
 396                          PCMK_XA_START, id);
 397         return pcmk_rc_unpack_error;
 398     }
 399 
 400     if (crm_time_compare(now, start) > 0) {
 401         crm_time_free(start);
 402         return pcmk_rc_within_range;
 403     }
 404 
 405     // Evaluation doesn't change until second after start time
 406     crm_time_add_seconds(start, 1);
 407     pcmk__set_time_if_earlier(next_change, start);
 408     crm_time_free(start);
 409     return pcmk_rc_before_range;
 410 }
 411 
 412 /*!
 413  * \internal
 414  * \brief Evaluate a less-than check for a given date/time
 415  *
 416  * \param[in]     date_expression  XML of PCMK_XE_DATE_EXPRESSION element
 417  * \param[in]     id               Expression ID for logging purposes
 418  * \param[in]     now              Date/time to compare
 419  * \param[in,out] next_change      If not NULL, set this to when the evaluation
 420  *                                 will change, if known and earlier than the
 421  *                                 original value
 422  *
 423  * \return Standard Pacemaker return code
 424  */
 425 static int
 426 evaluate_lt(const xmlNode *date_expression, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 427             const crm_time_t *now, crm_time_t *next_change)
 428 {
 429     crm_time_t *end = NULL;
 430 
 431     if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
 432                               &end) != pcmk_rc_ok) {
 433         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 434                          "passing because " PCMK_XA_END " is invalid", id);
 435         return pcmk_rc_unpack_error;
 436     }
 437 
 438     if (end == NULL) { // Not possible with schema validation enabled
 439         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
 440                          "passing because " PCMK_VALUE_GT " requires "
 441                          PCMK_XA_END, id);
 442         return pcmk_rc_unpack_error;
 443     }
 444 
 445     if (crm_time_compare(now, end) < 0) {
 446         pcmk__set_time_if_earlier(next_change, end);
 447         crm_time_free(end);
 448         return pcmk_rc_within_range;
 449     }
 450 
 451     crm_time_free(end);
 452     return pcmk_rc_after_range;
 453 }
 454 
 455 /*!
 456  * \internal
 457  * \brief Evaluate a rule's date expression for a given date/time
 458  *
 459  * \param[in]     date_expression  XML of a PCMK_XE_DATE_EXPRESSION element
 460  * \param[in]     now              Time to use for evaluation
 461  * \param[in,out] next_change      If not NULL, set this to when the evaluation
 462  *                                 will change, if known and earlier than the
 463  *                                 original value
 464  *
 465  * \return Standard Pacemaker return code (unlike most other evaluation
 466  *         functions, this can return either pcmk_rc_ok or pcmk_rc_within_range
 467  *         on success)
 468  */
 469 int
 470 pcmk__evaluate_date_expression(const xmlNode *date_expression,
     /* [previous][next][first][last][top][bottom][index][help] */
 471                                const crm_time_t *now, crm_time_t *next_change)
 472 {
 473     const char *id = NULL;
 474     const char *op = NULL;
 475     int rc = pcmk_rc_ok;
 476 
 477     if ((date_expression == NULL) || (now == NULL)) {
 478         return EINVAL;
 479     }
 480 
 481     // Get expression ID (for logging)
 482     id = pcmk__xe_id(date_expression);
 483     if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
 484         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " without "
 485                          PCMK_XA_ID " as not passing");
 486         return pcmk_rc_unpack_error;
 487     }
 488 
 489     op = crm_element_value(date_expression, PCMK_XA_OPERATION);
 490     if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
 491                      pcmk__str_null_matches|pcmk__str_casei)) {
 492         rc = evaluate_in_range(date_expression, id, now, next_change);
 493 
 494     } else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
 495         xmlNode *date_spec = pcmk__xe_first_child(date_expression,
 496                                                   PCMK_XE_DATE_SPEC, NULL,
 497                                                   NULL);
 498 
 499         if (date_spec == NULL) { // Not possible with schema validation enabled
 500             pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
 501                              "as not passing because " PCMK_VALUE_DATE_SPEC
 502                              " operations require a " PCMK_XE_DATE_SPEC
 503                              " subelement", id);
 504             return pcmk_rc_unpack_error;
 505         }
 506 
 507         // @TODO set next_change appropriately
 508         rc = pcmk__evaluate_date_spec(date_spec, now);
 509 
 510     } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
 511         rc = evaluate_gt(date_expression, id, now, next_change);
 512 
 513     } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
 514         rc = evaluate_lt(date_expression, id, now, next_change);
 515 
 516     } else { // Not possible with schema validation enabled
 517         pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION
 518                          " %s as not passing because '%s' is not a valid "
 519                          PCMK_XE_OPERATION, id, op);
 520         return pcmk_rc_unpack_error;
 521     }
 522 
 523     crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
 524               id, op, pcmk_rc_str(rc), rc);
 525     return rc;
 526 }
 527 
 528 /*!
 529  * \internal
 530  * \brief Go through submatches in a string, either counting how many bytes
 531  *        would be needed for the expansion, or performing the expansion,
 532  *        as requested
 533  *
 534  * \param[in]  string      String possibly containing submatch variables
 535  * \param[in]  match       String that matched the regular expression
 536  * \param[in]  submatches  Regular expression submatches (as set by regexec())
 537  * \param[in]  nmatches    Number of entries in \p submatches[]
 538  * \param[out] expansion   If not NULL, expand string here (must be
 539  *                         pre-allocated to appropriate size)
 540  * \param[out] nbytes      If not NULL, set to size needed for expansion
 541  *
 542  * \return true if any expansion is needed, otherwise false
 543  */
 544 static bool
 545 process_submatches(const char *string, const char *match,
     /* [previous][next][first][last][top][bottom][index][help] */
 546                    const regmatch_t submatches[], int nmatches,
 547                    char *expansion, size_t *nbytes)
 548 {
 549     bool expanded = false;
 550     const char *src = string;
 551 
 552     if (nbytes != NULL) {
 553         *nbytes = 1; // Include space for terminator
 554     }
 555 
 556     while (*src != '\0') {
 557         int submatch = 0;
 558         size_t match_len = 0;
 559 
 560         if ((src[0] != '%') || !isdigit(src[1])) {
 561             /* src does not point to the first character of a %N sequence,
 562              * so expand this character as-is
 563              */
 564             if (expansion != NULL) {
 565                 *expansion++ = *src;
 566             }
 567             if (nbytes != NULL) {
 568                 ++(*nbytes);
 569             }
 570             ++src;
 571             continue;
 572         }
 573 
 574         submatch = src[1] - '0';
 575         src += 2; // Skip over %N sequence in source string
 576         expanded = true; // Expansion will be different from source
 577 
 578         // Omit sequence from expansion unless it has a non-empty match
 579         if ((nmatches <= submatch)                // Not enough submatches
 580             || (submatches[submatch].rm_so < 0)   // Pattern did not match
 581             || (submatches[submatch].rm_eo
 582                 <= submatches[submatch].rm_so)) { // Match was empty
 583             continue;
 584         }
 585 
 586         match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
 587         if (nbytes != NULL) {
 588             *nbytes += match_len;
 589         }
 590         if (expansion != NULL) {
 591             memcpy(expansion, match + submatches[submatch].rm_so,
 592                    match_len);
 593             expansion += match_len;
 594         }
 595     }
 596 
 597     return expanded;
 598 }
 599 
 600 /*!
 601  * \internal
 602  * \brief Expand any regular expression submatches (%0-%9) in a string
 603  *
 604  * \param[in] string      String possibly containing submatch variables
 605  * \param[in] match       String that matched the regular expression
 606  * \param[in] submatches  Regular expression submatches (as set by regexec())
 607  * \param[in] nmatches    Number of entries in \p submatches[]
 608  *
 609  * \return Newly allocated string identical to \p string with submatches
 610  *         expanded on success, or NULL if no expansions were needed
 611  * \note The caller is responsible for freeing the result with free()
 612  */
 613 char *
 614 pcmk__replace_submatches(const char *string, const char *match,
     /* [previous][next][first][last][top][bottom][index][help] */
 615                          const regmatch_t submatches[], int nmatches)
 616 {
 617     size_t nbytes = 0;
 618     char *result = NULL;
 619 
 620     if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
 621         return NULL; // Nothing to expand
 622     }
 623 
 624     // Calculate how much space will be needed for expanded string
 625     if (!process_submatches(string, match, submatches, nmatches, NULL,
 626                             &nbytes)) {
 627         return NULL; // No expansions needed
 628     }
 629 
 630     // Allocate enough space for expanded string
 631     result = pcmk__assert_alloc(nbytes, sizeof(char));
 632 
 633     // Expand submatches
 634     (void) process_submatches(string, match, submatches, nmatches, result,
 635                               NULL);
 636     return result;
 637 }
 638 
 639 /*!
 640  * \internal
 641  * \brief Parse a comparison type from a string
 642  *
 643  * \param[in] op  String with comparison type (valid values are
 644  *                \c PCMK_VALUE_DEFINED, \c PCMK_VALUE_NOT_DEFINED,
 645  *                \c PCMK_VALUE_EQ, \c PCMK_VALUE_NE,
 646  *                \c PCMK_VALUE_LT, \c PCMK_VALUE_LTE,
 647  *                \c PCMK_VALUE_GT, or \c PCMK_VALUE_GTE)
 648  *
 649  * \return Comparison type corresponding to \p op
 650  */
 651 enum pcmk__comparison
 652 pcmk__parse_comparison(const char *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 653 {
 654     if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
 655         return pcmk__comparison_defined;
 656 
 657     } else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
 658         return pcmk__comparison_undefined;
 659 
 660     } else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
 661         return pcmk__comparison_eq;
 662 
 663     } else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
 664         return pcmk__comparison_ne;
 665 
 666     } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
 667         return pcmk__comparison_lt;
 668 
 669     } else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
 670         return pcmk__comparison_lte;
 671 
 672     } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
 673         return pcmk__comparison_gt;
 674 
 675     } else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
 676         return pcmk__comparison_gte;
 677     }
 678 
 679     return pcmk__comparison_unknown;
 680 }
 681 
 682 /*!
 683  * \internal
 684  * \brief Parse a value type from a string
 685  *
 686  * \param[in] type    String with value type (valid values are NULL,
 687  *                    \c PCMK_VALUE_STRING, \c PCMK_VALUE_INTEGER,
 688  *                    \c PCMK_VALUE_NUMBER, and \c PCMK_VALUE_VERSION)
 689  * \param[in] op      Operation type (used only to select default)
 690  * \param[in] value1  First value being compared (used only to select default)
 691  * \param[in] value2  Second value being compared (used only to select default)
 692  */
 693 enum pcmk__type
 694 pcmk__parse_type(const char *type, enum pcmk__comparison op,
     /* [previous][next][first][last][top][bottom][index][help] */
 695                  const char *value1, const char *value2)
 696 {
 697     if (type == NULL) {
 698         switch (op) {
 699             case pcmk__comparison_lt:
 700             case pcmk__comparison_lte:
 701             case pcmk__comparison_gt:
 702             case pcmk__comparison_gte:
 703                 if (((value1 != NULL) && (strchr(value1, '.') != NULL))
 704                     || ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
 705                     return pcmk__type_number;
 706                 }
 707                 return pcmk__type_integer;
 708 
 709             default:
 710                 return pcmk__type_string;
 711         }
 712     }
 713 
 714     if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
 715         return pcmk__type_string;
 716 
 717     } else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
 718         return pcmk__type_integer;
 719 
 720     } else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
 721         return pcmk__type_number;
 722 
 723     } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
 724         return pcmk__type_version;
 725     }
 726 
 727     return pcmk__type_unknown;
 728 }
 729 
 730 /*!
 731  * \internal
 732  * \brief Compare two strings according to a given type
 733  *
 734  * \param[in] value1  String with first value to compare
 735  * \param[in] value2  String with second value to compare
 736  * \param[in] type    How to interpret the values
 737  *
 738  * \return Standard comparison result (a negative integer if \p value1 is
 739  *         lesser, 0 if the values are equal, and a positive integer if
 740  *         \p value1 is greater)
 741  */
 742 int
 743 pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
     /* [previous][next][first][last][top][bottom][index][help] */
 744 {
 745     //  NULL compares as less than non-NULL
 746     if (value2 == NULL) {
 747         return (value1 == NULL)? 0 : 1;
 748     }
 749     if (value1 == NULL) {
 750         return -1;
 751     }
 752 
 753     switch (type) {
 754         case pcmk__type_string:
 755             return strcasecmp(value1, value2);
 756 
 757         case pcmk__type_integer:
 758             {
 759                 long long integer1;
 760                 long long integer2;
 761 
 762                 if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
 763                     || (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
 764                     crm_warn("Comparing '%s' and '%s' as strings because "
 765                              "invalid as integers", value1, value2);
 766                     return strcasecmp(value1, value2);
 767                 }
 768                 return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
 769             }
 770             break;
 771 
 772         case pcmk__type_number:
 773             {
 774                 double num1;
 775                 double num2;
 776 
 777                 if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
 778                     || (pcmk__scan_double(value2, &num2, NULL,
 779                                           NULL) != pcmk_rc_ok)) {
 780                     crm_warn("Comparing '%s' and '%s' as strings because invalid as "
 781                              "numbers", value1, value2);
 782                     return strcasecmp(value1, value2);
 783                 }
 784                 return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
 785             }
 786             break;
 787 
 788         case pcmk__type_version:
 789             return compare_version(value1, value2);
 790 
 791         default: // Invalid type
 792             return 0;
 793     }
 794 }
 795 
 796 /*!
 797  * \internal
 798  * \brief Parse a reference value source from a string
 799  *
 800  * \param[in] source  String indicating reference value source
 801  *
 802  * \return Reference value source corresponding to \p source
 803  */
 804 enum pcmk__reference_source
 805 pcmk__parse_source(const char *source)
     /* [previous][next][first][last][top][bottom][index][help] */
 806 {
 807     if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
 808                      pcmk__str_casei|pcmk__str_null_matches)) {
 809         return pcmk__source_literal;
 810 
 811     } else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
 812         return pcmk__source_instance_attrs;
 813 
 814     } else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
 815         return pcmk__source_meta_attrs;
 816 
 817     } else {
 818         return pcmk__source_unknown;
 819     }
 820 }
 821 
 822 /*!
 823  * \internal
 824  * \brief Parse a boolean operator from a string
 825  *
 826  * \param[in] combine  String indicating boolean operator
 827  *
 828  * \return Enumeration value corresponding to \p combine
 829  */
 830 enum pcmk__combine
 831 pcmk__parse_combine(const char *combine)
     /* [previous][next][first][last][top][bottom][index][help] */
 832 {
 833     if (pcmk__str_eq(combine, PCMK_VALUE_AND,
 834                      pcmk__str_null_matches|pcmk__str_casei)) {
 835         return pcmk__combine_and;
 836 
 837     } else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
 838         return pcmk__combine_or;
 839 
 840     } else {
 841         return pcmk__combine_unknown;
 842     }
 843 }
 844 
 845 /*!
 846  * \internal
 847  * \brief Get the result of a node attribute comparison for rule evaluation
 848  *
 849  * \param[in] actual      Actual node attribute value
 850  * \param[in] reference   Node attribute value from rule (ignored for
 851  *                        \p comparison of \c pcmk__comparison_defined or
 852  *                        \c pcmk__comparison_undefined)
 853  * \param[in] type        How to interpret the values
 854  * \param[in] comparison  How to compare the values
 855  *
 856  * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok if the
 857  *         comparison passes, and some other value if it does not)
 858  */
 859 static int
 860 evaluate_attr_comparison(const char *actual, const char *reference,
     /* [previous][next][first][last][top][bottom][index][help] */
 861                          enum pcmk__type type, enum pcmk__comparison comparison)
 862 {
 863     int cmp = 0;
 864 
 865     switch (comparison) {
 866         case pcmk__comparison_defined:
 867             return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
 868 
 869         case pcmk__comparison_undefined:
 870             return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
 871 
 872         default:
 873             break;
 874     }
 875 
 876     cmp = pcmk__cmp_by_type(actual, reference, type);
 877 
 878     switch (comparison) {
 879         case pcmk__comparison_eq:
 880             return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
 881 
 882         case pcmk__comparison_ne:
 883             return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
 884 
 885         default:
 886             break;
 887     }
 888 
 889     if ((actual == NULL) || (reference == NULL)) {
 890         return pcmk_rc_op_unsatisfied; // Comparison would be meaningless
 891     }
 892 
 893     switch (comparison) {
 894         case pcmk__comparison_lt:
 895             return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
 896 
 897         case pcmk__comparison_lte:
 898             return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
 899 
 900         case pcmk__comparison_gt:
 901             return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
 902 
 903         case pcmk__comparison_gte:
 904             return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
 905 
 906         default: // Not possible with schema validation enabled
 907             return pcmk_rc_op_unsatisfied;
 908     }
 909 }
 910 
 911 /*!
 912  * \internal
 913  * \brief Get a reference value from a configured source
 914  *
 915  * \param[in] value       Value given in rule expression
 916  * \param[in] source      Reference value source
 917  * \param[in] rule_input  Values used to evaluate rule criteria
 918  */
 919 static const char *
 920 value_from_source(const char *value, enum pcmk__reference_source source,
     /* [previous][next][first][last][top][bottom][index][help] */
 921                   const pcmk_rule_input_t *rule_input)
 922 {
 923     GHashTable *table = NULL;
 924 
 925     switch (source) {
 926         case pcmk__source_literal:
 927             return value;
 928 
 929         case pcmk__source_instance_attrs:
 930             table = rule_input->rsc_params;
 931             break;
 932 
 933         case pcmk__source_meta_attrs:
 934             table = rule_input->rsc_meta;
 935             break;
 936 
 937         default:
 938             return NULL; // Not possible
 939     }
 940 
 941     if (table == NULL) {
 942         return NULL;
 943     }
 944     return (const char *) g_hash_table_lookup(table, value);
 945 }
 946 
 947 /*!
 948  * \internal
 949  * \brief Evaluate a node attribute rule expression
 950  *
 951  * \param[in] expression  XML of a rule's PCMK_XE_EXPRESSION subelement
 952  * \param[in] rule_input  Values used to evaluate rule criteria
 953  *
 954  * \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
 955  *         passes, some other value if it does not)
 956  */
 957 int
 958 pcmk__evaluate_attr_expression(const xmlNode *expression,
     /* [previous][next][first][last][top][bottom][index][help] */
 959                                const pcmk_rule_input_t *rule_input)
 960 {
 961     const char *id = NULL;
 962     const char *op = NULL;
 963     const char *attr = NULL;
 964     const char *type_s = NULL;
 965     const char *value = NULL;
 966     const char *actual = NULL;
 967     const char *source_s = NULL;
 968     const char *reference = NULL;
 969     char *expanded_attr = NULL;
 970     int rc = pcmk_rc_ok;
 971 
 972     enum pcmk__type type = pcmk__type_unknown;
 973     enum pcmk__reference_source source = pcmk__source_unknown;
 974     enum pcmk__comparison comparison = pcmk__comparison_unknown;
 975 
 976     if ((expression == NULL) || (rule_input == NULL)) {
 977         return EINVAL;
 978     }
 979 
 980     // Get expression ID (for logging)
 981     id = pcmk__xe_id(expression);
 982     if (pcmk__str_empty(id)) {
 983         pcmk__config_err("Treating " PCMK_XE_EXPRESSION " without " PCMK_XA_ID
 984                          " as not passing");
 985         return pcmk_rc_unpack_error;
 986     }
 987 
 988     /* Get name of node attribute to compare (expanding any %0-%9 to
 989      * regular expression submatches)
 990      */
 991     attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
 992     if (attr == NULL) {
 993         pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
 994                          "because " PCMK_XA_ATTRIBUTE " was not specified", id);
 995         return pcmk_rc_unpack_error;
 996     }
 997     expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
 998                                              rule_input->rsc_id_submatches,
 999                                              rule_input->rsc_id_nmatches);
1000     if (expanded_attr != NULL) {
1001         attr = expanded_attr;
1002     }
1003 
1004     // Get and validate operation
1005     op = crm_element_value(expression, PCMK_XA_OPERATION);
1006     comparison = pcmk__parse_comparison(op);
1007     if (comparison == pcmk__comparison_unknown) {
1008         // Not possible with schema validation enabled
1009         if (op == NULL) {
1010             pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1011                              "passing because it has no " PCMK_XA_OPERATION,
1012                              id);
1013         } else {
1014             pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1015                              "passing because '%s' is not a valid "
1016                              PCMK_XA_OPERATION, id, op);
1017         }
1018         rc = pcmk_rc_unpack_error;
1019         goto done;
1020     }
1021 
1022     // How reference value is obtained (literal, resource meta-attribute, etc.)
1023     source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
1024     source = pcmk__parse_source(source_s);
1025     if (source == pcmk__source_unknown) {
1026         // Not possible with schema validation enabled
1027         pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1028                          "because '%s' is not a valid " PCMK_XA_VALUE_SOURCE,
1029                          id, source_s);
1030         rc = pcmk_rc_unpack_error;
1031         goto done;
1032     }
1033 
1034     // Get and validate reference value
1035     value = crm_element_value(expression, PCMK_XA_VALUE);
1036     switch (comparison) {
1037         case pcmk__comparison_defined:
1038         case pcmk__comparison_undefined:
1039             if (value != NULL) {
1040                 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1041                                  "passing because " PCMK_XA_VALUE " is not "
1042                                  "allowed when " PCMK_XA_OPERATION " is %s",
1043                                  id, op);
1044                 rc = pcmk_rc_unpack_error;
1045                 goto done;
1046             }
1047             break;
1048 
1049         default:
1050             if (value == NULL) {
1051                 pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1052                                  "passing because " PCMK_XA_VALUE " is "
1053                                  "required when " PCMK_XA_OPERATION " is %s",
1054                                  id, op);
1055                 rc = pcmk_rc_unpack_error;
1056                 goto done;
1057             }
1058             reference = value_from_source(value, source, rule_input);
1059             break;
1060     }
1061 
1062     // Get actual value of node attribute
1063     if (rule_input->node_attrs != NULL) {
1064         actual = g_hash_table_lookup(rule_input->node_attrs, attr);
1065     }
1066 
1067     // Get and validate value type (after expanding reference value)
1068     type_s = crm_element_value(expression, PCMK_XA_TYPE);
1069     type = pcmk__parse_type(type_s, comparison, actual, reference);
1070     if (type == pcmk__type_unknown) {
1071         // Not possible with schema validation enabled
1072         pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1073                          "because '%s' is not a valid type", id, type_s);
1074         rc = pcmk_rc_unpack_error;
1075         goto done;
1076     }
1077 
1078     rc = evaluate_attr_comparison(actual, reference, type, comparison);
1079     switch (comparison) {
1080         case pcmk__comparison_defined:
1081         case pcmk__comparison_undefined:
1082             crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
1083                       id, pcmk_rc_str(rc), attr, op);
1084             break;
1085 
1086         default:
1087             crm_trace(PCMK_XE_EXPRESSION " %s result: "
1088                       "%s (attribute %s %s '%s' via %s source as %s type)",
1089                       id, pcmk_rc_str(rc), attr, op, pcmk__s(reference, ""),
1090                       pcmk__s(source_s, "default"), pcmk__s(type_s, "default"));
1091             break;
1092     }
1093 
1094 done:
1095     free(expanded_attr);
1096     return rc;
1097 }
1098 
1099 /*!
1100  * \internal
1101  * \brief Evaluate a resource rule expression
1102  *
1103  * \param[in] rsc_expression  XML of rule's \c PCMK_XE_RSC_EXPRESSION subelement
1104  * \param[in] rule_input      Values used to evaluate rule criteria
1105  *
1106  * \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
1107  *         passes, some other value if it does not)
1108  */
1109 int
1110 pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
     /* [previous][next][first][last][top][bottom][index][help] */
1111                               const pcmk_rule_input_t *rule_input)
1112 {
1113     const char *id = NULL;
1114     const char *standard = NULL;
1115     const char *provider = NULL;
1116     const char *type = NULL;
1117 
1118     if ((rsc_expression == NULL) || (rule_input == NULL)) {
1119         return EINVAL;
1120     }
1121 
1122     // Validate XML ID
1123     id = pcmk__xe_id(rsc_expression);
1124     if (pcmk__str_empty(id)) {
1125         // Not possible with schema validation enabled
1126         pcmk__config_err("Treating " PCMK_XE_RSC_EXPRESSION " without "
1127                          PCMK_XA_ID " as not passing");
1128         return pcmk_rc_unpack_error;
1129     }
1130 
1131     // Compare resource standard
1132     standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
1133     if ((standard != NULL)
1134         && !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
1135         crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1136                   "actual standard '%s' doesn't match '%s'",
1137                   id, pcmk__s(rule_input->rsc_standard, ""), standard);
1138         return pcmk_rc_op_unsatisfied;
1139     }
1140 
1141     // Compare resource provider
1142     provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
1143     if ((provider != NULL)
1144         && !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
1145         crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1146                   "actual provider '%s' doesn't match '%s'",
1147                   id, pcmk__s(rule_input->rsc_provider, ""), provider);
1148         return pcmk_rc_op_unsatisfied;
1149     }
1150 
1151     // Compare resource agent type
1152     type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
1153     if ((type != NULL)
1154         && !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
1155         crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1156                   "actual agent '%s' doesn't match '%s'",
1157                   id, pcmk__s(rule_input->rsc_agent, ""), type);
1158         return pcmk_rc_op_unsatisfied;
1159     }
1160 
1161     crm_trace(PCMK_XE_RSC_EXPRESSION " %s is satisfied by %s%s%s:%s",
1162               id, pcmk__s(standard, ""),
1163               ((provider == NULL)? "" : ":"), pcmk__s(provider, ""),
1164               pcmk__s(type, ""));
1165     return pcmk_rc_ok;
1166 }
1167 
1168 /*!
1169  * \internal
1170  * \brief Evaluate an operation rule expression
1171  *
1172  * \param[in] op_expression  XML of a rule's \c PCMK_XE_OP_EXPRESSION subelement
1173  * \param[in] rule_input     Values used to evaluate rule criteria
1174  *
1175  * \return Standard Pacemaker return code (\c pcmk_rc_ok if the expression
1176  *         is satisfied, some other value if it is not)
1177  */
1178 int
1179 pcmk__evaluate_op_expression(const xmlNode *op_expression,
     /* [previous][next][first][last][top][bottom][index][help] */
1180                              const pcmk_rule_input_t *rule_input)
1181 {
1182     const char *id = NULL;
1183     const char *name = NULL;
1184     const char *interval_s = NULL;
1185     guint interval_ms = 0U;
1186 
1187     if ((op_expression == NULL) || (rule_input == NULL)) {
1188         return EINVAL;
1189     }
1190 
1191     // Get operation expression ID (for logging)
1192     id = pcmk__xe_id(op_expression);
1193     if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1194         pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " without "
1195                          PCMK_XA_ID " as not passing");
1196         return pcmk_rc_unpack_error;
1197     }
1198 
1199     // Validate operation name
1200     name = crm_element_value(op_expression, PCMK_XA_NAME);
1201     if (name == NULL) { // Not possible with schema validation enabled
1202         pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1203                          "passing because it has no " PCMK_XA_NAME, id);
1204         return pcmk_rc_unpack_error;
1205     }
1206 
1207     // Validate operation interval
1208     interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
1209     if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
1210         pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1211                          "passing because '%s' is not a valid "
1212                          PCMK_META_INTERVAL,
1213                          id, interval_s);
1214         return pcmk_rc_unpack_error;
1215     }
1216 
1217     // Compare operation name
1218     if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
1219         crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1220                   "actual name '%s' doesn't match '%s'",
1221                   id, pcmk__s(rule_input->op_name, ""), name);
1222         return pcmk_rc_op_unsatisfied;
1223     }
1224 
1225     // Compare operation interval (unspecified interval matches all)
1226     if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
1227         crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1228                   "actual interval %s doesn't match %s",
1229                   id, pcmk__readable_interval(rule_input->op_interval_ms),
1230                   pcmk__readable_interval(interval_ms));
1231         return pcmk_rc_op_unsatisfied;
1232     }
1233 
1234     crm_trace(PCMK_XE_OP_EXPRESSION " %s is satisfied (name %s, interval %s)",
1235               id, name, pcmk__readable_interval(rule_input->op_interval_ms));
1236     return pcmk_rc_ok;
1237 }
1238 
1239 /*!
1240  * \internal
1241  * \brief Evaluate a rule condition
1242  *
1243  * \param[in,out] condition    XML containing a rule condition (a subrule, or an
1244  *                             expression of any type)
1245  * \param[in]     rule_input   Values used to evaluate rule criteria
1246  * \param[out]    next_change  If not NULL, set to when evaluation will change
1247  *
1248  * \return Standard Pacemaker return code (\c pcmk_rc_ok if the condition
1249  *         passes, some other value if it does not)
1250  */
1251 int
1252 pcmk__evaluate_condition(xmlNode *condition,
     /* [previous][next][first][last][top][bottom][index][help] */
1253                          const pcmk_rule_input_t *rule_input,
1254                          crm_time_t *next_change)
1255 {
1256 
1257     if ((condition == NULL) || (rule_input == NULL)) {
1258         return EINVAL;
1259     }
1260 
1261     switch (pcmk__condition_type(condition)) {
1262         case pcmk__condition_rule:
1263             return pcmk_evaluate_rule(condition, rule_input, next_change);
1264 
1265         case pcmk__condition_attribute:
1266         case pcmk__condition_location:
1267             return pcmk__evaluate_attr_expression(condition, rule_input);
1268 
1269         case pcmk__condition_datetime:
1270             {
1271                 int rc = pcmk__evaluate_date_expression(condition,
1272                                                         rule_input->now,
1273                                                         next_change);
1274 
1275                 return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
1276             }
1277 
1278         case pcmk__condition_resource:
1279             return pcmk__evaluate_rsc_expression(condition, rule_input);
1280 
1281         case pcmk__condition_operation:
1282             return pcmk__evaluate_op_expression(condition, rule_input);
1283 
1284         default: // Not possible with schema validation enabled
1285             pcmk__config_err("Treating rule condition %s as not passing "
1286                              "because %s is not a valid condition type",
1287                              pcmk__s(pcmk__xe_id(condition), "without ID"),
1288                              (const char *) condition->name);
1289             return pcmk_rc_unpack_error;
1290     }
1291 }
1292 
1293 /*!
1294  * \brief Evaluate a single rule, including all its conditions
1295  *
1296  * \param[in,out] rule         XML containing a rule definition or its id-ref
1297  * \param[in]     rule_input   Values used to evaluate rule criteria
1298  * \param[out]    next_change  If not NULL, set to when evaluation will change
1299  *
1300  * \return Standard Pacemaker return code (\c pcmk_rc_ok if the rule is
1301  *         satisfied, some other value if it is not)
1302  */
1303 int
1304 pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
     /* [previous][next][first][last][top][bottom][index][help] */
1305                    crm_time_t *next_change)
1306 {
1307     bool empty = true;
1308     int rc = pcmk_rc_ok;
1309     const char *id = NULL;
1310     const char *value = NULL;
1311     enum pcmk__combine combine = pcmk__combine_unknown;
1312 
1313     if ((rule == NULL) || (rule_input == NULL)) {
1314         return EINVAL;
1315     }
1316 
1317     rule = pcmk__xe_resolve_idref(rule, NULL);
1318     if (rule == NULL) {
1319         // Not possible with schema validation enabled; message already logged
1320         return pcmk_rc_unpack_error;
1321     }
1322 
1323     // Validate XML ID
1324     id = pcmk__xe_id(rule);
1325     if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1326         pcmk__config_err("Treating " PCMK_XE_RULE " without " PCMK_XA_ID
1327                          " as not passing");
1328         return pcmk_rc_unpack_error;
1329     }
1330 
1331     value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
1332     combine = pcmk__parse_combine(value);
1333     switch (combine) {
1334         case pcmk__combine_and:
1335             // For "and", rc defaults to success (reset on failure below)
1336             break;
1337 
1338         case pcmk__combine_or:
1339             // For "or", rc defaults to failure (reset on success below)
1340             rc = pcmk_rc_op_unsatisfied;
1341             break;
1342 
1343         default: // Not possible with schema validation enabled
1344             pcmk__config_err("Treating " PCMK_XE_RULE " %s as not passing "
1345                              "because '%s' is not a valid " PCMK_XA_BOOLEAN_OP,
1346                              id, value);
1347             return pcmk_rc_unpack_error;
1348     }
1349 
1350     // Evaluate each condition
1351     for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
1352          condition != NULL; condition = pcmk__xe_next(condition, NULL)) {
1353 
1354         empty = false;
1355         if (pcmk__evaluate_condition(condition, rule_input,
1356                                      next_change) == pcmk_rc_ok) {
1357             if (combine == pcmk__combine_or) {
1358                 rc = pcmk_rc_ok; // Any pass is final for "or"
1359                 break;
1360             }
1361         } else if (combine == pcmk__combine_and) {
1362             rc = pcmk_rc_op_unsatisfied; // Any failure is final for "and"
1363             break;
1364         }
1365     }
1366 
1367     if (empty) { // Not possible with schema validation enabled
1368         pcmk__config_warn("Ignoring rule %s because it contains no conditions",
1369                           id);
1370         rc = pcmk_rc_ok;
1371     }
1372 
1373     crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
1374     return rc;
1375 }

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