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

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