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. phase_of_the_moon
  4. check_range
  5. pcmk__evaluate_date_spec
  6. pcmk__unpack_duration
  7. evaluate_in_range
  8. evaluate_gt
  9. evaluate_lt
  10. pcmk__evaluate_date_expression
  11. process_submatches
  12. pcmk__replace_submatches
  13. pcmk__parse_comparison
  14. pcmk__parse_type
  15. pcmk__cmp_by_type
  16. pcmk__parse_source
  17. pcmk__parse_combine
  18. evaluate_attr_comparison
  19. value_from_source
  20. pcmk__evaluate_attr_expression
  21. pcmk__evaluate_rsc_expression
  22. pcmk__evaluate_op_expression
  23. pcmk__evaluate_condition
  24. pcmk_evaluate_rule
  25. pcmk__evaluate_rules

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

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