Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4639285
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
84 KB
Referenced Files
None
Subscribers
None
View Options
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 <crm_internal.h>
#include <stdio.h>
#include <glib.h>
#include <regex.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <pacemaker-controld.h>
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 <crm_internal.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <crm/crm.h>
#include <glib.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // 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 <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <bzlib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // 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 <crm/common/xml_compat.h>
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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output_internal.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
#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, "<null>\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);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 10, 2:37 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2003909
Default Alt Text
(84 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment