diff --git a/include/crm/common/xml_idref_internal.h b/include/crm/common/xml_idref_internal.h index 58f1c1b9e3..29313f1353 100644 --- a/include/crm/common/xml_idref_internal.h +++ b/include/crm/common/xml_idref_internal.h @@ -1,26 +1,28 @@ /* * Copyright 2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_XML_IDREF_INTERNAL__H #define PCMK__CRM_COMMON_XML_IDREF_INTERNAL__H #include // gboolean, gpointer, GList, GHashTable #include // xmlNode // An XML ID and references to it (used for tags and templates) typedef struct { char *id; // XML ID of primary element GList *refs; // XML IDs of elements that reference the primary element } pcmk__idref_t; void pcmk__add_idref(GHashTable *table, const char *id, const char *referrer); void pcmk__free_idref(gpointer data); xmlNode *pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search); +GList *pcmk__xe_dereference_children(const xmlNode *xml_obj, + const char *set_name); #endif // PCMK__CRM_COMMON_XML_IDREF_INTERNAL__H diff --git a/lib/common/xml_idref.c b/lib/common/xml_idref.c index 2721b8bf7e..583018ceed 100644 --- a/lib/common/xml_idref.c +++ b/lib/common/xml_idref.c @@ -1,115 +1,147 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include // NULL #include // free() #include // GList, GHashTable, etc. #include // xmlNode #include #include // get_xpath_object(), PCMK_XA_ID_REF /*! * \internal * \brief Add an XML ID reference to a table * * \param[in,out] table Table of ID references to add to * \param[in] id ID of primary element being referred to * \param[in] referrer ID of element referring to \p id * * \note This refers to an ID reference in general, not necessarily connected to * an id-ref attribute. */ void pcmk__add_idref(GHashTable *table, const char *id, const char *referrer) { pcmk__idref_t *idref = NULL; pcmk__assert((table != NULL) && (id != NULL) && (referrer != NULL)); idref = g_hash_table_lookup(table, id); if (idref == NULL) { idref = pcmk__assert_alloc(1, sizeof(pcmk__idref_t)); idref->id = pcmk__str_copy(id); g_hash_table_insert(table, pcmk__str_copy(id), idref); } for (GList *iter = idref->refs; iter != NULL; iter = iter->next) { if (pcmk__str_eq(referrer, (const char *) iter->data, pcmk__str_none)) { return; // Already present } } idref->refs = g_list_append(idref->refs, pcmk__str_copy(referrer)); crm_trace("Added ID %s referrer %s", id, referrer); } /*! * \internal * \brief Free a pcmk__idref_t * * \param[in,out] data pcmk__idref_t to free */ void pcmk__free_idref(gpointer data) { pcmk__idref_t *idref = data; if (idref != NULL) { free(idref->id); g_list_free_full(idref->refs, free); free(idref); } } /*! * \internal * \brief Get the XML element whose \c PCMK_XA_ID matches an \c PCMK_XA_ID_REF * * \param[in] xml Element whose \c PCMK_XA_ID_REF attribute to check * \param[in] search Node whose document to search for node with matching * \c PCMK_XA_ID (\c NULL to use \p xml) * * \return If \p xml has a \c PCMK_XA_ID_REF attribute, node in * search's document whose \c PCMK_XA_ID attribute matches; * otherwise, \p xml */ xmlNode * pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search) { char *xpath = NULL; const char *ref = NULL; xmlNode *result = NULL; if (xml == NULL) { return NULL; } ref = crm_element_value(xml, PCMK_XA_ID_REF); if (ref == NULL) { return xml; } if (search == NULL) { search = xml; } xpath = crm_strdup_printf("//%s[@" PCMK_XA_ID "='%s']", xml->name, ref); result = get_xpath_object(xpath, search, LOG_DEBUG); if (result == NULL) { // Not possible with schema validation enabled pcmk__config_err("Ignoring invalid %s configuration: " PCMK_XA_ID_REF " '%s' does not reference " "a valid object " QB_XS " xpath=%s", xml->name, ref, xpath); } free(xpath); return result; } + +/*! + * \internal + * \brief Get list of resolved ID references for child elements of given element + * + * \param[in] xml_obj XML element containing blocks of nvpair elements + * \param[in] set_name If not NULL, only get blocks of this element + * + * \return List of XML blocks of name/value pairs + */ +GList * +pcmk__xe_dereference_children(const xmlNode *xml_obj, const char *set_name) +{ + GList *unsorted = NULL; + + if (xml_obj == NULL) { + return NULL; + } + for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL); + attr_set != NULL; attr_set = pcmk__xe_next(attr_set, NULL)) { + + if ((set_name == NULL) || pcmk__xe_is(attr_set, set_name)) { + xmlNode *expanded_attr_set = pcmk__xe_resolve_idref(attr_set, NULL); + + if (expanded_attr_set == NULL) { + continue; // Not possible with schema validation enabled + } + unsorted = g_list_prepend(unsorted, expanded_attr_set); + } + } + return unsorted; +} diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c index a01d42f9c9..6a46c1c032 100644 --- a/lib/pengine/rules.c +++ b/lib/pengine/rules.c @@ -1,257 +1,225 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include CRM_TRACE_INIT_DATA(pe_rules); /*! * \internal * \brief Map pe_rule_eval_data_t to pcmk_rule_input_t * * \param[out] new New data struct * \param[in] old Old data struct */ static void map_rule_input(pcmk_rule_input_t *new, const pe_rule_eval_data_t *old) { if (old == NULL) { return; } new->now = old->now; new->node_attrs = old->node_hash; if (old->rsc_data != NULL) { new->rsc_standard = old->rsc_data->standard; new->rsc_provider = old->rsc_data->provider; new->rsc_agent = old->rsc_data->agent; } if (old->match_data != NULL) { new->rsc_params = old->match_data->params; new->rsc_meta = old->match_data->meta; if (old->match_data->re != NULL) { new->rsc_id = old->match_data->re->string; new->rsc_id_submatches = old->match_data->re->pmatch; new->rsc_id_nmatches = old->match_data->re->nregs; } } if (old->op_data != NULL) { new->op_name = old->op_data->op_name; new->op_interval_ms = old->op_data->interval; } } static void populate_hash(xmlNode *nvpair_list, GHashTable *hash, bool overwrite) { if (pcmk__xe_is(nvpair_list->children, PCMK__XE_ATTRIBUTES)) { nvpair_list = nvpair_list->children; } for (xmlNode *nvpair = pcmk__xe_first_child(nvpair_list, PCMK_XE_NVPAIR, NULL, NULL); nvpair != NULL; nvpair = pcmk__xe_next(nvpair, PCMK_XE_NVPAIR)) { xmlNode *ref_nvpair = pcmk__xe_resolve_idref(nvpair, NULL); const char *name = NULL; const char *value = NULL; const char *old_value = NULL; if (ref_nvpair == NULL) { /* Not possible with schema validation enabled (error already * logged) */ continue; } name = crm_element_value(ref_nvpair, PCMK_XA_NAME); value = crm_element_value(ref_nvpair, PCMK_XA_VALUE); if ((name == NULL) || (value == NULL)) { continue; } old_value = g_hash_table_lookup(hash, name); if (pcmk__str_eq(value, "#default", pcmk__str_casei)) { // @COMPAT Deprecated since 2.1.8 pcmk__config_warn("Support for setting meta-attributes (such as " "%s) to the explicit value '#default' is " "deprecated and will be removed in a future " "release", name); if (old_value != NULL) { crm_trace("Letting %s default (removing explicit value \"%s\")", name, value); g_hash_table_remove(hash, name); } } else if (old_value == NULL) { crm_trace("Setting %s=\"%s\"", name, value); pcmk__insert_dup(hash, name, value); } else if (overwrite) { crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")", name, value, old_value); pcmk__insert_dup(hash, name, value); } } } static void unpack_attr_set(gpointer data, gpointer user_data) { xmlNode *pair = data; pcmk__nvpair_unpack_t *unpack_data = user_data; xmlNode *rule_xml = pcmk__xe_first_child(pair, PCMK_XE_RULE, NULL, NULL); if ((rule_xml != NULL) && (pcmk_evaluate_rule(rule_xml, &(unpack_data->rule_input), unpack_data->next_change) != pcmk_rc_ok)) { return; } crm_trace("Adding name/value pairs from %s %s overwrite", pcmk__xe_id(pair), (unpack_data->overwrite? "with" : "without")); populate_hash(pair, unpack_data->values, unpack_data->overwrite); } -/*! - * \internal - * \brief Create a sorted list of nvpair blocks - * - * \param[in] xml_obj XML element containing blocks of nvpair elements - * \param[in] set_name If not NULL, only get blocks of this element - * - * \return List of XML blocks of name/value pairs - */ -static GList * -make_pairs(const xmlNode *xml_obj, const char *set_name) -{ - GList *unsorted = NULL; - - if (xml_obj == NULL) { - return NULL; - } - for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL); - attr_set != NULL; attr_set = pcmk__xe_next(attr_set, NULL)) { - - if ((set_name == NULL) || pcmk__xe_is(attr_set, set_name)) { - xmlNode *expanded_attr_set = pcmk__xe_resolve_idref(attr_set, NULL); - - if (expanded_attr_set == NULL) { - continue; // Not possible with schema validation enabled - } - unsorted = g_list_prepend(unsorted, expanded_attr_set); - } - } - return unsorted; -} - /*! * \brief Extract nvpair blocks contained by an XML element into a hash table * * \param[in,out] top Ignored * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name If not NULL, only use blocks of this element * \param[in] rule_data Matching parameters to use when unpacking * \param[out] hash Where to store extracted name/value pairs * \param[in] always_first If not NULL, process block with this ID first * \param[in] overwrite Whether to replace existing values with same * name (all internal callers pass \c FALSE) * \param[out] next_change If not NULL, set to when evaluation will change */ void pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, const pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *next_change) { - GList *pairs = make_pairs(xml_obj, set_name); + GList *pairs = pcmk__xe_dereference_children(xml_obj, set_name); if (pairs) { pcmk__nvpair_unpack_t data = { .values = hash, .first_id = always_first, .overwrite = overwrite, .next_change = next_change, }; map_rule_input(&(data.rule_input), rule_data); pairs = g_list_sort_with_data(pairs, pcmk__cmp_nvpair_blocks, &data); g_list_foreach(pairs, unpack_attr_set, &data); g_list_free(pairs); } } /*! * \brief Extract nvpair blocks contained by an XML element into a hash table * * \param[in,out] top Ignored * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name Element name to identify nvpair blocks * \param[in] node_hash Node attributes to use when evaluating rules * \param[out] hash Where to store extracted name/value pairs * \param[in] always_first If not NULL, process block with this ID first * \param[in] overwrite Whether to replace existing values with same * name (all internal callers pass \c FALSE) * \param[in] now Time to use when evaluating rules * \param[out] next_change If not NULL, set to when evaluation will change */ void pe_unpack_nvpairs(xmlNode *top, const 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, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; pe_eval_nvpairs(NULL, xml_obj, set_name, &rule_data, hash, always_first, overwrite, next_change); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include gboolean test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { pcmk_rule_input_t rule_input = { .node_attrs = node_hash, .now = now, }; return pcmk_evaluate_rule(rule, &rule_input, NULL) == pcmk_rc_ok; } // LCOV_EXCL_STOP // End deprecated API