diff --git a/daemons/controld/controld_metadata.c b/daemons/controld/controld_metadata.c index c347f00ce9..48bbcfd493 100644 --- a/daemons/controld/controld_metadata.c +++ b/daemons/controld/controld_metadata.c @@ -1,320 +1,320 @@ /* * Copyright 2017-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include static void ra_param_free(void *param) { if (param) { struct ra_param_s *p = (struct ra_param_s *) param; if (p->rap_name) { free(p->rap_name); } free(param); } } static void metadata_free(void *metadata) { if (metadata) { struct ra_metadata_s *md = (struct ra_metadata_s *) metadata; g_list_free_full(md->ra_params, ra_param_free); free(metadata); } } GHashTable * metadata_cache_new(void) { return pcmk__strkey_table(free, metadata_free); } void metadata_cache_free(GHashTable *mdc) { if (mdc) { crm_trace("Destroying metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_destroy(mdc); } } void metadata_cache_reset(GHashTable *mdc) { if (mdc) { crm_trace("Resetting metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_remove_all(mdc); } } static struct ra_param_s * ra_param_from_xml(xmlNode *param_xml) { const char *param_name = crm_element_value(param_xml, PCMK_XA_NAME); struct ra_param_s *p; p = calloc(1, sizeof(struct ra_param_s)); if (p == NULL) { return NULL; } p->rap_name = strdup(param_name); if (p->rap_name == NULL) { free(p); return NULL; } if (pcmk__xe_attr_is_true(param_xml, "reloadable")) { controld_set_ra_param_flags(p, ra_param_reloadable); } if (pcmk__xe_attr_is_true(param_xml, "unique")) { controld_set_ra_param_flags(p, ra_param_unique); } if (pcmk__xe_attr_is_true(param_xml, "private")) { controld_set_ra_param_flags(p, ra_param_private); } return p; } static void log_ra_ocf_version(const char *ra_key, const char *ra_ocf_version) { if (pcmk__str_empty(ra_ocf_version)) { crm_warn("%s does not advertise OCF version supported", ra_key); } else if (compare_version(ra_ocf_version, "2") >= 0) { crm_warn("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not work properly with agent)", ra_key, ra_ocf_version); } else if (compare_version(ra_ocf_version, PCMK_OCF_VERSION) > 0) { crm_info("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not use all agent features)", ra_key, ra_ocf_version); } else { crm_debug("%s supports OCF version %s", ra_key, ra_ocf_version); } } struct ra_metadata_s * controld_cache_metadata(GHashTable *mdc, const lrmd_rsc_info_t *rsc, const char *metadata_str) { char *key = NULL; const char *reason = NULL; xmlNode *metadata = NULL; xmlNode *match = NULL; struct ra_metadata_s *md = NULL; bool any_private_params = false; bool ocf1_1 = false; CRM_CHECK(mdc && rsc && metadata_str, return NULL); key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (!key) { reason = "Invalid resource agent standard or type"; goto err; } metadata = string2xml(metadata_str); if (!metadata) { reason = "Metadata is not valid XML"; goto err; } md = calloc(1, sizeof(struct ra_metadata_s)); if (md == NULL) { reason = "Could not allocate memory"; goto err; } if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_OCF) == 0) { xmlChar *content = NULL; - xmlNode *version_element = first_named_child(metadata, "version"); + xmlNode *version_element = first_named_child(metadata, PCMK_XE_VERSION); if (version_element != NULL) { content = xmlNodeGetContent(version_element); } log_ra_ocf_version(key, (const char *) content); if (content != NULL) { ocf1_1 = (compare_version((const char *) content, "1.1") >= 0); xmlFree(content); } } // Check supported actions match = first_named_child(metadata, PCMK_XE_ACTIONS); for (match = first_named_child(match, PCMK_XE_ACTION); match != NULL; match = crm_next_same_xml(match)) { const char *action_name = crm_element_value(match, PCMK_XA_NAME); if (pcmk__str_eq(action_name, PCMK_ACTION_RELOAD_AGENT, pcmk__str_none)) { if (ocf1_1) { controld_set_ra_flags(md, key, ra_supports_reload_agent); } else { crm_notice("reload-agent action will not be used with %s " "because it does not support OCF 1.1 or later", key); } } else if (!ocf1_1 && pcmk__str_eq(action_name, PCMK_ACTION_RELOAD, pcmk__str_casei)) { controld_set_ra_flags(md, key, ra_supports_legacy_reload); } } // Build a parameter list match = first_named_child(metadata, PCMK_XE_PARAMETERS); for (match = first_named_child(match, PCMK_XE_PARAMETER); match != NULL; match = crm_next_same_xml(match)) { const char *param_name = crm_element_value(match, PCMK_XA_NAME); if (param_name == NULL) { crm_warn("Metadata for %s:%s:%s has parameter without a name", rsc->standard, rsc->provider, rsc->type); } else { struct ra_param_s *p = ra_param_from_xml(match); if (p == NULL) { reason = "Could not allocate memory"; goto err; } if (pcmk_is_set(p->rap_flags, ra_param_private)) { any_private_params = true; } md->ra_params = g_list_prepend(md->ra_params, p); } } /* Newer resource agents support the "private" parameter attribute to * indicate sensitive parameters. For backward compatibility with older * agents, implicitly treat a few common names as private when the agent * doesn't specify any explicitly. */ if (!any_private_params) { for (GList *iter = md->ra_params; iter != NULL; iter = iter->next) { struct ra_param_s *p = iter->data; if (pcmk__str_any_of(p->rap_name, "password", "passwd", "user", NULL)) { controld_set_ra_param_flags(p, ra_param_private); } } } g_hash_table_replace(mdc, key, md); free_xml(metadata); return md; err: crm_warn("Unable to update metadata for %s (%s%s%s:%s): %s", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), pcmk__s(rsc->provider, ""), rsc->type, reason); free(key); free_xml(metadata); metadata_free(md); return NULL; } /*! * \internal * \brief Get meta-data for a resource * * \param[in,out] lrm_state Use meta-data cache from this executor connection * \param[in] rsc Resource to get meta-data for * \param[in] source Allowed meta-data sources (bitmask of * enum controld_metadata_source_e values) * * \return Meta-data cache entry for given resource, or NULL if not available */ struct ra_metadata_s * controld_get_rsc_metadata(lrm_state_t *lrm_state, const lrmd_rsc_info_t *rsc, uint32_t source) { struct ra_metadata_s *metadata = NULL; char *metadata_str = NULL; char *key = NULL; int rc = pcmk_ok; CRM_CHECK((lrm_state != NULL) && (rsc != NULL), return NULL); if (pcmk_is_set(source, controld_metadata_from_cache)) { key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (key != NULL) { metadata = g_hash_table_lookup(lrm_state->metadata_cache, key); free(key); } if (metadata != NULL) { crm_debug("Retrieved metadata for %s (%s%s%s:%s) from cache", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type); return metadata; } } if (!pcmk_is_set(source, controld_metadata_from_agent)) { return NULL; } /* For most actions, metadata was cached asynchronously before action * execution (via metadata_complete()). * * However if that failed, and for other actions, retrieve the metadata now * via a local, synchronous, direct execution of the agent. * * This has multiple issues, which is why this is just a fallback: the * executor should execute agents, not the controller; metadata for * Pacemaker Remote nodes should be collected on those nodes, not locally; * the metadata call shouldn't eat into the timeout of the real action being * performed; and the synchronous call blocks the controller (which also * means that if the metadata action tries to contact the controller, * everything will hang until the timeout). */ crm_debug("Retrieving metadata for %s (%s%s%s:%s) synchronously", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type); rc = lrm_state_get_metadata(lrm_state, rsc->standard, rsc->provider, rsc->type, &metadata_str, 0); if (rc != pcmk_ok) { crm_warn("Failed to get metadata for %s (%s%s%s:%s): %s", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type, pcmk_strerror(rc)); return NULL; } metadata = controld_cache_metadata(lrm_state->metadata_cache, rsc, metadata_str); free(metadata_str); return metadata; } diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index db384377fa..348c5d0e31 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,561 +1,561 @@ /* * Copyright 2019-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 // pcmk__xml2fd static gboolean legacy_xml = FALSE; static gboolean simple_list = FALSE; static gboolean substitute = FALSE; GOptionEntry pcmk__xml_output_entries[] = { { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml, NULL, NULL }, { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list, NULL, NULL }, { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute, NULL, NULL }, { NULL } }; typedef struct subst_s { const char *from; const char *to; } subst_t; static subst_t substitutions[] = { { "Active Resources", PCMK_XE_RESOURCES, }, { "Assignment Scores", "allocations", }, { "Assignment Scores and Utilization Information", "allocations_utilizations", }, { "Cluster Summary", "summary", }, { "Current cluster status", "cluster_status", }, { "Executing Cluster Transition", "transition", }, { "Failed Resource Actions", "failures", }, { "Fencing History", "fence_history", }, { "Full List of Resources", PCMK_XE_RESOURCES, }, { "Inactive Resources", PCMK_XE_RESOURCES, }, { "Migration Summary", "node_history", }, { "Negative Location Constraints", "bans", }, { "Node Attributes", "node_attributes", }, { "Operations", "node_history", }, { "Resource Config", "resource_config", }, { "Resource Operations", "operations", }, { "Revised Cluster Status", "revised_cluster_status", }, { "Transition Summary", PCMK_XE_ACTIONS, }, { "Utilization Information", "utilizations", }, { NULL, NULL } }; /* The first several elements of this struct must be the same as the first * several elements of private_data_s in lib/common/output_html.c. That * struct gets passed to a bunch of the pcmk__output_xml_* functions which * assume an XML private_data_s. Keeping them laid out the same means this * still works. */ typedef struct private_data_s { /* Begin members that must match the HTML version */ xmlNode *root; GQueue *parent_q; GSList *errors; /* End members that must match the HTML version */ bool legacy_xml; } private_data_t; static void xml_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; free_xml(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); out->priv = NULL; } static bool xml_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); /* If xml_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } if (legacy_xml) { priv->root = create_xml_node(NULL, "crm_mon"); crm_xml_add(priv->root, PCMK_XA_VERSION, PACEMAKER_VERSION); } else { priv->root = create_xml_node(NULL, "pacemaker-result"); crm_xml_add(priv->root, "api-version", PCMK__API_VERSION); crm_xml_add(priv->root, PCMK_XA_REQUEST, out->request); } priv->parent_q = g_queue_new(); priv->errors = NULL; g_queue_push_tail(priv->parent_q, priv->root); /* Copy this from the file-level variable. This means that it is only settable * as a command line option, and that pcmk__output_new must be called after all * command line processing is completed. */ priv->legacy_xml = legacy_xml; return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; xmlNodePtr node = (xmlNodePtr) user_data; pcmk_create_xml_text_node(node, "error", str); } static void xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = NULL; xmlNodePtr node; CRM_ASSERT(out != NULL); priv = out->priv; /* If root is NULL, xml_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } if (legacy_xml) { GSList *node = priv->errors; if (exit_status != CRM_EX_OK) { fprintf(stderr, "%s\n", crm_exit_str(exit_status)); } while (node != NULL) { fprintf(stderr, "%s\n", (char *) node->data); node = node->next; } } else { char *rc_as_str = pcmk__itoa(exit_status); node = create_xml_node(priv->root, PCMK_XE_STATUS); pcmk__xe_set_props(node, "code", rc_as_str, "message", crm_exit_str(exit_status), NULL); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = create_xml_node(node, "errors"); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } free(rc_as_str); } if (print) { pcmk__xml2fd(fileno(out->dest), priv->root); } if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } } static void xml_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); xml_free_priv(out); xml_init(out); } static void xml_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { xmlNodePtr node, child_node; char *rc_as_str = NULL; CRM_ASSERT(out != NULL); rc_as_str = pcmk__itoa(exit_status); node = pcmk__output_xml_create_parent(out, "command", "code", rc_as_str, NULL); if (proc_stdout != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stdout); crm_xml_add(child_node, "source", "stdout"); } if (proc_stderr != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stderr); crm_xml_add(child_node, "source", "stderr"); } free(rc_as_str); } static void xml_version(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); - pcmk__output_create_xml_node(out, "version", + pcmk__output_create_xml_node(out, PCMK_XE_VERSION, "program", "Pacemaker", PCMK_XA_VERSION, PACEMAKER_VERSION, "author", "Andrew Beekhof and the " "Pacemaker project contributors", "build", BUILD_VERSION, "features", CRM_FEATURES, NULL); } G_GNUC_PRINTF(2, 3) static void xml_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static int xml_info(pcmk__output_t *out, const char *format, ...) { return pcmk_rc_no_output; } static void xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr parent = NULL; xmlNodePtr cdata_node = NULL; CRM_ASSERT(out != NULL); parent = pcmk__output_create_xml_node(out, name, NULL); if (parent == NULL) { return; } cdata_node = xmlNewCDataBlock(parent->doc, (pcmkXmlStr) buf, strlen(buf)); xmlAddChild(parent, cdata_node); } G_GNUC_PRINTF(4, 5) static void xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { va_list ap; char *name = NULL; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (substitute) { for (subst_t *s = substitutions; s->from != NULL; s++) { if (!strcmp(s->from, buf)) { name = g_strdup(s->to); break; } } } if (name == NULL) { name = g_ascii_strdown(buf, -1); } if (legacy_xml || simple_list) { pcmk__output_xml_create_parent(out, name, NULL); } else { pcmk__output_xml_create_parent(out, "list", PCMK_XA_NAME, name, NULL); } g_free(name); free(buf); } G_GNUC_PRINTF(3, 4) static void xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { xmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "item", buf); if (name != NULL) { crm_xml_add(item_node, PCMK_XA_NAME, name); } free(buf); } static void xml_increment_list(pcmk__output_t *out) { /* This function intentially left blank */ } static void xml_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (priv->legacy_xml || simple_list) { g_queue_pop_tail(priv->parent_q); } else { char *buf = NULL; xmlNodePtr node; node = g_queue_pop_tail(priv->parent_q); buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); crm_xml_add(node, "count", buf); free(buf); } } static bool xml_is_quiet(pcmk__output_t *out) { return false; } static void xml_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } static void xml_progress(pcmk__output_t *out, bool end) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_xml_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "xml"; retval->request = pcmk__quote_cmdline(argv); retval->init = xml_init; retval->free_priv = xml_free_priv; retval->finish = xml_finish; retval->reset = xml_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = xml_subprocess_output; retval->version = xml_version; retval->info = xml_info; retval->transient = xml_info; retval->err = xml_err; retval->output_xml = xml_output_xml; retval->begin_list = xml_begin_list; retval->list_item = xml_list_item; retval->increment_list = xml_increment_list; retval->end_list = xml_end_list; retval->is_quiet = xml_is_quiet; retval->spacer = xml_spacer; retval->progress = xml_progress; retval->prompt = pcmk__text_prompt; return retval; } xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) { va_list args; xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); node = pcmk__output_create_xml_node(out, name, NULL); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); pcmk__output_xml_push_parent(out, node); return node; } void pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node) { private_data_t *priv = NULL; xmlNodePtr parent = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(node != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; parent = g_queue_peek_tail(priv->parent_q); // Shouldn't happen unless the caller popped priv->root CRM_CHECK(parent != NULL, return); add_node_copy(parent, node); } xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) { xmlNodePtr node = NULL; private_data_t *priv = NULL; va_list args; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); priv = out->priv; node = create_xml_node(g_queue_peek_tail(priv->parent_q), name); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); return node; } xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) { xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); node = pcmk__output_create_xml_node(out, name, NULL); xmlNodeSetContent(node, (pcmkXmlStr) content); return node; } void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(parent != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; g_queue_push_tail(priv->parent_q, parent); } void pcmk__output_xml_pop_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); priv = out->priv; CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); g_queue_pop_tail(priv->parent_q); } xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); priv = out->priv; /* If queue is empty NULL will be returned */ return g_queue_peek_tail(priv->parent_q); } diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 63a2412e6d..5d743fb843 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -1,1503 +1,1503 @@ /* * 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 #include // CRM_XML_LOG_BASE, etc. #include "crmcommon_private.h" static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean *changed); /* Add changes for specified XML to patchset. * For patchset format, refer to diff schema. */ static void add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { xmlNode *cIter = NULL; xmlAttr *pIter = NULL; xmlNode *change = NULL; xml_node_private_t *nodepriv = xml->_private; const char *value = NULL; if (nodepriv == NULL) { /* Elements that shouldn't occur in a CIB don't have _private set. They * should be stripped out, ignored, or have an error thrown by any code * that processes their parent, so we ignore any changes to them. */ return; } // If this XML node is new, just report that if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(xml->parent); if (xpath != NULL) { int position = pcmk__xml_position(xml, pcmk__xf_deleted); change = create_xml_node(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, "create"); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); crm_xml_add_int(change, PCMK_XE_POSITION, position); add_node_copy(change, xml); g_string_free(xpath, TRUE); } return; } // Check each of the XML node's attributes for changes for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; pIter = pIter->next) { xmlNode *attr = NULL; nodepriv = pIter->_private; if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) { continue; } if (change == NULL) { GString *xpath = pcmk__element_xpath(xml); if (xpath != NULL) { change = create_xml_node(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, "modify"); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); change = create_xml_node(change, PCMK_XE_CHANGE_LIST); g_string_free(xpath, TRUE); } } attr = create_xml_node(change, PCMK_XE_CHANGE_ATTR); crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name); if (nodepriv->flags & pcmk__xf_deleted) { crm_xml_add(attr, PCMK_XA_OPERATION, "unset"); } else { crm_xml_add(attr, PCMK_XA_OPERATION, "set"); value = pcmk__xml_attr_value(pIter); crm_xml_add(attr, PCMK_XA_VALUE, value); } } if (change) { xmlNode *result = NULL; change = create_xml_node(change->parent, PCMK_XE_CHANGE_RESULT); result = create_xml_node(change, (const char *)xml->name); for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; pIter = pIter->next) { nodepriv = pIter->_private; if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { value = crm_element_value(xml, (const char *) pIter->name); crm_xml_add(result, (const char *)pIter->name, value); } } } // Now recursively do the same for each child node of this node for (cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { add_xml_changes_to_patchset(cIter, patchset); } nodepriv = xml->_private; if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) { GString *xpath = pcmk__element_xpath(xml); crm_trace("%s.%s moved to position %d", xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip)); if (xpath != NULL) { change = create_xml_node(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, "move"); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); crm_xml_add_int(change, PCMK_XE_POSITION, pcmk__xml_position(xml, pcmk__xf_deleted)); g_string_free(xpath, TRUE); } } } static bool is_config_change(xmlNode *xml) { GList *gIter = NULL; xml_node_private_t *nodepriv = NULL; xml_doc_private_t *docpriv; xmlNode *config = first_named_child(xml, PCMK_XE_CONFIGURATION); if (config) { nodepriv = config->_private; } if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) { return TRUE; } if ((xml->doc != NULL) && (xml->doc->_private != NULL)) { docpriv = xml->doc->_private; for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { pcmk__deleted_xml_t *deleted_obj = gIter->data; if (strstr(deleted_obj->path, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) { return TRUE; } } } return FALSE; } static void xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff, gboolean changed) { int lpc = 0; xmlNode *cib = NULL; xmlNode *diff_child = NULL; const char *tag = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; if (local_diff == NULL) { crm_trace("Nothing to do"); return; } tag = PCMK__XE_DIFF_REMOVED; diff_child = find_xml_node(local_diff, tag, FALSE); if (diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = PCMK_XE_CIB; cib = find_xml_node(diff_child, tag, FALSE); if (cib == NULL) { cib = create_xml_node(diff_child, tag); } for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) { const char *value = crm_element_value(last, vfields[lpc]); crm_xml_add(diff_child, vfields[lpc], value); if (changed || lpc == 2) { crm_xml_add(cib, vfields[lpc], value); } } tag = PCMK__XE_DIFF_ADDED; diff_child = find_xml_node(local_diff, tag, FALSE); if (diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = PCMK_XE_CIB; cib = find_xml_node(diff_child, tag, FALSE); if (cib == NULL) { cib = create_xml_node(diff_child, tag); } for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(next, vfields[lpc]); crm_xml_add(diff_child, vfields[lpc], value); } for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) { const char *p_value = pcmk__xml_attr_value(a); xmlSetProp(cib, a->name, (pcmkXmlStr) p_value); } crm_log_xml_explicit(local_diff, "Repaired-diff"); } static xmlNode * xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress) { xmlNode *patchset = diff_xml_object(source, target, suppress); if (patchset) { CRM_LOG_ASSERT(xml_document_dirty(target)); xml_repair_v1_diff(source, target, patchset, config); crm_xml_add(patchset, PCMK_XA_FORMAT, "1"); } return patchset; } static xmlNode * xml_create_patchset_v2(xmlNode *source, xmlNode *target) { int lpc = 0; GList *gIter = NULL; xml_doc_private_t *docpriv; xmlNode *v = NULL; xmlNode *version = NULL; xmlNode *patchset = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; CRM_ASSERT(target); if (!xml_document_dirty(target)) { return NULL; } CRM_ASSERT(target->doc); docpriv = target->doc->_private; patchset = create_xml_node(NULL, PCMK_XE_DIFF); crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2); version = create_xml_node(patchset, PCMK_XE_VERSION); v = create_xml_node(version, PCMK_XE_SOURCE); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(source, vfields[lpc]); if (value == NULL) { value = "1"; } crm_xml_add(v, vfields[lpc], value); } v = create_xml_node(version, PCMK_XE_TARGET); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(target, vfields[lpc]); if (value == NULL) { value = "1"; } crm_xml_add(v, vfields[lpc], value); } for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { pcmk__deleted_xml_t *deleted_obj = gIter->data; xmlNode *change = create_xml_node(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, "delete"); crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path); if (deleted_obj->position >= 0) { crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position); } } add_xml_changes_to_patchset(target, patchset); return patchset; } xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version) { int counter = 0; bool config = FALSE; xmlNode *patch = NULL; const char *version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET); xml_acl_disable(target); if (!xml_document_dirty(target)) { crm_trace("No change %d", format); return NULL; /* No change */ } config = is_config_change(target); if (config_changed) { *config_changed = config; } if (manage_version && config) { crm_trace("Config changed %d", format); crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0"); crm_element_value_int(target, PCMK_XA_EPOCH, &counter); crm_xml_add_int(target, PCMK_XA_EPOCH, counter+1); } else if (manage_version) { crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter); crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, PCMK_XA_NUM_UPDATES)); crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, (counter + 1)); } if (format == 0) { if (compare_version("3.0.8", version) < 0) { format = 2; } else { format = 1; } crm_trace("Using patch format %d for version: %s", format, version); } switch (format) { case 1: patch = xml_create_patchset_v1(source, target, config, FALSE); break; case 2: patch = xml_create_patchset_v2(source, target); break; default: crm_err("Unknown patch format: %d", format); return NULL; } return patch; } void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest) { int format = 1; const char *version = NULL; char *digest = NULL; if ((patch == NULL) || (source == NULL) || (target == NULL)) { return; } /* We should always call xml_accept_changes() before calculating a digest. * Otherwise, with an on-tracking dirty target, we could get a wrong digest. */ CRM_LOG_ASSERT(!xml_document_dirty(target)); crm_element_value_int(patch, PCMK_XA_FORMAT, &format); if ((format > 1) && !with_digest) { return; } version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET); digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version); crm_xml_add(patch, PCMK__XA_DIGEST, digest); free(digest); return; } // Return true if attribute name is not \c PCMK_XML_ID static bool not_id(xmlAttrPtr attr, void *user_data) { return strcmp((const char *) attr->name, PCMK_XA_ID) != 0; } // Apply the removals section of an v1 patchset to an XML node static void process_v1_removals(xmlNode *target, xmlNode *patch) { xmlNode *patch_child = NULL; xmlNode *cIter = NULL; char *id = NULL; const char *value = NULL; if ((target == NULL) || (patch == NULL)) { return; } if (target->type == XML_COMMENT_NODE) { gboolean dummy; subtract_xml_comment(target->parent, target, patch, &dummy); } CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return); CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return); // Check for PCMK__XA_CRM_DIFF_MARKER in a child id = crm_element_value_copy(target, PCMK_XA_ID); value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER); if ((value != NULL) && (strcmp(value, "removed:top") == 0)) { crm_trace("We are the root of the deletion: %s.id=%s", target->name, id); free_xml(target); free(id); return; } // Removing then restoring id would change ordering of properties pcmk__xe_remove_matching_attrs(patch, not_id, NULL); // Changes to child objects cIter = pcmk__xml_first_child(target); while (cIter) { xmlNode *target_child = cIter; cIter = pcmk__xml_next(cIter); patch_child = pcmk__xml_match(patch, target_child, false); process_v1_removals(target_child, patch_child); } free(id); } // Apply the additions section of an v1 patchset to an XML node static void process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch) { xmlNode *patch_child = NULL; xmlNode *target_child = NULL; xmlAttrPtr xIter = NULL; const char *id = NULL; const char *name = NULL; const char *value = NULL; if (patch == NULL) { return; } else if ((parent == NULL) && (target == NULL)) { return; } // Check for PCMK__XA_CRM_DIFF_MARKER in a child name = (const char *) patch->name; value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER); if ((target == NULL) && (value != NULL) && (strcmp(value, "added:top") == 0)) { id = ID(patch); crm_trace("We are the root of the addition: %s.id=%s", name, id); add_node_copy(parent, patch); return; } else if (target == NULL) { id = ID(patch); crm_err("Could not locate: %s.id=%s", name, id); return; } if (target->type == XML_COMMENT_NODE) { pcmk__xc_update(parent, target, patch); } CRM_CHECK(pcmk__xe_is(target, name), return); CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return); for (xIter = pcmk__xe_first_attr(patch); xIter != NULL; xIter = xIter->next) { const char *p_name = (const char *) xIter->name; const char *p_value = pcmk__xml_attr_value(xIter); xml_remove_prop(target, p_name); // Preserve patch order crm_xml_add(target, p_name, p_value); } // Changes to child objects for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL; patch_child = pcmk__xml_next(patch_child)) { target_child = pcmk__xml_match(target, patch_child, false); process_v1_additions(target, target_child, patch_child); } } /*! * \internal * \brief Find additions or removals in a patch set * * \param[in] patchset XML of patch * \param[in] format Patch version * \param[in] added TRUE if looking for additions, FALSE if removals * \param[in,out] patch_node Will be set to node if found * * \return TRUE if format is valid, FALSE if invalid */ static bool find_patch_xml_node(const xmlNode *patchset, int format, bool added, xmlNode **patch_node) { xmlNode *cib_node; const char *label; switch (format) { case 1: label = added? PCMK__XE_DIFF_ADDED : PCMK__XE_DIFF_REMOVED; *patch_node = find_xml_node(patchset, label, FALSE); cib_node = find_xml_node(*patch_node, PCMK_XE_CIB, FALSE); if (cib_node != NULL) { *patch_node = cib_node; } break; case 2: - label = added? "target" : "source"; - *patch_node = find_xml_node(patchset, "version", FALSE); + label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE; + *patch_node = find_xml_node(patchset, PCMK_XE_VERSION, FALSE); *patch_node = find_xml_node(*patch_node, label, FALSE); break; default: crm_warn("Unknown patch format: %d", format); *patch_node = NULL; return FALSE; } return TRUE; } // Get CIB versions used for additions and deletions in a patchset bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]) { int lpc = 0; int format = 1; xmlNode *tmp = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); /* Process removals */ if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) { return -EINVAL; } if (tmp != NULL) { for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(tmp, vfields[lpc], &(del[lpc])); crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]); } } /* Process additions */ if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) { return -EINVAL; } if (tmp != NULL) { for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(tmp, vfields[lpc], &(add[lpc])); crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]); } } return pcmk_ok; } /*! * \internal * \brief Check whether patchset can be applied to current CIB * * \param[in] xml Root of current CIB * \param[in] patchset Patchset to check * * \return Standard Pacemaker return code */ static int xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset) { int lpc = 0; bool changed = FALSE; int this[] = { 0, 0, 0 }; int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(xml, vfields[lpc], &(this[lpc])); crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]); if (this[lpc] < 0) { this[lpc] = 0; } } /* Set some defaults in case nothing is present */ add[0] = this[0]; add[1] = this[1]; add[2] = this[2] + 1; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { del[lpc] = this[lpc]; } xml_patch_versions(patchset, add, del); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { if (this[lpc] < del[lpc]) { crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc], this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]); return pcmk_rc_diff_resync; } else if (this[lpc] > del[lpc]) { crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc], this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset); crm_log_xml_info(patchset, "OldPatch"); return pcmk_rc_old_data; } } for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { if (add[lpc] > del[lpc]) { changed = TRUE; } } if (!changed) { crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]); return pcmk_rc_old_data; } crm_debug("Can apply patch %d.%d.%d to %d.%d.%d", add[0], add[1], add[2], this[0], this[1], this[2]); return pcmk_rc_ok; } /*! * \internal * \brief Apply a version 1 patchset to an XML node * * \param[in,out] xml XML to apply patchset to * \param[in] patchset Patchset to apply * * \return Standard Pacemaker return code */ static int apply_v1_patchset(xmlNode *xml, const xmlNode *patchset) { int rc = pcmk_rc_ok; int root_nodes_seen = 0; xmlNode *child_diff = NULL; xmlNode *added = find_xml_node(patchset, PCMK__XE_DIFF_ADDED, FALSE); xmlNode *removed = find_xml_node(patchset, PCMK__XE_DIFF_REMOVED, FALSE); xmlNode *old = copy_xml(xml); crm_trace("Subtraction Phase"); for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, rc = FALSE); if (root_nodes_seen == 0) { process_v1_removals(xml, child_diff); } root_nodes_seen++; } if (root_nodes_seen > 1) { crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); rc = ENOTUNIQ; } root_nodes_seen = 0; crm_trace("Addition Phase"); if (rc == pcmk_rc_ok) { xmlNode *child_diff = NULL; for (child_diff = pcmk__xml_first_child(added); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, rc = FALSE); if (root_nodes_seen == 0) { process_v1_additions(NULL, xml, child_diff); } root_nodes_seen++; } } if (root_nodes_seen > 1) { crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); rc = ENOTUNIQ; } purge_diff_markers(xml); // Purge prior to checking digest free_xml(old); return rc; } // Return first child matching element name and optionally id or position static xmlNode * first_matching_xml_child(const xmlNode *parent, const char *name, const char *id, int position) { xmlNode *cIter = NULL; for (cIter = pcmk__xml_first_child(parent); cIter != NULL; cIter = pcmk__xml_next(cIter)) { if (strcmp((const char *) cIter->name, name) != 0) { continue; } else if (id) { const char *cid = ID(cIter); if ((cid == NULL) || (strcmp(cid, id) != 0)) { continue; } } // "position" makes sense only for XML comments for now if ((cIter->type == XML_COMMENT_NODE) && (position >= 0) && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) { continue; } return cIter; } return NULL; } /*! * \internal * \brief Simplified, more efficient alternative to get_xpath_object() * * \param[in] top Root of XML to search * \param[in] key Search xpath * \param[in] target_position If deleting, where to delete * * \return XML child matching xpath if found, NULL otherwise * * \note This only works on simplified xpaths found in v2 patchset diffs, * i.e. the only allowed search predicate is [@id='XXX']. */ static xmlNode * search_v2_xpath(const xmlNode *top, const char *key, int target_position) { xmlNode *target = (xmlNode *) top->doc; const char *current = key; char *section; char *remainder; char *id; char *tag; char *path = NULL; int rc; size_t key_len; CRM_CHECK(key != NULL, return NULL); key_len = strlen(key); /* These are scanned from key after a slash, so they can't be bigger * than key_len - 1 characters plus a null terminator. */ remainder = calloc(key_len, sizeof(char)); CRM_ASSERT(remainder != NULL); section = calloc(key_len, sizeof(char)); CRM_ASSERT(section != NULL); id = calloc(key_len, sizeof(char)); CRM_ASSERT(id != NULL); tag = calloc(key_len, sizeof(char)); CRM_ASSERT(tag != NULL); do { // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS rc = sscanf(current, "/%[^/]%s", section, remainder); if (rc > 0) { // Separate FIRST_COMPONENT into TAG[@id='ID'] int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id); int current_position = -1; /* The target position is for the final component tag, so only use * it if there is nothing left to search after this component. */ if ((rc == 1) && (target_position >= 0)) { current_position = target_position; } switch (f) { case 1: target = first_matching_xml_child(target, tag, NULL, current_position); break; case 2: target = first_matching_xml_child(target, tag, id, current_position); break; default: // This should not be possible target = NULL; break; } current = remainder; } // Continue if something remains to search, and we've matched so far } while ((rc == 2) && target); if (target) { crm_trace("Found %s for %s", (path = (char *) xmlGetNodePath(target)), key); free(path); } else { crm_debug("No match for %s", key); } free(remainder); free(section); free(tag); free(id); return target; } typedef struct xml_change_obj_s { const xmlNode *change; xmlNode *match; } xml_change_obj_t; static gint sort_change_obj_by_position(gconstpointer a, gconstpointer b) { const xml_change_obj_t *change_obj_a = a; const xml_change_obj_t *change_obj_b = b; int position_a = -1; int position_b = -1; crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a); crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b); if (position_a < position_b) { return -1; } else if (position_a > position_b) { return 1; } return 0; } /*! * \internal * \brief Apply a version 2 patchset to an XML node * * \param[in,out] xml XML to apply patchset to * \param[in] patchset Patchset to apply * * \return Standard Pacemaker return code */ static int apply_v2_patchset(xmlNode *xml, const xmlNode *patchset) { int rc = pcmk_rc_ok; const xmlNode *change = NULL; GList *change_objs = NULL; GList *gIter = NULL; for (change = pcmk__xml_first_child(patchset); change != NULL; change = pcmk__xml_next(change)) { xmlNode *match = NULL; const char *op = crm_element_value(change, PCMK_XA_OPERATION); const char *xpath = crm_element_value(change, PCMK_XA_PATH); int position = -1; if (op == NULL) { continue; } crm_trace("Processing %s %s", change->name, op); // "delete" changes for XML comments are generated with PCMK_XE_POSITION if (strcmp(op, "delete") == 0) { crm_element_value_int(change, PCMK_XE_POSITION, &position); } match = search_v2_xpath(xml, xpath, position); crm_trace("Performing %s on %s with %p", op, xpath, match); if ((match == NULL) && (strcmp(op, "delete") == 0)) { crm_debug("No %s match for %s in %p", op, xpath, xml->doc); continue; } else if (match == NULL) { crm_err("No %s match for %s in %p", op, xpath, xml->doc); rc = pcmk_rc_diff_failed; continue; } else if ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) { // Delay the adding of a "create" object xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t)); CRM_ASSERT(change_obj != NULL); change_obj->change = change; change_obj->match = match; change_objs = g_list_append(change_objs, change_obj); if (strcmp(op, "move") == 0) { // Temporarily put the "move" object after the last sibling if ((match->parent != NULL) && (match->parent->last != NULL)) { xmlAddNextSibling(match->parent->last, match); } } } else if (strcmp(op, "delete") == 0) { free_xml(match); } else if (strcmp(op, "modify") == 0) { const xmlNode *child = first_named_child(change, PCMK_XE_CHANGE_RESULT); const xmlNode *attrs = pcmk__xml_first_child(child); if (attrs == NULL) { rc = ENOMSG; continue; } pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL; pIter = pIter->next) { const char *name = (const char *) pIter->name; const char *value = pcmk__xml_attr_value(pIter); crm_xml_add(match, name, value); } } else { crm_err("Unknown operation: %s", op); rc = pcmk_rc_diff_failed; } } // Changes should be generated in the right order. Double checking. change_objs = g_list_sort(change_objs, sort_change_obj_by_position); for (gIter = change_objs; gIter; gIter = gIter->next) { xml_change_obj_t *change_obj = gIter->data; xmlNode *match = change_obj->match; const char *op = NULL; const char *xpath = NULL; change = change_obj->change; op = crm_element_value(change, PCMK_XA_OPERATION); xpath = crm_element_value(change, PCMK_XA_PATH); crm_trace("Continue performing %s on %s with %p", op, xpath, match); if (strcmp(op, "create") == 0) { int position = 0; xmlNode *child = NULL; xmlNode *match_child = NULL; match_child = match->children; crm_element_value_int(change, PCMK_XE_POSITION, &position); while ((match_child != NULL) && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) { match_child = match_child->next; } child = xmlDocCopyNode(change->children, match->doc, 1); if (child == NULL) { return ENOMEM; } if (match_child) { crm_trace("Adding %s at position %d", child->name, position); xmlAddPrevSibling(match_child, child); } else if (match->last) { crm_trace("Adding %s at position %d (end)", child->name, position); xmlAddNextSibling(match->last, child); } else { crm_trace("Adding %s at position %d (first)", child->name, position); CRM_LOG_ASSERT(position == 0); xmlAddChild(match, child); } pcmk__mark_xml_created(child); } else if (strcmp(op, "move") == 0) { int position = 0; crm_element_value_int(change, PCMK_XE_POSITION, &position); if (position != pcmk__xml_position(match, pcmk__xf_skip)) { xmlNode *match_child = NULL; int p = position; if (p > pcmk__xml_position(match, pcmk__xf_skip)) { p++; // Skip ourselves } CRM_ASSERT(match->parent != NULL); match_child = match->parent->children; while ((match_child != NULL) && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) { match_child = match_child->next; } crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)", match->name, position, pcmk__xml_position(match, pcmk__xf_skip), match->prev, (match_child? "next":"last"), (match_child? match_child : match->parent->last)); if (match_child) { xmlAddPrevSibling(match_child, match); } else { CRM_ASSERT(match->parent->last != NULL); xmlAddNextSibling(match->parent->last, match); } } else { crm_trace("%s is already in position %d", match->name, position); } if (position != pcmk__xml_position(match, pcmk__xf_skip)) { crm_err("Moved %s.%s to position %d instead of %d (%p)", match->name, ID(match), pcmk__xml_position(match, pcmk__xf_skip), position, match->prev); rc = pcmk_rc_diff_failed; } } } g_list_free_full(change_objs, free); return rc; } int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version) { int format = 1; int rc = pcmk_ok; xmlNode *old = NULL; const char *digest = NULL; if (patchset == NULL) { return rc; } pcmk__log_xml_patchset(LOG_TRACE, patchset); if (check_version) { rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset)); if (rc != pcmk_ok) { return rc; } } digest = crm_element_value(patchset, PCMK__XA_DIGEST); if (digest != NULL) { /* Make original XML available for logging in case result doesn't have * expected digest */ pcmk__if_tracing(old = copy_xml(xml), {}); } if (rc == pcmk_ok) { crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset)); break; case 2: rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset)); break; default: crm_err("Unknown patch format: %d", format); rc = -EINVAL; } } if ((rc == pcmk_ok) && (digest != NULL)) { char *new_digest = NULL; char *version = crm_element_value_copy(xml, PCMK_XA_CRM_FEATURE_SET); new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version); if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) { crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest); rc = -pcmk_err_diff_failed; pcmk__if_tracing( { save_xml_to_file(old, "PatchDigest:input", NULL); save_xml_to_file(xml, "PatchDigest:result", NULL); save_xml_to_file(patchset, "PatchDigest:diff", NULL); }, {} ); } else { crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest); } free(new_digest); free(version); } free_xml(old); return rc; } void purge_diff_markers(xmlNode *a_node) { xmlNode *child = NULL; CRM_CHECK(a_node != NULL, return); xml_remove_prop(a_node, PCMK__XA_CRM_DIFF_MARKER); for (child = pcmk__xml_first_child(a_node); child != NULL; child = pcmk__xml_next(child)) { purge_diff_markers(child); } } xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress) { xmlNode *tmp1 = NULL; xmlNode *diff = create_xml_node(NULL, PCMK_XE_DIFF); xmlNode *removed = create_xml_node(diff, PCMK__XE_DIFF_REMOVED); xmlNode *added = create_xml_node(diff, PCMK__XE_DIFF_ADDED); crm_xml_add(diff, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top"); if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) { free_xml(tmp1); } tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top"); if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) { free_xml(tmp1); } if ((added->children == NULL) && (removed->children == NULL)) { free_xml(diff); diff = NULL; } return diff; } static xmlNode * subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean *changed) { CRM_CHECK(left != NULL, return NULL); CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL); if ((right == NULL) || !pcmk__str_eq((const char *)left->content, (const char *)right->content, pcmk__str_casei)) { xmlNode *deleted = NULL; deleted = add_node_copy(parent, left); *changed = TRUE; return deleted; } return NULL; } xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker) { gboolean dummy = FALSE; xmlNode *diff = NULL; xmlNode *right_child = NULL; xmlNode *left_child = NULL; xmlAttrPtr xIter = NULL; const char *id = NULL; const char *name = NULL; const char *value = NULL; const char *right_val = NULL; if (changed == NULL) { changed = &dummy; } if (left == NULL) { return NULL; } if (left->type == XML_COMMENT_NODE) { return subtract_xml_comment(parent, left, right, changed); } id = ID(left); name = (const char *) left->name; if (right == NULL) { xmlNode *deleted = NULL; crm_trace("Processing <%s " PCMK_XA_ID "=%s> (complete copy)", name, id); deleted = add_node_copy(parent, left); crm_xml_add(deleted, PCMK__XA_CRM_DIFF_MARKER, marker); *changed = TRUE; return deleted; } CRM_CHECK(name != NULL, return NULL); CRM_CHECK(pcmk__xe_is(left, (const char *) right->name), return NULL); // Check for PCMK__XA_CRM_DIFF_MARKER in a child value = crm_element_value(right, PCMK__XA_CRM_DIFF_MARKER); if ((value != NULL) && (strcmp(value, "removed:top") == 0)) { crm_trace("We are the root of the deletion: %s.id=%s", name, id); *changed = TRUE; return NULL; } // @TODO Avoiding creating the full hierarchy would save work here diff = create_xml_node(parent, name); // Changes to child objects for (left_child = pcmk__xml_first_child(left); left_child != NULL; left_child = pcmk__xml_next(left_child)) { gboolean child_changed = FALSE; right_child = pcmk__xml_match(right, left_child, false); subtract_xml_object(diff, left_child, right_child, full, &child_changed, marker); if (child_changed) { *changed = TRUE; } } if (!*changed) { /* Nothing to do */ } else if (full) { xmlAttrPtr pIter = NULL; for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *)pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } // We have everything we need goto done; } // Changes to name/value pairs for (xIter = pcmk__xe_first_attr(left); xIter != NULL; xIter = xIter->next) { const char *prop_name = (const char *) xIter->name; xmlAttrPtr right_attr = NULL; xml_node_private_t *nodepriv = NULL; if (strcmp(prop_name, PCMK_XA_ID) == 0) { // id already obtained when present ~ this case, so just reuse xmlSetProp(diff, (pcmkXmlStr) PCMK_XA_ID, (pcmkXmlStr) id); continue; } if (pcmk__xa_filterable(prop_name)) { continue; } right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name); if (right_attr) { nodepriv = right_attr->_private; } right_val = crm_element_value(right, prop_name); if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) { /* new */ *changed = TRUE; if (full) { xmlAttrPtr pIter = NULL; for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *) pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } break; } else { const char *left_value = pcmk__xml_attr_value(xIter); xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value); crm_xml_add(diff, prop_name, left_value); } } else { /* Only now do we need the left value */ const char *left_value = pcmk__xml_attr_value(xIter); if (strcmp(left_value, right_val) == 0) { /* unchanged */ } else { *changed = TRUE; if (full) { xmlAttrPtr pIter = NULL; crm_trace("Changes detected to %s in " "<%s " PCMK_XA_ID "=%s>", prop_name, name, id); for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *) pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } break; } else { crm_trace("Changes detected to %s (%s -> %s) in " "<%s " PCMK_XA_ID "=%s>", prop_name, left_value, right_val, name, id); crm_xml_add(diff, prop_name, left_value); } } } } if (!*changed) { free_xml(diff); return NULL; } else if (!full && (id != NULL)) { crm_xml_add(diff, PCMK_XA_ID, id); } done: return diff; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml) { gboolean result = TRUE; int root_nodes_seen = 0; const char *digest = crm_element_value(diff, PCMK__XA_DIGEST); const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET); xmlNode *child_diff = NULL; xmlNode *added = find_xml_node(diff, PCMK__XE_DIFF_ADDED, FALSE); xmlNode *removed = find_xml_node(diff, PCMK__XE_DIFF_REMOVED, FALSE); CRM_CHECK(new_xml != NULL, return FALSE); crm_trace("Subtraction Phase"); for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, result = FALSE); if (root_nodes_seen == 0) { *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE, NULL, NULL); } root_nodes_seen++; } if (root_nodes_seen == 0) { *new_xml = copy_xml(old_xml); } else if (root_nodes_seen > 1) { crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); result = FALSE; } root_nodes_seen = 0; crm_trace("Addition Phase"); if (result) { xmlNode *child_diff = NULL; for (child_diff = pcmk__xml_first_child(added); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, result = FALSE); if (root_nodes_seen == 0) { pcmk__xml_update(NULL, *new_xml, child_diff, true); } root_nodes_seen++; } } if (root_nodes_seen > 1) { crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); result = FALSE; } else if (result && (digest != NULL)) { char *new_digest = NULL; purge_diff_markers(*new_xml); // Purge now so diff is ok new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE, version); if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) { crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest); result = FALSE; pcmk__if_tracing( { save_xml_to_file(old_xml, "diff:original", NULL); save_xml_to_file(diff, "diff:input", NULL); save_xml_to_file(*new_xml, "diff:new", NULL); }, {} ); } else { crm_trace("Digest matched: expected %s, calculated %s", digest, new_digest); } free(new_digest); } else if (result) { purge_diff_markers(*new_xml); // Purge now so diff is ok } return result; } // LCOV_EXCL_STOP // End deprecated API diff --git a/tools/crm_diff.c b/tools/crm_diff.c index 99df7a76f0..350fd3a9e5 100644 --- a/tools/crm_diff.c +++ b/tools/crm_diff.c @@ -1,380 +1,380 @@ /* * Copyright 2005-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \ "or apply such an output as a patch" struct { gboolean apply; gboolean as_cib; gboolean no_version; gboolean raw_1; gboolean raw_2; gboolean use_stdin; char *xml_file_1; char *xml_file_2; } options; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry original_xml_entries[] = { { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1, "XML is contained in the named file", "FILE" }, { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb, "XML is contained in the supplied string", "STRING" }, { NULL } }; static GOptionEntry operation_entries[] = { { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2, "Compare the original XML to the contents of the named file", "FILE" }, { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb, "Compare the original XML with the contents of the supplied string", "STRING" }, { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb, "Patch the original XML with the contents of the named file", "FILE" }, { NULL } }; static GOptionEntry addl_entries[] = { { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib, "Compare/patch the inputs as a CIB (includes versions details)", NULL }, { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin, "", NULL }, { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version, "Generate the difference without versions details", NULL }, { NULL } }; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_2 = TRUE; pcmk__str_update(&options.xml_file_2, optarg); return TRUE; } gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_1 = TRUE; pcmk__str_update(&options.xml_file_1, optarg); return TRUE; } gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.apply = TRUE; pcmk__str_update(&options.xml_file_2, optarg); return TRUE; } static void print_patch(xmlNode *patch) { char *buffer = dump_xml_formatted(patch); printf("%s", buffer); free(buffer); fflush(stdout); } // \return Standard Pacemaker return code static int apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib) { xmlNode *output = copy_xml(input); int rc = xml_apply_patchset(output, patch, as_cib); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc)); free_xml(output); return rc; } if (output != NULL) { const char *version; char *buffer; print_patch(output); version = crm_element_value(output, PCMK_XA_CRM_FEATURE_SET); buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version); crm_trace("Digest: %s", pcmk__s(buffer, "\n")); free(buffer); free_xml(output); } return pcmk_rc_ok; } static void log_patch_cib_versions(xmlNode *patch) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; const char *fmt = NULL; const char *digest = NULL; xml_patch_versions(patch, add, del); fmt = crm_element_value(patch, PCMK_XA_FORMAT); digest = crm_element_value(patch, PCMK__XA_DIGEST); if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) { crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); } } static void strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields) { int format = 1; crm_element_value_int(patch, PCMK_XA_FORMAT, &format); if (format == 2) { - xmlNode *version_xml = find_xml_node(patch, "version", FALSE); + xmlNode *version_xml = find_xml_node(patch, PCMK_XE_VERSION, FALSE); if (version_xml) { free_xml(version_xml); } } else { int i = 0; const char *tags[] = { PCMK__XE_DIFF_REMOVED, PCMK__XE_DIFF_ADDED, }; for (i = 0; i < PCMK__NELEM(tags); i++) { xmlNode *tmp = NULL; int lpc; tmp = find_xml_node(patch, tags[i], FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } tmp = find_xml_node(tmp, PCMK_XE_CIB, FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } } } } } } // \return Standard Pacemaker return code static int generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2, gboolean as_cib, gboolean no_version) { const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; xmlNode *output = NULL; /* If we're ignoring the version, make the version information * identical, so it isn't detected as a change. */ if (no_version) { int lpc; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_copy_xml_element(object_1, object_2, vfields[lpc]); } } xml_track_changes(object_2, NULL, object_2, FALSE); if(as_cib) { xml_calculate_significant_changes(object_1, object_2); } else { xml_calculate_changes(object_1, object_2); } crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target")); output = xml_create_patchset(0, object_1, object_2, NULL, FALSE); pcmk__log_xml_changes(LOG_INFO, object_2); xml_accept_changes(object_2); if (output == NULL) { return pcmk_rc_ok; // No changes } patchset_process_digest(output, object_1, object_2, as_cib); if (as_cib) { log_patch_cib_versions(output); } else if (no_version) { strip_patch_cib_version(output, vfields, PCMK__NELEM(vfields)); } pcmk__log_xml_patchset(LOG_NOTICE, output); print_patch(output); free_xml(output); /* pcmk_rc_error means there's a non-empty diff. * @COMPAT Choose a more descriptive return code, like one that maps to * CRM_EX_DIGEST? */ return pcmk_rc_error; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "Examples:\n\n" "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n" "\t# cibadmin --query > cib-old.xml\n\n" "\t# cibadmin --query > cib-new.xml\n\n" "Calculate and save the difference between the two files:\n\n" "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n" "Apply the patch to the original file:\n\n" "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n" "Apply the patch to the running cluster:\n\n" "\t# cibadmin --patch -x patch.xml\n"; context = pcmk__build_arg_context(args, NULL, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "xml", "Original XML:", "Show original XML options", original_xml_entries); pcmk__add_arg_group(context, "operation", "Operation:", "Show operation options", operation_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { xmlNode *object_1 = NULL; xmlNode *object_2 = NULL; crm_exit_t exit_code = CRM_EX_OK; GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO"); GOptionContext *context = build_arg_context(args); int rc = pcmk_rc_ok; if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_diff", args->verbosity); if (args->version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_diff is converted to use formatted output, this can go. */ pcmk__cli_help('v'); } if (options.apply && options.no_version) { fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n"); } else if (options.as_cib && options.no_version) { fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n"); exit_code = CRM_EX_USAGE; goto done; } if (options.raw_1) { object_1 = string2xml(options.xml_file_1); } else if (options.use_stdin) { fprintf(stderr, "Input first XML fragment:"); object_1 = stdin2xml(); } else if (options.xml_file_1 != NULL) { object_1 = filename2xml(options.xml_file_1); } if (options.raw_2) { object_2 = string2xml(options.xml_file_2); } else if (options.use_stdin) { fprintf(stderr, "Input second XML fragment:"); object_2 = stdin2xml(); } else if (options.xml_file_2 != NULL) { object_2 = filename2xml(options.xml_file_2); } if (object_1 == NULL) { fprintf(stderr, "Could not parse the first XML fragment\n"); exit_code = CRM_EX_DATAERR; goto done; } if (object_2 == NULL) { fprintf(stderr, "Could not parse the second XML fragment\n"); exit_code = CRM_EX_DATAERR; goto done; } if (options.apply) { rc = apply_patch(object_1, object_2, options.as_cib); } else { rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version); } exit_code = pcmk_rc2exitc(rc); done: g_strfreev(processed_args); pcmk__free_arg_context(context); free(options.xml_file_1); free(options.xml_file_2); free_xml(object_1); free_xml(object_2); pcmk__output_and_clear_error(&error, NULL); crm_exit(exit_code); }