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

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