diff options
Diffstat (limited to '')
-rw-r--r-- | lib/cib/Makefile.am | 22 | ||||
-rw-r--r-- | lib/cib/cib_attrs.c | 19 | ||||
-rw-r--r-- | lib/cib/cib_client.c | 112 | ||||
-rw-r--r-- | lib/cib/cib_file.c | 477 | ||||
-rw-r--r-- | lib/cib/cib_native.c | 56 | ||||
-rw-r--r-- | lib/cib/cib_ops.c | 228 | ||||
-rw-r--r-- | lib/cib/cib_remote.c | 38 | ||||
-rw-r--r-- | lib/cib/cib_utils.c | 511 |
8 files changed, 1112 insertions, 351 deletions
diff --git a/lib/cib/Makefile.am b/lib/cib/Makefile.am index 721fca1..a74c4b1 100644 --- a/lib/cib/Makefile.am +++ b/lib/cib/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004-2018 the Pacemaker project contributors +# Copyright 2004-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,18 +11,20 @@ include $(top_srcdir)/mk/common.mk ## libraries lib_LTLIBRARIES = libcib.la -## SOURCES -libcib_la_SOURCES = cib_ops.c cib_utils.c cib_client.c cib_native.c cib_attrs.c -libcib_la_SOURCES += cib_file.c cib_remote.c +## Library sources (*must* use += format for bumplibs) +libcib_la_SOURCES = cib_attrs.c +libcib_la_SOURCES += cib_client.c +libcib_la_SOURCES += cib_file.c +libcib_la_SOURCES += cib_native.c +libcib_la_SOURCES += cib_ops.c +libcib_la_SOURCES += cib_remote.c +libcib_la_SOURCES += cib_utils.c -libcib_la_LDFLAGS = -version-info 31:0:4 +libcib_la_LDFLAGS = -version-info 32:0:5 libcib_la_CPPFLAGS = -I$(top_srcdir) $(AM_CPPFLAGS) libcib_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcib_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) -libcib_la_LIBADD = $(top_builddir)/lib/pengine/libpe_rules.la \ - $(top_builddir)/lib/common/libcrmcommon.la - -clean-generic: - rm -f *.log *.debug *.xml *~ +libcib_la_LIBADD = $(top_builddir)/lib/pengine/libpe_rules.la \ + $(top_builddir)/lib/common/libcrmcommon.la diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c index 5f3a722..11629b8 100644 --- a/lib/cib/cib_attrs.c +++ b/lib/cib/cib_attrs.c @@ -152,16 +152,15 @@ find_attr(cib_t *cib, const char *section, const char *node_uuid, static int handle_multiples(pcmk__output_t *out, xmlNode *search, const char *attr_name) { - if (xml_has_children(search)) { + if ((search != NULL) && (search->children != NULL)) { xmlNode *child = NULL; - out->info(out, "Multiple attributes match name=%s", attr_name); + out->info(out, "Multiple attributes match name=%s", attr_name); for (child = pcmk__xml_first_child(search); child != NULL; child = pcmk__xml_next(child)) { out->info(out, " Value: %s \t(id=%s)", crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); } - return ENOTUNIQ; } else { @@ -184,9 +183,9 @@ cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const c char *local_attr_id = NULL; char *local_set_name = NULL; - CRM_CHECK(section != NULL, return EINVAL); - CRM_CHECK(attr_value != NULL, return EINVAL); - CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL); + CRM_CHECK((out != NULL) && (cib != NULL) && (section != NULL) + && ((attr_id != NULL) || (attr_name != NULL)) + && (attr_value != NULL), return EINVAL); rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, attr_name, user_name, &xml_search); @@ -360,7 +359,7 @@ cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section, crm_trace("Query failed for attribute %s (section=%s node=%s set=%s): %s", pcmk__s(attr_name, "with unspecified name"), section, pcmk__s(set_name, "<null>"), - pcmk__s(node_uuid, "<null>"), pcmk_strerror(rc)); + pcmk__s(node_uuid, "<null>"), pcmk_rc_str(rc)); } return rc; @@ -487,7 +486,7 @@ read_attr_delegate(cib_t *cib, const char *section, const char *node_uuid, attr_id, attr_name, user_name, &result); if (rc == pcmk_rc_ok) { - if (!xml_has_children(result)) { + if (result->children == NULL) { pcmk__str_update(attr_value, crm_element_value(result, XML_NVPAIR_ATTR_VALUE)); } else { rc = ENOTUNIQ; @@ -677,9 +676,7 @@ query_node_uname(cib_t * the_cib, const char *uuid, char **uname) } xml_obj = fragment; - CRM_CHECK(pcmk__str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES, pcmk__str_casei), - return -ENOMSG); - CRM_ASSERT(xml_obj != NULL); + CRM_CHECK(pcmk__xe_is(xml_obj, XML_CIB_TAG_NODES), return -ENOMSG); crm_log_xml_trace(xml_obj, "Result section"); rc = -ENXIO; diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c index 2d179e0..32e1f83 100644 --- a/lib/cib/cib_client.c +++ b/lib/cib/cib_client.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -253,14 +253,15 @@ cib_client_noop(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_NOOP, NULL, NULL, NULL, NULL, - call_options, NULL); + call_options, cib->user); } static int cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options) { op_common(cib); - return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, call_options, NULL); + return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, + call_options, cib->user); } static int @@ -275,7 +276,7 @@ cib_client_query_from(cib_t * cib, const char *host, const char *section, { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, host, section, NULL, - output_data, call_options, NULL); + output_data, call_options, cib->user); } static int @@ -283,7 +284,7 @@ is_primary(cib_t *cib) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_IS_PRIMARY, NULL, NULL, NULL, - NULL, cib_scope_local|cib_sync_call, NULL); + NULL, cib_scope_local|cib_sync_call, cib->user); } static int @@ -291,7 +292,7 @@ set_secondary(cib_t *cib, int call_options) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_SECONDARY, NULL, NULL, NULL, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -306,7 +307,7 @@ set_primary(cib_t *cib, int call_options) op_common(cib); crm_trace("Adding cib_scope_local to options"); return cib_internal_op(cib, PCMK__CIB_REQUEST_PRIMARY, NULL, NULL, NULL, - NULL, call_options|cib_scope_local, NULL); + NULL, call_options|cib_scope_local, cib->user); } static int @@ -314,7 +315,7 @@ cib_client_bump_epoch(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_BUMP, NULL, NULL, NULL, NULL, - call_options, NULL); + call_options, cib->user); } static int @@ -322,7 +323,7 @@ cib_client_upgrade(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_UPGRADE, NULL, NULL, NULL, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -336,7 +337,7 @@ cib_client_sync_from(cib_t * cib, const char *host, const char *section, int cal { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC_TO_ALL, host, section, - NULL, NULL, call_options, NULL); + NULL, NULL, call_options, cib->user); } static int @@ -344,7 +345,7 @@ cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_opt { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_CREATE, NULL, section, data, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -352,7 +353,7 @@ cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_opt { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, data, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -360,7 +361,7 @@ cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_op { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_REPLACE, NULL, section, data, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -368,7 +369,7 @@ cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_opt { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, data, - NULL, call_options, NULL); + NULL, call_options, cib->user); } static int @@ -376,7 +377,7 @@ cib_client_delete_absolute(cib_t * cib, const char *section, xmlNode * data, int { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_ABS_DELETE, NULL, section, - data, NULL, call_options, NULL); + data, NULL, call_options, cib->user); } static int @@ -384,7 +385,76 @@ cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options) { op_common(cib); return cib_internal_op(cib, PCMK__CIB_REQUEST_ERASE, NULL, NULL, NULL, - output_data, call_options, NULL); + output_data, call_options, cib->user); +} + +static int +cib_client_init_transaction(cib_t *cib) +{ + int rc = pcmk_rc_ok; + + op_common(cib); + + if (cib->transaction != NULL) { + // A client can have at most one transaction at a time + rc = pcmk_rc_already; + } + + if (rc == pcmk_rc_ok) { + cib->transaction = create_xml_node(NULL, T_CIB_TRANSACTION); + if (cib->transaction == NULL) { + rc = ENOMEM; + } + } + + if (rc != pcmk_rc_ok) { + const char *client_id = NULL; + + cib->cmds->client_id(cib, NULL, &client_id); + crm_err("Failed to initialize CIB transaction for client %s: %s", + client_id, pcmk_rc_str(rc)); + } + return pcmk_rc2legacy(rc); +} + +static int +cib_client_end_transaction(cib_t *cib, bool commit, int call_options) +{ + const char *client_id = NULL; + int rc = pcmk_ok; + + op_common(cib); + cib->cmds->client_id(cib, NULL, &client_id); + client_id = pcmk__s(client_id, "(unidentified)"); + + if (commit) { + if (cib->transaction == NULL) { + rc = pcmk_rc_no_transaction; + + crm_err("Failed to commit transaction for CIB client %s: %s", + client_id, pcmk_rc_str(rc)); + return pcmk_rc2legacy(rc); + } + rc = cib_internal_op(cib, PCMK__CIB_REQUEST_COMMIT_TRANSACT, NULL, NULL, + cib->transaction, NULL, call_options, cib->user); + + } else { + // Discard always succeeds + if (cib->transaction != NULL) { + crm_trace("Discarded transaction for CIB client %s", client_id); + } else { + crm_trace("No transaction found for CIB client %s", client_id); + } + } + free_xml(cib->transaction); + cib->transaction = NULL; + return rc; +} + +static void +cib_client_set_user(cib_t *cib, const char *user) +{ + pcmk__str_update(&(cib->user), user); } static void @@ -622,13 +692,15 @@ cib_new_variant(void) return NULL; } + // Deprecated method new_cib->cmds->set_op_callback = cib_client_set_op_callback; + new_cib->cmds->add_notify_callback = cib_client_add_notify_callback; new_cib->cmds->del_notify_callback = cib_client_del_notify_callback; new_cib->cmds->register_callback = cib_client_register_callback; new_cib->cmds->register_callback_full = cib_client_register_callback_full; - new_cib->cmds->noop = cib_client_noop; + new_cib->cmds->noop = cib_client_noop; // Deprecated method new_cib->cmds->ping = cib_client_ping; new_cib->cmds->query = cib_client_query; new_cib->cmds->sync = cib_client_sync; @@ -656,8 +728,14 @@ cib_new_variant(void) new_cib->cmds->remove = cib_client_delete; new_cib->cmds->erase = cib_client_erase; + // Deprecated method new_cib->cmds->delete_absolute = cib_client_delete_absolute; + new_cib->cmds->init_transaction = cib_client_init_transaction; + new_cib->cmds->end_transaction = cib_client_end_transaction; + + new_cib->cmds->set_user = cib_client_set_user; + return new_cib; } diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c index 7d05965..a279823 100644 --- a/lib/cib/cib_file.c +++ b/lib/cib/cib_file.c @@ -37,35 +37,100 @@ #define CIB_LIVE_NAME CIB_SERIES ".xml" +// key: client ID (const char *) -> value: client (cib_t *) +static GHashTable *client_table = NULL; + enum cib_file_flags { cib_file_flag_dirty = (1 << 0), cib_file_flag_live = (1 << 1), }; typedef struct cib_file_opaque_s { - uint32_t flags; // Group of enum cib_file_flags + char *id; char *filename; + uint32_t flags; // Group of enum cib_file_flags + xmlNode *cib_xml; } cib_file_opaque_t; -struct cib_func_entry { - const char *op; - gboolean read_only; - cib_op_t fn; -}; +static int cib_file_process_commit_transaction(const char *op, int options, + const char *section, + xmlNode *req, xmlNode *input, + xmlNode *existing_cib, + xmlNode **result_cib, + xmlNode **answer); -static struct cib_func_entry cib_file_ops[] = { - { PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query }, - { PCMK__CIB_REQUEST_MODIFY, FALSE, cib_process_modify }, - { PCMK__CIB_REQUEST_APPLY_PATCH, FALSE, cib_process_diff }, - { PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump }, - { PCMK__CIB_REQUEST_REPLACE, FALSE, cib_process_replace }, - { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create }, - { PCMK__CIB_REQUEST_DELETE, FALSE, cib_process_delete }, - { PCMK__CIB_REQUEST_ERASE, FALSE, cib_process_erase }, - { PCMK__CIB_REQUEST_UPGRADE, FALSE, cib_process_upgrade }, -}; +/*! + * \internal + * \brief Add a CIB file client to client table + * + * \param[in] cib CIB client + */ +static void +register_client(const cib_t *cib) +{ + cib_file_opaque_t *private = cib->variant_opaque; + + if (client_table == NULL) { + client_table = pcmk__strkey_table(NULL, NULL); + } + g_hash_table_insert(client_table, private->id, (gpointer) cib); +} + +/*! + * \internal + * \brief Remove a CIB file client from client table + * + * \param[in] cib CIB client + */ +static void +unregister_client(const cib_t *cib) +{ + cib_file_opaque_t *private = cib->variant_opaque; -static xmlNode *in_mem_cib = NULL; + if (client_table == NULL) { + return; + } + + g_hash_table_remove(client_table, private->id); + + /* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged, + * instead of destroying the client table when there are no more clients. + */ + if (g_hash_table_size(client_table) == 0) { + g_hash_table_destroy(client_table); + client_table = NULL; + } +} + +/*! + * \internal + * \brief Look up a CIB file client by its ID + * + * \param[in] client_id CIB client ID + * + * \return CIB client with matching ID if found, or \p NULL otherwise + */ +static cib_t * +get_client(const char *client_id) +{ + if (client_table == NULL) { + return NULL; + } + return g_hash_table_lookup(client_table, (gpointer) client_id); +} + +static const cib__op_fn_t cib_op_functions[] = { + [cib__op_apply_patch] = cib_process_diff, + [cib__op_bump] = cib_process_bump, + [cib__op_commit_transact] = cib_file_process_commit_transaction, + [cib__op_create] = cib_process_create, + [cib__op_delete] = cib_process_delete, + [cib__op_erase] = cib_process_erase, + [cib__op_modify] = cib_process_modify, + [cib__op_query] = cib_process_query, + [cib__op_replace] = cib_process_replace, + [cib__op_upgrade] = cib_process_upgrade, +}; /* cib_file_backup() and cib_file_write_with_digest() need to chown the * written files only in limited circumstances, so these variables allow @@ -95,6 +160,27 @@ static gboolean cib_do_chown = FALSE; /*! * \internal + * \brief Get the function that performs a given CIB file operation + * + * \param[in] operation Operation whose function to look up + * + * \return Function that performs \p operation for a CIB file client + */ +static cib__op_fn_t +file_get_op_function(const cib__operation_t *operation) +{ + enum cib__op_type type = operation->type; + + CRM_ASSERT(type >= 0); + + if (type >= PCMK__NELEM(cib_op_functions)) { + return NULL; + } + return cib_op_functions[type]; +} + +/*! + * \internal * \brief Check whether a file is the live CIB * * \param[in] filename Name of file to check @@ -125,114 +211,148 @@ cib_file_is_live(const char *filename) } static int -cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, - const char *section, xmlNode *data, - xmlNode **output_data, int call_options, - const char *user_name) +cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output) { int rc = pcmk_ok; - char *effective_user = NULL; - gboolean query = FALSE; - gboolean changed = FALSE; - xmlNode *request = NULL; - xmlNode *output = NULL; - xmlNode *cib_diff = NULL; + const cib__operation_t *operation = NULL; + cib__op_fn_t op_function = NULL; + + int call_id = 0; + int call_options = cib_none; + const char *op = crm_element_value(request, F_CIB_OPERATION); + const char *section = crm_element_value(request, F_CIB_SECTION); + xmlNode *data = get_message_xml(request, F_CIB_CALLDATA); + + bool changed = false; + bool read_only = false; xmlNode *result_cib = NULL; - cib_op_t *fn = NULL; - int lpc = 0; - static int max_msg_types = PCMK__NELEM(cib_file_ops); + xmlNode *cib_diff = NULL; + cib_file_opaque_t *private = cib->variant_opaque; - crm_info("Handling %s operation for %s as %s", - (op? op : "invalid"), (section? section : "entire CIB"), - (user_name? user_name : "default user")); + // We error checked these in callers + cib__get_operation(op, &operation); + op_function = file_get_op_function(operation); - cib__set_call_options(call_options, "file operation", - cib_no_mtime|cib_inhibit_bcast|cib_scope_local); + crm_element_value_int(request, F_CIB_CALLID, &call_id); + crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); - if (cib->state == cib_disconnected) { - return -ENOTCONN; - } + read_only = !pcmk_is_set(operation->flags, cib__op_attr_modifies); - if (output_data != NULL) { - *output_data = NULL; + // Mirror the logic in prepare_input() in pacemaker-based + if ((section != NULL) && pcmk__xe_is(data, XML_TAG_CIB)) { + + data = pcmk_find_cib_element(data, section); } - if (op == NULL) { - return -EINVAL; + rc = cib_perform_op(op, call_options, op_function, read_only, section, + request, data, true, &changed, &private->cib_xml, + &result_cib, &cib_diff, output); + + if (pcmk_is_set(call_options, cib_transaction)) { + /* The rest of the logic applies only to the transaction as a whole, not + * to individual requests. + */ + goto done; } - for (lpc = 0; lpc < max_msg_types; lpc++) { - if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) { - fn = &(cib_file_ops[lpc].fn); - query = cib_file_ops[lpc].read_only; - break; + if (rc == -pcmk_err_schema_validation) { + validate_xml_verbose(result_cib); + + } else if ((rc == pcmk_ok) && !read_only) { + pcmk__log_xml_patchset(LOG_DEBUG, cib_diff); + + if (result_cib != private->cib_xml) { + free_xml(private->cib_xml); + private->cib_xml = result_cib; } + cib_set_file_flags(private, cib_file_flag_dirty); } - if (fn == NULL) { - return -EPROTONOSUPPORT; + // Global operation callback (deprecated) + if (cib->op_callback != NULL) { + cib->op_callback(NULL, call_id, rc, *output); } - cib->call_id++; - request = cib_create_op(cib->call_id, op, host, section, data, call_options, - user_name); - if(user_name) { - crm_xml_add(request, XML_ACL_TAG_USER, user_name); +done: + if ((result_cib != private->cib_xml) && (result_cib != *output)) { + free_xml(result_cib); } + free_xml(cib_diff); + return rc; +} - /* Mirror the logic in cib_prepare_common() */ - if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { - data = pcmk_find_cib_element(data, section); - } +static int +cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, + const char *user_name) +{ + int rc = pcmk_ok; + xmlNode *request = NULL; + xmlNode *output = NULL; + cib_file_opaque_t *private = cib->variant_opaque; - rc = cib_perform_op(op, call_options, fn, query, - section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff, - &output); + const cib__operation_t *operation = NULL; - free_xml(request); - if (rc == -pcmk_err_schema_validation) { - validate_xml_verbose(result_cib); + crm_info("Handling %s operation for %s as %s", + pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"), + pcmk__s(user_name, "default user")); + + if (output_data != NULL) { + *output_data = NULL; } - if (rc != pcmk_ok) { - free_xml(result_cib); + if (cib->state == cib_disconnected) { + return -ENOTCONN; + } - } else if (query == FALSE) { - pcmk__output_t *out = NULL; + rc = cib__get_operation(op, &operation); + rc = pcmk_rc2legacy(rc); + if (rc != pcmk_ok) { + // @COMPAT: At compatibility break, use rc directly + return -EPROTONOSUPPORT; + } - rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); - CRM_CHECK(rc == pcmk_ok, goto done); + if (file_get_op_function(operation) == NULL) { + // @COMPAT: At compatibility break, use EOPNOTSUPP + crm_err("Operation %s is not supported by CIB file clients", op); + return -EPROTONOSUPPORT; + } - pcmk__output_set_log_level(out, LOG_DEBUG); - rc = out->message(out, "xml-patchset", cib_diff); - out->finish(out, pcmk_rc2exitc(rc), true, NULL); - pcmk__output_free(out); - rc = pcmk_ok; + cib__set_call_options(call_options, "file operation", cib_no_mtime); - free_xml(in_mem_cib); - in_mem_cib = result_cib; - cib_set_file_flags(private, cib_file_flag_dirty); + rc = cib__create_op(cib, op, host, section, data, call_options, user_name, + NULL, &request); + if (rc != pcmk_ok) { + return rc; } + crm_xml_add(request, XML_ACL_TAG_USER, user_name); + crm_xml_add(request, F_CIB_CLIENTID, private->id); - if (cib->op_callback != NULL) { - cib->op_callback(NULL, cib->call_id, rc, output); + if (pcmk_is_set(call_options, cib_transaction)) { + rc = cib__extend_transaction(cib, request); + goto done; } + rc = cib_file_process_request(cib, request, &output); + if ((output_data != NULL) && (output != NULL)) { - *output_data = (output == in_mem_cib)? copy_xml(output) : output; + if (output->doc == private->cib_xml->doc) { + *output_data = copy_xml(output); + } else { + *output_data = output; + } } done: - free_xml(cib_diff); + if ((output != NULL) + && (output->doc != private->cib_xml->doc) + && ((output_data == NULL) || (output != *output_data))) { - if ((output_data == NULL) && (output != in_mem_cib)) { - /* Don't free output if we're still using it. (output_data != NULL) - * means we may have assigned *output_data = output above. - */ free_xml(output); } - free(effective_user); + free_xml(request); return rc; } @@ -240,7 +360,8 @@ done: * \internal * \brief Read CIB from disk and validate it against XML schema * - * \param[in] filename Name of file to read CIB from + * \param[in] filename Name of file to read CIB from + * \param[out] output Where to store the read CIB XML * * \return pcmk_ok on success, * -ENXIO if file does not exist (or stat() otherwise fails), or @@ -251,7 +372,7 @@ done: * because some callers might not need to write. */ static int -load_file_cib(const char *filename) +load_file_cib(const char *filename, xmlNode **output) { struct stat buf; xmlNode *root = NULL; @@ -282,7 +403,7 @@ load_file_cib(const char *filename) } /* Remember the parsed XML for later use */ - in_mem_cib = root; + *output = root; return pcmk_ok; } @@ -295,7 +416,7 @@ cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) if (private->filename == NULL) { rc = -EINVAL; } else { - rc = load_file_cib(private->filename); + rc = load_file_cib(private->filename, &private->cib_xml); } if (rc == pcmk_ok) { @@ -303,10 +424,11 @@ cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) private->filename, name); cib->state = cib_connected_command; cib->type = cib_command; + register_client(cib); } else { - crm_info("Connection to local file '%s' for %s failed: %s\n", - private->filename, name, pcmk_strerror(rc)); + crm_info("Connection to local file '%s' for %s (client %s) failed: %s", + private->filename, name, private->id, pcmk_strerror(rc)); } return rc; } @@ -315,12 +437,13 @@ cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) * \internal * \brief Write out the in-memory CIB to a live CIB file * - * param[in,out] path Full path to file to write + * param[in] cib_root Root of XML tree to write + * param[in,out] path Full path to file to write * * \return 0 on success, -1 on failure */ static int -cib_file_write_live(char *path) +cib_file_write_live(xmlNode *cib_root, char *path) { uid_t uid = geteuid(); struct passwd *daemon_pwent; @@ -370,7 +493,7 @@ cib_file_write_live(char *path) } /* write the file */ - if (cib_file_write_with_digest(in_mem_cib, cib_dirname, + if (cib_file_write_with_digest(cib_root, cib_dirname, cib_filename) != pcmk_ok) { rc = -1; } @@ -410,13 +533,15 @@ cib_file_signoff(cib_t *cib) crm_debug("Disconnecting from the CIB manager"); cib->state = cib_disconnected; cib->type = cib_no_connection; + unregister_client(cib); + cib->cmds->end_transaction(cib, false, cib_none); /* If the in-memory CIB has been changed, write it to disk */ if (pcmk_is_set(private->flags, cib_file_flag_dirty)) { /* If this is the live CIB, write it out with a digest */ if (pcmk_is_set(private->flags, cib_file_flag_live)) { - if (cib_file_write_live(private->filename) < 0) { + if (cib_file_write_live(private->cib_xml, private->filename) < 0) { rc = pcmk_err_generic; } @@ -424,7 +549,8 @@ cib_file_signoff(cib_t *cib) } else { gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2"); - if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) { + if (write_xml_file(private->cib_xml, private->filename, + do_bzip) <= 0) { rc = pcmk_err_generic; } } @@ -438,8 +564,8 @@ cib_file_signoff(cib_t *cib) } /* Free the in-memory CIB */ - free_xml(in_mem_cib); - in_mem_cib = NULL; + free_xml(private->cib_xml); + private->cib_xml = NULL; return rc; } @@ -455,9 +581,11 @@ cib_file_free(cib_t *cib) if (rc == pcmk_ok) { cib_file_opaque_t *private = cib->variant_opaque; + free(private->id); free(private->filename); - free(cib->cmds); free(private); + free(cib->cmds); + free(cib->user); free(cib); } else { @@ -494,24 +622,24 @@ cib_file_set_connection_dnotify(cib_t *cib, * \param[out] async_id If not \p NULL, where to store asynchronous client ID * \param[out] sync_id If not \p NULL, where to store synchronous client ID * - * \return Legacy Pacemaker return code (specifically, \p -EPROTONOSUPPORT) + * \return Legacy Pacemaker return code * * \note This is the \p cib_file variant implementation of * \p cib_api_operations_t:client_id(). - * \note A \p cib_file object doesn't connect to the CIB and is never assigned a - * client ID. */ static int cib_file_client_id(const cib_t *cib, const char **async_id, const char **sync_id) { + cib_file_opaque_t *private = cib->variant_opaque; + if (async_id != NULL) { - *async_id = NULL; + *async_id = private->id; } if (sync_id != NULL) { - *sync_id = NULL; + *sync_id = private->id; } - return -EPROTONOSUPPORT; + return pcmk_ok; } cib_t * @@ -530,6 +658,7 @@ cib_file_new(const char *cib_location) free(cib); return NULL; } + private->id = crm_generate_uuid(); cib->variant = cib_file; cib->variant_opaque = private; @@ -550,7 +679,7 @@ cib_file_new(const char *cib_location) cib->cmds->signon = cib_file_signon; cib->cmds->signoff = cib_file_signoff; cib->cmds->free = cib_file_free; - cib->cmds->inputfd = cib_file_inputfd; + cib->cmds->inputfd = cib_file_inputfd; // Deprecated method cib->cmds->register_notification = cib_file_register_notification; cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify; @@ -917,3 +1046,133 @@ cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, free(tmp_cib); return exit_rc; } + +/*! + * \internal + * \brief Process requests in a CIB transaction + * + * Stop when a request fails or when all requests have been processed. + * + * \param[in,out] cib CIB client + * \param[in,out] transaction CIB transaction + * + * \return Standard Pacemaker return code + */ +static int +cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction) +{ + cib_file_opaque_t *private = cib->variant_opaque; + + for (xmlNode *request = first_named_child(transaction, T_CIB_COMMAND); + request != NULL; request = crm_next_same_xml(request)) { + + xmlNode *output = NULL; + const char *op = crm_element_value(request, F_CIB_OPERATION); + + int rc = cib_file_process_request(cib, request, &output); + + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + crm_err("Aborting transaction for CIB file client (%s) on file " + "'%s' due to failed %s request: %s", + private->id, private->filename, op, pcmk_rc_str(rc)); + crm_log_xml_info(request, "Failed request"); + return rc; + } + + crm_trace("Applied %s request to transaction working CIB for CIB file " + "client (%s) on file '%s'", + op, private->id, private->filename); + crm_log_xml_trace(request, "Successful request"); + } + + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Commit a given CIB file client's transaction to a working CIB copy + * + * \param[in,out] cib CIB file client + * \param[in] transaction CIB transaction + * \param[in,out] result_cib Where to store result CIB + * + * \return Standard Pacemaker return code + * + * \note The caller is responsible for replacing the \p cib argument's + * \p private->cib_xml with \p result_cib on success, and for freeing + * \p result_cib using \p free_xml() on failure. + */ +static int +cib_file_commit_transaction(cib_t *cib, xmlNode *transaction, + xmlNode **result_cib) +{ + int rc = pcmk_rc_ok; + cib_file_opaque_t *private = cib->variant_opaque; + xmlNode *saved_cib = private->cib_xml; + + CRM_CHECK(pcmk__xe_is(transaction, T_CIB_TRANSACTION), + return pcmk_rc_no_transaction); + + /* *result_cib should be a copy of private->cib_xml (created by + * cib_perform_op()). If not, make a copy now. Change tracking isn't + * strictly required here because: + * * Each request in the transaction will have changes tracked and ACLs + * checked if appropriate. + * * cib_perform_op() will infer changes for the commit request at the end. + */ + CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml), + *result_cib = copy_xml(private->cib_xml)); + + crm_trace("Committing transaction for CIB file client (%s) on file '%s' to " + "working CIB", + private->id, private->filename); + + // Apply all changes to a working copy of the CIB + private->cib_xml = *result_cib; + + rc = cib_file_process_transaction_requests(cib, transaction); + + crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'", + ((rc == pcmk_rc_ok)? "succeeded" : "failed"), + private->id, private->filename); + + /* Some request types (for example, erase) may have freed private->cib_xml + * (the working copy) and pointed it at a new XML object. In that case, it + * follows that *result_cib (the working copy) was freed. + * + * Point *result_cib at the updated working copy stored in private->cib_xml. + */ + *result_cib = private->cib_xml; + + // Point private->cib_xml back to the unchanged original copy + private->cib_xml = saved_cib; + + return rc; +} + +static int +cib_file_process_commit_transaction(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer) +{ + int rc = pcmk_rc_ok; + const char *client_id = crm_element_value(req, F_CIB_CLIENTID); + cib_t *cib = NULL; + + CRM_CHECK(client_id != NULL, return -EINVAL); + + cib = get_client(client_id); + CRM_CHECK(cib != NULL, return -EINVAL); + + rc = cib_file_commit_transaction(cib, input, result_cib); + if (rc != pcmk_rc_ok) { + cib_file_opaque_t *private = cib->variant_opaque; + + crm_err("Could not commit transaction for CIB file client (%s) on " + "file '%s': %s", + private->id, private->filename, pcmk_rc_str(rc)); + } + return pcmk_rc2legacy(rc); +} diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c index 4a87f56..c5e8b9e 100644 --- a/lib/cib/cib_native.c +++ b/lib/cib/cib_native.c @@ -69,20 +69,19 @@ cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, pcmk__set_ipc_flags(ipc_flags, "client", crm_ipc_client_response); } - cib->call_id++; - if (cib->call_id < 1) { - cib->call_id = 1; + rc = cib__create_op(cib, op, host, section, data, call_options, user_name, + NULL, &op_msg); + if (rc != pcmk_ok) { + return rc; } - op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, - user_name); - if (op_msg == NULL) { - return -EPROTO; + if (pcmk_is_set(call_options, cib_transaction)) { + rc = cib__extend_transaction(cib, op_msg); + goto done; } crm_trace("Sending %s message to the CIB manager (timeout=%ds)", op, cib->call_timeout); rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, cib->call_timeout * 1000, &op_reply); - free_xml(op_msg); if (rc < 0) { crm_err("Couldn't perform %s operation (timeout=%ds): %s (%d)", op, @@ -168,6 +167,7 @@ cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, cib->state = cib_disconnected; } + free_xml(op_msg); free_xml(op_reply); return rc; } @@ -255,6 +255,7 @@ cib_native_signoff(cib_t *cib) crm_ipc_destroy(ipc); } + cib->cmds->end_transaction(cib, false, cib_none); cib->state = cib_disconnected; cib->type = cib_no_connection; @@ -268,6 +269,7 @@ cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type, int rc = pcmk_ok; const char *channel = NULL; cib_native_opaque_t *native = cib->variant_opaque; + xmlNode *hello = NULL; struct ipc_client_callbacks cib_callbacks = { .dispatch = cib_native_dispatch_internal, @@ -296,12 +298,16 @@ cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type, if (async_fd != NULL) { native->ipc = crm_ipc_new(channel, 0); - - if (native->ipc && crm_ipc_connect(native->ipc)) { - *async_fd = crm_ipc_get_fd(native->ipc); - - } else if (native->ipc) { - rc = -ENOTCONN; + if (native->ipc != NULL) { + rc = pcmk__connect_generic_ipc(native->ipc); + if (rc == pcmk_rc_ok) { + rc = pcmk__ipc_fd(native->ipc, async_fd); + if (rc != pcmk_rc_ok) { + crm_info("Couldn't get file descriptor for %s IPC", + channel); + } + } + rc = pcmk_rc2legacy(rc); } } else { @@ -317,23 +323,23 @@ cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type, } if (rc == pcmk_ok) { - xmlNode *reply = NULL; - xmlNode *hello = create_xml_node(NULL, "cib_command"); + rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, + cib_sync_call, NULL, name, &hello); + } - crm_xml_add(hello, F_TYPE, T_CIB); - crm_xml_add(hello, F_CIB_OPERATION, CRM_OP_REGISTER); - crm_xml_add(hello, F_CIB_CLIENTNAME, name); - crm_xml_add_int(hello, F_CIB_CALLOPTS, cib_sync_call); + if (rc == pcmk_ok) { + xmlNode *reply = NULL; - if (crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply) > 0) { + if (crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, + &reply) > 0) { const char *msg_type = crm_element_value(reply, F_CIB_OPERATION); - rc = pcmk_ok; crm_log_xml_trace(reply, "reg-reply"); if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) { - crm_info("Reply to CIB registration message has " - "unknown type '%s'", msg_type); + crm_info("Reply to CIB registration message has unknown type " + "'%s'", + msg_type); rc = -EPROTO; } else { @@ -347,7 +353,6 @@ cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type, } else { rc = -ECOMM; } - free_xml(hello); } @@ -383,6 +388,7 @@ cib_native_free(cib_t *cib) free(native->token); free(cib->variant_opaque); free(cib->cmds); + free(cib->user); free(cib); } diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c index d3293c4..c324304 100644 --- a/lib/cib/cib_ops.c +++ b/lib/cib/cib_ops.c @@ -19,6 +19,9 @@ #include <sys/param.h> #include <sys/types.h> +#include <glib.h> +#include <libxml/tree.h> + #include <crm/crm.h> #include <crm/cib/internal.h> #include <crm/msg_xml.h> @@ -26,6 +29,139 @@ #include <crm/common/xml.h> #include <crm/common/xml_internal.h> +// @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 + }, +}; + +/*! + * \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) +{ + CRM_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) @@ -54,8 +190,8 @@ cib_process_query(const char *op, int options, const char *section, xmlNode * re result = -ENXIO; } else if (options & cib_no_children) { - const char *tag = TYPE(obj_root); - xmlNode *shallow = create_xml_node(*answer, tag); + xmlNode *shallow = create_xml_node(*answer, + (const char *) obj_root->name); copy_in_properties(shallow, obj_root); *answer = shallow; @@ -107,12 +243,14 @@ cib_process_erase(const char *op, int options, const char *section, xmlNode * re int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); - *answer = NULL; - free_xml(*result_cib); - *result_cib = createEmptyCib(0); + if (*result_cib != existing_cib) { + free_xml(*result_cib); + } + *result_cib = createEmptyCib(0); copy_in_properties(*result_cib, existing_cib); update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, false); + *answer = NULL; return result; } @@ -172,7 +310,6 @@ cib_process_replace(const char *op, int options, const char *section, xmlNode * xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { - const char *tag = NULL; int result = pcmk_ok; crm_trace("Processing %s for %s section", @@ -189,16 +326,14 @@ cib_process_replace(const char *op, int options, const char *section, xmlNode * return -EINVAL; } - tag = crm_element_name(input); - if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) { section = NULL; - } else if (pcmk__str_eq(tag, section, pcmk__str_casei)) { + } else if (pcmk__xe_is(input, section)) { section = NULL; } - if (pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) { + if (pcmk__xe_is(input, XML_TAG_CIB)) { int updates = 0; int epoch = 0; int admin_epoch = 0; @@ -262,7 +397,9 @@ cib_process_replace(const char *op, int options, const char *section, xmlNode * replace_admin_epoch, replace_epoch, replace_updates, peer); } - free_xml(*result_cib); + if (*result_cib != existing_cib) { + free_xml(*result_cib); + } *result_cib = copy_xml(input); } else { @@ -299,7 +436,7 @@ cib_process_delete(const char *op, int options, const char *section, xmlNode * r } obj_root = pcmk_find_cib_element(*result_cib, section); - if(pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) { + if (pcmk__xe_is(input, section)) { xmlNode *child = NULL; for (child = pcmk__xml_first_child(input); child; child = pcmk__xml_next(child)) { @@ -360,7 +497,8 @@ cib_process_modify(const char *op, int options, const char *section, xmlNode * r } } - if(options & cib_mixed_update) { + // @COMPAT cib_mixed_update is deprecated as of 2.1.7 + if (pcmk_is_set(options, cib_mixed_update)) { int max = 0, lpc; xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__"); @@ -396,7 +534,7 @@ update_cib_object(xmlNode * parent, xmlNode * update) CRM_CHECK(update != NULL, return -EINVAL); CRM_CHECK(parent != NULL, return -EINVAL); - object_name = crm_element_name(update); + object_name = (const char *) update->name; CRM_CHECK(object_name != NULL, return -EINVAL); object_id = ID(update); @@ -425,33 +563,25 @@ update_cib_object(xmlNode * parent, xmlNode * update) // @COMPAT: XML_CIB_ATTR_REPLACE is unused internally. Remove at break. replace = crm_element_value(update, XML_CIB_ATTR_REPLACE); if (replace != NULL) { - xmlNode *remove = NULL; - int last = 0, lpc = 0, len = 0; + int last = 0; + int len = strlen(replace); - len = strlen(replace); - while (lpc <= len) { + for (int lpc = 0; lpc <= len; ++lpc) { if (replace[lpc] == ',' || replace[lpc] == 0) { - char *replace_item = NULL; - - if (last == lpc) { - /* nothing to do */ - last = lpc + 1; - goto incr; - } - - replace_item = strndup(replace + last, lpc - last); - remove = find_xml_node(target, replace_item, FALSE); - if (remove != NULL) { - crm_trace("Replacing node <%s> in <%s>", - replace_item, crm_element_name(target)); - free_xml(remove); - remove = NULL; + if (last != lpc) { + char *replace_item = strndup(replace + last, lpc - last); + xmlNode *remove = find_xml_node(target, replace_item, + FALSE); + + if (remove != NULL) { + crm_trace("Replacing node <%s> in <%s>", + replace_item, target->name); + free_xml(remove); + } + free(replace_item); } - free(replace_item); last = lpc + 1; } - incr: - lpc++; } xml_remove_prop(update, XML_CIB_ATTR_REPLACE); xml_remove_prop(target, XML_CIB_ATTR_REPLACE); @@ -475,7 +605,7 @@ update_cib_object(xmlNode * parent, xmlNode * update) a_child = pcmk__xml_next(a_child)) { int tmp_result = 0; - crm_trace("Updating child <%s%s%s%s>", crm_element_name(a_child), + crm_trace("Updating child <%s%s%s%s>", a_child->name, ((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"), pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'")); @@ -484,7 +614,7 @@ update_cib_object(xmlNode * parent, xmlNode * update) /* only the first error is likely to be interesting */ if (tmp_result != pcmk_ok) { crm_err("Error updating child <%s%s%s%s>", - crm_element_name(a_child), + a_child->name, ((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"), pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'")); @@ -514,7 +644,7 @@ add_cib_object(xmlNode * parent, xmlNode * new_obj) return -EINVAL; } - object_name = crm_element_name(new_obj); + object_name = (const char *) new_obj->name; if (object_name == NULL) { return -EINVAL; } @@ -555,7 +685,8 @@ update_results(xmlNode *failed, xmlNode *target, const char *operation, add_node_copy(xml_node, target); crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID, ID(target)); - crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, TYPE(target)); + crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, + (const char *) target->name); crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP, operation); crm_xml_add(xml_node, XML_FAILCIB_ATTR_REASON, error_msg); @@ -582,7 +713,7 @@ cib_process_create(const char *op, int options, const char *section, xmlNode * r } else if (pcmk__str_eq(XML_TAG_CIB, section, pcmk__str_casei)) { section = NULL; - } else if (pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) { + } else if (pcmk__xe_is(input, XML_TAG_CIB)) { section = NULL; } @@ -601,7 +732,7 @@ cib_process_create(const char *op, int options, const char *section, xmlNode * r failed = create_xml_node(NULL, XML_TAG_FAILED); update_section = pcmk_find_cib_element(*result_cib, section); - if (pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) { + if (pcmk__xe_is(input, section)) { xmlNode *a_child = NULL; for (a_child = pcmk__xml_first_child(input); a_child != NULL; @@ -617,7 +748,7 @@ cib_process_create(const char *op, int options, const char *section, xmlNode * r update_results(failed, input, op, result); } - if ((result == pcmk_ok) && xml_has_children(failed)) { + if ((result == pcmk_ok) && (failed->children != NULL)) { result = -EINVAL; } @@ -646,8 +777,11 @@ cib_process_diff(const char *op, int options, const char *section, xmlNode * req op, originator, (pcmk_is_set(options, cib_force_diff)? " (global update)" : "")); - free_xml(*result_cib); + if (*result_cib != existing_cib) { + free_xml(*result_cib); + } *result_cib = copy_xml(existing_cib); + return xml_apply_patchset(*result_cib, input, TRUE); } @@ -670,7 +804,7 @@ cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff) goto done; } - crm_element_value_int(*diff, "format", &format); + crm_element_value_int(*diff, PCMK_XA_FORMAT, &format); CRM_LOG_ASSERT(format == 1); xpathObj = xpath_search(*diff, "//" XML_CIB_TAG_CONFIGURATION); @@ -803,8 +937,8 @@ cib_process_xpath(const char *op, int options, const char *section, } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { if (options & cib_no_children) { - const char *tag = TYPE(match); - xmlNode *shallow = create_xml_node(*answer, tag); + xmlNode *shallow = create_xml_node(*answer, + (const char *) match->name); copy_in_properties(shallow, match); diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c index 28095b3..77479d7 100644 --- a/lib/cib/cib_remote.c +++ b/lib/cib/cib_remote.c @@ -55,7 +55,8 @@ typedef struct cib_remote_opaque_s { static int cib_remote_perform_op(cib_t *cib, const char *op, const char *host, const char *section, xmlNode *data, - xmlNode **output_data, int call_options, const char *name) + xmlNode **output_data, int call_options, + const char *user_name) { int rc; int remaining_time = 0; @@ -79,15 +80,16 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, return -EINVAL; } - cib->call_id++; - if (cib->call_id < 1) { - cib->call_id = 1; + rc = cib__create_op(cib, op, host, section, data, call_options, user_name, + NULL, &op_msg); + if (rc != pcmk_ok) { + return rc; } - op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, - NULL); - if (op_msg == NULL) { - return -EPROTO; + if (pcmk_is_set(call_options, cib_transaction)) { + rc = cib__extend_transaction(cib, op_msg); + free_xml(op_msg); + return rc; } crm_trace("Sending %s message to the CIB manager", op); @@ -378,7 +380,7 @@ cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel) } /* login to server */ - login = create_xml_node(NULL, "cib_command"); + login = create_xml_node(NULL, T_CIB_COMMAND); crm_xml_add(login, "op", "authenticate"); crm_xml_add(login, "user", private->user); crm_xml_add(login, "password", private->passwd); @@ -434,6 +436,7 @@ cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type) { int rc = pcmk_ok; cib_remote_opaque_t *private = cib->variant_opaque; + xmlNode *hello = NULL; if (private->passwd == NULL) { if (private->out == NULL) { @@ -459,10 +462,13 @@ cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type) } if (rc == pcmk_ok) { - xmlNode *hello = cib_create_op(0, CRM_OP_REGISTER, NULL, NULL, NULL, 0, - NULL); - crm_xml_add(hello, F_CIB_CLIENTNAME, name); - pcmk__remote_send_xml(&private->command, hello); + rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, cib_none, + NULL, name, &hello); + } + + if (rc == pcmk_ok) { + rc = pcmk__remote_send_xml(&private->command, hello); + rc = pcmk_rc2legacy(rc); free_xml(hello); } @@ -490,6 +496,7 @@ cib_remote_signoff(cib_t *cib) cib_tls_close(cib); #endif + cib->cmds->end_transaction(cib, false, cib_none); cib->state = cib_disconnected; cib->type = cib_no_connection; @@ -511,6 +518,7 @@ cib_remote_free(cib_t *cib) free(private->user); free(private->passwd); free(cib->cmds); + free(cib->user); free(private); free(cib); } @@ -530,7 +538,7 @@ cib_remote_inputfd(cib_t * cib) static int cib_remote_register_notification(cib_t * cib, const char *callback, int enabled) { - xmlNode *notify_msg = create_xml_node(NULL, "cib_command"); + xmlNode *notify_msg = create_xml_node(NULL, T_CIB_COMMAND); cib_remote_opaque_t *private = cib->variant_opaque; crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY); @@ -614,7 +622,7 @@ cib_remote_new(const char *server, const char *user, const char *passwd, int por cib->cmds->signon = cib_remote_signon; cib->cmds->signoff = cib_remote_signoff; cib->cmds->free = cib_remote_free; - cib->cmds->inputfd = cib_remote_inputfd; + cib->cmds->inputfd = cib_remote_inputfd; // Deprecated method cib->cmds->register_notification = cib_remote_register_notification; cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify; diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index c75d844..0082eef 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -20,6 +20,7 @@ #include <crm/crm.h> #include <crm/cib/internal.h> #include <crm/msg_xml.h> +#include <crm/common/cib_internal.h> #include <crm/common/xml.h> #include <crm/common/xml_internal.h> #include <crm/pengine/rules.h> @@ -78,6 +79,154 @@ cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *upda } /*! + * \internal + * \brief Get the XML patchset from a CIB diff notification + * + * \param[in] msg CIB diff notification + * \param[out] patchset Where to store XML patchset + * + * \return Standard Pacemaker return code + */ +int +cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset) +{ + int rc = pcmk_err_generic; + + CRM_ASSERT(patchset != NULL); + *patchset = NULL; + + if (msg == NULL) { + crm_err("CIB diff notification received with no XML"); + return ENOMSG; + } + + if ((crm_element_value_int(msg, F_CIB_RC, &rc) != 0) || (rc != pcmk_ok)) { + crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d", + pcmk_strerror(rc), rc); + crm_log_xml_debug(msg, "failed"); + return pcmk_legacy2rc(rc); + } + + *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); + + if (*patchset == NULL) { + crm_err("CIB diff notification received with no patchset"); + return ENOMSG; + } + return pcmk_rc_ok; +} + +#define XPATH_DIFF_V1 "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED + +/*! + * \internal + * \brief Check whether a given CIB element was modified in a CIB patchset (v1) + * + * \param[in] patchset CIB XML patchset + * \param[in] element XML tag of CIB element to check (\c NULL is equivalent + * to \c XML_TAG_CIB) + * + * \return \c true if \p element was modified, or \c false otherwise + */ +static bool +element_in_patchset_v1(const xmlNode *patchset, const char *element) +{ + char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s", + pcmk__s(element, XML_TAG_CIB)); + xmlXPathObject *xpath_obj = xpath_search(patchset, xpath); + + free(xpath); + + if (xpath_obj == NULL) { + return false; + } + freeXpathObject(xpath_obj); + return true; +} + +/*! + * \internal + * \brief Check whether a given CIB element was modified in a CIB patchset (v2) + * + * \param[in] patchset CIB XML patchset + * \param[in] element XML tag of CIB element to check (\c NULL is equivalent + * to \c XML_TAG_CIB). Supported values include any CIB + * element supported by \c pcmk__cib_abs_xpath_for(). + * + * \return \c true if \p element was modified, or \c false otherwise + */ +static bool +element_in_patchset_v2(const xmlNode *patchset, const char *element) +{ + const char *element_xpath = pcmk__cib_abs_xpath_for(element); + const char *parent_xpath = pcmk_cib_parent_name_for(element); + char *element_regex = NULL; + bool rc = false; + + CRM_CHECK(element_xpath != NULL, return false); // Unsupported element + + // Matches if and only if element_xpath is part of a changed path + element_regex = crm_strdup_printf("^%s(/|$)", element_xpath); + + for (const xmlNode *change = first_named_child(patchset, XML_DIFF_CHANGE); + change != NULL; change = crm_next_same_xml(change)) { + + const char *op = crm_element_value(change, F_CIB_OPERATION); + const char *diff_xpath = crm_element_value(change, XML_DIFF_PATH); + + if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) { + // Change to an existing element + rc = true; + break; + } + + if (pcmk__str_eq(op, "create", pcmk__str_none) + && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none) + && pcmk__xe_is(pcmk__xml_first_child(change), element)) { + + // Newly added element + rc = true; + break; + } + } + + free(element_regex); + return rc; +} + +/*! + * \internal + * \brief Check whether a given CIB element was modified in a CIB patchset + * + * \param[in] patchset CIB XML patchset + * \param[in] element XML tag of CIB element to check (\c NULL is equivalent + * to \c XML_TAG_CIB). Supported values include any CIB + * element supported by \c pcmk__cib_abs_xpath_for(). + * + * \return \c true if \p element was modified, or \c false otherwise + */ +bool +cib__element_in_patchset(const xmlNode *patchset, const char *element) +{ + int format = 1; + + CRM_ASSERT(patchset != NULL); + + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); + switch (format) { + case 1: + return element_in_patchset_v1(patchset, element); + + case 2: + return element_in_patchset_v2(patchset, element); + + default: + crm_warn("Unknown patch format: %d", format); + return false; + } +} + +/*! * \brief Create XML for a new (empty) CIB * * \param[in] cib_epoch What to use as "epoch" CIB property @@ -141,30 +290,79 @@ cib_acl_enabled(xmlNode *xml, const char *user) return rc; } +/*! + * \internal + * \brief Determine whether to perform operations on a scratch copy of the CIB + * + * \param[in] op CIB operation + * \param[in] section CIB section + * \param[in] call_options CIB call options + * + * \return \p true if we should make a copy of the CIB, or \p false otherwise + */ +static bool +should_copy_cib(const char *op, const char *section, int call_options) +{ + if (pcmk_is_set(call_options, cib_dryrun)) { + // cib_dryrun implies a scratch copy by definition; no side effects + return true; + } + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) { + /* Commit-transaction must make a copy for atomicity. We must revert to + * the original CIB if the entire transaction cannot be applied + * successfully. + */ + return true; + } + + if (pcmk_is_set(call_options, cib_transaction)) { + /* If cib_transaction is set, then we're in the process of committing a + * transaction. The commit-transaction request already made a scratch + * copy, and we're accumulating changes in that copy. + */ + return false; + } + + if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_none)) { + /* Copying large CIBs accounts for a huge percentage of our CIB usage, + * and this avoids some of it. + * + * @TODO: Is this safe? See discussion at + * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690. + */ + return false; + } + + // Default behavior is to operate on a scratch copy + return true; +} + int -cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, - const char *section, xmlNode * req, xmlNode * input, - gboolean manage_counters, gboolean * config_changed, - xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output) +cib_perform_op(const char *op, int call_options, cib__op_fn_t fn, bool is_query, + const char *section, xmlNode *req, xmlNode *input, + bool manage_counters, bool *config_changed, + xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff, + xmlNode **output) { int rc = pcmk_ok; - gboolean check_schema = TRUE; + bool check_schema = true; + bool make_copy = true; xmlNode *top = NULL; xmlNode *scratch = NULL; + xmlNode *patchset_cib = NULL; xmlNode *local_diff = NULL; const char *new_version = NULL; const char *user = crm_element_value(req, F_CIB_USER); - bool with_digest = FALSE; - - pcmk__output_t *out = NULL; - int out_rc = pcmk_rc_no_output; + bool with_digest = false; crm_trace("Begin %s%s%s op", (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""), (is_query? "read-only " : ""), op); CRM_CHECK(output != NULL, return -ENOMSG); + CRM_CHECK(current_cib != NULL, return -ENOMSG); CRM_CHECK(result_cib != NULL, return -ENOMSG); CRM_CHECK(config_changed != NULL, return -ENOMSG); @@ -173,25 +371,26 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer } *result_cib = NULL; - *config_changed = FALSE; + *config_changed = false; if (fn == NULL) { return -EINVAL; } if (is_query) { - xmlNode *cib_ro = current_cib; + xmlNode *cib_ro = *current_cib; xmlNode *cib_filtered = NULL; - if(cib_acl_enabled(cib_ro, user)) { - if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) { - if (cib_filtered == NULL) { - crm_debug("Pre-filtered the entire cib"); - return -EACCES; - } - cib_ro = cib_filtered; - crm_log_xml_trace(cib_ro, "filtered"); + if (cib_acl_enabled(cib_ro, user) + && xml_acl_filtered_copy(user, *current_cib, *current_cib, + &cib_filtered)) { + + if (cib_filtered == NULL) { + crm_debug("Pre-filtered the entire cib"); + return -EACCES; } + cib_ro = cib_filtered; + crm_log_xml_trace(cib_ro, "filtered"); } rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output); @@ -202,14 +401,14 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer } else if(cib_filtered == *output) { cib_filtered = NULL; /* Let them have this copy */ - } else if(*output == current_cib) { + } else if (*output == *current_cib) { /* They already know not to free it */ } else if(cib_filtered && (*output)->doc == cib_filtered->doc) { /* We're about to free the document of which *output is a part */ *output = copy_xml(*output); - } else if((*output)->doc == current_cib->doc) { + } else if ((*output)->doc == (*current_cib)->doc) { /* Give them a copy they can free */ *output = copy_xml(*output); } @@ -218,31 +417,41 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer return rc; } + make_copy = should_copy_cib(op, section, call_options); - if (pcmk_is_set(call_options, cib_zero_copy)) { + if (!make_copy) { /* Conditional on v2 patch style */ - scratch = current_cib; + scratch = *current_cib; - /* Create a shallow copy of current_cib for the version details */ - current_cib = create_xml_node(NULL, (const char *)scratch->name); - copy_in_properties(current_cib, scratch); - top = current_cib; + // Make a copy of the top-level element to store version details + top = create_xml_node(NULL, (const char *) scratch->name); + copy_in_properties(top, scratch); + patchset_cib = top; xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output); + /* If scratch points to a new object now (for example, after an erase + * operation), then *current_cib should point to the same object. + */ + *current_cib = scratch; + } else { - scratch = copy_xml(current_cib); + scratch = copy_xml(*current_cib); + patchset_cib = *current_cib; + xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); - rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output); + rc = (*fn) (op, call_options, section, req, input, *current_cib, + &scratch, output); - if(scratch && xml_tracking_changes(scratch) == FALSE) { + if ((scratch != NULL) && !xml_tracking_changes(scratch)) { crm_trace("Inferring changes after %s op", op); - xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user)); - xml_calculate_changes(current_cib, scratch); + xml_track_changes(scratch, user, *current_cib, + cib_acl_enabled(*current_cib, user)); + xml_calculate_changes(*current_cib, scratch); } - CRM_CHECK(current_cib != scratch, return -EINVAL); + CRM_CHECK(*current_cib != scratch, return -EINVAL); } xml_acl_disable(scratch); /* Allow the system to make any additional changes */ @@ -271,12 +480,12 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer } } - if (current_cib) { + if (patchset_cib != NULL) { int old = 0; int new = 0; crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new); - crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old); + crm_element_value_int(patchset_cib, XML_ATTR_GENERATION_ADMIN, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: %#x)", @@ -287,7 +496,7 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer } else if (old == new) { crm_element_value_int(scratch, XML_ATTR_GENERATION, &new); - crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old); + crm_element_value_int(patchset_cib, XML_ATTR_GENERATION, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: %#x)", XML_ATTR_GENERATION, old, new, call_options); @@ -302,13 +511,14 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer pcmk__strip_xml_text(scratch); fix_plus_plus_recursive(scratch); - if (pcmk_is_set(call_options, cib_zero_copy)) { - /* At this point, current_cib is just the 'cib' tag and its properties, + if (!make_copy) { + /* At this point, patchset_cib is just the "cib" tag and its properties. * * The v1 format would barf on this, but we know the v2 patch * format only needs it for the top-level version fields */ - local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters); + local_diff = xml_create_patchset(2, patchset_cib, scratch, + config_changed, manage_counters); } else { static time_t expires = 0; @@ -316,63 +526,38 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer if (expires < tm_now) { expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */ - with_digest = TRUE; + with_digest = true; } - local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters); + local_diff = xml_create_patchset(0, patchset_cib, scratch, + config_changed, manage_counters); } - // Create a log output object only if we're going to use it - pcmk__if_tracing( - { - rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); - CRM_CHECK(rc == pcmk_ok, goto done); - - pcmk__output_set_log_level(out, LOG_TRACE); - out_rc = pcmk__xml_show_changes(out, scratch); - }, - {} - ); + pcmk__log_xml_changes(LOG_TRACE, scratch); xml_accept_changes(scratch); if(local_diff) { - int temp_rc = pcmk_rc_no_output; - - patchset_process_digest(local_diff, current_cib, scratch, with_digest); - - if (out == NULL) { - rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); - CRM_CHECK(rc == pcmk_ok, goto done); - } - pcmk__output_set_log_level(out, LOG_INFO); - temp_rc = out->message(out, "xml-patchset", local_diff); - out_rc = pcmk__output_select_rc(rc, temp_rc); - + patchset_process_digest(local_diff, patchset_cib, scratch, with_digest); + pcmk__log_xml_patchset(LOG_INFO, local_diff); crm_log_xml_trace(local_diff, "raw patch"); } - if (out != NULL) { - out->finish(out, pcmk_rc2exitc(out_rc), true, NULL); - pcmk__output_free(out); - out = NULL; - } - - if (!pcmk_is_set(call_options, cib_zero_copy) && (local_diff != NULL)) { + if (make_copy && (local_diff != NULL)) { // Original to compare against doesn't exist pcmk__if_tracing( { // Validate the calculated patch set int test_rc = pcmk_ok; int format = 1; - xmlNode *cib_copy = copy_xml(current_cib); + xmlNode *cib_copy = copy_xml(patchset_cib); - crm_element_value_int(local_diff, "format", &format); + crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format); test_rc = xml_apply_patchset(cib_copy, local_diff, manage_counters); if (test_rc != pcmk_ok) { save_xml_to_file(cib_copy, "PatchApply:calculated", NULL); - save_xml_to_file(current_cib, "PatchApply:input", NULL); + save_xml_to_file(patchset_cib, "PatchApply:input", NULL); save_xml_to_file(scratch, "PatchApply:actual", NULL); save_xml_to_file(local_diff, "PatchApply:diff", NULL); crm_err("v%d patchset error, patch failed to apply: %s " @@ -391,7 +576,7 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer * a) we don't really care whats in the status section * b) we don't validate any of its contents at the moment anyway */ - check_schema = FALSE; + check_schema = false; } /* === scratch must not be modified after this point === @@ -420,19 +605,35 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer /* Does the CIB support the "update-*" attributes... */ if (current_schema >= minimum_schema) { + /* Ensure values of origin, client, and user in scratch match + * the values in req + */ const char *origin = crm_element_value(req, F_ORIG); + const char *client = crm_element_value(req, F_CIB_CLIENTNAME); + + if (origin != NULL) { + crm_xml_add(scratch, XML_ATTR_UPDATE_ORIG, origin); + } else { + xml_remove_prop(scratch, XML_ATTR_UPDATE_ORIG); + } - CRM_LOG_ASSERT(origin != NULL); - crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin); - crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT, - crm_element_value(req, F_CIB_CLIENTNAME)); - crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER)); + if (client != NULL) { + crm_xml_add(scratch, XML_ATTR_UPDATE_CLIENT, user); + } else { + xml_remove_prop(scratch, XML_ATTR_UPDATE_CLIENT); + } + + if (user != NULL) { + crm_xml_add(scratch, XML_ATTR_UPDATE_USER, user); + } else { + xml_remove_prop(scratch, XML_ATTR_UPDATE_USER); + } } } } crm_trace("Perform validation: %s", pcmk__btoa(check_schema)); - if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) { + if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, true)) { const char *current_schema = crm_element_value(scratch, XML_ATTR_VALIDATION); @@ -444,13 +645,17 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer done: *result_cib = scratch; - if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) { - if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) { - if (*result_cib == NULL) { - crm_debug("Pre-filtered the entire cib result"); - } - free_xml(scratch); + + /* @TODO: This may not work correctly with !make_copy, since we don't + * keep the original CIB. + */ + if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user) + && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) { + + if (*result_cib == NULL) { + crm_debug("Pre-filtered the entire cib result"); } + free_xml(scratch); } if(diff) { @@ -464,36 +669,117 @@ cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_quer return rc; } -xmlNode * -cib_create_op(int call_id, const char *op, const char *host, - const char *section, xmlNode *data, int call_options, - const char *user_name) +int +cib__create_op(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, int call_options, + const char *user_name, const char *client_name, + xmlNode **op_msg) { - xmlNode *op_msg = create_xml_node(NULL, "cib_command"); + CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO); - CRM_CHECK(op_msg != NULL, return NULL); - - crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command"); + *op_msg = create_xml_node(NULL, T_CIB_COMMAND); + if (*op_msg == NULL) { + return -EPROTO; + } - crm_xml_add(op_msg, F_TYPE, T_CIB); - crm_xml_add(op_msg, F_CIB_OPERATION, op); - crm_xml_add(op_msg, F_CIB_HOST, host); - crm_xml_add(op_msg, F_CIB_SECTION, section); - crm_xml_add_int(op_msg, F_CIB_CALLID, call_id); - if (user_name) { - crm_xml_add(op_msg, F_CIB_USER, user_name); + cib->call_id++; + if (cib->call_id < 1) { + cib->call_id = 1; } + + crm_xml_add(*op_msg, F_XML_TAGNAME, T_CIB_COMMAND); + crm_xml_add(*op_msg, F_TYPE, T_CIB); + crm_xml_add(*op_msg, F_CIB_OPERATION, op); + crm_xml_add(*op_msg, F_CIB_HOST, host); + crm_xml_add(*op_msg, F_CIB_SECTION, section); + crm_xml_add(*op_msg, F_CIB_USER, user_name); + crm_xml_add(*op_msg, F_CIB_CLIENTNAME, client_name); + crm_xml_add_int(*op_msg, F_CIB_CALLID, cib->call_id); + crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options); - crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options); + crm_xml_add_int(*op_msg, F_CIB_CALLOPTS, call_options); if (data != NULL) { - add_message_xml(op_msg, F_CIB_CALLDATA, data); + add_message_xml(*op_msg, F_CIB_CALLDATA, data); } - if (call_options & cib_inhibit_bcast) { - CRM_CHECK((call_options & cib_scope_local), return NULL); + if (pcmk_is_set(call_options, cib_inhibit_bcast)) { + CRM_CHECK(pcmk_is_set(call_options, cib_scope_local), + free_xml(*op_msg); return -EPROTO); } - return op_msg; + return pcmk_ok; +} + +/*! + * \internal + * \brief Check whether a CIB request is supported in a transaction + * + * \param[in] request CIB request + * + * \return Standard Pacemaker return code + */ +static int +validate_transaction_request(const xmlNode *request) +{ + const char *op = crm_element_value(request, F_CIB_OPERATION); + const char *host = crm_element_value(request, F_CIB_HOST); + const cib__operation_t *operation = NULL; + int rc = cib__get_operation(op, &operation); + + if (rc != pcmk_rc_ok) { + // cib__get_operation() logs error + return rc; + } + + if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) { + crm_err("Operation %s is not supported in CIB transactions", op); + return EOPNOTSUPP; + } + + if (host != NULL) { + crm_err("Operation targeting a specific node (%s) is not supported in " + "a CIB transaction", + host); + return EOPNOTSUPP; + } + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Append a CIB request to a CIB transaction + * + * \param[in,out] cib CIB client whose transaction to extend + * \param[in,out] request Request to add to transaction + * + * \return Legacy Pacemaker return code + */ +int +cib__extend_transaction(cib_t *cib, xmlNode *request) +{ + int rc = pcmk_rc_ok; + + CRM_ASSERT((cib != NULL) && (request != NULL)); + + rc = validate_transaction_request(request); + + if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) { + rc = pcmk_rc_no_transaction; + } + + if (rc == pcmk_rc_ok) { + add_node_copy(cib->transaction, request); + + } else { + const char *op = crm_element_value(request, F_CIB_OPERATION); + const char *client_id = NULL; + + cib->cmds->client_id(cib, NULL, &client_id); + crm_err("Failed to add '%s' operation to transaction for client %s: %s", + op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc)); + crm_log_xml_info(request, "failed"); + } + return pcmk_rc2legacy(rc); } void @@ -701,16 +987,7 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, } if (level > LOG_CRIT) { - pcmk__output_t *out = NULL; - - rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); - CRM_CHECK(rc == pcmk_ok, return rc); - - pcmk__output_set_log_level(out, level); - rc = out->message(out, "xml-patchset", diff); - out->finish(out, pcmk_rc2exitc(rc), true, NULL); - pcmk__output_free(out); - rc = pcmk_ok; + pcmk__log_xml_patchset(level, diff); } if (input != NULL) { |