| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <crm_internal.h> |
| #include <crm/crm.h> |
| #include <crm/msg_xml.h> |
| #include <crm/common/xml.h> |
| #include <crm/common/xml_internal.h> |
| |
| #include <glib.h> |
| |
| #include <crm/pengine/rules.h> |
| #include <crm/pengine/rules_internal.h> |
| #include <crm/pengine/internal.h> |
| |
| #include <sys/types.h> |
| #include <regex.h> |
| #include <ctype.h> |
| |
| CRM_TRACE_INIT_DATA(pe_rules); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| gboolean |
| pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now, |
| crm_time_t *next_change) |
| { |
| pe_rule_eval_data_t rule_data = { |
| .node_hash = node_hash, |
| .role = RSC_ROLE_UNKNOWN, |
| .now = now, |
| .match_data = NULL, |
| .rsc_data = NULL, |
| .op_data = NULL |
| }; |
| |
| return pe_eval_rules(ruleset, &rule_data, next_change); |
| } |
| |
| gboolean |
| pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, |
| crm_time_t *now, crm_time_t *next_change, |
| pe_match_data_t *match_data) |
| { |
| pe_rule_eval_data_t rule_data = { |
| .node_hash = node_hash, |
| .role = role, |
| .now = now, |
| .match_data = match_data, |
| .rsc_data = NULL, |
| .op_data = NULL |
| }; |
| |
| return pe_eval_expr(rule, &rule_data, next_change); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| gboolean |
| pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role, |
| crm_time_t *now, crm_time_t *next_change, |
| pe_match_data_t *match_data) |
| { |
| pe_rule_eval_data_t rule_data = { |
| .node_hash = node_hash, |
| .role = role, |
| .now = now, |
| .match_data = match_data, |
| .rsc_data = NULL, |
| .op_data = NULL |
| }; |
| |
| return pe_eval_subexpr(expr, &rule_data, next_change); |
| } |
| |
| enum expression_type |
| find_expression_type(xmlNode * expr) |
| { |
| const char *tag = NULL; |
| const char *attr = NULL; |
| |
| attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); |
| tag = crm_element_name(expr); |
| |
| if (pcmk__str_eq(tag, "date_expression", pcmk__str_casei)) { |
| return time_expr; |
| |
| } else if (pcmk__str_eq(tag, "rsc_expression", pcmk__str_casei)) { |
| return rsc_expr; |
| |
| } else if (pcmk__str_eq(tag, "op_expression", pcmk__str_casei)) { |
| return op_expr; |
| |
| } else if (pcmk__str_eq(tag, XML_TAG_RULE, pcmk__str_casei)) { |
| return nested_rule; |
| |
| } else if (!pcmk__str_eq(tag, "expression", pcmk__str_casei)) { |
| return not_expr; |
| |
| } else if (pcmk__strcase_any_of(attr, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID, NULL)) { |
| return loc_expr; |
| |
| } else if (pcmk__str_eq(attr, CRM_ATTR_ROLE, pcmk__str_casei)) { |
| return role_expr; |
| |
| #if ENABLE_VERSIONED_ATTRS |
| } else if (pcmk__str_eq(attr, CRM_ATTR_RA_VERSION, pcmk__str_casei)) { |
| return version_expr; |
| #endif |
| } |
| |
| return attr_expr; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int |
| phase_of_the_moon(crm_time_t * now) |
| { |
| uint32_t epact, diy, goldn; |
| uint32_t y; |
| |
| crm_time_get_ordinal(now, &y, &diy); |
| |
| goldn = (y % 19) + 1; |
| epact = (11 * goldn + 18) % 30; |
| if ((epact == 25 && goldn > 11) || epact == 24) |
| epact++; |
| |
| return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7); |
| } |
| |
| static int |
| check_one(xmlNode *cron_spec, const char *xml_field, uint32_t time_field) { |
| int rc = pcmk_rc_undetermined; |
| const char *value = crm_element_value(cron_spec, xml_field); |
| long long low, high; |
| |
| if (value == NULL) { |
| |
| goto bail; |
| } |
| |
| if (pcmk__parse_ll_range(value, &low, &high) == pcmk_rc_unknown_format) { |
| goto bail; |
| } else if (low == high) { |
| |
| if (time_field < low) { |
| rc = pcmk_rc_before_range; |
| } else if (time_field > high) { |
| rc = pcmk_rc_after_range; |
| } else { |
| rc = pcmk_rc_within_range; |
| } |
| } else if (low != -1 && high != -1) { |
| |
| if (time_field < low) { |
| rc = pcmk_rc_before_range; |
| } else if (time_field > high) { |
| rc = pcmk_rc_after_range; |
| } else { |
| rc = pcmk_rc_within_range; |
| } |
| } else if (low == -1) { |
| |
| rc = time_field <= high ? pcmk_rc_within_range : pcmk_rc_after_range; |
| } else if (high == -1) { |
| |
| rc = time_field >= low ? pcmk_rc_within_range : pcmk_rc_before_range; |
| } |
| |
| bail: |
| if (rc == pcmk_rc_within_range) { |
| crm_debug("Condition '%s' in %s: passed", value, xml_field); |
| } else { |
| crm_debug("Condition '%s' in %s: failed", value, xml_field); |
| } |
| |
| return rc; |
| } |
| |
| static gboolean |
| check_passes(int rc) { |
| |
| |
| |
| |
| |
| return rc == pcmk_rc_within_range || rc == pcmk_rc_undetermined; |
| } |
| |
| #define CHECK_ONE(spec, name, var) do { \ |
| int subpart_rc = check_one(spec, name, var); \ |
| if (check_passes(subpart_rc) == FALSE) { \ |
| return subpart_rc; \ |
| } \ |
| } while (0) |
| |
| int |
| pe_cron_range_satisfied(crm_time_t * now, xmlNode * cron_spec) |
| { |
| uint32_t h, m, s, y, d, w; |
| |
| CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied); |
| |
| crm_time_get_gregorian(now, &y, &m, &d); |
| CHECK_ONE(cron_spec, "years", y); |
| CHECK_ONE(cron_spec, "months", m); |
| CHECK_ONE(cron_spec, "monthdays", d); |
| |
| crm_time_get_timeofday(now, &h, &m, &s); |
| CHECK_ONE(cron_spec, "hours", h); |
| CHECK_ONE(cron_spec, "minutes", m); |
| CHECK_ONE(cron_spec, "seconds", s); |
| |
| crm_time_get_ordinal(now, &y, &d); |
| CHECK_ONE(cron_spec, "yeardays", d); |
| |
| crm_time_get_isoweek(now, &y, &w, &d); |
| CHECK_ONE(cron_spec, "weekyears", y); |
| CHECK_ONE(cron_spec, "weeks", w); |
| CHECK_ONE(cron_spec, "weekdays", d); |
| |
| CHECK_ONE(cron_spec, "moon", phase_of_the_moon(now)); |
| |
| |
| |
| |
| |
| return pcmk_rc_ok; |
| } |
| |
| #define update_field(xml_field, time_fn) \ |
| value = crm_element_value(duration_spec, xml_field); \ |
| if(value != NULL) { \ |
| int value_i = crm_parse_int(value, "0"); \ |
| time_fn(end, value_i); \ |
| } |
| |
| crm_time_t * |
| pe_parse_xml_duration(crm_time_t * start, xmlNode * duration_spec) |
| { |
| crm_time_t *end = NULL; |
| const char *value = NULL; |
| |
| end = crm_time_new(NULL); |
| crm_time_set(end, start); |
| |
| update_field("years", crm_time_add_years); |
| update_field("months", crm_time_add_months); |
| update_field("weeks", crm_time_add_weeks); |
| update_field("days", crm_time_add_days); |
| update_field("hours", crm_time_add_hours); |
| update_field("minutes", crm_time_add_minutes); |
| update_field("seconds", crm_time_add_seconds); |
| |
| return end; |
| } |
| |
| |
| static void |
| crm_time_set_if_earlier(crm_time_t *next_change, crm_time_t *t) |
| { |
| if ((next_change != NULL) && (t != NULL)) { |
| if (!crm_time_is_defined(next_change) |
| || (crm_time_compare(t, next_change) < 0)) { |
| crm_time_set(next_change, t); |
| } |
| } |
| } |
| |
| |
| typedef struct sorted_set_s { |
| int score; |
| const char *name; |
| const char *special_name; |
| xmlNode *attr_set; |
| } sorted_set_t; |
| |
| static gint |
| sort_pairs(gconstpointer a, gconstpointer b) |
| { |
| const sorted_set_t *pair_a = a; |
| const sorted_set_t *pair_b = b; |
| |
| if (a == NULL && b == NULL) { |
| return 0; |
| } else if (a == NULL) { |
| return 1; |
| } else if (b == NULL) { |
| return -1; |
| } |
| |
| if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) { |
| return -1; |
| |
| } else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) { |
| return 1; |
| } |
| |
| if (pair_a->score < pair_b->score) { |
| return 1; |
| } else if (pair_a->score > pair_b->score) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static void |
| populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top) |
| { |
| const char *name = NULL; |
| const char *value = NULL; |
| const char *old_value = NULL; |
| xmlNode *list = nvpair_list; |
| xmlNode *an_attr = NULL; |
| |
| name = crm_element_name(list->children); |
| if (pcmk__str_eq(XML_TAG_ATTRS, name, pcmk__str_casei)) { |
| list = list->children; |
| } |
| |
| for (an_attr = pcmk__xe_first_child(list); an_attr != NULL; |
| an_attr = pcmk__xe_next(an_attr)) { |
| |
| if (pcmk__str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, pcmk__str_none)) { |
| xmlNode *ref_nvpair = expand_idref(an_attr, top); |
| |
| name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME); |
| if (name == NULL) { |
| name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME); |
| } |
| |
| value = crm_element_value(an_attr, XML_NVPAIR_ATTR_VALUE); |
| if (value == NULL) { |
| value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE); |
| } |
| |
| if (name == NULL || value == NULL) { |
| continue; |
| } |
| |
| old_value = g_hash_table_lookup(hash, name); |
| |
| if (pcmk__str_eq(value, "#default", pcmk__str_casei)) { |
| if (old_value) { |
| crm_trace("Letting %s default (removing explicit value \"%s\")", |
| name, value); |
| g_hash_table_remove(hash, name); |
| } |
| continue; |
| |
| } else if (old_value == NULL) { |
| crm_trace("Setting %s=\"%s\"", name, value); |
| g_hash_table_insert(hash, strdup(name), strdup(value)); |
| |
| } else if (overwrite) { |
| crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")", |
| name, value, old_value); |
| g_hash_table_replace(hash, strdup(name), strdup(value)); |
| } |
| } |
| } |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| static xmlNode* |
| get_versioned_rule(xmlNode * attr_set) |
| { |
| xmlNode * rule = NULL; |
| xmlNode * expr = NULL; |
| |
| for (rule = pcmk__xe_first_child(attr_set); rule != NULL; |
| rule = pcmk__xe_next(rule)) { |
| |
| if (pcmk__str_eq((const char *)rule->name, XML_TAG_RULE, |
| pcmk__str_none)) { |
| for (expr = pcmk__xe_first_child(rule); expr != NULL; |
| expr = pcmk__xe_next(expr)) { |
| |
| if (find_expression_type(expr) == version_expr) { |
| return rule; |
| } |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| add_versioned_attributes(xmlNode * attr_set, xmlNode * versioned_attrs) |
| { |
| xmlNode *attr_set_copy = NULL; |
| xmlNode *rule = NULL; |
| xmlNode *expr = NULL; |
| |
| if (!attr_set || !versioned_attrs) { |
| return; |
| } |
| |
| attr_set_copy = copy_xml(attr_set); |
| |
| rule = get_versioned_rule(attr_set_copy); |
| if (!rule) { |
| free_xml(attr_set_copy); |
| return; |
| } |
| |
| expr = pcmk__xe_first_child(rule); |
| while (expr != NULL) { |
| if (find_expression_type(expr) != version_expr) { |
| xmlNode *node = expr; |
| |
| expr = pcmk__xe_next(expr); |
| free_xml(node); |
| } else { |
| expr = pcmk__xe_next(expr); |
| } |
| } |
| |
| add_node_nocopy(versioned_attrs, NULL, attr_set_copy); |
| } |
| #endif |
| |
| typedef struct unpack_data_s { |
| gboolean overwrite; |
| void *hash; |
| crm_time_t *next_change; |
| pe_rule_eval_data_t *rule_data; |
| xmlNode *top; |
| } unpack_data_t; |
| |
| static void |
| unpack_attr_set(gpointer data, gpointer user_data) |
| { |
| sorted_set_t *pair = data; |
| unpack_data_t *unpack_data = user_data; |
| |
| if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data, |
| unpack_data->next_change)) { |
| return; |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| if (get_versioned_rule(pair->attr_set) && !(unpack_data->rule_data->node_hash && |
| g_hash_table_lookup_extended(unpack_data->rule_data->node_hash, |
| CRM_ATTR_RA_VERSION, NULL, NULL))) { |
| |
| return; |
| } |
| #endif |
| |
| crm_trace("Adding attributes from %s (score %d) %s overwrite", |
| pair->name, pair->score, |
| (unpack_data->overwrite? "with" : "without")); |
| populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top); |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| static void |
| unpack_versioned_attr_set(gpointer data, gpointer user_data) |
| { |
| sorted_set_t *pair = data; |
| unpack_data_t *unpack_data = user_data; |
| |
| if (pe_eval_rules(pair->attr_set, unpack_data->rule_data, |
| unpack_data->next_change)) { |
| add_versioned_attributes(pair->attr_set, unpack_data->hash); |
| } |
| } |
| #endif |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static GList * |
| make_pairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| const char *always_first) |
| { |
| GList *unsorted = NULL; |
| |
| if (xml_obj == NULL) { |
| return NULL; |
| } |
| for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL; |
| attr_set = pcmk__xe_next(attr_set)) { |
| |
| if (pcmk__str_eq(set_name, (const char *) attr_set->name, |
| pcmk__str_null_matches)) { |
| const char *score = NULL; |
| sorted_set_t *pair = NULL; |
| xmlNode *expanded_attr_set = expand_idref(attr_set, top); |
| |
| if (expanded_attr_set == NULL) { |
| |
| continue; |
| } |
| |
| pair = calloc(1, sizeof(sorted_set_t)); |
| pair->name = ID(expanded_attr_set); |
| pair->special_name = always_first; |
| pair->attr_set = expanded_attr_set; |
| |
| score = crm_element_value(expanded_attr_set, XML_RULE_ATTR_SCORE); |
| pair->score = char2score(score); |
| |
| unsorted = g_list_prepend(unsorted, pair); |
| } |
| } |
| return g_list_sort(unsorted, sort_pairs); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static void |
| unpack_nvpair_blocks(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| void *hash, const char *always_first, gboolean overwrite, |
| pe_rule_eval_data_t *rule_data, crm_time_t *next_change, |
| GFunc unpack_func) |
| { |
| GList *pairs = make_pairs(top, xml_obj, set_name, always_first); |
| |
| if (pairs) { |
| unpack_data_t data = { |
| .hash = hash, |
| .overwrite = overwrite, |
| .next_change = next_change, |
| .top = top, |
| .rule_data = rule_data |
| }; |
| |
| g_list_foreach(pairs, unpack_func, &data); |
| g_list_free_full(pairs, free); |
| } |
| } |
| |
| void |
| pe_eval_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| pe_rule_eval_data_t *rule_data, GHashTable *hash, |
| const char *always_first, gboolean overwrite, |
| crm_time_t *next_change) |
| { |
| unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first, |
| overwrite, rule_data, next_change, unpack_attr_set); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void |
| pe_unpack_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| GHashTable *node_hash, GHashTable *hash, |
| const char *always_first, gboolean overwrite, |
| crm_time_t *now, crm_time_t *next_change) |
| { |
| pe_rule_eval_data_t rule_data = { |
| .node_hash = node_hash, |
| .role = RSC_ROLE_UNKNOWN, |
| .now = now, |
| .match_data = NULL, |
| .rsc_data = NULL, |
| .op_data = NULL |
| }; |
| |
| pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, |
| always_first, overwrite, next_change); |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| void |
| pe_eval_versioned_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| pe_rule_eval_data_t *rule_data, xmlNode *hash, |
| crm_time_t *next_change) |
| { |
| unpack_nvpair_blocks(top, xml_obj, set_name, hash, NULL, FALSE, rule_data, |
| next_change, unpack_versioned_attr_set); |
| } |
| #endif |
| |
| char * |
| pe_expand_re_matches(const char *string, pe_re_match_data_t *match_data) |
| { |
| size_t len = 0; |
| int i; |
| const char *p, *last_match_index; |
| char *p_dst, *result = NULL; |
| |
| if (pcmk__str_empty(string) || !match_data) { |
| return NULL; |
| } |
| |
| p = last_match_index = string; |
| |
| while (*p) { |
| if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) { |
| i = *(p + 1) - '0'; |
| if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 && |
| match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) { |
| len += p - last_match_index + (match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so); |
| last_match_index = p + 2; |
| } |
| p++; |
| } |
| p++; |
| } |
| len += p - last_match_index + 1; |
| |
| |
| if (len - 1 <= 0) { |
| return NULL; |
| } |
| |
| p_dst = result = calloc(1, len); |
| p = string; |
| |
| while (*p) { |
| if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) { |
| i = *(p + 1) - '0'; |
| if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 && |
| match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) { |
| |
| int match_len = match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so; |
| memcpy(p_dst, match_data->string + match_data->pmatch[i].rm_so, match_len); |
| p_dst += match_len; |
| } |
| p++; |
| } else { |
| *(p_dst) = *(p); |
| p_dst++; |
| } |
| p++; |
| } |
| |
| return result; |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| GHashTable* |
| pe_unpack_versioned_parameters(xmlNode *versioned_params, const char *ra_version) |
| { |
| GHashTable *hash = crm_str_table_new(); |
| |
| if (versioned_params && ra_version) { |
| GHashTable *node_hash = crm_str_table_new(); |
| xmlNode *attr_set = pcmk__xe_first_child(versioned_params); |
| |
| if (attr_set) { |
| g_hash_table_insert(node_hash, strdup(CRM_ATTR_RA_VERSION), |
| strdup(ra_version)); |
| pe_unpack_nvpairs(NULL, versioned_params, |
| crm_element_name(attr_set), node_hash, hash, NULL, |
| FALSE, NULL, NULL); |
| } |
| |
| g_hash_table_destroy(node_hash); |
| } |
| |
| return hash; |
| } |
| #endif |
| |
| gboolean |
| pe_eval_rules(xmlNode *ruleset, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) |
| { |
| |
| gboolean ruleset_default = TRUE; |
| |
| for (xmlNode *rule = first_named_child(ruleset, XML_TAG_RULE); |
| rule != NULL; rule = crm_next_same_xml(rule)) { |
| |
| ruleset_default = FALSE; |
| if (pe_eval_expr(rule, rule_data, next_change)) { |
| |
| |
| |
| |
| |
| |
| return TRUE; |
| } |
| } |
| |
| return ruleset_default; |
| } |
| |
| gboolean |
| pe_eval_expr(xmlNode *rule, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) |
| { |
| xmlNode *expr = NULL; |
| gboolean test = TRUE; |
| gboolean empty = TRUE; |
| gboolean passed = TRUE; |
| gboolean do_and = TRUE; |
| const char *value = NULL; |
| |
| rule = expand_idref(rule, NULL); |
| value = crm_element_value(rule, XML_RULE_ATTR_BOOLEAN_OP); |
| if (pcmk__str_eq(value, "or", pcmk__str_casei)) { |
| do_and = FALSE; |
| passed = FALSE; |
| } |
| |
| crm_trace("Testing rule %s", ID(rule)); |
| for (expr = pcmk__xe_first_child(rule); expr != NULL; |
| expr = pcmk__xe_next(expr)) { |
| |
| test = pe_eval_subexpr(expr, rule_data, next_change); |
| empty = FALSE; |
| |
| if (test && do_and == FALSE) { |
| crm_trace("Expression %s/%s passed", ID(rule), ID(expr)); |
| return TRUE; |
| |
| } else if (test == FALSE && do_and) { |
| crm_trace("Expression %s/%s failed", ID(rule), ID(expr)); |
| return FALSE; |
| } |
| } |
| |
| if (empty) { |
| crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule)); |
| } |
| |
| crm_trace("Rule %s %s", ID(rule), passed ? "passed" : "failed"); |
| return passed; |
| } |
| |
| gboolean |
| pe_eval_subexpr(xmlNode *expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) |
| { |
| gboolean accept = FALSE; |
| const char *uname = NULL; |
| |
| switch (find_expression_type(expr)) { |
| case nested_rule: |
| accept = pe_eval_expr(expr, rule_data, next_change); |
| break; |
| case attr_expr: |
| case loc_expr: |
| |
| |
| |
| if (rule_data->node_hash != NULL) { |
| accept = pe__eval_attr_expr(expr, rule_data); |
| } |
| break; |
| |
| case time_expr: |
| switch (pe__eval_date_expr(expr, rule_data, next_change)) { |
| case pcmk_rc_within_range: |
| case pcmk_rc_ok: |
| accept = TRUE; |
| break; |
| |
| default: |
| accept = FALSE; |
| break; |
| } |
| break; |
| |
| case role_expr: |
| accept = pe__eval_role_expr(expr, rule_data); |
| break; |
| |
| case rsc_expr: |
| accept = pe__eval_rsc_expr(expr, rule_data); |
| break; |
| |
| case op_expr: |
| accept = pe__eval_op_expr(expr, rule_data); |
| break; |
| |
| #if ENABLE_VERSIONED_ATTRS |
| case version_expr: |
| if (rule_data->node_hash && |
| g_hash_table_lookup_extended(rule_data->node_hash, |
| CRM_ATTR_RA_VERSION, NULL, NULL)) { |
| accept = pe__eval_attr_expr(expr, rule_data); |
| } else { |
| |
| accept = TRUE; |
| } |
| break; |
| #endif |
| |
| default: |
| CRM_CHECK(FALSE , return FALSE); |
| accept = FALSE; |
| } |
| if (rule_data->node_hash) { |
| uname = g_hash_table_lookup(rule_data->node_hash, CRM_ATTR_UNAME); |
| } |
| |
| crm_trace("Expression %s %s on %s", |
| ID(expr), accept ? "passed" : "failed", uname ? uname : "all nodes"); |
| return accept; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int |
| compare_attr_expr_vals(const char *l_val, const char *r_val, const char *type, |
| const char *op) |
| { |
| int cmp = 0; |
| |
| if (l_val != NULL && r_val != NULL) { |
| if (type == NULL) { |
| if (pcmk__strcase_any_of(op, "lt", "lte", "gt", "gte", NULL)) { |
| if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) { |
| type = "number"; |
| } else { |
| type = "integer"; |
| } |
| |
| } else { |
| type = "string"; |
| } |
| crm_trace("Defaulting to %s based comparison for '%s' op", type, op); |
| } |
| |
| if (pcmk__str_eq(type, "string", pcmk__str_casei)) { |
| cmp = strcasecmp(l_val, r_val); |
| |
| } else if (pcmk__str_eq(type, "integer", pcmk__str_casei)) { |
| long long l_val_num = crm_parse_ll(l_val, NULL); |
| int rc1 = errno; |
| |
| long long r_val_num = crm_parse_ll(r_val, NULL); |
| int rc2 = errno; |
| |
| if (rc1 == 0 && rc2 == 0) { |
| if (l_val_num < r_val_num) { |
| cmp = -1; |
| } else if (l_val_num > r_val_num) { |
| cmp = 1; |
| } else { |
| cmp = 0; |
| } |
| |
| } else { |
| crm_debug("Integer parse error. Comparing %s and %s as strings", |
| l_val, r_val); |
| cmp = compare_attr_expr_vals(l_val, r_val, "string", op); |
| } |
| |
| } else if (pcmk__str_eq(type, "number", pcmk__str_casei)) { |
| double l_val_num; |
| double r_val_num; |
| |
| int rc1 = pcmk__scan_double(l_val, &l_val_num, NULL, NULL); |
| int rc2 = pcmk__scan_double(r_val, &r_val_num, NULL, NULL); |
| |
| if (rc1 == pcmk_rc_ok && rc2 == pcmk_rc_ok) { |
| if (l_val_num < r_val_num) { |
| cmp = -1; |
| } else if (l_val_num > r_val_num) { |
| cmp = 1; |
| } else { |
| cmp = 0; |
| } |
| |
| } else { |
| crm_debug("Floating-point parse error. Comparing %s and %s as " |
| "strings", l_val, r_val); |
| cmp = compare_attr_expr_vals(l_val, r_val, "string", op); |
| } |
| |
| } else if (pcmk__str_eq(type, "version", pcmk__str_casei)) { |
| cmp = compare_version(l_val, r_val); |
| |
| } |
| |
| } else if (l_val == NULL && r_val == NULL) { |
| cmp = 0; |
| } else if (r_val == NULL) { |
| cmp = 1; |
| } else { |
| cmp = -1; |
| } |
| |
| return cmp; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static bool |
| accept_attr_expr(const char *l_val, const char *r_val, const char *type, |
| const char *op) |
| { |
| int cmp; |
| |
| if (pcmk__str_eq(op, "defined", pcmk__str_casei)) { |
| return (l_val != NULL); |
| |
| } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) { |
| return (l_val == NULL); |
| |
| } |
| |
| cmp = compare_attr_expr_vals(l_val, r_val, type, op); |
| |
| if (pcmk__str_eq(op, "eq", pcmk__str_casei)) { |
| return (cmp == 0); |
| |
| } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) { |
| return (cmp != 0); |
| |
| } else if (l_val == NULL || r_val == NULL) { |
| |
| return false; |
| |
| } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) { |
| return (cmp < 0); |
| |
| } else if (pcmk__str_eq(op, "lte", pcmk__str_casei)) { |
| return (cmp <= 0); |
| |
| } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { |
| return (cmp > 0); |
| |
| } else if (pcmk__str_eq(op, "gte", pcmk__str_casei)) { |
| return (cmp >= 0); |
| } |
| |
| return false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| gboolean |
| pe__eval_attr_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) |
| { |
| gboolean attr_allocated = FALSE; |
| const char *h_val = NULL; |
| GHashTable *table = NULL; |
| bool literal = true; |
| |
| const char *op = NULL; |
| const char *type = NULL; |
| const char *attr = NULL; |
| const char *value = NULL; |
| const char *value_source = NULL; |
| |
| attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); |
| op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); |
| value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); |
| type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); |
| value_source = crm_element_value(expr, XML_EXPR_ATTR_VALUE_SOURCE); |
| |
| if (attr == NULL || op == NULL) { |
| pe_err("Invalid attribute or operation in expression" |
| " (\'%s\' \'%s\' \'%s\')", crm_str(attr), crm_str(op), crm_str(value)); |
| return FALSE; |
| } |
| |
| if (rule_data->match_data) { |
| if (rule_data->match_data->re) { |
| char *resolved_attr = pe_expand_re_matches(attr, rule_data->match_data->re); |
| |
| if (resolved_attr) { |
| attr = (const char *) resolved_attr; |
| attr_allocated = TRUE; |
| } |
| } |
| |
| if (pcmk__str_eq(value_source, "param", pcmk__str_casei)) { |
| literal = false; |
| table = rule_data->match_data->params; |
| } else if (pcmk__str_eq(value_source, "meta", pcmk__str_casei)) { |
| literal = false; |
| table = rule_data->match_data->meta; |
| } |
| } |
| |
| if (!literal) { |
| const char *param_name = value; |
| const char *param_value = NULL; |
| |
| value = NULL; |
| if ((table != NULL) && !pcmk__str_empty(param_name)) { |
| param_value = (const char *)g_hash_table_lookup(table, param_name); |
| if (param_value != NULL) { |
| value = param_value; |
| } |
| } |
| } |
| |
| if (rule_data->node_hash != NULL) { |
| h_val = (const char *)g_hash_table_lookup(rule_data->node_hash, attr); |
| } |
| |
| if (attr_allocated) { |
| free((char *)attr); |
| attr = NULL; |
| } |
| |
| return accept_attr_expr(h_val, value, type, op); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int |
| pe__eval_date_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) |
| { |
| crm_time_t *start = NULL; |
| crm_time_t *end = NULL; |
| const char *value = NULL; |
| const char *op = crm_element_value(expr, "operation"); |
| |
| xmlNode *duration_spec = NULL; |
| xmlNode *date_spec = NULL; |
| |
| |
| int rc = pcmk_rc_undetermined; |
| |
| crm_trace("Testing expression: %s", ID(expr)); |
| |
| duration_spec = first_named_child(expr, "duration"); |
| date_spec = first_named_child(expr, "date_spec"); |
| |
| value = crm_element_value(expr, "start"); |
| if (value != NULL) { |
| start = crm_time_new(value); |
| } |
| value = crm_element_value(expr, "end"); |
| if (value != NULL) { |
| end = crm_time_new(value); |
| } |
| |
| if (start != NULL && end == NULL && duration_spec != NULL) { |
| end = pe_parse_xml_duration(start, duration_spec); |
| } |
| |
| if (pcmk__str_eq(op, "in_range", pcmk__str_null_matches | pcmk__str_casei)) { |
| if ((start == NULL) && (end == NULL)) { |
| |
| } else if ((start != NULL) && (crm_time_compare(rule_data->now, start) < 0)) { |
| rc = pcmk_rc_before_range; |
| crm_time_set_if_earlier(next_change, start); |
| } else if ((end != NULL) && (crm_time_compare(rule_data->now, end) > 0)) { |
| rc = pcmk_rc_after_range; |
| } else { |
| rc = pcmk_rc_within_range; |
| if (end && next_change) { |
| |
| crm_time_add_seconds(end, 1); |
| crm_time_set_if_earlier(next_change, end); |
| } |
| } |
| |
| } else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) { |
| rc = pe_cron_range_satisfied(rule_data->now, date_spec); |
| |
| |
| } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { |
| if (start == NULL) { |
| |
| } else if (crm_time_compare(rule_data->now, start) > 0) { |
| rc = pcmk_rc_within_range; |
| } else { |
| rc = pcmk_rc_before_range; |
| |
| |
| crm_time_add_seconds(start, 1); |
| crm_time_set_if_earlier(next_change, start); |
| } |
| |
| } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) { |
| if (end == NULL) { |
| |
| } else if (crm_time_compare(rule_data->now, end) < 0) { |
| rc = pcmk_rc_within_range; |
| crm_time_set_if_earlier(next_change, end); |
| } else { |
| rc = pcmk_rc_after_range; |
| } |
| } |
| |
| crm_time_free(start); |
| crm_time_free(end); |
| return rc; |
| } |
| |
| gboolean |
| pe__eval_op_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) { |
| const char *name = crm_element_value(expr, XML_NVPAIR_ATTR_NAME); |
| const char *interval_s = crm_element_value(expr, XML_LRM_ATTR_INTERVAL); |
| guint interval; |
| |
| crm_trace("Testing op_defaults expression: %s", ID(expr)); |
| |
| if (rule_data->op_data == NULL) { |
| crm_trace("No operations data provided"); |
| return FALSE; |
| } |
| |
| interval = crm_parse_interval_spec(interval_s); |
| if (interval == 0 && errno != 0) { |
| crm_trace("Could not parse interval: %s", interval_s); |
| return FALSE; |
| } |
| |
| if (interval_s != NULL && interval != rule_data->op_data->interval) { |
| crm_trace("Interval doesn't match: %d != %d", interval, rule_data->op_data->interval); |
| return FALSE; |
| } |
| |
| if (!pcmk__str_eq(name, rule_data->op_data->op_name, pcmk__str_none)) { |
| crm_trace("Name doesn't match: %s != %s", name, rule_data->op_data->op_name); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| gboolean |
| pe__eval_role_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) |
| { |
| gboolean accept = FALSE; |
| const char *op = NULL; |
| const char *value = NULL; |
| |
| if (rule_data->role == RSC_ROLE_UNKNOWN) { |
| return accept; |
| } |
| |
| value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); |
| op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); |
| |
| if (pcmk__str_eq(op, "defined", pcmk__str_casei)) { |
| if (rule_data->role > RSC_ROLE_STARTED) { |
| accept = TRUE; |
| } |
| |
| } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) { |
| if (rule_data->role < RSC_ROLE_SLAVE && rule_data->role > RSC_ROLE_UNKNOWN) { |
| accept = TRUE; |
| } |
| |
| } else if (pcmk__str_eq(op, "eq", pcmk__str_casei)) { |
| if (text2role(value) == rule_data->role) { |
| accept = TRUE; |
| } |
| |
| } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) { |
| |
| if (rule_data->role < RSC_ROLE_SLAVE && rule_data->role > RSC_ROLE_UNKNOWN) { |
| accept = FALSE; |
| |
| } else if (text2role(value) != rule_data->role) { |
| accept = TRUE; |
| } |
| } |
| return accept; |
| } |
| |
| gboolean |
| pe__eval_rsc_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) |
| { |
| const char *class = crm_element_value(expr, XML_AGENT_ATTR_CLASS); |
| const char *provider = crm_element_value(expr, XML_AGENT_ATTR_PROVIDER); |
| const char *type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); |
| |
| crm_trace("Testing rsc_defaults expression: %s", ID(expr)); |
| |
| if (rule_data->rsc_data == NULL) { |
| crm_trace("No resource data provided"); |
| return FALSE; |
| } |
| |
| if (class != NULL && |
| !pcmk__str_eq(class, rule_data->rsc_data->standard, pcmk__str_none)) { |
| crm_trace("Class doesn't match: %s != %s", class, rule_data->rsc_data->standard); |
| return FALSE; |
| } |
| |
| if ((provider == NULL && rule_data->rsc_data->provider != NULL) || |
| (provider != NULL && rule_data->rsc_data->provider == NULL) || |
| !pcmk__str_eq(provider, rule_data->rsc_data->provider, pcmk__str_none)) { |
| crm_trace("Provider doesn't match: %s != %s", provider, rule_data->rsc_data->provider); |
| return FALSE; |
| } |
| |
| if (type != NULL && |
| !pcmk__str_eq(type, rule_data->rsc_data->agent, pcmk__str_none)) { |
| crm_trace("Agent doesn't match: %s != %s", type, rule_data->rsc_data->agent); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| gboolean test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now); |
| gboolean test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, |
| crm_time_t *now); |
| gboolean pe_test_rule_re(xmlNode *rule, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now, |
| pe_re_match_data_t *re_match_data); |
| gboolean pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now, |
| pe_match_data_t *match_data); |
| gboolean test_expression(xmlNode *expr, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now); |
| gboolean pe_test_expression_re(xmlNode *expr, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now, |
| pe_re_match_data_t *re_match_data); |
| gboolean pe_test_expression_full(xmlNode *expr, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now, |
| pe_match_data_t *match_data); |
| void unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, |
| const char *set_name, GHashTable *node_hash, |
| GHashTable *hash, const char *always_first, |
| gboolean overwrite, crm_time_t *now); |
| |
| gboolean |
| test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now) |
| { |
| return pe_evaluate_rules(ruleset, node_hash, now, NULL); |
| } |
| |
| gboolean |
| test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) |
| { |
| return pe_test_rule(rule, node_hash, role, now, NULL, NULL); |
| } |
| |
| gboolean |
| pe_test_rule_re(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data) |
| { |
| pe_match_data_t match_data = { |
| .re = re_match_data, |
| .params = NULL, |
| .meta = NULL, |
| }; |
| return pe_test_rule(rule, node_hash, role, now, NULL, &match_data); |
| } |
| |
| gboolean |
| pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, |
| crm_time_t *now, pe_match_data_t *match_data) |
| { |
| return pe_test_rule(rule, node_hash, role, now, NULL, match_data); |
| } |
| |
| gboolean |
| test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) |
| { |
| return pe_test_expression(expr, node_hash, role, now, NULL, NULL); |
| } |
| |
| gboolean |
| pe_test_expression_re(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data) |
| { |
| pe_match_data_t match_data = { |
| .re = re_match_data, |
| .params = NULL, |
| .meta = NULL, |
| }; |
| return pe_test_expression(expr, node_hash, role, now, NULL, &match_data); |
| } |
| |
| gboolean |
| pe_test_expression_full(xmlNode *expr, GHashTable *node_hash, |
| enum rsc_role_e role, crm_time_t *now, |
| pe_match_data_t *match_data) |
| { |
| return pe_test_expression(expr, node_hash, role, now, NULL, match_data); |
| } |
| |
| void |
| unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, |
| GHashTable *node_hash, GHashTable *hash, |
| const char *always_first, gboolean overwrite, |
| crm_time_t *now) |
| { |
| pe_rule_eval_data_t rule_data = { |
| .node_hash = node_hash, |
| .role = RSC_ROLE_UNKNOWN, |
| .now = now, |
| .match_data = NULL, |
| .rsc_data = NULL, |
| .op_data = NULL |
| }; |
| |
| unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first, |
| overwrite, &rule_data, NULL, unpack_attr_set); |
| } |