diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c index 5ba95338b9..0c7d9815b0 100644 --- a/lib/cib/cib_ops.c +++ b/lib/cib/cib_ops.c @@ -1,827 +1,813 @@ /* * Copyright 2004-2025 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 // xmlXPathObject #include #include #include #include // @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon static GHashTable *operation_table = NULL; static const cib__operation_t cib_ops[] = { { PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete, cib__op_attr_modifies|cib__op_attr_privileged }, { PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_BUMP, cib__op_bump, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_writes_through }, { PCMK__CIB_REQUEST_CREATE, cib__op_create, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_DELETE, cib__op_delete, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_ERASE, cib__op_erase, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_MODIFY, cib__op_modify, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none }, { CRM_OP_PING, cib__op_ping, cib__op_attr_none }, { // @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support PCMK__CIB_REQUEST_PRIMARY, cib__op_primary, cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local }, { PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none }, { PCMK__CIB_REQUEST_REPLACE, cib__op_replace, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_writes_through |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary, cib__op_attr_privileged|cib__op_attr_local }, { PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_writes_through |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local } }; /*! * \internal * \brief Get the \c cib__operation_t object for a given CIB operation name * * \param[in] op CIB operation name * \param[out] operation Where to store CIB operation object * * \return Standard Pacemaker return code */ int cib__get_operation(const char *op, const cib__operation_t **operation) { pcmk__assert((op != NULL) && (operation != NULL)); if (operation_table == NULL) { operation_table = pcmk__strkey_table(NULL, NULL); for (int lpc = 0; lpc < PCMK__NELEM(cib_ops); lpc++) { const cib__operation_t *oper = &(cib_ops[lpc]); g_hash_table_insert(operation_table, (gpointer) oper->name, (gpointer) oper); } } *operation = g_hash_table_lookup(operation_table, op); if (*operation == NULL) { crm_err("Operation %s is invalid", op); return EINVAL; } return pcmk_rc_ok; } int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } CRM_CHECK(*answer == NULL, pcmk__xml_free(*answer)); *answer = NULL; if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } obj_root = pcmk_find_cib_element(existing_cib, section); if (obj_root == NULL) { result = -ENXIO; } else if (options & cib_no_children) { xmlNode *shallow = pcmk__xe_create(*answer, (const char *) obj_root->name); pcmk__xe_copy_attrs(shallow, obj_root, pcmk__xaf_none); *answer = shallow; } else { *answer = obj_root; } if (result == pcmk_ok && *answer == NULL) { crm_err("Error creating query response"); result = -ENOMSG; } return result; } static int update_counter(xmlNode *xml_obj, const char *field, bool reset) { char *new_value = NULL; char *old_value = NULL; int int_value = -1; if (!reset && crm_element_value(xml_obj, field) != NULL) { old_value = crm_element_value_copy(xml_obj, field); } if (old_value != NULL) { int_value = atoi(old_value); new_value = pcmk__itoa(++int_value); } else { new_value = pcmk__str_copy("1"); } crm_trace("Update %s from %s to %s", field, pcmk__s(old_value, "unset"), new_value); crm_xml_add(xml_obj, field, new_value); free(new_value); free(old_value); return pcmk_ok; } int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = createEmptyCib(0); pcmk__xe_copy_attrs(*result_cib, existing_cib, pcmk__xaf_none); update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); *answer = NULL; return result; } int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = 0; const char *max_schema = crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX); const char *original_schema = NULL; const char *new_schema = NULL; *answer = NULL; crm_trace("Processing \"%s\" event with max=%s", op, max_schema); original_schema = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH); rc = pcmk__update_schema(result_cib, max_schema, true, !pcmk_is_set(options, cib_verbose)); rc = pcmk_rc2legacy(rc); new_schema = crm_element_value(*result_cib, PCMK_XA_VALIDATE_WITH); if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); update_counter(*result_cib, PCMK_XA_EPOCH, true); update_counter(*result_cib, PCMK_XA_NUM_UPDATES, true); return pcmk_ok; } return rc; } int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing %s for epoch='%s'", op, pcmk__s(crm_element_value(existing_cib, PCMK_XA_EPOCH), "")); *answer = NULL; update_counter(*result_cib, PCMK_XA_EPOCH, false); return result; } int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } *answer = NULL; if (input == NULL) { return -EINVAL; } if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__xe_is(input, section)) { section = NULL; } if (pcmk__xe_is(input, PCMK_XE_CIB)) { int updates = 0; int epoch = 0; int admin_epoch = 0; int replace_updates = 0; int replace_epoch = 0; int replace_admin_epoch = 0; const char *reason = NULL; const char *peer = crm_element_value(req, PCMK__XA_SRC); const char *digest = crm_element_value(req, PCMK__XA_DIGEST); if (digest) { char *digest_verify = pcmk__digest_xml(input, true); if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) { crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer, digest_verify, digest); reason = "digest mismatch"; } else { crm_info("Digest matched on replace from %s: %s", peer, digest); } free(digest_verify); } else { crm_trace("No digest to verify"); } cib_version_details(existing_cib, &admin_epoch, &epoch, &updates); cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates); if (replace_admin_epoch < admin_epoch) { reason = PCMK_XA_ADMIN_EPOCH; } else if (replace_admin_epoch > admin_epoch) { /* no more checks */ } else if (replace_epoch < epoch) { reason = PCMK_XA_EPOCH; } else if (replace_epoch > epoch) { /* no more checks */ } else if (replace_updates < updates) { reason = PCMK_XA_NUM_UPDATES; } if (reason != NULL) { crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:" " current %s is greater than the replacement", replace_admin_epoch, replace_epoch, replace_updates, peer, admin_epoch, epoch, updates, reason); result = -pcmk_err_old_data; } else { crm_info("Replaced %d.%d.%d with %d.%d.%d from %s", admin_epoch, epoch, updates, replace_admin_epoch, replace_epoch, replace_updates, peer); } if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = pcmk__xml_copy(NULL, input); } else { xmlNode *obj_root = NULL; obj_root = pcmk_find_cib_element(*result_cib, section); result = pcmk__xe_replace_match(obj_root, input); result = pcmk_rc2legacy(result); if (result != pcmk_ok) { crm_trace("No matching object to replace"); } } return result; } static int delete_child(xmlNode *child, void *userdata) { xmlNode *obj_root = userdata; if (pcmk__xe_delete_match(obj_root, child) != pcmk_rc_ok) { crm_trace("No matching object to delete: %s=%s", child->name, pcmk__xe_id(child)); } return pcmk_rc_ok; } int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if (pcmk__xe_is(input, section)) { pcmk__xe_foreach_child(input, NULL, delete_child, obj_root); } else { delete_child(input, obj_root); } return pcmk_ok; } int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; uint32_t flags = pcmk__xaf_none; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if (obj_root == NULL) { xmlNode *tmp_section = NULL; const char *path = pcmk_cib_parent_name_for(section); if (path == NULL) { return -EINVAL; } tmp_section = pcmk__xe_create(NULL, section); cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section, NULL, result_cib, answer); pcmk__xml_free(tmp_section); obj_root = pcmk_find_cib_element(*result_cib, section); } CRM_CHECK(obj_root != NULL, return -EINVAL); if (pcmk_is_set(options, cib_score_update)) { flags |= pcmk__xaf_score_update; } if (pcmk__xe_update_match(obj_root, input, flags) != pcmk_rc_ok) { if (options & cib_can_create) { pcmk__xml_copy(obj_root, input); } else { return -ENXIO; } } return pcmk_ok; } static int add_cib_object(xmlNode * parent, xmlNode * new_obj) { const char *object_name = NULL; const char *object_id = NULL; if ((parent == NULL) || (new_obj == NULL)) { return -EINVAL; } object_name = (const char *) new_obj->name; if (object_name == NULL) { return -EINVAL; } object_id = pcmk__xe_id(new_obj); if (pcmk__xe_first_child(parent, object_name, ((object_id != NULL)? PCMK_XA_ID : NULL), object_id)) { return -EEXIST; } if (object_id != NULL) { crm_trace("Processing creation of <%s " PCMK_XA_ID "='%s'>", object_name, object_id); } else { crm_trace("Processing creation of <%s>", object_name); } /* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use * case, PCMK__XA_REPLACE has special meaning and should not be included in * the newly created object until we can break behavioral backward * compatibility. * * At a compatibility break, drop this and drop the definition of * PCMK__XA_REPLACE. Treat it like any other attribute. */ pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb, (void *) PCMK__XA_REPLACE); pcmk__xml_copy(parent, new_obj); return pcmk_ok; } static bool update_results(xmlNode *failed, xmlNode *target, const char *operation, int return_code) { xmlNode *xml_node = NULL; bool was_error = false; const char *error_msg = NULL; if (return_code != pcmk_ok) { error_msg = pcmk_strerror(return_code); was_error = true; xml_node = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE); pcmk__xml_copy(xml_node, target); crm_xml_add(xml_node, PCMK_XA_ID, pcmk__xe_id(target)); crm_xml_add(xml_node, PCMK_XA_OBJECT_TYPE, (const char *) target->name); crm_xml_add(xml_node, PCMK_XA_OPERATION, operation); crm_xml_add(xml_node, PCMK_XA_REASON, error_msg); crm_warn("Action %s failed: %s (cde=%d)", operation, error_msg, return_code); } return was_error; } int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *failed = NULL; int result = pcmk_ok; xmlNode *update_section = NULL; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) { section = NULL; } else if (pcmk__xe_is(input, PCMK_XE_CIB)) { section = NULL; } CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL); if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } if (section == NULL) { return cib_process_modify(op, options, section, req, input, existing_cib, result_cib, answer); } // @COMPAT Deprecated since 2.1.8 failed = pcmk__xe_create(NULL, PCMK__XE_FAILED); update_section = pcmk_find_cib_element(*result_cib, section); if (pcmk__xe_is(input, section)) { xmlNode *a_child = NULL; for (a_child = pcmk__xml_first_child(input); a_child != NULL; a_child = pcmk__xml_next(a_child)) { result = add_cib_object(update_section, a_child); if (update_results(failed, a_child, op, result)) { break; } } } else { result = add_cib_object(update_section, input); update_results(failed, input, op, result); } if ((result == pcmk_ok) && (failed->children != NULL)) { result = -EINVAL; } if (result != pcmk_ok) { crm_log_xml_err(failed, "CIB Update failures"); *answer = failed; } else { pcmk__xml_free(failed); } return result; } int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *originator = NULL; if (req != NULL) { originator = crm_element_value(req, PCMK__XA_SRC); } crm_trace("Processing \"%s\" event from %s%s", op, originator, (pcmk_is_set(options, cib_force_diff)? " (global update)" : "")); if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = pcmk__xml_copy(NULL, existing_cib); return xml_apply_patchset(*result_cib, input, TRUE); } int cib_process_xpath(const char *op, int options, const char *section, const xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { int lpc = 0; int max = 0; int rc = pcmk_ok; bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none); xmlXPathObject *xpathObj = NULL; crm_trace("Processing \"%s\" event", op); if (is_query) { xpathObj = pcmk__xpath_search(existing_cib->doc, section); } else { xpathObj = pcmk__xpath_search((*result_cib)->doc, section); } max = pcmk__xpath_num_results(xpathObj); if ((max < 1) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { crm_debug("%s was already removed", section); } else if (max < 1) { crm_debug("%s: %s does not exist", op, section); rc = -ENXIO; } else if (is_query) { if (max > 1) { *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } } if (pcmk_is_set(options, cib_multiple) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { dedupXpathResults(xpathObj); } for (lpc = 0; lpc < max; lpc++) { xmlChar *path = NULL; xmlNode *match = pcmk__xpath_result(xpathObj, lpc); if (match == NULL) { continue; } - /* @TODO This is a longstanding bug in the CIB API. If an XPath - * expression matches a non-element node (for example, an attribute), - * then we operate on the parent element. Similarly, if it matches the - * document itself, then we operate on the root element (which at least - * seems more reasonable). - * - * Some CIB operations might not make much sense for non-element nodes. - * However, operating on a matched node's parent is surprising behavior. - */ - match = pcmk__xpath_match_element(match); - if (match == NULL) { - continue; - } - path = xmlGetNodePath(match); crm_debug("Processing %s op for %s with %s", op, section, path); free(path); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { if (match == *result_cib) { /* Attempting to delete the whole "/cib" */ crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section); rc = -EINVAL; break; } pcmk__xml_free(match); if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { uint32_t flags = pcmk__xaf_none; if (pcmk_is_set(options, cib_score_update)) { flags |= pcmk__xaf_score_update; } if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) { rc = -ENXIO; } else if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) { pcmk__xml_copy(match, input); break; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { if (options & cib_no_children) { xmlNode *shallow = pcmk__xe_create(*answer, (const char *) match->name); pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); if (*answer == NULL) { *answer = shallow; } } else if (options & cib_xpath_address) { char *path = NULL; xmlNode *parent = match; while (parent && parent->type == XML_ELEMENT_NODE) { const char *id = crm_element_value(parent, PCMK_XA_ID); char *new_path = NULL; if (id) { new_path = crm_strdup_printf("/%s[@" PCMK_XA_ID "='%s']" "%s", parent->name, id, pcmk__s(path, "")); } else { new_path = crm_strdup_printf("/%s%s", parent->name, pcmk__s(path, "")); } free(path); path = new_path; parent = parent->parent; } crm_trace("Got: %s", path); if (*answer == NULL) { *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH); crm_xml_add(parent, PCMK_XA_ID, path); free(path); } else if (*answer) { pcmk__xml_copy(*answer, match); } else { *answer = match; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { xmlNode *parent = match->parent; pcmk__xml_free(match); pcmk__xml_copy(parent, input); if ((options & cib_multiple) == 0) { break; } } } freeXpathObject(xpathObj); return rc; }