LCOV - code coverage report
Current view: top level - common - rules.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 494 530 93.2 %
Date: 2024-05-07 11:09:47 Functions: 23 25 92.0 %

          Line data    Source code
       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           0 : pcmk__condition_type(const xmlNode *condition)
      38             : {
      39           0 :     const char *name = NULL;
      40             : 
      41             :     // Expression types based on element name
      42             : 
      43           0 :     if (pcmk__xe_is(condition, PCMK_XE_DATE_EXPRESSION)) {
      44           0 :         return pcmk__condition_datetime;
      45             : 
      46           0 :     } else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
      47           0 :         return pcmk__condition_resource;
      48             : 
      49           0 :     } else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
      50           0 :         return pcmk__condition_operation;
      51             : 
      52           0 :     } else if (pcmk__xe_is(condition, PCMK_XE_RULE)) {
      53           0 :         return pcmk__condition_rule;
      54             : 
      55           0 :     } else if (!pcmk__xe_is(condition, PCMK_XE_EXPRESSION)) {
      56           0 :         return pcmk__condition_unknown;
      57             :     }
      58             : 
      59             :     // Expression types based on node attribute name
      60             : 
      61           0 :     name = crm_element_value(condition, PCMK_XA_ATTRIBUTE);
      62             : 
      63           0 :     if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
      64             :                          NULL)) {
      65           0 :         return pcmk__condition_location;
      66             :     }
      67             : 
      68           0 :     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          58 : loggable_parent_id(const xmlNode *xml)
      81             : {
      82             :     // Default if called without parent (likely for unit testing)
      83          58 :     const char *parent_id = "implied";
      84             : 
      85          58 :     if ((xml != NULL) && (xml->parent != NULL)) {
      86          52 :         parent_id = pcmk__xe_id(xml->parent);
      87          52 :         if (parent_id == NULL) { // Not possible with schema validation enabled
      88          31 :             parent_id = "without ID";
      89             :         }
      90             :     }
      91          58 :     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          28 : phase_of_the_moon(const crm_time_t *now)
     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          28 :     crm_time_get_ordinal(now, &y, &diy);
     123          28 :     goldn = (y % 19) + 1;
     124          28 :     epact = (11 * goldn + 18) % 30;
     125          28 :     if (((epact == 25) && (goldn > 11)) || (epact == 24)) {
     126           2 :         epact++;
     127             :     }
     128          28 :     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         222 : check_range(const xmlNode *date_spec, const char *id, const char *attr,
     148             :             uint32_t value)
     149             : {
     150         222 :     int rc = pcmk_rc_ok;
     151         222 :     const char *range = crm_element_value(date_spec, attr);
     152             :     long long low, high;
     153             : 
     154         222 :     if (range == NULL) { // Attribute not present
     155         185 :         goto bail;
     156             :     }
     157             : 
     158          37 :     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           2 :         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          35 :     } else if ((low != -1) && (value < low)) {
     168           4 :         rc = pcmk_rc_before_range;
     169             : 
     170          31 :     } else if ((high != -1) && (value > high)) {
     171           6 :         rc = pcmk_rc_after_range;
     172             :     }
     173             : 
     174          25 : bail:
     175         222 :     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         222 :     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          31 : pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
     194             : {
     195          31 :     const char *id = NULL;
     196          31 :     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          31 :     } 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          31 :     if ((date_spec == NULL) || (now == NULL)) {
     217           3 :         return EINVAL;
     218             :     }
     219             : 
     220             :     // Get specification ID (for logging)
     221          28 :     id = pcmk__xe_id(date_spec);
     222          28 :     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           7 :         pcmk__config_warn(PCMK_XE_DATE_SPEC " subelement of "
     227             :                           PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
     228             :                           parent_id);
     229           7 :         id = "without ID"; // for logging
     230             :     }
     231             : 
     232             :     // Year, month, day
     233          28 :     crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
     234             :                            &(ranges[2].value));
     235             : 
     236             :     // Hour, minute, second
     237          28 :     crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
     238             :                            &(ranges[5].value));
     239             : 
     240             :     // Year (redundant) and day of year
     241          28 :     crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
     242             : 
     243             :     // Week year, week of week year, day of week
     244          28 :     crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
     245             :                          &(ranges[9].value));
     246             : 
     247             :     // Moon phase (deprecated)
     248          28 :     ranges[10].value = phase_of_the_moon(now);
     249          28 :     if (crm_element_value(date_spec, PCMK__XA_MOON) != NULL) {
     250           0 :         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         240 :     for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
     257         222 :         int rc = check_range(date_spec, id, ranges[i].attr, ranges[i].value);
     258             : 
     259         222 :         if (rc != pcmk_rc_ok) {
     260          10 :             return rc;
     261             :         }
     262             :     }
     263             : 
     264             :     // All specified ranges passed, or none were given (also considered a pass)
     265          18 :     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          27 : pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
     293             :                       crm_time_t **end)
     294             : {
     295          27 :     int rc = pcmk_rc_ok;
     296          27 :     const char *id = NULL;
     297          27 :     const char *parent_id = loggable_parent_id(duration);
     298             : 
     299          27 :     if ((start == NULL) || (duration == NULL)
     300          21 :         || (end == NULL) || (*end != NULL)) {
     301           8 :         return EINVAL;
     302             :     }
     303             : 
     304             :     // Get duration ID (for logging)
     305          19 :     id = pcmk__xe_id(duration);
     306          19 :     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           1 :         pcmk__config_warn(PCMK_XE_DURATION " subelement of "
     311             :                           PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
     312             :                           parent_id);
     313           1 :         id = "without ID";
     314             :     }
     315             : 
     316          19 :     *end = pcmk_copy_time(start);
     317             : 
     318          19 :     ADD_COMPONENT(pcmk__time_years);
     319          19 :     ADD_COMPONENT(pcmk__time_months);
     320          19 :     ADD_COMPONENT(pcmk__time_weeks);
     321          19 :     ADD_COMPONENT(pcmk__time_days);
     322          19 :     ADD_COMPONENT(pcmk__time_hours);
     323          19 :     ADD_COMPONENT(pcmk__time_minutes);
     324          19 :     ADD_COMPONENT(pcmk__time_seconds);
     325             : 
     326          19 :     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          43 : evaluate_in_range(const xmlNode *date_expression, const char *id,
     344             :                   const crm_time_t *now, crm_time_t *next_change)
     345             : {
     346          43 :     crm_time_t *start = NULL;
     347          43 :     crm_time_t *end = NULL;
     348             : 
     349          43 :     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           6 :         pcmk__config_warn("Ignoring " PCMK_XA_START " in "
     355             :                           PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
     356             :                           id);
     357             :     }
     358             : 
     359          43 :     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           6 :         pcmk__config_warn("Ignoring " PCMK_XA_END " in "
     365             :                           PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
     366             :                           id);
     367             :     }
     368             : 
     369          43 :     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           4 :         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           4 :         return pcmk_rc_undetermined;
     378             :     }
     379             : 
     380          39 :     if (end == NULL) {
     381          24 :         xmlNode *duration = pcmk__xe_first_child(date_expression,
     382             :                                                  PCMK_XE_DURATION, NULL, NULL);
     383             : 
     384          24 :         if (duration != NULL) {
     385             :             /* @COMPAT When we can break behavioral backward compatibility,
     386             :              * return the result of this if not OK
     387             :              */
     388          16 :             pcmk__unpack_duration(duration, start, &end);
     389             :         }
     390             :     }
     391             : 
     392          39 :     if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
     393          13 :         pcmk__set_time_if_earlier(next_change, start);
     394          13 :         crm_time_free(start);
     395          13 :         crm_time_free(end);
     396          13 :         return pcmk_rc_before_range;
     397             :     }
     398             : 
     399          26 :     if (end != NULL) {
     400          22 :         if (crm_time_compare(now, end) > 0) {
     401           6 :             crm_time_free(start);
     402           6 :             crm_time_free(end);
     403           6 :             return pcmk_rc_after_range;
     404             :         }
     405             : 
     406             :         // Evaluation doesn't change until second after end
     407          16 :         if (next_change != NULL) {
     408          16 :             crm_time_add_seconds(end, 1);
     409          16 :             pcmk__set_time_if_earlier(next_change, end);
     410             :         }
     411             :     }
     412             : 
     413          20 :     crm_time_free(start);
     414          20 :     crm_time_free(end);
     415          20 :     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           7 : evaluate_gt(const xmlNode *date_expression, const char *id,
     433             :             const crm_time_t *now, crm_time_t *next_change)
     434             : {
     435           7 :     crm_time_t *start = NULL;
     436             : 
     437           7 :     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           1 :         pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
     443             :                           "passing because " PCMK_XA_START " is invalid",
     444             :                           id);
     445           1 :         return pcmk_rc_undetermined;
     446             :     }
     447             : 
     448           6 :     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           1 :         pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
     453             :                           "passing because " PCMK_VALUE_GT " requires "
     454             :                           PCMK_XA_START, id);
     455           1 :         return pcmk_rc_undetermined;
     456             :     }
     457             : 
     458           5 :     if (crm_time_compare(now, start) > 0) {
     459           2 :         crm_time_free(start);
     460           2 :         return pcmk_rc_within_range;
     461             :     }
     462             : 
     463             :     // Evaluation doesn't change until second after start time
     464           3 :     crm_time_add_seconds(start, 1);
     465           3 :     pcmk__set_time_if_earlier(next_change, start);
     466           3 :     crm_time_free(start);
     467           3 :     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           8 : evaluate_lt(const xmlNode *date_expression, const char *id,
     485             :             const crm_time_t *now, crm_time_t *next_change)
     486             : {
     487           8 :     crm_time_t *end = NULL;
     488             : 
     489           8 :     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           1 :         pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
     495             :                           "passing because " PCMK_XA_END " is invalid", id);
     496           1 :         return pcmk_rc_undetermined;
     497             :     }
     498             : 
     499           7 :     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           1 :         pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
     504             :                           "passing because " PCMK_VALUE_GT " requires "
     505             :                           PCMK_XA_END, id);
     506           1 :         return pcmk_rc_undetermined;
     507             :     }
     508             : 
     509           6 :     if (crm_time_compare(now, end) < 0) {
     510           4 :         pcmk__set_time_if_earlier(next_change, end);
     511           4 :         crm_time_free(end);
     512           4 :         return pcmk_rc_within_range;
     513             :     }
     514             : 
     515           2 :     crm_time_free(end);
     516           2 :     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          74 : pcmk__evaluate_date_expression(const xmlNode *date_expression,
     535             :                                const crm_time_t *now, crm_time_t *next_change)
     536             : {
     537          74 :     const char *id = NULL;
     538          74 :     const char *op = NULL;
     539          74 :     int rc = pcmk_rc_undetermined;
     540             : 
     541          74 :     if ((date_expression == NULL) || (now == NULL)) {
     542           3 :         return EINVAL;
     543             :     }
     544             : 
     545             :     // Get expression ID (for logging)
     546          71 :     id = pcmk__xe_id(date_expression);
     547          71 :     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           7 :         pcmk__config_warn(PCMK_XE_DATE_EXPRESSION " element has no "
     552             :                           PCMK_XA_ID);
     553           7 :         id = "without ID"; // for logging
     554             :     }
     555             : 
     556          71 :     op = crm_element_value(date_expression, PCMK_XA_OPERATION);
     557          71 :     if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
     558             :                      pcmk__str_null_matches|pcmk__str_casei)) {
     559          43 :         rc = evaluate_in_range(date_expression, id, now, next_change);
     560             : 
     561          28 :     } else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
     562          12 :         xmlNode *date_spec = pcmk__xe_first_child(date_expression,
     563             :                                                   PCMK_XE_DATE_SPEC, NULL,
     564             :                                                   NULL);
     565             : 
     566          12 :         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           1 :             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          11 :             rc = pcmk__evaluate_date_spec(date_spec, now);
     577             :         }
     578             : 
     579          16 :     } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
     580           7 :         rc = evaluate_gt(date_expression, id, now, next_change);
     581             : 
     582           9 :     } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
     583           8 :         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           1 :         pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION
     590             :                           " %s as not passing because '%s' is not a valid "
     591             :                           PCMK_XE_OPERATION, op);
     592             :     }
     593             : 
     594          71 :     crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
     595             :               id, op, pcmk_rc_str(rc), rc);
     596          71 :     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          64 : process_submatches(const char *string, const char *match,
     617             :                    const regmatch_t submatches[], int nmatches,
     618             :                    char *expansion, size_t *nbytes)
     619             : {
     620          64 :     bool expanded = false;
     621          64 :     const char *src = string;
     622             : 
     623          64 :     if (nbytes != NULL) {
     624          57 :         *nbytes = 1; // Include space for terminator
     625             :     }
     626             : 
     627         487 :     while (*src != '\0') {
     628         423 :         int submatch = 0;
     629         423 :         size_t match_len = 0;
     630             : 
     631         423 :         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         409 :             if (expansion != NULL) {
     636          90 :                 *expansion++ = *src;
     637             :             }
     638         409 :             if (nbytes != NULL) {
     639         319 :                 ++(*nbytes);
     640             :             }
     641         409 :             ++src;
     642         409 :             continue;
     643             :         }
     644             : 
     645          14 :         submatch = src[1] - '0';
     646          14 :         src += 2; // Skip over %N sequence in source string
     647          14 :         expanded = true; // Expansion will be different from source
     648             : 
     649             :         // Omit sequence from expansion unless it has a non-empty match
     650          14 :         if ((nmatches <= submatch)                // Not enough submatches
     651          12 :             || (submatches[submatch].rm_so < 0)   // Pattern did not match
     652          12 :             || (submatches[submatch].rm_eo
     653          12 :                 <= submatches[submatch].rm_so)) { // Match was empty
     654           4 :             continue;
     655             :         }
     656             : 
     657          10 :         match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
     658          10 :         if (nbytes != NULL) {
     659           5 :             *nbytes += match_len;
     660             :         }
     661          10 :         if (expansion != NULL) {
     662           5 :             memcpy(expansion, match + submatches[submatch].rm_so,
     663             :                    match_len);
     664           5 :             expansion += match_len;
     665             :         }
     666             :     }
     667             : 
     668          64 :     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          62 : pcmk__replace_submatches(const char *string, const char *match,
     686             :                          const regmatch_t submatches[], int nmatches)
     687             : {
     688          62 :     size_t nbytes = 0;
     689          62 :     char *result = NULL;
     690             : 
     691          62 :     if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
     692           5 :         return NULL; // Nothing to expand
     693             :     }
     694             : 
     695             :     // Calculate how much space will be needed for expanded string
     696          57 :     if (!process_submatches(string, match, submatches, nmatches, NULL,
     697             :                             &nbytes)) {
     698          50 :         return NULL; // No expansions needed
     699             :     }
     700             : 
     701             :     // Allocate enough space for expanded string
     702           7 :     result = pcmk__assert_alloc(nbytes, sizeof(char));
     703             : 
     704             :     // Expand submatches
     705           7 :     (void) process_submatches(string, match, submatches, nmatches, result,
     706             :                               NULL);
     707           7 :     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          63 : pcmk__parse_comparison(const char *op)
     724             : {
     725          63 :     if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
     726           9 :         return pcmk__comparison_defined;
     727             : 
     728          54 :     } else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
     729           3 :         return pcmk__comparison_undefined;
     730             : 
     731          51 :     } else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
     732          26 :         return pcmk__comparison_eq;
     733             : 
     734          25 :     } else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
     735           3 :         return pcmk__comparison_ne;
     736             : 
     737          22 :     } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
     738           3 :         return pcmk__comparison_lt;
     739             : 
     740          19 :     } else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
     741           4 :         return pcmk__comparison_lte;
     742             : 
     743          15 :     } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
     744           6 :         return pcmk__comparison_gt;
     745             : 
     746           9 :     } else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
     747           5 :         return pcmk__comparison_gte;
     748             :     }
     749             : 
     750           4 :     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          72 : pcmk__parse_type(const char *type, enum pcmk__comparison op,
     766             :                  const char *value1, const char *value2)
     767             : {
     768          72 :     if (type == NULL) {
     769          41 :         switch (op) {
     770          13 :             case pcmk__comparison_lt:
     771             :             case pcmk__comparison_lte:
     772             :             case pcmk__comparison_gt:
     773             :             case pcmk__comparison_gte:
     774          13 :                 if (((value1 != NULL) && (strchr(value1, '.') != NULL))
     775          10 :                     || ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
     776           6 :                     return pcmk__type_number;
     777             :                 }
     778           7 :                 return pcmk__type_integer;
     779             : 
     780          28 :             default:
     781          28 :                 return pcmk__type_string;
     782             :         }
     783             :     }
     784             : 
     785          31 :     if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
     786           5 :         return pcmk__type_string;
     787             : 
     788          26 :     } else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
     789          17 :         return pcmk__type_integer;
     790             : 
     791           9 :     } else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
     792           4 :         return pcmk__type_number;
     793             : 
     794           5 :     } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
     795           4 :         return pcmk__type_version;
     796             :     }
     797             : 
     798           1 :     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          79 : pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
     815             : {
     816             :     //  NULL compares as less than non-NULL
     817          79 :     if (value2 == NULL) {
     818           6 :         return (value1 == NULL)? 0 : 1;
     819             :     }
     820          73 :     if (value1 == NULL) {
     821           2 :         return -1;
     822             :     }
     823             : 
     824          71 :     switch (type) {
     825          25 :         case pcmk__type_string:
     826          25 :             return strcasecmp(value1, value2);
     827             : 
     828          25 :         case pcmk__type_integer:
     829             :             {
     830             :                 long long integer1;
     831             :                 long long integer2;
     832             : 
     833          25 :                 if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
     834          23 :                     || (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
     835           4 :                     crm_warn("Comparing '%s' and '%s' as strings because "
     836             :                              "invalid as integers", value1, value2);
     837           4 :                     return strcasecmp(value1, value2);
     838             :                 }
     839          21 :                 return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
     840             :             }
     841             :             break;
     842             : 
     843          11 :         case pcmk__type_number:
     844             :             {
     845             :                 double num1;
     846             :                 double num2;
     847             : 
     848          11 :                 if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
     849           8 :                     || (pcmk__scan_double(value2, &num2, NULL,
     850             :                                           NULL) != pcmk_rc_ok)) {
     851           4 :                     crm_warn("Comparing '%s' and '%s' as strings because invalid as "
     852             :                              "numbers", value1, value2);
     853           4 :                     return strcasecmp(value1, value2);
     854             :                 }
     855           7 :                 return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
     856             :             }
     857             :             break;
     858             : 
     859           7 :         case pcmk__type_version:
     860           7 :             return compare_version(value1, value2);
     861             : 
     862           3 :         default: // Invalid type
     863           3 :             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          59 : pcmk__parse_source(const char *source)
     877             : {
     878          59 :     if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
     879             :                      pcmk__str_casei|pcmk__str_null_matches)) {
     880          45 :         return pcmk__source_literal;
     881             : 
     882          14 :     } else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
     883           5 :         return pcmk__source_instance_attrs;
     884             : 
     885           9 :     } else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
     886           5 :         return pcmk__source_meta_attrs;
     887             : 
     888             :     } else {
     889           4 :         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          24 : pcmk__parse_combine(const char *combine)
     903             : {
     904          24 :     if (pcmk__str_eq(combine, PCMK_VALUE_AND,
     905             :                      pcmk__str_null_matches|pcmk__str_casei)) {
     906          13 :         return pcmk__combine_and;
     907             : 
     908          11 :     } else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
     909           7 :         return pcmk__combine_or;
     910             : 
     911             :     } else {
     912           4 :         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          49 : evaluate_attr_comparison(const char *actual, const char *reference,
     932             :                          enum pcmk__type type, enum pcmk__comparison comparison)
     933             : {
     934          49 :     int cmp = 0;
     935             : 
     936          49 :     switch (comparison) {
     937           7 :         case pcmk__comparison_defined:
     938           7 :             return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
     939             : 
     940           1 :         case pcmk__comparison_undefined:
     941           1 :             return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
     942             : 
     943          41 :         default:
     944          41 :             break;
     945             :     }
     946             : 
     947          41 :     cmp = pcmk__cmp_by_type(actual, reference, type);
     948             : 
     949          41 :     switch (comparison) {
     950          25 :         case pcmk__comparison_eq:
     951          25 :             return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
     952             : 
     953           2 :         case pcmk__comparison_ne:
     954           2 :             return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
     955             : 
     956          14 :         default:
     957          14 :             break;
     958             :     }
     959             : 
     960          14 :     if ((actual == NULL) || (reference == NULL)) {
     961           0 :         return pcmk_rc_op_unsatisfied; // Comparison would be meaningless
     962             :     }
     963             : 
     964          14 :     switch (comparison) {
     965           2 :         case pcmk__comparison_lt:
     966           2 :             return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
     967             : 
     968           3 :         case pcmk__comparison_lte:
     969           3 :             return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
     970             : 
     971           5 :         case pcmk__comparison_gt:
     972           5 :             return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
     973             : 
     974           4 :         case pcmk__comparison_gte:
     975           4 :             return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
     976             : 
     977           0 :         default: // Not possible with schema validation enabled
     978           0 :             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          49 : value_from_source(const char *value, enum pcmk__reference_source source,
     992             :                   const pcmk_rule_input_t *rule_input)
     993             : {
     994          49 :     GHashTable *table = NULL;
     995             : 
     996          49 :     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           8 :         return NULL;
    1003             :     }
    1004             : 
    1005          41 :     switch (source) {
    1006          35 :         case pcmk__source_literal:
    1007          35 :             return value;
    1008             : 
    1009           3 :         case pcmk__source_instance_attrs:
    1010           3 :             table = rule_input->rsc_params;
    1011           3 :             break;
    1012             : 
    1013           3 :         case pcmk__source_meta_attrs:
    1014           3 :             table = rule_input->rsc_meta;
    1015           3 :             break;
    1016             : 
    1017           0 :         default:
    1018           0 :             return NULL; // Not possible
    1019             :     }
    1020             : 
    1021           6 :     if (table == NULL) {
    1022           0 :         return NULL;
    1023             :     }
    1024           6 :     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          55 : pcmk__evaluate_attr_expression(const xmlNode *expression,
    1039             :                                const pcmk_rule_input_t *rule_input)
    1040             : {
    1041          55 :     const char *id = NULL;
    1042          55 :     const char *op = NULL;
    1043          55 :     const char *attr = NULL;
    1044          55 :     const char *type_s = NULL;
    1045          55 :     const char *value = NULL;
    1046          55 :     const char *actual = NULL;
    1047          55 :     const char *source_s = NULL;
    1048          55 :     const char *reference = NULL;
    1049          55 :     char *expanded_attr = NULL;
    1050          55 :     int rc = pcmk_rc_ok;
    1051             : 
    1052          55 :     enum pcmk__type type = pcmk__type_unknown;
    1053          55 :     enum pcmk__reference_source source = pcmk__source_unknown;
    1054          55 :     enum pcmk__comparison comparison = pcmk__comparison_unknown;
    1055             : 
    1056          55 :     if ((expression == NULL) || (rule_input == NULL)) {
    1057           3 :         return EINVAL;
    1058             :     }
    1059             : 
    1060             :     // Get expression ID (for logging)
    1061          52 :     id = pcmk__xe_id(expression);
    1062          52 :     if (pcmk__str_empty(id)) {
    1063             :         /* @COMPAT When we can break behavioral backward compatibility,
    1064             :          * fail the expression
    1065             :          */
    1066           1 :         pcmk__config_warn(PCMK_XE_EXPRESSION " element has no " PCMK_XA_ID);
    1067           1 :         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          52 :     attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
    1074          52 :     if (attr == NULL) {
    1075           1 :         pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
    1076             :                          "because " PCMK_XA_ATTRIBUTE " was not specified", id);
    1077           1 :         return pcmk_rc_unpack_error;
    1078             :     }
    1079          51 :     expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
    1080          51 :                                              rule_input->rsc_id_submatches,
    1081          51 :                                              rule_input->rsc_id_nmatches);
    1082          51 :     if (expanded_attr != NULL) {
    1083           2 :         attr = expanded_attr;
    1084             :     }
    1085             : 
    1086             :     // Get and validate operation
    1087          51 :     op = crm_element_value(expression, PCMK_XA_OPERATION);
    1088          51 :     comparison = pcmk__parse_comparison(op);
    1089          51 :     if (comparison == pcmk__comparison_unknown) {
    1090             :         // Not possible with schema validation enabled
    1091           2 :         if (op == NULL) {
    1092           1 :             pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
    1093             :                              "passing because it has no " PCMK_XA_OPERATION,
    1094             :                              id);
    1095             :         } else {
    1096           1 :             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           2 :         rc = pcmk_rc_unpack_error;
    1101           2 :         goto done;
    1102             :     }
    1103             : 
    1104             :     // How reference value is obtained (literal, resource meta-attribute, etc.)
    1105          49 :     source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
    1106          49 :     source = pcmk__parse_source(source_s);
    1107          49 :     if (source == pcmk__source_unknown) {
    1108             :         // Not possible with schema validation enabled
    1109             :         // @COMPAT Fail expression once we can break backward compatibility
    1110           1 :         pcmk__config_warn("Expression %s has invalid " PCMK_XA_VALUE_SOURCE
    1111             :                           " value '%s', using default "
    1112             :                           "('" PCMK_VALUE_LITERAL "')", id, source_s);
    1113           1 :         source = pcmk__source_literal;
    1114             :     }
    1115             : 
    1116             :     // Get and validate reference value
    1117          49 :     value = crm_element_value(expression, PCMK_XA_VALUE);
    1118          49 :     switch (comparison) {
    1119           8 :         case pcmk__comparison_defined:
    1120             :         case pcmk__comparison_undefined:
    1121           8 :             if (value != NULL) {
    1122           1 :                 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           8 :             break;
    1127             : 
    1128          41 :         default:
    1129          41 :             if (value == NULL) {
    1130           1 :                 pcmk__config_warn(PCMK_XE_EXPRESSION " %s has no "
    1131             :                                   PCMK_XA_VALUE, id);
    1132             :             }
    1133          41 :             break;
    1134             :     }
    1135          49 :     reference = value_from_source(value, source, rule_input);
    1136             : 
    1137             :     // Get actual value of node attribute
    1138          49 :     if (rule_input->node_attrs != NULL) {
    1139          49 :         actual = g_hash_table_lookup(rule_input->node_attrs, attr);
    1140             :     }
    1141             : 
    1142             :     // Get and validate value type (after expanding reference value)
    1143          49 :     type_s = crm_element_value(expression, PCMK_XA_TYPE);
    1144          49 :     type = pcmk__parse_type(type_s, comparison, actual, reference);
    1145          49 :     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           0 :         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);
    1154             :     }
    1155             : 
    1156          49 :     rc = evaluate_attr_comparison(actual, reference, type, comparison);
    1157          49 :     switch (comparison) {
    1158           8 :         case pcmk__comparison_defined:
    1159             :         case pcmk__comparison_undefined:
    1160           8 :             crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
    1161             :                       id, pcmk_rc_str(rc), attr, op);
    1162           8 :             break;
    1163             : 
    1164          41 :         default:
    1165          41 :             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          41 :             break;
    1170             :     }
    1171             : 
    1172          51 : done:
    1173          51 :     free(expanded_attr);
    1174          51 :     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          32 : pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
    1189             :                               const pcmk_rule_input_t *rule_input)
    1190             : {
    1191          32 :     const char *id = NULL;
    1192          32 :     const char *standard = NULL;
    1193          32 :     const char *provider = NULL;
    1194          32 :     const char *type = NULL;
    1195             : 
    1196          32 :     if ((rsc_expression == NULL) || (rule_input == NULL)) {
    1197           3 :         return EINVAL;
    1198             :     }
    1199             : 
    1200             :     // Validate XML ID
    1201          29 :     id = pcmk__xe_id(rsc_expression);
    1202          29 :     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           2 :         pcmk__config_warn(PCMK_XE_RSC_EXPRESSION " has no " PCMK_XA_ID);
    1208           2 :         id = "without ID"; // for logging
    1209             :     }
    1210             : 
    1211             :     // Compare resource standard
    1212          29 :     standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
    1213          29 :     if ((standard != NULL)
    1214          18 :         && !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
    1215           4 :         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           4 :         return pcmk_rc_op_unsatisfied;
    1219             :     }
    1220             : 
    1221             :     // Compare resource provider
    1222          25 :     provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
    1223          25 :     if ((provider != NULL)
    1224          13 :         && !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
    1225           4 :         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           4 :         return pcmk_rc_op_unsatisfied;
    1229             :     }
    1230             : 
    1231             :     // Compare resource agent type
    1232          21 :     type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
    1233          21 :     if ((type != NULL)
    1234          19 :         && !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
    1235          11 :         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          11 :         return pcmk_rc_op_unsatisfied;
    1239             :     }
    1240             : 
    1241          10 :     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          10 :     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          24 : pcmk__evaluate_op_expression(const xmlNode *op_expression,
    1260             :                              const pcmk_rule_input_t *rule_input)
    1261             : {
    1262          24 :     const char *id = NULL;
    1263          24 :     const char *name = NULL;
    1264          24 :     const char *interval_s = NULL;
    1265          24 :     guint interval_ms = 0U;
    1266             : 
    1267          24 :     if ((op_expression == NULL) || (rule_input == NULL)) {
    1268           3 :         return EINVAL;
    1269             :     }
    1270             : 
    1271             :     // Get operation expression ID (for logging)
    1272          21 :     id = pcmk__xe_id(op_expression);
    1273          21 :     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           2 :         pcmk__config_warn(PCMK_XE_OP_EXPRESSION " element has no " PCMK_XA_ID);
    1278           2 :         id = "without ID"; // for logging
    1279             :     }
    1280             : 
    1281             :     // Validate operation name
    1282          21 :     name = crm_element_value(op_expression, PCMK_XA_NAME);
    1283          21 :     if (name == NULL) { // Not possible with schema validation enabled
    1284           1 :         pcmk__config_warn("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
    1285             :                           "passing because it has no " PCMK_XA_NAME, id);
    1286           1 :         return pcmk_rc_unpack_error;
    1287             :     }
    1288             : 
    1289             :     // Validate operation interval
    1290          20 :     interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
    1291          20 :     if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
    1292           2 :         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           2 :         return pcmk_rc_unpack_error;
    1296             :     }
    1297             : 
    1298             :     // Compare operation name
    1299          18 :     if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
    1300           5 :         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           5 :         return pcmk_rc_op_unsatisfied;
    1304             :     }
    1305             : 
    1306             :     // Compare operation interval (unspecified interval matches all)
    1307          13 :     if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
    1308           2 :         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           2 :         return pcmk_rc_op_unsatisfied;
    1313             :     }
    1314             : 
    1315          11 :     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          11 :     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          26 : pcmk__evaluate_condition(xmlNode *condition,
    1334             :                          const pcmk_rule_input_t *rule_input,
    1335             :                          crm_time_t *next_change)
    1336             : {
    1337             : 
    1338          26 :     if ((condition == NULL) || (rule_input == NULL)) {
    1339           3 :         return EINVAL;
    1340             :     }
    1341             : 
    1342          23 :     switch (pcmk__condition_type(condition)) {
    1343           1 :         case pcmk__condition_rule:
    1344           1 :             return pcmk_evaluate_rule(condition, rule_input, next_change);
    1345             : 
    1346           2 :         case pcmk__condition_attribute:
    1347             :         case pcmk__condition_location:
    1348           2 :             return pcmk__evaluate_attr_expression(condition, rule_input);
    1349             : 
    1350           1 :         case pcmk__condition_datetime:
    1351             :             {
    1352           1 :                 int rc = pcmk__evaluate_date_expression(condition,
    1353           1 :                                                         rule_input->now,
    1354             :                                                         next_change);
    1355             : 
    1356           1 :                 return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
    1357             :             }
    1358             : 
    1359          10 :         case pcmk__condition_resource:
    1360          10 :             return pcmk__evaluate_rsc_expression(condition, rule_input);
    1361             : 
    1362           8 :         case pcmk__condition_operation:
    1363           8 :             return pcmk__evaluate_op_expression(condition, rule_input);
    1364             : 
    1365           1 :         default: // Not possible with schema validation enabled
    1366           1 :             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           1 :             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          20 : pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
    1386             :                    crm_time_t *next_change)
    1387             : {
    1388          20 :     bool empty = true;
    1389          20 :     int rc = pcmk_rc_ok;
    1390          20 :     const char *id = NULL;
    1391          20 :     const char *value = NULL;
    1392          20 :     enum pcmk__combine combine = pcmk__combine_unknown;
    1393             : 
    1394          20 :     if ((rule == NULL) || (rule_input == NULL)) {
    1395           3 :         return EINVAL;
    1396             :     }
    1397             : 
    1398          17 :     rule = expand_idref(rule, NULL);
    1399          17 :     if (rule == NULL) {
    1400             :         // Not possible with schema validation enabled; message already logged
    1401           1 :         return pcmk_rc_unpack_error;
    1402             :     }
    1403             : 
    1404             :     // Validate XML ID
    1405          16 :     id = pcmk__xe_id(rule);
    1406          16 :     if (pcmk__str_empty(id)) {
    1407             :         /* @COMPAT When we can break behavioral backward compatibility,
    1408             :          * fail the rule
    1409             :          */
    1410           1 :         pcmk__config_warn(PCMK_XE_RULE " has no " PCMK_XA_ID);
    1411           1 :         id = "without ID"; // for logging
    1412             :     }
    1413             : 
    1414          16 :     value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
    1415          16 :     combine = pcmk__parse_combine(value);
    1416          16 :     switch (combine) {
    1417          10 :         case pcmk__combine_and:
    1418             :             // For "and", rc defaults to success (reset on failure below)
    1419          10 :             break;
    1420             : 
    1421           5 :         case pcmk__combine_or:
    1422             :             // For "or", rc defaults to failure (reset on success below)
    1423           5 :             rc = pcmk_rc_op_unsatisfied;
    1424           5 :             break;
    1425             : 
    1426           1 :         default:
    1427             :             /* @COMPAT When we can break behavioral backward compatibility,
    1428             :              * return pcmk_rc_unpack_error
    1429             :              */
    1430           1 :             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           1 :             combine = pcmk__combine_and;
    1434           1 :             break;
    1435             :     }
    1436             : 
    1437             :     // Evaluate each condition
    1438          16 :     for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
    1439          25 :          condition != NULL; condition = pcmk__xe_next(condition)) {
    1440             : 
    1441          16 :         empty = false;
    1442          16 :         if (pcmk__evaluate_condition(condition, rule_input,
    1443             :                                      next_change) == pcmk_rc_ok) {
    1444           8 :             if (combine == pcmk__combine_or) {
    1445           3 :                 rc = pcmk_rc_ok; // Any pass is final for "or"
    1446           3 :                 break;
    1447             :             }
    1448           8 :         } else if (combine == pcmk__combine_and) {
    1449           4 :             rc = pcmk_rc_op_unsatisfied; // Any failure is final for "and"
    1450           4 :             break;
    1451             :         }
    1452             :     }
    1453             : 
    1454          16 :     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           4 :         pcmk__config_warn("Ignoring rule %s because it contains no conditions",
    1460             :                           id);
    1461             :     }
    1462             : 
    1463          16 :     crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
    1464          16 :     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           0 : pcmk__evaluate_rules(xmlNode *xml, const pcmk_rule_input_t *rule_input,
    1487             :                      crm_time_t *next_change)
    1488             : {
    1489             :     // If there are no rules, pass by default
    1490           0 :     int rc = pcmk_rc_ok;
    1491           0 :     bool have_rule = false;
    1492             : 
    1493           0 :     for (xmlNode *rule = pcmk__xe_first_child(xml, PCMK_XE_RULE, NULL, NULL);
    1494           0 :          rule != NULL; rule = pcmk__xe_next_same(rule)) {
    1495             : 
    1496           0 :         if (have_rule) {
    1497           0 :             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           0 :             have_rule = true;
    1504             :         }
    1505             : 
    1506           0 :         rc = pcmk_evaluate_rule(rule, rule_input, next_change);
    1507           0 :         if (rc == pcmk_rc_ok) {
    1508           0 :             break;
    1509             :         }
    1510             :     }
    1511           0 :     return rc;
    1512             : }

Generated by: LCOV version 1.14