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