pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
rules.c
Go to the documentation of this file.
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 
26 #include "crmcommon_private.h"
27 
36 enum expression_type
37 pcmk__condition_type(const xmlNode *condition)
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)) {
45 
46  } else if (pcmk__xe_is(condition, PCMK_XE_RSC_EXPRESSION)) {
48 
49  } else if (pcmk__xe_is(condition, PCMK_XE_OP_EXPRESSION)) {
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)) {
57  }
58 
59  // Expression types based on node attribute name
60 
62 
64  NULL)) {
66  }
67 
69 }
70 
79 static const char *
80 loggable_parent_id(const xmlNode *xml)
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 
109 static int
110 check_range(const xmlNode *date_spec, const char *id, const char *attr,
111  uint32_t value)
112 {
113  int rc = pcmk_rc_ok;
114  const char *range = crm_element_value(date_spec, attr);
115  long long low, high;
116 
117  if (range == NULL) {
118  // Attribute not present
119 
120  } else if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
121  // Invalid range
122  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
123  "as not passing because '%s' is not a valid range "
124  "for " PCMK_XE_DATE_SPEC " attribute %s",
125  id, range, attr);
127 
128  } else if ((low != -1) && (value < low)) {
130 
131  } else if ((high != -1) && (value > high)) {
132  rc = pcmk_rc_after_range;
133  }
134 
136  " %s='%s' for %" PRIu32 ": %s",
137  id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
138  return rc;
139 }
140 
153 int
154 pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
155 {
156  const char *id = NULL;
157  const char *parent_id = loggable_parent_id(date_spec);
158 
159  // Range attributes that can be specified for a PCMK_XE_DATE_SPEC element
160  struct range {
161  const char *attr;
162  uint32_t value;
163  } ranges[] = {
164  { PCMK_XA_YEARS, 0U },
165  { PCMK_XA_MONTHS, 0U },
166  { PCMK_XA_MONTHDAYS, 0U },
167  { PCMK_XA_HOURS, 0U },
168  { PCMK_XA_MINUTES, 0U },
169  { PCMK_XA_SECONDS, 0U },
170  { PCMK_XA_YEARDAYS, 0U },
171  { PCMK_XA_WEEKYEARS, 0U },
172  { PCMK_XA_WEEKS, 0U },
173  { PCMK_XA_WEEKDAYS, 0U },
174  };
175 
176  if ((date_spec == NULL) || (now == NULL)) {
177  return EINVAL;
178  }
179 
180  // Get specification ID (for logging)
181  id = pcmk__xe_id(date_spec);
182  if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
183  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
184  "passing because " PCMK_XE_DATE_SPEC
185  " subelement has no " PCMK_XA_ID, parent_id);
186  return pcmk_rc_unpack_error;
187  }
188 
189  // Year, month, day
190  crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
191  &(ranges[2].value));
192 
193  // Hour, minute, second
194  crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
195  &(ranges[5].value));
196 
197  // Year (redundant) and day of year
198  crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
199 
200  // Week year, week of week year, day of week
201  crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
202  &(ranges[9].value));
203 
204  for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
205  int rc = check_range(date_spec, parent_id, ranges[i].attr,
206  ranges[i].value);
207 
208  if (rc != pcmk_rc_ok) {
209  return rc;
210  }
211  }
212 
213  // All specified ranges passed, or none were given (also considered a pass)
214  return pcmk_rc_ok;
215 }
216 
217 #define ADD_COMPONENT(component) do { \
218  int rc = pcmk__add_time_from_xml(*end, component, duration); \
219  if (rc != pcmk_rc_ok) { \
220  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s " \
221  "as not passing because " PCMK_XE_DURATION \
222  " %s attribute %s is invalid: %s", \
223  parent_id, id, \
224  pcmk__time_component_attr(component), \
225  pcmk_rc_str(rc)); \
226  crm_time_free(*end); \
227  *end = NULL; \
228  return rc; \
229  } \
230  } while (0)
231 
244 int
245 pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
246  crm_time_t **end)
247 {
248  const char *id = NULL;
249  const char *parent_id = loggable_parent_id(duration);
250 
251  if ((start == NULL) || (duration == NULL)
252  || (end == NULL) || (*end != NULL)) {
253  return EINVAL;
254  }
255 
256  // Get duration ID (for logging)
257  id = pcmk__xe_id(duration);
258  if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
259  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
260  "as not passing because " PCMK_XE_DURATION
261  " subelement has no " PCMK_XA_ID, parent_id);
262  return pcmk_rc_unpack_error;
263  }
264 
265  *end = pcmk_copy_time(start);
266 
274 
275  return pcmk_rc_ok;
276 }
277 
291 static int
292 evaluate_in_range(const xmlNode *date_expression, const char *id,
293  const crm_time_t *now, crm_time_t *next_change)
294 {
295  crm_time_t *start = NULL;
296  crm_time_t *end = NULL;
297 
298  if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
299  &start) != pcmk_rc_ok) {
300  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
301  "passing because " PCMK_XA_START " is invalid", id);
302  return pcmk_rc_unpack_error;
303  }
304 
305  if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
306  &end) != pcmk_rc_ok) {
307  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
308  "passing because " PCMK_XA_END " is invalid", id);
309  crm_time_free(start);
310  return pcmk_rc_unpack_error;
311  }
312 
313  if ((start == NULL) && (end == NULL)) {
314  // Not possible with schema validation enabled
315  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
316  "passing because " PCMK_VALUE_IN_RANGE
317  " requires at least one of " PCMK_XA_START " or "
318  PCMK_XA_END, id);
319  return pcmk_rc_unpack_error;
320  }
321 
322  if (end == NULL) {
323  xmlNode *duration = pcmk__xe_first_child(date_expression,
324  PCMK_XE_DURATION, NULL, NULL);
325 
326  if (duration != NULL) {
327  int rc = pcmk__unpack_duration(duration, start, &end);
328 
329  if (rc != pcmk_rc_ok) {
331  " %s as not passing because duration "
332  "is invalid", id);
333  crm_time_free(start);
334  return rc;
335  }
336  }
337  }
338 
339  if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
340  pcmk__set_time_if_earlier(next_change, start);
341  crm_time_free(start);
342  crm_time_free(end);
343  return pcmk_rc_before_range;
344  }
345 
346  if (end != NULL) {
347  if (crm_time_compare(now, end) > 0) {
348  crm_time_free(start);
349  crm_time_free(end);
350  return pcmk_rc_after_range;
351  }
352 
353  // Evaluation doesn't change until second after end
354  if (next_change != NULL) {
355  crm_time_add_seconds(end, 1);
356  pcmk__set_time_if_earlier(next_change, end);
357  }
358  }
359 
360  crm_time_free(start);
361  crm_time_free(end);
362  return pcmk_rc_within_range;
363 }
364 
378 static int
379 evaluate_gt(const xmlNode *date_expression, const char *id,
380  const crm_time_t *now, crm_time_t *next_change)
381 {
382  crm_time_t *start = NULL;
383 
384  if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
385  &start) != pcmk_rc_ok) {
386  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
387  "passing because " PCMK_XA_START " is invalid",
388  id);
389  return pcmk_rc_unpack_error;
390  }
391 
392  if (start == NULL) { // Not possible with schema validation enabled
393  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
394  "passing because " PCMK_VALUE_GT " requires "
395  PCMK_XA_START, id);
396  return pcmk_rc_unpack_error;
397  }
398 
399  if (crm_time_compare(now, start) > 0) {
400  crm_time_free(start);
401  return pcmk_rc_within_range;
402  }
403 
404  // Evaluation doesn't change until second after start time
405  crm_time_add_seconds(start, 1);
406  pcmk__set_time_if_earlier(next_change, start);
407  crm_time_free(start);
408  return pcmk_rc_before_range;
409 }
410 
424 static int
425 evaluate_lt(const xmlNode *date_expression, const char *id,
426  const crm_time_t *now, crm_time_t *next_change)
427 {
428  crm_time_t *end = NULL;
429 
430  if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
431  &end) != pcmk_rc_ok) {
432  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
433  "passing because " PCMK_XA_END " is invalid", id);
434  return pcmk_rc_unpack_error;
435  }
436 
437  if (end == NULL) { // Not possible with schema validation enabled
438  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
439  "passing because " PCMK_VALUE_GT " requires "
440  PCMK_XA_END, id);
441  return pcmk_rc_unpack_error;
442  }
443 
444  if (crm_time_compare(now, end) < 0) {
445  pcmk__set_time_if_earlier(next_change, end);
446  crm_time_free(end);
447  return pcmk_rc_within_range;
448  }
449 
450  crm_time_free(end);
451  return pcmk_rc_after_range;
452 }
453 
468 int
469 pcmk__evaluate_date_expression(const xmlNode *date_expression,
470  const crm_time_t *now, crm_time_t *next_change)
471 {
472  const char *id = NULL;
473  const char *op = NULL;
474  int rc = pcmk_rc_ok;
475 
476  if ((date_expression == NULL) || (now == NULL)) {
477  return EINVAL;
478  }
479 
480  // Get expression ID (for logging)
481  id = pcmk__xe_id(date_expression);
482  if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
483  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " without "
484  PCMK_XA_ID " as not passing");
485  return pcmk_rc_unpack_error;
486  }
487 
488  op = crm_element_value(date_expression, PCMK_XA_OPERATION);
489  if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
491  rc = evaluate_in_range(date_expression, id, now, next_change);
492 
493  } else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
494  xmlNode *date_spec = pcmk__xe_first_child(date_expression,
495  PCMK_XE_DATE_SPEC, NULL,
496  NULL);
497 
498  if (date_spec == NULL) { // Not possible with schema validation enabled
499  pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s "
500  "as not passing because " PCMK_VALUE_DATE_SPEC
501  " operations require a " PCMK_XE_DATE_SPEC
502  " subelement", id);
503  return pcmk_rc_unpack_error;
504  }
505 
506  // @TODO set next_change appropriately
507  rc = pcmk__evaluate_date_spec(date_spec, now);
508 
509  } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
510  rc = evaluate_gt(date_expression, id, now, next_change);
511 
512  } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
513  rc = evaluate_lt(date_expression, id, now, next_change);
514 
515  } else { // Not possible with schema validation enabled
517  " %s as not passing because '%s' is not a valid "
518  PCMK_XE_OPERATION, id, op);
519  return pcmk_rc_unpack_error;
520  }
521 
522  crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
523  id, op, pcmk_rc_str(rc), rc);
524  return rc;
525 }
526 
543 static bool
544 process_submatches(const char *string, const char *match,
545  const regmatch_t submatches[], int nmatches,
546  char *expansion, size_t *nbytes)
547 {
548  bool expanded = false;
549  const char *src = string;
550 
551  if (nbytes != NULL) {
552  *nbytes = 1; // Include space for terminator
553  }
554 
555  while (*src != '\0') {
556  int submatch = 0;
557  size_t match_len = 0;
558 
559  if ((src[0] != '%') || !isdigit(src[1])) {
560  /* src does not point to the first character of a %N sequence,
561  * so expand this character as-is
562  */
563  if (expansion != NULL) {
564  *expansion++ = *src;
565  }
566  if (nbytes != NULL) {
567  ++(*nbytes);
568  }
569  ++src;
570  continue;
571  }
572 
573  submatch = src[1] - '0';
574  src += 2; // Skip over %N sequence in source string
575  expanded = true; // Expansion will be different from source
576 
577  // Omit sequence from expansion unless it has a non-empty match
578  if ((nmatches <= submatch) // Not enough submatches
579  || (submatches[submatch].rm_so < 0) // Pattern did not match
580  || (submatches[submatch].rm_eo
581  <= submatches[submatch].rm_so)) { // Match was empty
582  continue;
583  }
584 
585  match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
586  if (nbytes != NULL) {
587  *nbytes += match_len;
588  }
589  if (expansion != NULL) {
590  memcpy(expansion, match + submatches[submatch].rm_so,
591  match_len);
592  expansion += match_len;
593  }
594  }
595 
596  return expanded;
597 }
598 
612 char *
613 pcmk__replace_submatches(const char *string, const char *match,
614  const regmatch_t submatches[], int nmatches)
615 {
616  size_t nbytes = 0;
617  char *result = NULL;
618 
619  if (pcmk__str_empty(string) || pcmk__str_empty(match)) {
620  return NULL; // Nothing to expand
621  }
622 
623  // Calculate how much space will be needed for expanded string
624  if (!process_submatches(string, match, submatches, nmatches, NULL,
625  &nbytes)) {
626  return NULL; // No expansions needed
627  }
628 
629  // Allocate enough space for expanded string
630  result = pcmk__assert_alloc(nbytes, sizeof(char));
631 
632  // Expand submatches
633  (void) process_submatches(string, match, submatches, nmatches, result,
634  NULL);
635  return result;
636 }
637 
650 enum pcmk__comparison
651 pcmk__parse_comparison(const char *op)
652 {
653  if (pcmk__str_eq(op, PCMK_VALUE_DEFINED, pcmk__str_casei)) {
655 
656  } else if (pcmk__str_eq(op, PCMK_VALUE_NOT_DEFINED, pcmk__str_casei)) {
658 
659  } else if (pcmk__str_eq(op, PCMK_VALUE_EQ, pcmk__str_casei)) {
660  return pcmk__comparison_eq;
661 
662  } else if (pcmk__str_eq(op, PCMK_VALUE_NE, pcmk__str_casei)) {
663  return pcmk__comparison_ne;
664 
665  } else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
666  return pcmk__comparison_lt;
667 
668  } else if (pcmk__str_eq(op, PCMK_VALUE_LTE, pcmk__str_casei)) {
669  return pcmk__comparison_lte;
670 
671  } else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
672  return pcmk__comparison_gt;
673 
674  } else if (pcmk__str_eq(op, PCMK_VALUE_GTE, pcmk__str_casei)) {
675  return pcmk__comparison_gte;
676  }
677 
679 }
680 
692 enum pcmk__type
694  const char *value1, const char *value2)
695 {
696  if (type == NULL) {
697  switch (op) {
698  case pcmk__comparison_lt:
700  case pcmk__comparison_gt:
702  if (((value1 != NULL) && (strchr(value1, '.') != NULL))
703  || ((value2 != NULL) && (strchr(value2, '.') != NULL))) {
704  return pcmk__type_number;
705  }
706  return pcmk__type_integer;
707 
708  default:
709  return pcmk__type_string;
710  }
711  }
712 
713  if (pcmk__str_eq(type, PCMK_VALUE_STRING, pcmk__str_casei)) {
714  return pcmk__type_string;
715 
716  } else if (pcmk__str_eq(type, PCMK_VALUE_INTEGER, pcmk__str_casei)) {
717  return pcmk__type_integer;
718 
719  } else if (pcmk__str_eq(type, PCMK_VALUE_NUMBER, pcmk__str_casei)) {
720  return pcmk__type_number;
721 
722  } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_casei)) {
723  return pcmk__type_version;
724  }
725 
726  return pcmk__type_unknown;
727 }
728 
741 int
742 pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
743 {
744  // NULL compares as less than non-NULL
745  if (value2 == NULL) {
746  return (value1 == NULL)? 0 : 1;
747  }
748  if (value1 == NULL) {
749  return -1;
750  }
751 
752  switch (type) {
753  case pcmk__type_string:
754  return strcasecmp(value1, value2);
755 
756  case pcmk__type_integer:
757  {
758  long long integer1;
759  long long integer2;
760 
761  if ((pcmk__scan_ll(value1, &integer1, 0LL) != pcmk_rc_ok)
762  || (pcmk__scan_ll(value2, &integer2, 0LL) != pcmk_rc_ok)) {
763  crm_warn("Comparing '%s' and '%s' as strings because "
764  "invalid as integers", value1, value2);
765  return strcasecmp(value1, value2);
766  }
767  return (integer1 < integer2)? -1 : (integer1 > integer2)? 1 : 0;
768  }
769  break;
770 
771  case pcmk__type_number:
772  {
773  double num1;
774  double num2;
775 
776  if ((pcmk__scan_double(value1, &num1, NULL, NULL) != pcmk_rc_ok)
777  || (pcmk__scan_double(value2, &num2, NULL,
778  NULL) != pcmk_rc_ok)) {
779  crm_warn("Comparing '%s' and '%s' as strings because invalid as "
780  "numbers", value1, value2);
781  return strcasecmp(value1, value2);
782  }
783  return (num1 < num2)? -1 : (num1 > num2)? 1 : 0;
784  }
785  break;
786 
787  case pcmk__type_version:
788  return compare_version(value1, value2);
789 
790  default: // Invalid type
791  return 0;
792  }
793 }
794 
804 pcmk__parse_source(const char *source)
805 {
806  if (pcmk__str_eq(source, PCMK_VALUE_LITERAL,
808  return pcmk__source_literal;
809 
810  } else if (pcmk__str_eq(source, PCMK_VALUE_PARAM, pcmk__str_casei)) {
812 
813  } else if (pcmk__str_eq(source, PCMK_VALUE_META, pcmk__str_casei)) {
815 
816  } else {
817  return pcmk__source_unknown;
818  }
819 }
820 
829 enum pcmk__combine
830 pcmk__parse_combine(const char *combine)
831 {
832  if (pcmk__str_eq(combine, PCMK_VALUE_AND,
834  return pcmk__combine_and;
835 
836  } else if (pcmk__str_eq(combine, PCMK_VALUE_OR, pcmk__str_casei)) {
837  return pcmk__combine_or;
838 
839  } else {
840  return pcmk__combine_unknown;
841  }
842 }
843 
858 static int
859 evaluate_attr_comparison(const char *actual, const char *reference,
860  enum pcmk__type type, enum pcmk__comparison comparison)
861 {
862  int cmp = 0;
863 
864  switch (comparison) {
866  return (actual != NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
867 
869  return (actual == NULL)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
870 
871  default:
872  break;
873  }
874 
875  cmp = pcmk__cmp_by_type(actual, reference, type);
876 
877  switch (comparison) {
878  case pcmk__comparison_eq:
879  return (cmp == 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
880 
881  case pcmk__comparison_ne:
882  return (cmp != 0)? pcmk_rc_ok : pcmk_rc_op_unsatisfied;
883 
884  default:
885  break;
886  }
887 
888  if ((actual == NULL) || (reference == NULL)) {
889  return pcmk_rc_op_unsatisfied; // Comparison would be meaningless
890  }
891 
892  switch (comparison) {
893  case pcmk__comparison_lt:
894  return (cmp < 0)? pcmk_rc_ok : pcmk_rc_after_range;
895 
897  return (cmp <= 0)? pcmk_rc_ok : pcmk_rc_after_range;
898 
899  case pcmk__comparison_gt:
900  return (cmp > 0)? pcmk_rc_ok : pcmk_rc_before_range;
901 
903  return (cmp >= 0)? pcmk_rc_ok : pcmk_rc_before_range;
904 
905  default: // Not possible with schema validation enabled
906  return pcmk_rc_op_unsatisfied;
907  }
908 }
909 
918 static const char *
919 value_from_source(const char *value, enum pcmk__reference_source source,
920  const pcmk_rule_input_t *rule_input)
921 {
922  GHashTable *table = NULL;
923 
924  switch (source) {
926  return value;
927 
929  table = rule_input->rsc_params;
930  break;
931 
933  table = rule_input->rsc_meta;
934  break;
935 
936  default:
937  return NULL; // Not possible
938  }
939 
940  if (table == NULL) {
941  return NULL;
942  }
943  return (const char *) g_hash_table_lookup(table, value);
944 }
945 
956 int
957 pcmk__evaluate_attr_expression(const xmlNode *expression,
958  const pcmk_rule_input_t *rule_input)
959 {
960  const char *id = NULL;
961  const char *op = NULL;
962  const char *attr = NULL;
963  const char *type_s = NULL;
964  const char *value = NULL;
965  const char *actual = NULL;
966  const char *source_s = NULL;
967  const char *reference = NULL;
968  char *expanded_attr = NULL;
969  int rc = pcmk_rc_ok;
970 
973  enum pcmk__comparison comparison = pcmk__comparison_unknown;
974 
975  if ((expression == NULL) || (rule_input == NULL)) {
976  return EINVAL;
977  }
978 
979  // Get expression ID (for logging)
980  id = pcmk__xe_id(expression);
981  if (pcmk__str_empty(id)) {
982  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " without " PCMK_XA_ID
983  " as not passing");
984  return pcmk_rc_unpack_error;
985  }
986 
987  /* Get name of node attribute to compare (expanding any %0-%9 to
988  * regular expression submatches)
989  */
990  attr = crm_element_value(expression, PCMK_XA_ATTRIBUTE);
991  if (attr == NULL) {
992  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
993  "because " PCMK_XA_ATTRIBUTE " was not specified", id);
994  return pcmk_rc_unpack_error;
995  }
996  expanded_attr = pcmk__replace_submatches(attr, rule_input->rsc_id,
997  rule_input->rsc_id_submatches,
998  rule_input->rsc_id_nmatches);
999  if (expanded_attr != NULL) {
1000  attr = expanded_attr;
1001  }
1002 
1003  // Get and validate operation
1004  op = crm_element_value(expression, PCMK_XA_OPERATION);
1005  comparison = pcmk__parse_comparison(op);
1006  if (comparison == pcmk__comparison_unknown) {
1007  // Not possible with schema validation enabled
1008  if (op == NULL) {
1009  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1010  "passing because it has no " PCMK_XA_OPERATION,
1011  id);
1012  } else {
1013  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1014  "passing because '%s' is not a valid "
1015  PCMK_XA_OPERATION, id, op);
1016  }
1017  rc = pcmk_rc_unpack_error;
1018  goto done;
1019  }
1020 
1021  // How reference value is obtained (literal, resource meta-attribute, etc.)
1022  source_s = crm_element_value(expression, PCMK_XA_VALUE_SOURCE);
1023  source = pcmk__parse_source(source_s);
1024  if (source == pcmk__source_unknown) {
1025  // Not possible with schema validation enabled
1026  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1027  "because '%s' is not a valid " PCMK_XA_VALUE_SOURCE,
1028  id, source_s);
1029  rc = pcmk_rc_unpack_error;
1030  goto done;
1031  }
1032 
1033  // Get and validate reference value
1034  value = crm_element_value(expression, PCMK_XA_VALUE);
1035  switch (comparison) {
1038  if (value != NULL) {
1039  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1040  "passing because " PCMK_XA_VALUE " is not "
1041  "allowed when " PCMK_XA_OPERATION " is %s",
1042  id, op);
1043  rc = pcmk_rc_unpack_error;
1044  goto done;
1045  }
1046  break;
1047 
1048  default:
1049  if (value == NULL) {
1050  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not "
1051  "passing because " PCMK_XA_VALUE " is "
1052  "required when " PCMK_XA_OPERATION " is %s",
1053  id, op);
1054  rc = pcmk_rc_unpack_error;
1055  goto done;
1056  }
1057  reference = value_from_source(value, source, rule_input);
1058  break;
1059  }
1060 
1061  // Get actual value of node attribute
1062  if (rule_input->node_attrs != NULL) {
1063  actual = g_hash_table_lookup(rule_input->node_attrs, attr);
1064  }
1065 
1066  // Get and validate value type (after expanding reference value)
1067  type_s = crm_element_value(expression, PCMK_XA_TYPE);
1068  type = pcmk__parse_type(type_s, comparison, actual, reference);
1069  if (type == pcmk__type_unknown) {
1070  // Not possible with schema validation enabled
1071  pcmk__config_err("Treating " PCMK_XE_EXPRESSION " %s as not passing "
1072  "because '%s' is not a valid type", id, type_s);
1073  rc = pcmk_rc_unpack_error;
1074  goto done;
1075  }
1076 
1077  rc = evaluate_attr_comparison(actual, reference, type, comparison);
1078  switch (comparison) {
1081  crm_trace(PCMK_XE_EXPRESSION " %s result: %s (for attribute %s %s)",
1082  id, pcmk_rc_str(rc), attr, op);
1083  break;
1084 
1085  default:
1086  crm_trace(PCMK_XE_EXPRESSION " %s result: "
1087  "%s (attribute %s %s '%s' via %s source as %s type)",
1088  id, pcmk_rc_str(rc), attr, op, pcmk__s(reference, ""),
1089  pcmk__s(source_s, "default"), pcmk__s(type_s, "default"));
1090  break;
1091  }
1092 
1093 done:
1094  free(expanded_attr);
1095  return rc;
1096 }
1097 
1108 int
1109 pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression,
1110  const pcmk_rule_input_t *rule_input)
1111 {
1112  const char *id = NULL;
1113  const char *standard = NULL;
1114  const char *provider = NULL;
1115  const char *type = NULL;
1116 
1117  if ((rsc_expression == NULL) || (rule_input == NULL)) {
1118  return EINVAL;
1119  }
1120 
1121  // Validate XML ID
1122  id = pcmk__xe_id(rsc_expression);
1123  if (pcmk__str_empty(id)) {
1124  // Not possible with schema validation enabled
1125  pcmk__config_err("Treating " PCMK_XE_RSC_EXPRESSION " without "
1126  PCMK_XA_ID " as not passing");
1127  return pcmk_rc_unpack_error;
1128  }
1129 
1130  // Compare resource standard
1131  standard = crm_element_value(rsc_expression, PCMK_XA_CLASS);
1132  if ((standard != NULL)
1133  && !pcmk__str_eq(standard, rule_input->rsc_standard, pcmk__str_none)) {
1134  crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1135  "actual standard '%s' doesn't match '%s'",
1136  id, pcmk__s(rule_input->rsc_standard, ""), standard);
1137  return pcmk_rc_op_unsatisfied;
1138  }
1139 
1140  // Compare resource provider
1141  provider = crm_element_value(rsc_expression, PCMK_XA_PROVIDER);
1142  if ((provider != NULL)
1143  && !pcmk__str_eq(provider, rule_input->rsc_provider, pcmk__str_none)) {
1144  crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1145  "actual provider '%s' doesn't match '%s'",
1146  id, pcmk__s(rule_input->rsc_provider, ""), provider);
1147  return pcmk_rc_op_unsatisfied;
1148  }
1149 
1150  // Compare resource agent type
1151  type = crm_element_value(rsc_expression, PCMK_XA_TYPE);
1152  if ((type != NULL)
1153  && !pcmk__str_eq(type, rule_input->rsc_agent, pcmk__str_none)) {
1154  crm_trace(PCMK_XE_RSC_EXPRESSION " %s is unsatisfied because "
1155  "actual agent '%s' doesn't match '%s'",
1156  id, pcmk__s(rule_input->rsc_agent, ""), type);
1157  return pcmk_rc_op_unsatisfied;
1158  }
1159 
1160  crm_trace(PCMK_XE_RSC_EXPRESSION " %s is satisfied by %s%s%s:%s",
1161  id, pcmk__s(standard, ""),
1162  ((provider == NULL)? "" : ":"), pcmk__s(provider, ""),
1163  pcmk__s(type, ""));
1164  return pcmk_rc_ok;
1165 }
1166 
1177 int
1178 pcmk__evaluate_op_expression(const xmlNode *op_expression,
1179  const pcmk_rule_input_t *rule_input)
1180 {
1181  const char *id = NULL;
1182  const char *name = NULL;
1183  const char *interval_s = NULL;
1184  guint interval_ms = 0U;
1185 
1186  if ((op_expression == NULL) || (rule_input == NULL)) {
1187  return EINVAL;
1188  }
1189 
1190  // Get operation expression ID (for logging)
1191  id = pcmk__xe_id(op_expression);
1192  if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1193  pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " without "
1194  PCMK_XA_ID " as not passing");
1195  return pcmk_rc_unpack_error;
1196  }
1197 
1198  // Validate operation name
1199  name = crm_element_value(op_expression, PCMK_XA_NAME);
1200  if (name == NULL) { // Not possible with schema validation enabled
1201  pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1202  "passing because it has no " PCMK_XA_NAME, id);
1203  return pcmk_rc_unpack_error;
1204  }
1205 
1206  // Validate operation interval
1207  interval_s = crm_element_value(op_expression, PCMK_META_INTERVAL);
1208  if (pcmk_parse_interval_spec(interval_s, &interval_ms) != pcmk_rc_ok) {
1209  pcmk__config_err("Treating " PCMK_XE_OP_EXPRESSION " %s as not "
1210  "passing because '%s' is not a valid "
1212  id, interval_s);
1213  return pcmk_rc_unpack_error;
1214  }
1215 
1216  // Compare operation name
1217  if (!pcmk__str_eq(name, rule_input->op_name, pcmk__str_none)) {
1218  crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1219  "actual name '%s' doesn't match '%s'",
1220  id, pcmk__s(rule_input->op_name, ""), name);
1221  return pcmk_rc_op_unsatisfied;
1222  }
1223 
1224  // Compare operation interval (unspecified interval matches all)
1225  if ((interval_s != NULL) && (interval_ms != rule_input->op_interval_ms)) {
1226  crm_trace(PCMK_XE_OP_EXPRESSION " %s is unsatisfied because "
1227  "actual interval %s doesn't match %s",
1228  id, pcmk__readable_interval(rule_input->op_interval_ms),
1229  pcmk__readable_interval(interval_ms));
1230  return pcmk_rc_op_unsatisfied;
1231  }
1232 
1233  crm_trace(PCMK_XE_OP_EXPRESSION " %s is satisfied (name %s, interval %s)",
1234  id, name, pcmk__readable_interval(rule_input->op_interval_ms));
1235  return pcmk_rc_ok;
1236 }
1237 
1250 int
1251 pcmk__evaluate_condition(xmlNode *condition,
1252  const pcmk_rule_input_t *rule_input,
1253  crm_time_t *next_change)
1254 {
1255 
1256  if ((condition == NULL) || (rule_input == NULL)) {
1257  return EINVAL;
1258  }
1259 
1260  switch (pcmk__condition_type(condition)) {
1261  case pcmk__condition_rule:
1262  return pcmk_evaluate_rule(condition, rule_input, next_change);
1263 
1266  return pcmk__evaluate_attr_expression(condition, rule_input);
1267 
1269  {
1270  int rc = pcmk__evaluate_date_expression(condition,
1271  rule_input->now,
1272  next_change);
1273 
1274  return (rc == pcmk_rc_within_range)? pcmk_rc_ok : rc;
1275  }
1276 
1278  return pcmk__evaluate_rsc_expression(condition, rule_input);
1279 
1281  return pcmk__evaluate_op_expression(condition, rule_input);
1282 
1283  default: // Not possible with schema validation enabled
1284  pcmk__config_err("Treating rule condition %s as not passing "
1285  "because %s is not a valid condition type",
1286  pcmk__s(pcmk__xe_id(condition), "without ID"),
1287  (const char *) condition->name);
1288  return pcmk_rc_unpack_error;
1289  }
1290 }
1291 
1302 int
1303 pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input,
1304  crm_time_t *next_change)
1305 {
1306  bool empty = true;
1307  int rc = pcmk_rc_ok;
1308  const char *id = NULL;
1309  const char *value = NULL;
1310  enum pcmk__combine combine = pcmk__combine_unknown;
1311 
1312  if ((rule == NULL) || (rule_input == NULL)) {
1313  return EINVAL;
1314  }
1315 
1316  rule = pcmk__xe_resolve_idref(rule, NULL);
1317  if (rule == NULL) {
1318  // Not possible with schema validation enabled; message already logged
1319  return pcmk_rc_unpack_error;
1320  }
1321 
1322  // Validate XML ID
1323  id = pcmk__xe_id(rule);
1324  if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
1325  pcmk__config_err("Treating " PCMK_XE_RULE " without " PCMK_XA_ID
1326  " as not passing");
1327  return pcmk_rc_unpack_error;
1328  }
1329 
1330  value = crm_element_value(rule, PCMK_XA_BOOLEAN_OP);
1331  combine = pcmk__parse_combine(value);
1332  switch (combine) {
1333  case pcmk__combine_and:
1334  // For "and", rc defaults to success (reset on failure below)
1335  break;
1336 
1337  case pcmk__combine_or:
1338  // For "or", rc defaults to failure (reset on success below)
1340  break;
1341 
1342  default: // Not possible with schema validation enabled
1343  pcmk__config_err("Treating " PCMK_XE_RULE " %s as not passing "
1344  "because '%s' is not a valid " PCMK_XA_BOOLEAN_OP,
1345  id, value);
1346  return pcmk_rc_unpack_error;
1347  }
1348 
1349  // Evaluate each condition
1350  for (xmlNode *condition = pcmk__xe_first_child(rule, NULL, NULL, NULL);
1351  condition != NULL; condition = pcmk__xe_next(condition, NULL)) {
1352 
1353  empty = false;
1354  if (pcmk__evaluate_condition(condition, rule_input,
1355  next_change) == pcmk_rc_ok) {
1356  if (combine == pcmk__combine_or) {
1357  rc = pcmk_rc_ok; // Any pass is final for "or"
1358  break;
1359  }
1360  } else if (combine == pcmk__combine_and) {
1361  rc = pcmk_rc_op_unsatisfied; // Any failure is final for "and"
1362  break;
1363  }
1364  }
1365 
1366  if (empty) { // Not possible with schema validation enabled
1367  pcmk__config_warn("Ignoring rule %s because it contains no conditions",
1368  id);
1369  rc = pcmk_rc_ok;
1370  }
1371 
1372  crm_trace("Rule %s is %ssatisfied", id, ((rc == pcmk_rc_ok)? "" : "not "));
1373  return rc;
1374 }
#define PCMK_XA_YEARS
Definition: xml_names.h:456
xmlNode * pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search)
Definition: xml_idref.c:85
enum pcmk__type pcmk__parse_type(const char *type, enum pcmk__comparison op, const char *value1, const char *value2)
Definition: rules.c:693
#define PCMK_VALUE_NUMBER
Definition: options.h:183
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml_element.c:42
int pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, crm_time_t *next_change)
Evaluate a single rule, including all its conditions.
Definition: rules.c:1303
GHashTable * rsc_meta
Definition: rules.h:93
#define PCMK_XA_HOURS
Definition: xml_names.h:300
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition: iso8601.c:421
#define PCMK_XA_NAME
Definition: xml_names.h:330
void crm_time_add_seconds(crm_time_t *dt, int value)
Add a given number of seconds to a date/time or duration.
Definition: iso8601.c:1766
const crm_time_t * now
Current time for rule evaluation purposes.
Definition: rules.h:59
Data used to evaluate a rule (any NULL items are ignored)
Definition: rules.h:57
pcmk__comparison
#define CRM_ATTR_KIND
Definition: crm.h:94
GHashTable * rsc_params
Definition: rules.h:86
#define PCMK_XA_YEARDAYS
Definition: xml_names.h:455
enum expression_type pcmk__condition_type(const xmlNode *condition)
Definition: rules.c:37
int pcmk__evaluate_attr_expression(const xmlNode *expression, const pcmk_rule_input_t *rule_input)
Definition: rules.c:957
const char * rsc_provider
Resource provider that rule applies to.
Definition: rules.h:63
const char * name
Definition: cib.c:26
enum pcmk_ipc_server type
Definition: cpg.c:51
struct crm_time_s crm_time_t
Definition: iso8601.h:32
#define PCMK_VALUE_DATE_SPEC
Definition: options.h:142
#define pcmk__config_warn(fmt...)
#define PCMK_VALUE_OR
Definition: options.h:189
#define PCMK_XA_WEEKYEARS
Definition: xml_names.h:448
pcmk__reference_source
#define PCMK_VALUE_NOT_DEFINED
Definition: options.h:181
#define PCMK_XA_WEEKDAYS
Definition: xml_names.h:446
const char * op_name
Operation name that rule applies to.
Definition: rules.h:67
#define pcmk__config_err(fmt...)
enum pcmk__comparison pcmk__parse_comparison(const char *op)
Definition: rules.c:651
#define PCMK_XE_DURATION
Definition: xml_names.h:104
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1471
#define PCMK_XE_EXPRESSION
Definition: xml_names.h:109
pcmk__type
#define PCMK_VALUE_AND
Definition: options.h:133
#define PCMK_XA_PROVIDER
Definition: xml_names.h:364
int rsc_id_nmatches
Number of entries in rsc_id_submatches.
Definition: rules.h:102
expression_type
Definition: rules.h:33
int pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start, crm_time_t **end)
Definition: rules.c:245
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:609
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition: iso8601.c:413
#define PCMK_XA_TYPE
Definition: xml_names.h:430
#define PCMK_XA_OPERATION
Definition: xml_names.h:349
#define PCMK_XA_START
Definition: xml_names.h:408
const char * rsc_agent
Resource agent that rule applies to.
Definition: rules.h:64
#define PCMK_VALUE_EQ
Definition: options.h:150
enum pcmk__reference_source pcmk__parse_source(const char *source)
Definition: rules.c:804
Scheduler API.
pcmk__combine
#define PCMK_VALUE_META
Definition: options.h:171
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:92
int pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t)
Definition: xml_element.c:1436
#define crm_warn(fmt, args...)
Definition: logging.h:362
int pcmk__evaluate_op_expression(const xmlNode *op_expression, const pcmk_rule_input_t *rule_input)
Definition: rules.c:1178
#define PCMK_XE_OP_EXPRESSION
Definition: xml_names.h:148
#define PCMK_VALUE_LTE
Definition: options.h:168
#define PCMK_VALUE_PARAM
Definition: options.h:191
#define PCMK_XE_DATE_EXPRESSION
Definition: xml_names.h:96
#define PCMK_VALUE_LITERAL
Definition: options.h:166
int pcmk_parse_interval_spec(const char *input, guint *result_ms)
Parse milliseconds from a Pacemaker interval specification.
Definition: strings.c:452
#define PCMK_XE_DATE_SPEC
Definition: xml_names.h:97
#define CRM_ATTR_UNAME
Definition: crm.h:92
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define crm_trace(fmt, args...)
Definition: logging.h:372
#define PCMK_XA_WEEKS
Definition: xml_names.h:447
int pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
Definition: rules.c:154
guint op_interval_ms
Operation interval that rule applies to.
Definition: rules.h:68
#define PCMK_VALUE_GTE
Definition: options.h:160
#define PCMK__NELEM(a)
Definition: internal.h:49
enum pcmk__combine pcmk__parse_combine(const char *combine)
Definition: rules.c:830
const char * rsc_standard
Resource standard that rule applies to.
Definition: rules.h:62
#define PCMK_VALUE_IN_RANGE
Definition: options.h:163
#define PCMK_XA_ID
Definition: xml_names.h:301
#define PCMK_XA_VALUE
Definition: xml_names.h:442
int pcmk__evaluate_condition(xmlNode *condition, const pcmk_rule_input_t *rule_input, crm_time_t *next_change)
Definition: rules.c:1251
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition: iso8601.c:380
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1051
#define PCMK_XE_RSC_EXPRESSION
Definition: xml_names.h:187
#define PCMK_XA_MINUTES
Definition: xml_names.h:325
int pcmk__scan_double(const char *text, double *result, const char *default_text, char **end_text)
Definition: strings.c:191
#define PCMK_XE_RULE
Definition: xml_names.h:191
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition: iso8601.c:1736
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
Definition: xml_element.c:106
#define PCMK_XA_MONTHDAYS
Definition: xml_names.h:327
int pcmk__evaluate_date_expression(const xmlNode *date_expression, const crm_time_t *now, crm_time_t *next_change)
Definition: rules.c:469
#define PCMK_XA_SECONDS
Definition: xml_names.h:399
#define PCMK_XA_CLASS
Definition: xml_names.h:246
#define PCMK_META_INTERVAL
Definition: options.h:91
pcmk__action_result_t result
Definition: pcmk_fence.c:37
char * pcmk__replace_submatches(const char *string, const char *match, const regmatch_t submatches[], int nmatches)
Definition: rules.c:613
G_GNUC_INTERNAL void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
Definition: iso8601.c:1461
const regmatch_t * rsc_id_submatches
Resource pattern submatches (as set by regexec()) for rsc_id.
Definition: rules.h:99
#define PCMK_XE_OPERATION
Definition: xml_names.h:149
int compare_version(const char *version1, const char *version2)
Definition: utils.c:202
#define PCMK_XA_ATTRIBUTE
Definition: xml_names.h:236
#define PCMK_VALUE_GT
Definition: options.h:159
#define PCMK_VALUE_VERSION
Definition: options.h:222
GHashTable * node_attrs
Definition: rules.h:77
#define PCMK_VALUE_STRING
Definition: options.h:211
#define PCMK_VALUE_LT
Definition: options.h:167
#define PCMK_XA_BOOLEAN_OP
Definition: xml_names.h:240
int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
Definition: strings.c:905
const char * pcmk__readable_interval(guint interval_ms)
Definition: iso8601.c:2206
#define PCMK_VALUE_INTEGER
Definition: options.h:165
#define PCMK_VALUE_NE
Definition: options.h:177
#define PCMK_XA_END
Definition: xml_names.h:267
int pcmk__cmp_by_type(const char *value1, const char *value2, enum pcmk__type type)
Definition: rules.c:742
#define PCMK_VALUE_DEFINED
Definition: options.h:144
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
#define CRM_ATTR_ID
Definition: crm.h:93
const char * rsc_id
Resource ID to compare against a location constraint&#39;s resource pattern.
Definition: rules.h:96
#define PCMK_XA_VALUE_SOURCE
Definition: xml_names.h:443
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition: iso8601.c:314
#define ADD_COMPONENT(component)
Definition: rules.c:217
int pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression, const pcmk_rule_input_t *rule_input)
Definition: rules.c:1109
#define PCMK_XA_MONTHS
Definition: xml_names.h:328
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:150