Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/logging_internal.h b/include/crm/common/logging_internal.h
index 97adc8876e..d1c30fb4b9 100644
--- a/include/crm/common/logging_internal.h
+++ b/include/crm/common/logging_internal.h
@@ -1,90 +1,91 @@
/*
* Copyright 2015-2023 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.
*/
#ifdef __cplusplus
extern "C" {
#endif
#ifndef PCMK__LOGGING_INTERNAL_H
# define PCMK__LOGGING_INTERNAL_H
# include <crm/common/logging.h>
/*!
* \internal
* \brief Log a configuration error
*
* \param[in] fmt printf(3)-style format string
* \param[in] ... Arguments for format string
*/
# define pcmk__config_err(fmt...) do { \
crm_config_error = TRUE; \
crm_err(fmt); \
} while (0)
/*!
* \internal
* \brief Log a configuration warning
*
* \param[in] fmt printf(3)-style format string
* \param[in] ... Arguments for format string
*/
# define pcmk__config_warn(fmt...) do { \
crm_config_warning = TRUE; \
crm_warn(fmt); \
} while (0)
/*!
* \internal
* \brief Execute code depending on whether trace logging is enabled
*
* This is similar to \p do_crm_log_unlikely() except instead of logging, it
* selects one of two code blocks to execute.
*
* \param[in] if_action Code block to execute if trace logging is enabled
* \param[in] else_action Code block to execute if trace logging is not enabled
*
* \note Neither \p if_action nor \p else_action can contain a \p break or
* \p continue statement.
*/
# define pcmk__if_tracing(if_action, else_action) do { \
static struct qb_log_callsite *trace_cs = NULL; \
\
if (trace_cs == NULL) { \
trace_cs = qb_log_callsite_get(__func__, __FILE__, \
"if_tracing", LOG_TRACE, \
__LINE__, crm_trace_nonlog); \
} \
- if (crm_is_callsite_active(trace_cs, LOG_TRACE, 0)) { \
+ if (crm_is_callsite_active(trace_cs, LOG_TRACE, \
+ crm_trace_nonlog)) { \
if_action; \
} else { \
else_action; \
} \
} while (0)
/*!
* \internal
* \brief Initialize logging for command line tools
*
* \param[in] name The name of the program
* \param[in] verbosity How verbose to be in logging
*
* \note \p verbosity is not the same as the logging level (LOG_ERR, etc.).
*/
void pcmk__cli_init_logging(const char *name, unsigned int verbosity);
int pcmk__add_logfile(const char *filename);
void pcmk__free_common_logger(void);
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 5daa781e3e..7df7657a93 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,834 +1,837 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2023 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sys/utsname.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/pengine/rules.h>
xmlNode *
cib_get_generation(cib_t * cib)
{
xmlNode *the_cib = NULL;
xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
if (the_cib != NULL) {
copy_in_properties(generation, the_cib);
free_xml(the_cib);
}
return generation;
}
gboolean
cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
{
*epoch = -1;
*updates = -1;
*admin_epoch = -1;
if (cib == NULL) {
return FALSE;
} else {
crm_element_value_int(cib, XML_ATTR_GENERATION, epoch);
crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates);
crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch);
}
return TRUE;
}
gboolean
cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(diff, add, del);
*admin_epoch = add[0];
*epoch = add[1];
*updates = add[2];
*_admin_epoch = del[0];
*_epoch = del[1];
*_updates = del[2];
return TRUE;
}
/*!
* \brief Create XML for a new (empty) CIB
*
* \param[in] cib_epoch What to use as "epoch" CIB property
*
* \return Newly created XML for empty CIB
* \note It is the caller's responsibility to free the result with free_xml().
*/
xmlNode *
createEmptyCib(int cib_epoch)
{
xmlNode *cib_root = NULL, *config = NULL;
cib_root = create_xml_node(NULL, XML_TAG_CIB);
crm_xml_add(cib_root, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
crm_xml_add(cib_root, XML_ATTR_VALIDATION, xml_latest_schema());
crm_xml_add_int(cib_root, XML_ATTR_GENERATION, cib_epoch);
crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0);
crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0);
config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION);
create_xml_node(cib_root, XML_CIB_TAG_STATUS);
create_xml_node(config, XML_CIB_TAG_CRMCONFIG);
create_xml_node(config, XML_CIB_TAG_NODES);
create_xml_node(config, XML_CIB_TAG_RESOURCES);
create_xml_node(config, XML_CIB_TAG_CONSTRAINTS);
#if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
{
xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG);
xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS);
xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR);
crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults");
crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS);
crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS);
crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE,
PCMK__RESOURCE_STICKINESS_DEFAULT);
}
#endif
return cib_root;
}
static bool
cib_acl_enabled(xmlNode *xml, const char *user)
{
bool rc = FALSE;
if(pcmk_acl_required(user)) {
const char *value = NULL;
GHashTable *options = pcmk__strkey_table(free, free);
cib_read_config(options, xml);
value = cib_pref(options, "enable-acl");
rc = crm_is_true(value);
g_hash_table_destroy(options);
}
crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
return rc;
}
int
cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query,
const char *section, xmlNode * req, xmlNode * input,
gboolean manage_counters, gboolean * config_changed,
xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output)
{
int rc = pcmk_ok;
gboolean check_schema = TRUE;
xmlNode *top = NULL;
xmlNode *scratch = NULL;
xmlNode *local_diff = NULL;
const char *new_version = NULL;
- static struct qb_log_callsite *diff_cs = NULL;
const char *user = crm_element_value(req, F_CIB_USER);
bool with_digest = FALSE;
crm_trace("Begin %s%s%s op",
(pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
(is_query? "read-only " : ""), op);
CRM_CHECK(output != NULL, return -ENOMSG);
CRM_CHECK(result_cib != NULL, return -ENOMSG);
CRM_CHECK(config_changed != NULL, return -ENOMSG);
if(output) {
*output = NULL;
}
*result_cib = NULL;
*config_changed = FALSE;
if (fn == NULL) {
return -EINVAL;
}
if (is_query) {
xmlNode *cib_ro = current_cib;
xmlNode *cib_filtered = NULL;
if(cib_acl_enabled(cib_ro, user)) {
if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) {
if (cib_filtered == NULL) {
crm_debug("Pre-filtered the entire cib");
return -EACCES;
}
cib_ro = cib_filtered;
crm_log_xml_trace(cib_ro, "filtered");
}
}
rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
if(output == NULL || *output == NULL) {
/* nothing */
} else if(cib_filtered == *output) {
cib_filtered = NULL; /* Let them have this copy */
} else if(*output == current_cib) {
/* They already know not to free it */
} else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
/* We're about to free the document of which *output is a part */
*output = copy_xml(*output);
} else if((*output)->doc == current_cib->doc) {
/* Give them a copy they can free */
*output = copy_xml(*output);
}
free_xml(cib_filtered);
return rc;
}
if (pcmk_is_set(call_options, cib_zero_copy)) {
/* Conditional on v2 patch style */
scratch = current_cib;
/* Create a shallow copy of current_cib for the version details */
current_cib = create_xml_node(NULL, (const char *)scratch->name);
copy_in_properties(current_cib, scratch);
top = current_cib;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
} else {
scratch = copy_xml(current_cib);
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output);
if(scratch && xml_tracking_changes(scratch) == FALSE) {
crm_trace("Inferring changes after %s op", op);
xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user));
xml_calculate_changes(current_cib, scratch);
}
CRM_CHECK(current_cib != scratch, return -EINVAL);
}
xml_acl_disable(scratch); /* Allow the system to make any additional changes */
if (rc == pcmk_ok && scratch == NULL) {
rc = -EINVAL;
goto done;
} else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
crm_trace("ACL rejected part or all of the proposed changes");
rc = -EACCES;
goto done;
} else if (rc != pcmk_ok) {
goto done;
}
if (scratch) {
new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION);
if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) {
crm_err("Discarding update with feature set '%s' greater than our own '%s'",
new_version, CRM_FEATURE_SET);
rc = -EPROTONOSUPPORT;
goto done;
}
}
if (current_cib) {
int old = 0;
int new = 0;
crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new);
crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
XML_ATTR_GENERATION_ADMIN, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
} else if (old == new) {
crm_element_value_int(scratch, XML_ATTR_GENERATION, &new);
crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
XML_ATTR_GENERATION, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
}
}
}
crm_trace("Massaging CIB contents");
pcmk__strip_xml_text(scratch);
fix_plus_plus_recursive(scratch);
if (pcmk_is_set(call_options, cib_zero_copy)) {
/* At this point, current_cib is just the 'cib' tag and its properties,
*
* The v1 format would barf on this, but we know the v2 patch
* format only needs it for the top-level version fields
*/
local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters);
} else {
static time_t expires = 0;
time_t tm_now = time(NULL);
if (expires < tm_now) {
expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
with_digest = TRUE;
}
local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters);
}
// Create a log output object only if we're going to use it
pcmk__if_tracing(
{
pcmk__output_t *out = NULL;
int rc = pcmk__log_output_new(&out);
CRM_LOG_ASSERT(rc == pcmk_rc_ok);
if (rc == pcmk_rc_ok) {
pcmk__output_set_log_level(out, LOG_TRACE);
pcmk__xml_show_changes(out, scratch);
out->finish(out, CRM_EX_OK, true, NULL);
pcmk__output_free(out);
}
},
{}
);
xml_accept_changes(scratch);
- if (diff_cs == NULL) {
- diff_cs = qb_log_callsite_get(__PRETTY_FUNCTION__, __FILE__, "diff-validation", LOG_DEBUG, __LINE__, crm_trace_nonlog);
- }
-
if(local_diff) {
patchset_process_digest(local_diff, current_cib, scratch, with_digest);
pcmk__xml_log_patchset(LOG_INFO, local_diff);
crm_log_xml_trace(local_diff, "raw patch");
}
- if (!pcmk_is_set(call_options, cib_zero_copy) // Original to compare against doesn't exist
- && local_diff
- && crm_is_callsite_active(diff_cs, LOG_TRACE, 0)) {
-
- /* Validate the calculated patch set */
- int test_rc, format = 1;
- xmlNode * c = copy_xml(current_cib);
-
- crm_element_value_int(local_diff, "format", &format);
- test_rc = xml_apply_patchset(c, local_diff, manage_counters);
-
- if(test_rc != pcmk_ok) {
- save_xml_to_file(c, "PatchApply:calculated", NULL);
- save_xml_to_file(current_cib, "PatchApply:input", NULL);
- save_xml_to_file(scratch, "PatchApply:actual", NULL);
- save_xml_to_file(local_diff, "PatchApply:diff", NULL);
- crm_err("v%d patchset error, patch failed to apply: %s (%d)", format, pcmk_strerror(test_rc), test_rc);
- }
- free_xml(c);
+ if (!pcmk_is_set(call_options, cib_zero_copy) && (local_diff != NULL)) {
+ // Original to compare against doesn't exist
+ pcmk__if_tracing(
+ {
+ // Validate the calculated patch set
+ int test_rc = pcmk_ok;
+ int format = 1;
+ xmlNode *cib_copy = copy_xml(current_cib);
+
+ crm_element_value_int(local_diff, "format", &format);
+ test_rc = xml_apply_patchset(cib_copy, local_diff,
+ manage_counters);
+
+ if (test_rc != pcmk_ok) {
+ save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
+ save_xml_to_file(current_cib, "PatchApply:input", NULL);
+ save_xml_to_file(scratch, "PatchApply:actual", NULL);
+ save_xml_to_file(local_diff, "PatchApply:diff", NULL);
+ crm_err("v%d patchset error, patch failed to apply: %s "
+ "(%d)",
+ format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
+ test_rc);
+ }
+ free_xml(cib_copy);
+ },
+ {}
+ );
}
if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
/* Throttle the amount of costly validation we perform due to status updates
* a) we don't really care whats in the status section
* b) we don't validate any of its contents at the moment anyway
*/
check_schema = FALSE;
}
/* === scratch must not be modified after this point ===
* Exceptions, anything in:
static filter_t filter[] = {
{ 0, XML_ATTR_ORIGIN },
{ 0, XML_CIB_ATTR_WRITTEN },
{ 0, XML_ATTR_UPDATE_ORIG },
{ 0, XML_ATTR_UPDATE_CLIENT },
{ 0, XML_ATTR_UPDATE_USER },
};
*/
if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION);
pcmk__xe_add_last_written(scratch);
if (schema) {
static int minimum_schema = 0;
int current_schema = get_schema_version(schema);
if (minimum_schema == 0) {
minimum_schema = get_schema_version("pacemaker-1.2");
}
/* Does the CIB support the "update-*" attributes... */
if (current_schema >= minimum_schema) {
const char *origin = crm_element_value(req, F_ORIG);
CRM_LOG_ASSERT(origin != NULL);
crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin);
crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT,
crm_element_value(req, F_CIB_CLIENTNAME));
crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER));
}
}
}
crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) {
const char *current_schema = crm_element_value(scratch,
XML_ATTR_VALIDATION);
crm_warn("Updated CIB does not validate against %s schema",
pcmk__s(current_schema, "unspecified"));
rc = -pcmk_err_schema_validation;
}
done:
*result_cib = scratch;
if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) {
if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) {
if (*result_cib == NULL) {
crm_debug("Pre-filtered the entire cib result");
}
free_xml(scratch);
}
}
if(diff) {
*diff = local_diff;
} else {
free_xml(local_diff);
}
free_xml(top);
crm_trace("Done");
return rc;
}
xmlNode *
cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section,
xmlNode * data, int call_options, const char *user_name)
{
xmlNode *op_msg = create_xml_node(NULL, "cib_command");
CRM_CHECK(op_msg != NULL, return NULL);
CRM_CHECK(token != NULL, return NULL);
crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command");
crm_xml_add(op_msg, F_TYPE, T_CIB);
crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token);
crm_xml_add(op_msg, F_CIB_OPERATION, op);
crm_xml_add(op_msg, F_CIB_HOST, host);
crm_xml_add(op_msg, F_CIB_SECTION, section);
crm_xml_add_int(op_msg, F_CIB_CALLID, call_id);
if (user_name) {
crm_xml_add(op_msg, F_CIB_USER, user_name);
}
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options);
if (data != NULL) {
add_message_xml(op_msg, F_CIB_CALLDATA, data);
}
if (call_options & cib_inhibit_bcast) {
CRM_CHECK((call_options & cib_scope_local), return NULL);
}
return op_msg;
}
void
cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
{
xmlNode *output = NULL;
cib_callback_client_t *blob = NULL;
if (msg != NULL) {
crm_element_value_int(msg, F_CIB_RC, &rc);
crm_element_value_int(msg, F_CIB_CALLID, &call_id);
output = get_message_xml(msg, F_CIB_CALLDATA);
}
blob = cib__lookup_id(call_id);
if (blob == NULL) {
crm_trace("No callback found for call %d", call_id);
}
if (cib == NULL) {
crm_debug("No cib object supplied");
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
crm_trace("Invoking callback %s for call %d",
pcmk__s(blob->id, "without ID"), call_id);
blob->callback(msg, call_id, rc, output, blob->user_data);
} else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
crm_warn("CIB command failed: %s", pcmk_strerror(rc));
crm_log_xml_debug(msg, "Failed CIB Update");
}
/* This may free user_data, so do it after the callback */
if (blob) {
remove_cib_op_callback(call_id, FALSE);
}
if (cib && cib->op_callback != NULL) {
crm_trace("Invoking global callback for call %d", call_id);
cib->op_callback(msg, call_id, rc, output);
}
crm_trace("OP callback activated for %d", call_id);
}
void
cib_native_notify(gpointer data, gpointer user_data)
{
xmlNode *msg = user_data;
cib_notify_client_t *entry = data;
const char *event = NULL;
if (msg == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(msg, F_SUBTYPE);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->callback == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->callback(event, msg);
crm_trace("Callback invoked...");
}
static pcmk__cluster_option_t cib_opts[] = {
/* name, legacy name, type, allowed values,
* default value, validator,
* short description,
* long description
*/
{
"enable-acl", NULL, "boolean", NULL,
"false", pcmk__valid_boolean,
N_("Enable Access Control Lists (ACLs) for the CIB"),
NULL
},
{
"cluster-ipc-limit", NULL, "integer", NULL,
"500", pcmk__valid_positive_number,
N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
N_("Raise this if log has \"Evicting client\" messages for cluster daemon"
" PIDs (a good value is the number of resources in the cluster"
" multiplied by the number of nodes).")
},
};
void
cib_metadata(void)
{
const char *desc_short = "Cluster Information Base manager options";
const char *desc_long = "Cluster options used by Pacemaker's Cluster "
"Information Base manager";
gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short,
desc_long, cib_opts,
PCMK__NELEM(cib_opts));
printf("%s", s);
g_free(s);
}
void
verify_cib_options(GHashTable * options)
{
pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts));
}
const char *
cib_pref(GHashTable * options, const char *name)
{
return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts),
name);
}
gboolean
cib_read_config(GHashTable * options, xmlNode * current_cib)
{
xmlNode *config = NULL;
crm_time_t *now = NULL;
if (options == NULL || current_cib == NULL) {
return FALSE;
}
now = crm_time_new(NULL);
g_hash_table_remove_all(options);
config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG);
if (config) {
pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL,
options, CIB_OPTIONS_FIRST, TRUE, now, NULL);
}
verify_cib_options(options);
crm_time_free(now);
return TRUE;
}
/* v2 and v2 patch formats */
#define XPATH_CONFIG_CHANGE \
"//" XML_CIB_TAG_CRMCONFIG " | " \
"//" XML_DIFF_CHANGE "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]"
gboolean
cib_internal_config_changed(xmlNode *diff)
{
gboolean changed = FALSE;
if (diff) {
xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE);
if (numXpathResults(xpathObj) > 0) {
changed = TRUE;
}
freeXpathObject(xpathObj);
}
return changed;
}
int
cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name)
{
int (*delegate) (cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name) =
cib->delegate_fn;
if(user_name == NULL) {
user_name = getenv("CIB_user");
}
return delegate(cib, op, host, section, data, output_data, call_options, user_name);
}
/*!
* \brief Apply a CIB update patch to a given CIB
*
* \param[in] event CIB update patch
* \param[in] input CIB to patch
* \param[out] output Resulting CIB after patch
* \param[in] level Log the patch at this log level (unless LOG_CRIT)
*
* \return Legacy Pacemaker return code
* \note sbd calls this function
*/
int
cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
int level)
{
int rc = pcmk_err_generic;
xmlNode *diff = NULL;
CRM_ASSERT(event);
CRM_ASSERT(input);
CRM_ASSERT(output);
crm_element_value_int(event, F_CIB_RC, &rc);
diff = get_message_xml(event, F_CIB_UPDATE_RESULT);
if (rc < pcmk_ok || diff == NULL) {
return rc;
}
if (level > LOG_CRIT) {
pcmk__xml_log_patchset(level, diff);
}
if (input != NULL) {
rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
NULL);
if (rc != pcmk_ok) {
crm_debug("Update didn't apply: %s (%d) %p",
pcmk_strerror(rc), rc, *output);
if (rc == -pcmk_err_old_data) {
crm_trace("Masking error, we already have the supplied update");
return pcmk_ok;
}
free_xml(*output);
*output = NULL;
return rc;
}
}
return rc;
}
#define log_signon_query_err(out, fmt, args...) do { \
if (out != NULL) { \
out->err(out, fmt, ##args); \
} else { \
crm_err(fmt, ##args); \
} \
} while (0)
int
cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
{
int rc = pcmk_rc_ok;
cib_t *cib_conn = NULL;
CRM_ASSERT(cib_object != NULL);
if (cib == NULL) {
cib_conn = cib_new();
} else {
if (*cib == NULL) {
*cib = cib_new();
}
cib_conn = *cib;
}
if (cib_conn == NULL) {
return ENOMEM;
}
if (cib_conn->state == cib_disconnected) {
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
}
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "Could not connect to the CIB: %s",
pcmk_rc_str(rc));
goto done;
}
if (out != NULL) {
out->transient(out, "Querying CIB...");
}
rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
cib_scope_local|cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
}
done:
if (cib == NULL) {
cib__clean_up_connection(&cib_conn);
}
if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
return pcmk_rc_no_input;
}
return rc;
}
int
cib__clean_up_connection(cib_t **cib)
{
int rc;
if (*cib == NULL) {
return pcmk_rc_ok;
}
rc = (*cib)->cmds->signoff(*cib);
cib_delete(*cib);
*cib = NULL;
return pcmk_legacy2rc(rc);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/cib/util_compat.h>
const char *
get_object_path(const char *object_type)
{
return pcmk_cib_xpath_for(object_type);
}
const char *
get_object_parent(const char *object_type)
{
return pcmk_cib_parent_name_for(object_type);
}
xmlNode *
get_object_root(const char *object_type, xmlNode *the_root)
{
return pcmk_find_cib_element(the_root, object_type);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/digest.c b/lib/common/digest.c
index d764b6aee1..3bf04bfe49 100644
--- a/lib/common/digest.c
+++ b/lib/common/digest.c
@@ -1,281 +1,278 @@
/*
* Copyright 2015-2022 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 <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <md5.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include "crmcommon_private.h"
#define BEST_EFFORT_STATUS 0
/*!
* \internal
* \brief Dump XML in a format used with v1 digests
*
* \param[in] xml Root of XML to dump
*
* \return Newly allocated buffer containing dumped XML
*/
static GString *
dump_xml_for_digest(xmlNodePtr xml)
{
GString *buffer = g_string_sized_new(1024);
/* for compatibility with the old result which is used for v1 digests */
g_string_append_c(buffer, ' ');
pcmk__xml2text(xml, 0, buffer, 0);
g_string_append_c(buffer, '\n');
return buffer;
}
/*!
* \brief Calculate and return v1 digest of XML tree
*
* \param[in] input Root of XML to digest
* \param[in] sort Whether to sort the XML before calculating digest
* \param[in] ignored Not used
*
* \return Newly allocated string containing digest
* \note Example return value: "c048eae664dba840e1d2060f00299e9d"
*/
static char *
calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored)
{
char *digest = NULL;
GString *buffer = NULL;
xmlNode *copy = NULL;
if (sort) {
crm_trace("Sorting xml...");
copy = sorted_xml(input, NULL, TRUE);
crm_trace("Done");
input = copy;
}
buffer = dump_xml_for_digest(input);
CRM_CHECK(buffer->len > 0, free_xml(copy);
g_string_free(buffer, TRUE);
return NULL);
digest = crm_md5sum((const char *) buffer->str);
crm_log_xml_trace(input, "digest:source");
g_string_free(buffer, TRUE);
free_xml(copy);
return digest;
}
/*!
* \brief Calculate and return v2 digest of XML tree
*
* \param[in] source Root of XML to digest
* \param[in] do_filter Whether to filter certain XML attributes
*
* \return Newly allocated string containing digest
*/
static char *
calculate_xml_digest_v2(xmlNode *source, gboolean do_filter)
{
char *digest = NULL;
GString *buffer = g_string_sized_new(1024);
- static struct qb_log_callsite *digest_cs = NULL;
-
crm_trace("Begin digest %s", do_filter?"filtered":"");
pcmk__xml2text(source, (do_filter? pcmk__xml_fmt_filtered : 0), buffer, 0);
CRM_ASSERT(buffer != NULL);
digest = crm_md5sum((const char *) buffer->str);
- if (digest_cs == NULL) {
- digest_cs = qb_log_callsite_get(__func__, __FILE__, "cib-digest", LOG_TRACE, __LINE__,
- crm_trace_nonlog);
- }
- if (digest_cs && digest_cs->targets) {
- char *trace_file = crm_strdup_printf("%s/digest-%s",
- pcmk__get_tmpdir(), digest);
-
- crm_trace("Saving %s.%s.%s to %s",
- crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
- crm_element_value(source, XML_ATTR_GENERATION),
- crm_element_value(source, XML_ATTR_NUMUPDATES), trace_file);
- save_xml_to_file(source, "digest input", trace_file);
- free(trace_file);
- }
+ pcmk__if_tracing(
+ {
+ char *trace_file = crm_strdup_printf("%s/digest-%s",
+ pcmk__get_tmpdir(), digest);
+ crm_trace("Saving %s.%s.%s to %s",
+ crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(source, XML_ATTR_GENERATION),
+ crm_element_value(source, XML_ATTR_NUMUPDATES),
+ trace_file);
+ save_xml_to_file(source, "digest input", trace_file);
+ free(trace_file);
+ },
+ {}
+ );
g_string_free(buffer, TRUE);
crm_trace("End digest");
return digest;
}
/*!
* \brief Calculate and return digest of XML tree, suitable for storing on disk
*
* \param[in] input Root of XML to digest
*
* \return Newly allocated string containing digest
*/
char *
calculate_on_disk_digest(xmlNode *input)
{
/* Always use the v1 format for on-disk digests
* a) it's a compatibility nightmare
* b) we only use this once at startup, all other
* invocations are in a separate child process
*/
return calculate_xml_digest_v1(input, FALSE, FALSE);
}
/*!
* \brief Calculate and return digest of XML operation
*
* \param[in] input Root of XML to digest
* \param[in] version Unused
*
* \return Newly allocated string containing digest
*/
char *
calculate_operation_digest(xmlNode *input, const char *version)
{
/* We still need the sorting for operation digests */
return calculate_xml_digest_v1(input, TRUE, FALSE);
}
/*!
* \brief Calculate and return digest of XML tree
*
* \param[in] input Root of XML to digest
* \param[in] sort Whether to sort XML before calculating digest
* \param[in] do_filter Whether to filter certain XML attributes
* \param[in] version CRM feature set version (used to select v1/v2 digest)
*
* \return Newly allocated string containing digest
*/
char *
calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
gboolean do_filter, const char *version)
{
/*
* @COMPAT digests (on-disk or in diffs/patchsets) created <1.1.4;
* removing this affects even full-restart upgrades from old versions
*
* The sorting associated with v1 digest creation accounted for 23% of
* the CIB manager's CPU usage on the server. v2 drops this.
*
* The filtering accounts for an additional 2.5% and we may want to
* remove it in future.
*
* v2 also uses the xmlBuffer contents directly to avoid additional copying
*/
if (version == NULL || compare_version("3.0.5", version) > 0) {
crm_trace("Using v1 digest algorithm for %s",
pcmk__s(version, "unknown feature set"));
return calculate_xml_digest_v1(input, sort, do_filter);
}
crm_trace("Using v2 digest algorithm for %s",
pcmk__s(version, "unknown feature set"));
return calculate_xml_digest_v2(input, do_filter);
}
/*!
* \internal
* \brief Check whether calculated digest of given XML matches expected digest
*
* \param[in] input Root of XML tree to digest
* \param[in] expected Expected digest in on-disk format
*
* \return true if digests match, false on mismatch or error
*/
bool
pcmk__verify_digest(xmlNode *input, const char *expected)
{
char *calculated = NULL;
bool passed;
if (input != NULL) {
calculated = calculate_on_disk_digest(input);
if (calculated == NULL) {
crm_perror(LOG_ERR, "Could not calculate digest for comparison");
return false;
}
}
passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
if (passed) {
crm_trace("Digest comparison passed: %s", calculated);
} else {
crm_err("Digest comparison failed: expected %s, calculated %s",
expected, calculated);
}
free(calculated);
return passed;
}
/*!
* \internal
* \brief Check whether an XML attribute should be excluded from CIB digests
*
* \param[in] name XML attribute name
*
* \return true if XML attribute should be excluded from CIB digest calculation
*/
bool
pcmk__xa_filterable(const char *name)
{
static const char *filter[] = {
XML_ATTR_ORIGIN,
XML_CIB_ATTR_WRITTEN,
XML_ATTR_UPDATE_ORIG,
XML_ATTR_UPDATE_CLIENT,
XML_ATTR_UPDATE_USER,
};
for (int i = 0; i < PCMK__NELEM(filter); i++) {
if (strcmp(name, filter[i]) == 0) {
return true;
}
}
return false;
}
char *
crm_md5sum(const char *buffer)
{
int lpc = 0, len = 0;
char *digest = NULL;
unsigned char raw_digest[MD5_DIGEST_SIZE];
if (buffer == NULL) {
buffer = "";
}
len = strlen(buffer);
crm_trace("Beginning digest of %d bytes", len);
digest = malloc(2 * MD5_DIGEST_SIZE + 1);
if (digest) {
md5_buffer(buffer, len, raw_digest);
for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
}
digest[(2 * MD5_DIGEST_SIZE)] = 0;
crm_trace("Digest %s.", digest);
} else {
crm_err("Could not create digest");
}
return digest;
}
diff --git a/lib/common/logging.c b/lib/common/logging.c
index 227991aba6..6b57ecd14e 100644
--- a/lib/common/logging.c
+++ b/lib/common/logging.c
@@ -1,1168 +1,1167 @@
/*
* Copyright 2004-2023 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 <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <libgen.h>
#include <signal.h>
#include <bzlib.h>
#include <qb/qbdefs.h>
#include <crm/crm.h>
#include <crm/common/mainloop.h>
// Use high-resolution (millisecond) timestamps if libqb supports them
#ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
#define TIMESTAMP_FORMAT_SPEC "%%T"
typedef struct timespec *log_time_t;
#else
#define TIMESTAMP_FORMAT_SPEC "%%t"
typedef time_t log_time_t;
#endif
unsigned int crm_log_level = LOG_INFO;
unsigned int crm_trace_nonlog = 0;
bool pcmk__is_daemon = false;
char *pcmk__our_nodename = NULL;
static unsigned int crm_log_priority = LOG_NOTICE;
static GLogFunc glib_log_default = NULL;
static pcmk__output_t *logger_out = NULL;
static gboolean crm_tracing_enabled(void);
static void
crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message,
gpointer user_data)
{
int log_level = LOG_WARNING;
GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK);
static struct qb_log_callsite *glib_cs = NULL;
if (glib_cs == NULL) {
glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
LOG_DEBUG, __LINE__, crm_trace_nonlog);
}
-
switch (msg_level) {
case G_LOG_LEVEL_CRITICAL:
log_level = LOG_CRIT;
- if (crm_is_callsite_active(glib_cs, LOG_DEBUG, 0) == FALSE) {
+ if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) {
/* log and record how we got here */
crm_abort(__FILE__, __func__, __LINE__, message, TRUE, TRUE);
}
break;
case G_LOG_LEVEL_ERROR:
log_level = LOG_ERR;
break;
case G_LOG_LEVEL_MESSAGE:
log_level = LOG_NOTICE;
break;
case G_LOG_LEVEL_INFO:
log_level = LOG_INFO;
break;
case G_LOG_LEVEL_DEBUG:
log_level = LOG_DEBUG;
break;
case G_LOG_LEVEL_WARNING:
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_MASK:
log_level = LOG_WARNING;
break;
}
do_crm_log(log_level, "%s: %s", log_domain, message);
}
#ifndef NAME_MAX
# define NAME_MAX 256
#endif
/*!
* \internal
* \brief Write out a blackbox (enabling blackboxes if needed)
*
* \param[in] nsig Signal number that was received
*
* \note This is a true signal handler, and so must be async-safe.
*/
static void
crm_trigger_blackbox(int nsig)
{
if(nsig == SIGTRAP) {
/* Turn it on if it wasn't already */
crm_enable_blackbox(nsig);
}
crm_write_blackbox(nsig, NULL);
}
void
crm_log_deinit(void)
{
if (glib_log_default != NULL) {
g_log_set_default_handler(glib_log_default, NULL);
}
}
#define FMT_MAX 256
/*!
* \internal
* \brief Set the log format string based on the passed-in method
*
* \param[in] method The detail level of the log output
* \param[in] daemon The daemon ID included in error messages
* \param[in] use_pid Cached result of getpid() call, for efficiency
* \param[in] use_nodename Cached result of uname() call, for efficiency
*
*/
/* XXX __attribute__((nonnull)) for use_nodename parameter */
static void
set_format_string(int method, const char *daemon, pid_t use_pid,
const char *use_nodename)
{
if (method == QB_LOG_SYSLOG) {
// The system log gets a simplified, user-friendly format
crm_extended_logging(method, QB_FALSE);
qb_log_format_set(method, "%g %p: %b");
} else {
// Everything else gets more detail, for advanced troubleshooting
int offset = 0;
char fmt[FMT_MAX];
if (method > QB_LOG_STDERR) {
// If logging to file, prefix with timestamp, node name, daemon ID
offset += snprintf(fmt + offset, FMT_MAX - offset,
TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ",
use_nodename, daemon, (unsigned long) use_pid);
}
// Add function name (in parentheses)
offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n");
if (crm_tracing_enabled()) {
// When tracing, add file and line number
offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l");
}
offset += snprintf(fmt + offset, FMT_MAX - offset, ")");
// Add tag (if any), severity, and actual message
offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b");
CRM_LOG_ASSERT(offset > 0);
qb_log_format_set(method, fmt);
}
}
#define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
static bool
logfile_disabled(const char *filename)
{
return pcmk__str_eq(filename, PCMK__VALUE_NONE, pcmk__str_casei)
|| pcmk__str_eq(filename, "/dev/null", pcmk__str_none);
}
/*!
* \internal
* \brief Fix log file ownership if group is wrong or doesn't have access
*
* \param[in] filename Log file name (for logging only)
* \param[in] logfd Log file descriptor
*
* \return Standard Pacemaker return code
*/
static int
chown_logfile(const char *filename, int logfd)
{
uid_t pcmk_uid = 0;
gid_t pcmk_gid = 0;
struct stat st;
int rc;
// Get the log file's current ownership and permissions
if (fstat(logfd, &st) < 0) {
return errno;
}
// Any other errors don't prevent file from being used as log
rc = pcmk_daemon_user(&pcmk_uid, &pcmk_gid);
if (rc != pcmk_ok) {
rc = pcmk_legacy2rc(rc);
crm_warn("Not changing '%s' ownership because user information "
"unavailable: %s", filename, pcmk_rc_str(rc));
return pcmk_rc_ok;
}
if ((st.st_gid == pcmk_gid)
&& ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
return pcmk_rc_ok;
}
if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
crm_warn("Couldn't change '%s' ownership to user %s gid %d: %s",
filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
}
return pcmk_rc_ok;
}
// Reset log file permissions (using environment variable if set)
static void
chmod_logfile(const char *filename, int logfd)
{
const char *modestr = getenv("PCMK_logfile_mode");
mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
if (modestr != NULL) {
long filemode_l = strtol(modestr, NULL, 8);
if ((filemode_l != LONG_MIN) && (filemode_l != LONG_MAX)) {
filemode = (mode_t) filemode_l;
}
}
if ((filemode != 0) && (fchmod(logfd, filemode) < 0)) {
crm_warn("Couldn't change '%s' mode to %04o: %s",
filename, filemode, strerror(errno));
}
}
// If we're root, correct a log file's permissions if needed
static int
set_logfile_permissions(const char *filename, FILE *logfile)
{
if (geteuid() == 0) {
int logfd = fileno(logfile);
int rc = chown_logfile(filename, logfd);
if (rc != pcmk_rc_ok) {
return rc;
}
chmod_logfile(filename, logfd);
}
return pcmk_rc_ok;
}
// Enable libqb logging to a new log file
static void
enable_logfile(int fd)
{
qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE);
#if 0
qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); // Turn on synchronous writes
#endif
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
// Longer than default, for logging long XML lines
qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800);
#endif
crm_update_callsites();
}
static inline void
disable_logfile(int fd)
{
qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_FALSE);
}
static void
setenv_logfile(const char *filename)
{
// Some resource agents will log only if environment variable is set
if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
pcmk__set_env_option(PCMK__ENV_LOGFILE, filename);
}
}
/*!
* \brief Add a file to be used as a Pacemaker detail log
*
* \param[in] filename Name of log file to use
*
* \return Standard Pacemaker return code
*/
int
pcmk__add_logfile(const char *filename)
{
/* No log messages from this function will be logged to the new log!
* If another target such as syslog has already been added, the messages
* should show up there.
*/
int fd = 0;
int rc = pcmk_rc_ok;
FILE *logfile = NULL;
bool is_default = false;
static int default_fd = -1;
static bool have_logfile = false;
// Use default if caller didn't specify (and we don't already have one)
if (filename == NULL) {
if (have_logfile) {
return pcmk_rc_ok;
}
filename = DEFAULT_LOG_FILE;
}
// If the user doesn't want logging, we're done
if (logfile_disabled(filename)) {
return pcmk_rc_ok;
}
// If the caller wants the default and we already have it, we're done
is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
if (is_default && (default_fd >= 0)) {
return pcmk_rc_ok;
}
// Check whether we have write access to the file
logfile = fopen(filename, "a");
if (logfile == NULL) {
rc = errno;
crm_warn("Logging to '%s' is disabled: %s " CRM_XS " uid=%u gid=%u",
filename, strerror(rc), geteuid(), getegid());
return rc;
}
rc = set_logfile_permissions(filename, logfile);
if (rc != pcmk_rc_ok) {
crm_warn("Logging to '%s' is disabled: %s " CRM_XS " permissions",
filename, strerror(rc));
fclose(logfile);
return rc;
}
// Close and reopen as libqb logging target
fclose(logfile);
fd = qb_log_file_open(filename);
if (fd < 0) {
crm_warn("Logging to '%s' is disabled: %s " CRM_XS " qb_log_file_open",
filename, strerror(-fd));
return -fd; // == +errno
}
if (is_default) {
default_fd = fd;
setenv_logfile(filename);
} else if (default_fd >= 0) {
crm_notice("Switching logging to %s", filename);
disable_logfile(default_fd);
}
crm_notice("Additional logging available in %s", filename);
enable_logfile(fd);
have_logfile = true;
return pcmk_rc_ok;
}
static int blackbox_trigger = 0;
static volatile char *blackbox_file_prefix = NULL;
static void
blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
const char *msg)
{
if(cs && cs->priority < LOG_ERR) {
crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
} else {
crm_write_blackbox(0, cs);
}
}
static void
crm_control_blackbox(int nsig, bool enable)
{
int lpc = 0;
if (blackbox_file_prefix == NULL) {
pid_t pid = getpid();
blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu",
CRM_BLACKBOX_DIR,
crm_system_name,
(unsigned long) pid);
}
if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); /* Setting the size seems to disable it */
/* Enable synchronous logging */
for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
}
crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
/* Save to disk on abnormal termination */
crm_signal_handler(SIGSEGV, crm_trigger_blackbox);
crm_signal_handler(SIGABRT, crm_trigger_blackbox);
crm_signal_handler(SIGILL, crm_trigger_blackbox);
crm_signal_handler(SIGBUS, crm_trigger_blackbox);
crm_signal_handler(SIGFPE, crm_trigger_blackbox);
crm_update_callsites();
blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
crm_trace("Trigger: %d is %d %d", blackbox_trigger,
qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED);
crm_update_callsites();
} else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
/* Disable synchronous logging again when the blackbox is disabled */
for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
}
}
}
void
crm_enable_blackbox(int nsig)
{
crm_control_blackbox(nsig, TRUE);
}
void
crm_disable_blackbox(int nsig)
{
crm_control_blackbox(nsig, FALSE);
}
/*!
* \internal
* \brief Write out a blackbox, if blackboxes are enabled
*
* \param[in] nsig Signal that was received
* \param[in] cs libqb callsite
*
* \note This may be called via a true signal handler and so must be async-safe.
* @TODO actually make this async-safe
*/
void
crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
{
static volatile int counter = 1;
static volatile time_t last = 0;
char buffer[NAME_MAX];
time_t now = time(NULL);
if (blackbox_file_prefix == NULL) {
return;
}
switch (nsig) {
case 0:
case SIGTRAP:
/* The graceful case - such as assertion failure or user request */
if (nsig == 0 && now == last) {
/* Prevent over-dumping */
return;
}
snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++);
if (nsig == SIGTRAP) {
crm_notice("Blackbox dump requested, please see %s for contents", buffer);
} else if (cs) {
syslog(LOG_NOTICE,
"Problem detected at %s:%d (%s), please see %s for additional details",
cs->function, cs->lineno, cs->filename, buffer);
} else {
crm_notice("Problem detected, please see %s for additional details", buffer);
}
last = now;
qb_log_blackbox_write_to_file(buffer);
/* Flush the existing contents
* A size change would also work
*/
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
break;
default:
/* Do as little as possible, just try to get what we have out
* We logged the filename when the blackbox was enabled
*/
crm_signal_handler(nsig, SIG_DFL);
qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
raise(nsig);
break;
}
}
static const char *
crm_quark_to_string(uint32_t tag)
{
const char *text = g_quark_to_string(tag);
if (text) {
return text;
}
return "";
}
static void
crm_log_filter_source(int source, const char *trace_files, const char *trace_fns,
const char *trace_fmts, const char *trace_tags, const char *trace_blackbox,
struct qb_log_callsite *cs)
{
if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
return;
} else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) {
/* Blackbox gets everything if enabled */
qb_bit_set(cs->targets, source);
} else if (source == blackbox_trigger && blackbox_trigger > 0) {
/* Should this log message result in the blackbox being dumped */
if (cs->priority <= LOG_ERR) {
qb_bit_set(cs->targets, source);
} else if (trace_blackbox) {
char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno);
if (strstr(trace_blackbox, key) != NULL) {
qb_bit_set(cs->targets, source);
}
free(key);
}
} else if (source == QB_LOG_SYSLOG) { /* No tracing to syslog */
if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) {
qb_bit_set(cs->targets, source);
}
/* Log file tracing options... */
} else if (cs->priority <= crm_log_level) {
qb_bit_set(cs->targets, source);
} else if (trace_files && strstr(trace_files, cs->filename) != NULL) {
qb_bit_set(cs->targets, source);
} else if (trace_fns && strstr(trace_fns, cs->function) != NULL) {
qb_bit_set(cs->targets, source);
} else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) {
qb_bit_set(cs->targets, source);
} else if (trace_tags
&& cs->tags != 0
&& cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) {
qb_bit_set(cs->targets, source);
}
}
static void
crm_log_filter(struct qb_log_callsite *cs)
{
int lpc = 0;
static int need_init = 1;
static const char *trace_fns = NULL;
static const char *trace_tags = NULL;
static const char *trace_fmts = NULL;
static const char *trace_files = NULL;
static const char *trace_blackbox = NULL;
if (need_init) {
need_init = 0;
trace_fns = getenv("PCMK_trace_functions");
trace_fmts = getenv("PCMK_trace_formats");
trace_tags = getenv("PCMK_trace_tags");
trace_files = getenv("PCMK_trace_files");
trace_blackbox = getenv("PCMK_trace_blackbox");
if (trace_tags != NULL) {
uint32_t tag;
char token[500];
const char *offset = NULL;
const char *next = trace_tags;
do {
offset = next;
next = strchrnul(offset, ',');
snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset);
tag = g_quark_from_string(token);
crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags);
if (next[0] != 0) {
next++;
}
} while (next != NULL && next[0] != 0);
}
}
cs->targets = 0; /* Reset then find targets to enable */
for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox,
cs);
}
}
gboolean
crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
{
gboolean refilter = FALSE;
if (cs == NULL) {
return FALSE;
}
if (cs->priority != level) {
cs->priority = level;
refilter = TRUE;
}
if (cs->tags != tags) {
cs->tags = tags;
refilter = TRUE;
}
if (refilter) {
crm_log_filter(cs);
}
if (cs->targets == 0) {
return FALSE;
}
return TRUE;
}
void
crm_update_callsites(void)
{
static gboolean log = TRUE;
if (log) {
log = FALSE;
crm_debug
("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s",
crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"),
getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags"));
}
qb_log_filter_fn_set(crm_log_filter);
}
static gboolean
crm_tracing_enabled(void)
{
if (crm_log_level == LOG_TRACE) {
return TRUE;
} else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions")
|| getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) {
return TRUE;
}
return FALSE;
}
static int
crm_priority2int(const char *name)
{
struct syslog_names {
const char *name;
int priority;
};
static struct syslog_names p_names[] = {
{"emerg", LOG_EMERG},
{"alert", LOG_ALERT},
{"crit", LOG_CRIT},
{"error", LOG_ERR},
{"warning", LOG_WARNING},
{"notice", LOG_NOTICE},
{"info", LOG_INFO},
{"debug", LOG_DEBUG},
{NULL, -1}
};
int lpc;
for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) {
if (pcmk__str_eq(p_names[lpc].name, name, pcmk__str_none)) {
return p_names[lpc].priority;
}
}
return crm_log_priority;
}
/*!
* \internal
* \brief Set the identifier for the current process
*
* If the identifier crm_system_name is not already set, then it is set as follows:
* - it is passed to the function via the "entity" parameter, or
* - it is derived from the executable name
*
* The identifier can be used in logs, IPC, and more.
*
* This method also sets the PCMK_service environment variable.
*
* \param[in] entity If not NULL, will be assigned to the identifier
* \param[in] argc The number of command line parameters
* \param[in] argv The command line parameter values
*/
static void
set_identity(const char *entity, int argc, char *const *argv)
{
if (crm_system_name != NULL) {
return; // Already set, don't overwrite
}
if (entity != NULL) {
crm_system_name = strdup(entity);
} else if ((argc > 0) && (argv != NULL)) {
char *mutable = strdup(argv[0]);
char *modified = basename(mutable);
if (strstr(modified, "lt-") == modified) {
modified += 3;
}
crm_system_name = strdup(modified);
free(mutable);
} else {
crm_system_name = strdup("Unknown");
}
CRM_ASSERT(crm_system_name != NULL);
setenv("PCMK_service", crm_system_name, 1);
}
void
crm_log_preinit(const char *entity, int argc, char *const *argv)
{
/* Configure libqb logging with nothing turned on */
struct utsname res;
int lpc = 0;
int32_t qb_facility = 0;
pid_t pid = getpid();
const char *nodename = "localhost";
static bool have_logging = false;
if (have_logging) {
return;
}
have_logging = true;
crm_xml_init(); /* Sets buffer allocation strategy */
if (crm_trace_nonlog == 0) {
crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
}
umask(S_IWGRP | S_IWOTH | S_IROTH);
/* Redirect messages from glib functions to our handler */
glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL);
/* and for good measure... - this enum is a bit field (!) */
g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */
/* Set crm_system_name, which is used as the logging name. It may also
* be used for other purposes such as an IPC client name.
*/
set_identity(entity, argc, argv);
qb_facility = qb_log_facility2int("local0");
qb_log_init(crm_system_name, qb_facility, LOG_ERR);
crm_log_level = LOG_CRIT;
/* Nuke any syslog activity until it's asked for */
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
// Shorter than default, generous for what we *should* send to syslog
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
#endif
if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') {
nodename = res.nodename;
}
/* Set format strings and disable threading
* Pacemaker and threads do not mix well (due to the amount of forking)
*/
qb_log_tags_stringify_fn_set(crm_quark_to_string);
for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE);
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
// End truncated lines with '...'
qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
#endif
set_format_string(lpc, crm_system_name, pid, nodename);
}
#ifdef ENABLE_NLS
/* Enable translations (experimental). Currently we only have a few
* proof-of-concept translations for some option help. The goal would be to
* offer translations for option help and man pages rather than logs or
* documentation, to reduce the burden of maintaining them.
*/
// Load locale information for the local host from the environment
setlocale(LC_ALL, "");
// Tell gettext where to find Pacemaker message catalogs
CRM_ASSERT(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
// Tell gettext to use the Pacemaker message catalogs
CRM_ASSERT(textdomain(PACKAGE) != NULL);
// Tell gettext that the translated strings are stored in UTF-8
bind_textdomain_codeset(PACKAGE, "UTF-8");
#endif
}
gboolean
crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
int argc, char **argv, gboolean quiet)
{
const char *syslog_priority = NULL;
const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
const char *f_copy = facility;
pcmk__is_daemon = daemon;
crm_log_preinit(entity, argc, argv);
if (level > LOG_TRACE) {
level = LOG_TRACE;
}
if(level > crm_log_level) {
crm_log_level = level;
}
/* Should we log to syslog */
if (facility == NULL) {
if (pcmk__is_daemon) {
facility = "daemon";
} else {
facility = PCMK__VALUE_NONE;
}
pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility);
}
if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) {
quiet = TRUE;
} else {
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
}
if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
/* Override the default setting */
crm_log_level = LOG_DEBUG;
}
/* What lower threshold do we have for sending to syslog */
syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
if (syslog_priority) {
crm_log_priority = crm_priority2int(syslog_priority);
}
qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
crm_log_priority);
// Log to syslog unless requested to be quiet
if (!quiet) {
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
}
/* Should we log to stderr */
if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
/* Override the default setting */
to_stderr = TRUE;
}
crm_enable_stderr(to_stderr);
// Log to a file if we're a daemon or user asked for one
{
const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
if (!pcmk__str_eq(PCMK__VALUE_NONE, logfile, pcmk__str_casei)
&& (pcmk__is_daemon || (logfile != NULL))) {
// Daemons always get a log file, unless explicitly set to "none"
pcmk__add_logfile(logfile);
}
}
if (pcmk__is_daemon
&& pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
crm_enable_blackbox(0);
}
/* Summary */
crm_trace("Quiet: %d, facility %s", quiet, f_copy);
pcmk__env_option(PCMK__ENV_LOGFILE);
pcmk__env_option(PCMK__ENV_LOGFACILITY);
crm_update_callsites();
/* Ok, now we can start logging... */
// Disable daemon request if user isn't root or Pacemaker daemon user
if (pcmk__is_daemon) {
const char *user = getenv("USER");
if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
crm_trace("Not switching to corefile directory for %s", user);
pcmk__is_daemon = false;
}
}
if (pcmk__is_daemon) {
int user = getuid();
struct passwd *pwent = getpwuid(user);
if (pwent == NULL) {
crm_perror(LOG_ERR, "Cannot get name for uid: %d", user);
} else if (!pcmk__strcase_any_of(pwent->pw_name, "root", CRM_DAEMON_USER, NULL)) {
crm_trace("Don't change active directory for regular user: %s", pwent->pw_name);
} else if (chdir(CRM_CORE_DIR) < 0) {
crm_perror(LOG_INFO, "Cannot change active directory to " CRM_CORE_DIR);
} else {
crm_info("Changed active directory to " CRM_CORE_DIR);
}
/* Original meanings from signal(7)
*
* Signal Value Action Comment
* SIGTRAP 5 Core Trace/breakpoint trap
* SIGUSR1 30,10,16 Term User-defined signal 1
* SIGUSR2 31,12,17 Term User-defined signal 2
*
* Our usage is as similar as possible
*/
mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
mainloop_add_signal(SIGTRAP, crm_trigger_blackbox);
} else if (!quiet) {
crm_log_args(argc, argv);
}
return TRUE;
}
/* returns the old value */
unsigned int
set_crm_log_level(unsigned int level)
{
unsigned int old = crm_log_level;
if (level > LOG_TRACE) {
level = LOG_TRACE;
}
crm_log_level = level;
crm_update_callsites();
crm_trace("New log level: %d", level);
return old;
}
void
crm_enable_stderr(int enable)
{
if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
crm_update_callsites();
} else if (enable == FALSE) {
qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
}
}
/*!
* \brief Make logging more verbose
*
* If logging to stderr is not already enabled when this function is called,
* enable it. Otherwise, increase the log level by 1.
*
* \param[in] argc Ignored
* \param[in] argv Ignored
*/
void
crm_bump_log_level(int argc, char **argv)
{
if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
!= QB_LOG_STATE_ENABLED) {
crm_enable_stderr(TRUE);
} else {
set_crm_log_level(crm_log_level + 1);
}
}
unsigned int
get_crm_log_level(void)
{
return crm_log_level;
}
/*!
* \brief Log the command line (once)
*
* \param[in] Number of values in \p argv
* \param[in] Command-line arguments (including command name)
*
* \note This function will only log once, even if called with different
* arguments.
*/
void
crm_log_args(int argc, char **argv)
{
static bool logged = false;
gchar *arg_string = NULL;
if ((argc == 0) || (argv == NULL) || logged) {
return;
}
logged = true;
arg_string = g_strjoinv(" ", argv);
crm_notice("Invoked: %s", arg_string);
g_free(arg_string);
}
void
crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
const char *output)
{
const char *next = NULL;
const char *offset = NULL;
if (level == LOG_NEVER) {
return;
}
if (output == NULL) {
if (level != LOG_STDOUT) {
level = LOG_TRACE;
}
output = "-- empty --";
}
next = output;
do {
offset = next;
next = strchrnul(offset, '\n');
do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix,
(int)(next - offset), offset);
if (next[0] != 0) {
next++;
}
} while (next != NULL && next[0] != 0);
}
void
pcmk__cli_init_logging(const char *name, unsigned int verbosity)
{
crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
for (int i = 0; i < verbosity; i++) {
/* These arguments are ignored, so pass placeholders. */
crm_bump_log_level(0, NULL);
}
}
/*!
* \brief Log XML line-by-line in a formatted fashion
*
* \param[in] level Priority at which to log the messages
* \param[in] text Prefix for each line
* \param[in] xml XML to log
*
* \note This does nothing when \p level is \p LOG_STDOUT.
* \note Do not call this function directly. It should be called only from the
* \p do_crm_log_xml() macro.
*/
void
pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml)
{
if (xml == NULL) {
do_crm_log(level, "%s%sNo data to dump as XML",
pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
} else {
if (logger_out == NULL) {
CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
}
pcmk__output_set_log_level(logger_out, level);
pcmk__xml_show(logger_out, text, xml, 1,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
}
/*!
* \internal
* \brief Free the logging library's internal log output object
*/
void
pcmk__free_common_logger(void)
{
if (logger_out != NULL) {
logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
pcmk__output_free(logger_out);
logger_out = NULL;
}
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/logging_compat.h>
gboolean
crm_log_cli_init(const char *entity)
{
pcmk__cli_init_logging(entity, 0);
return TRUE;
}
gboolean
crm_add_logfile(const char *filename)
{
return pcmk__add_logfile(filename) == pcmk_rc_ok;
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/patchset.c b/lib/common/patchset.c
index 089e941c1c..4a8dd460df 100644
--- a/lib/common/patchset.c
+++ b/lib/common/patchset.c
@@ -1,1863 +1,1860 @@
/*
* Copyright 2004-2023 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/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
#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);
/*
<diff format="2.0">
<version>
<source admin_epoch="1" epoch="2" num_updates="3"/>
<target admin_epoch="1" epoch="3" num_updates="0"/>
</version>
<change operation="add" xpath="/cib/configuration/nodes">
<node id="node2" uname="node2" description="foo"/>
</change>
<change operation="add" xpath="/cib/configuration/nodes/node[node2]">
<instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
<nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
</instance_attributes>
</change>
<change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
<change-list>
<change-attr operation="set" name="type" value="member"/>
<change-attr operation="unset" name="description"/>
</change-list>
<change-result>
<node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
<change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
<change-list>
<change-attr operation="set" name="description" value="some garbage here"/>
</change-list>
<change-result>
<group id="g1" description="some garbage here"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
<change-list>
<change-attr operation="set" name="oper" value="member"/>
<change-attr operation="set" name="operation_key" value="Fence_start_0"/>
<change-attr operation="set" name="operation" value="start"/>
<change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="call-id" value="2"/>
<change-attr operation="set" name="rc-code" value="0"/>
</change-list>
<change-result>
<lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate" transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
</change-result>
</change>
</diff>
*/
// Add changes for specified XML to patchset
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 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, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "create");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
crm_xml_add_int(change, XML_DIFF_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, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "modify");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
change = create_xml_node(change, XML_DIFF_LIST);
g_string_free(xpath, TRUE);
}
}
attr = create_xml_node(change, XML_DIFF_ATTR);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
if (nodepriv->flags & pcmk__xf_deleted) {
crm_xml_add(attr, XML_DIFF_OP, "unset");
} else {
crm_xml_add(attr, XML_DIFF_OP, "set");
value = crm_element_value(xml, (const char *) pIter->name);
crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
}
}
if (change) {
xmlNode *result = NULL;
change = create_xml_node(change->parent, XML_DIFF_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, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "move");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
crm_xml_add_int(change, XML_DIFF_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, XML_CIB_TAG_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,
"/" XML_TAG_CIB "/" XML_CIB_TAG_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[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
if (local_diff == NULL) {
crm_trace("Nothing to do");
return;
}
tag = "diff-removed";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_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 = "diff-added";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_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 = crm_element_value(next, (const char *) a->name);
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, "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[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
CRM_ASSERT(target);
if (!xml_document_dirty(target)) {
return NULL;
}
CRM_ASSERT(target->doc);
docpriv = target->doc->_private;
patchset = create_xml_node(NULL, XML_TAG_DIFF);
crm_xml_add_int(patchset, "format", 2);
version = create_xml_node(patchset, XML_DIFF_VERSION);
v = create_xml_node(version, XML_DIFF_VSOURCE);
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, XML_DIFF_VTARGET);
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, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "delete");
crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
if (deleted_obj->position >= 0) {
crm_xml_add_int(change, XML_DIFF_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, XML_ATTR_CRM_VERSION);
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, XML_ATTR_NUMUPDATES, "0");
crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
} else if (manage_version) {
crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
crm_trace("Status changed %d - %d %s", format, counter,
crm_element_value(source, XML_ATTR_NUMUPDATES));
crm_xml_add_int(target, XML_ATTR_NUMUPDATES, (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, "format", &format);
if ((format > 1) && !with_digest) {
return;
}
version = crm_element_value(source, XML_ATTR_CRM_VERSION);
digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
crm_xml_add(patch, XML_ATTR_DIGEST, digest);
free(digest);
return;
}
/*!
* \internal
* \brief Output an XML patchset header
*
* This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
* element and its children).
*
* All header lines contain three integers separated by dots, of the form
* <tt>{0}.{1}.{2}</tt>:
* * \p {0}: \p XML_ATTR_GENERATION_ADMIN
* * \p {1}: \p XML_ATTR_GENERATION
* * \p {2}: \p XML_ATTR_NUMUPDATES
*
* Lines containing \p "---" describe removals and end with the patch format
* number. Lines containing \p "+++" describe additions and end with the patch
* digest.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*/
static void
xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(patchset, add, del);
if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
const char *fmt = crm_element_value(patchset, "format");
const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
out->info(out, "Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
} else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
out->info(out, "Local-only Change: %d.%d.%d", add[0], add[1], add[2]);
}
}
/*!
* \internal
* \brief Output a user-friendly form of XML additions or removals
*
* \param[in,out] out Output object
* \param[in] prefix String to prepend to every line of output
* \param[in] data XML node to output
* \param[in] depth Current indentation level
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*/
static void
xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
const xmlNode *data, int depth, uint32_t options)
{
if (!xml_has_children(data)
|| (crm_element_value(data, XML_DIFF_MARKER) != NULL)) {
// Found a change; clear the pcmk__xml_fmt_diff_short option if set
options &= ~pcmk__xml_fmt_diff_short;
if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
prefix = PCMK__XML_PREFIX_CREATED;
} else { // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
prefix = PCMK__XML_PREFIX_DELETED;
}
}
if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
// Keep looking for the actual change
for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
child = pcmk__xml_next(child)) {
xml_show_patchset_v1_recursive(out, prefix, child, depth + 1,
options);
}
} else {
pcmk__xml_show(out, prefix, data, depth,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 1)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*/
static void
xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset)
{
uint32_t options = pcmk__xml_fmt_pretty;
const xmlNode *removed = NULL;
const xmlNode *added = NULL;
const xmlNode *child = NULL;
bool is_first = true;
// @FIXME: Use message functions to get rid of explicit fmt_name check
if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)
&& (pcmk__output_get_log_level(out) < LOG_DEBUG)) {
options |= pcmk__xml_fmt_diff_short;
}
xml_show_patchset_header(out, patchset);
/* It's not clear whether "- " or "+ " ever does *not* get overridden by
* PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
* However, v1 patchsets can only exist during rolling upgrades from
* Pacemaker 1.1.11, so not worth worrying about.
*/
removed = find_xml_node(patchset, "diff-removed", FALSE);
for (child = pcmk__xml_first_child(removed); child != NULL;
child = pcmk__xml_next(child)) {
xml_show_patchset_v1_recursive(out, "- ", child, 0,
options|pcmk__xml_fmt_diff_minus);
if (is_first) {
is_first = false;
} else {
out->info(out, " --- ");
}
}
is_first = true;
added = find_xml_node(patchset, "diff-added", FALSE);
for (child = pcmk__xml_first_child(added); child != NULL;
child = pcmk__xml_next(child)) {
xml_show_patchset_v1_recursive(out, "+ ", child, 0,
options|pcmk__xml_fmt_diff_plus);
if (is_first) {
is_first = false;
} else {
out->info(out, " +++ ");
}
}
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 2)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*/
static void
xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
{
xml_show_patchset_header(out, patchset);
for (const xmlNode *change = pcmk__xml_first_child(patchset);
change != NULL; change = pcmk__xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if (op == NULL) {
continue;
}
if (strcmp(op, "create") == 0) {
char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
xpath);
pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
// Overwrite all except the first two characters with spaces
for (char *ch = prefix + 2; *ch != '\0'; ch++) {
*ch = ' ';
}
pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
free(prefix);
} else if (strcmp(op, "move") == 0) {
const char *position = crm_element_value(change, XML_DIFF_POSITION);
out->info(out, PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
xpath, position);
} else if (strcmp(op, "modify") == 0) {
xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
GString *buffer_set = NULL;
GString *buffer_unset = NULL;
for (const xmlNode *child = pcmk__xml_first_child(clist);
child != NULL; child = pcmk__xml_next(child)) {
const char *name = crm_element_value(child, "name");
op = crm_element_value(child, XML_DIFF_OP);
if (op == NULL) {
continue;
}
if (strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, "value");
pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
pcmk__g_strcat(buffer_set, name, "=", value, NULL);
} else if (strcmp(op, "unset") == 0) {
pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
g_string_append(buffer_unset, name);
}
}
if (buffer_set != NULL) {
out->info(out, "+ %s: %s", xpath, buffer_set->str);
g_string_free(buffer_set, TRUE);
}
if (buffer_unset != NULL) {
out->info(out, "-- %s: %s", xpath, buffer_unset->str);
g_string_free(buffer_unset, TRUE);
}
} else if (strcmp(op, "delete") == 0) {
int position = -1;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if (position >= 0) {
out->info(out, "-- %s (%d)", xpath, position);
} else {
out->info(out, "-- %s", xpath);
}
}
}
}
/*!
* \internal
* \brief Log a user-friendly form of an XML patchset
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output. Depending on the value
* of \p log_level, the output may be written to \p stdout or to a log file.
*
* \param[in] log_level Priority at which to log the messages
* \param[in] patchset XML patchset to log
*/
void
pcmk__xml_log_patchset(uint8_t log_level, const xmlNode *patchset)
{
int format = 1;
pcmk__output_t *out = NULL;
static struct qb_log_callsite *patchset_cs = NULL;
switch (log_level) {
case LOG_NEVER:
return;
case LOG_STDOUT:
CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
break;
default:
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__,
"xml-patchset", log_level,
__LINE__, crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level,
crm_trace_nonlog)) {
return;
}
CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
pcmk__output_set_log_level(out, log_level);
break;
}
if (patchset == NULL) {
// Should come after the LOG_NEVER check
crm_trace("Empty patch");
goto done;
}
crm_element_value_int(patchset, "format", &format);
switch (format) {
case 1:
xml_show_patchset_v1(out, patchset);
break;
case 2:
xml_show_patchset_v2(out, patchset);
break;
default:
crm_err("Unknown patch format: %d", format);
break;
}
done:
out->finish(out, CRM_EX_OK, true, NULL);
pcmk__output_free(out);
}
// Return true if attribute name is not "id"
static bool
not_id(xmlAttrPtr attr, void *user_data)
{
return strcmp((const char *) attr->name, XML_ATTR_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 *name = 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);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
pcmk__str_casei),
return);
CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
// Check for XML_DIFF_MARKER in a child
id = crm_element_value_copy(target, XML_ATTR_ID);
value = crm_element_value(patch, XML_DIFF_MARKER);
if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
crm_trace("We are the root of the deletion: %s.id=%s", 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 XML_DIFF_MARKER in a child
value = crm_element_value(patch, XML_DIFF_MARKER);
if ((target == NULL) && (value != NULL)
&& (strcmp(value, "added:top") == 0)) {
id = ID(patch);
name = crm_element_name(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);
name = crm_element_name(patch);
crm_err("Could not locate: %s.id=%s", name, id);
return;
}
if (target->type == XML_COMMENT_NODE) {
pcmk__xc_update(parent, target, patch);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
pcmk__str_casei),
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 = crm_element_value(patch, p_name);
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? "diff-added" : "diff-removed";
*patch_node = find_xml_node(patchset, label, FALSE);
cib_node = find_xml_node(*patch_node, "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);
*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[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
crm_element_value_int(patchset, "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
* \param[in] format Patchset version
*
* \return Standard Pacemaker return code
*/
static int
xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset, int format)
{
int lpc = 0;
bool changed = FALSE;
int this[] = { 0, 0, 0 };
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
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, "diff-added", FALSE);
xmlNode *removed = find_xml_node(patchset, "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, "%[^[][@" XML_ATTR_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, XML_DIFF_POSITION, &position_a);
crm_element_value_int(change_obj_b->change, XML_DIFF_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, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
int position = -1;
if (op == NULL) {
continue;
}
crm_trace("Processing %s %s", change->name, op);
// "delete" changes for XML comments are generated with "position"
if (strcmp(op, "delete") == 0) {
crm_element_value_int(change, XML_DIFF_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) {
xmlNode *attrs = NULL;
attrs = pcmk__xml_first_child(first_named_child(change,
XML_DIFF_RESULT));
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 = crm_element_value(attrs, name);
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, XML_DIFF_OP);
xpath = crm_element_value(change, XML_DIFF_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, XML_DIFF_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 (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, XML_DIFF_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 = crm_element_value(patchset, XML_ATTR_DIGEST);
if (patchset == NULL) {
return rc;
}
pcmk__xml_log_patchset(LOG_TRACE, patchset);
crm_element_value_int(patchset, "format", &format);
if (check_version) {
rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format));
if (rc != pcmk_ok) {
return rc;
}
}
if (digest) {
// Make it available for logging if result doesn't have expected digest
old = copy_xml(xml);
}
if (rc == pcmk_ok) {
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, XML_ATTR_CRM_VERSION);
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, XML_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, "diff");
xmlNode *removed = create_xml_node(diff, "diff-removed");
xmlNode *added = create_xml_node(diff, "diff-added");
crm_xml_add(diff, XML_ATTR_CRM_VERSION, 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);
if (right == NULL) {
xmlNode *deleted = NULL;
crm_trace("Processing <%s " XML_ATTR_ID "=%s> (complete copy)",
crm_element_name(left), id);
deleted = add_node_copy(parent, left);
crm_xml_add(deleted, XML_DIFF_MARKER, marker);
*changed = TRUE;
return deleted;
}
name = crm_element_name(left);
CRM_CHECK(name != NULL, return NULL);
CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right),
pcmk__str_casei),
return NULL);
// Check for XML_DIFF_MARKER in a child
value = crm_element_value(right, XML_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, XML_ATTR_ID) == 0) {
// id already obtained when present ~ this case, so just reuse
xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_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 = crm_element_value(left, prop_name);
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 = crm_element_value(left, prop_name);
if (strcmp(left_value, right_val) == 0) {
/* unchanged */
} else {
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
crm_trace("Changes detected to %s in "
"<%s " XML_ATTR_ID "=%s>",
prop_name, crm_element_name(left), 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 " XML_ATTR_ID "=%s>",
prop_name, left_value, right_val,
crm_element_name(left), 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, XML_ATTR_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;
- static struct qb_log_callsite *digest_cs = NULL;
const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
CRM_CHECK(new_xml != NULL, return FALSE);
- if (digest_cs == NULL) {
- digest_cs = qb_log_callsite_get(__func__, __FILE__, "diff-digest",
- LOG_TRACE, __LINE__, crm_trace_nonlog);
- }
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;
- crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
- if ((digest_cs != NULL) && digest_cs->targets) {
- 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);
- }
+ 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;
}
void
xml_log_patchset(uint8_t log_level, const char *function,
const xmlNode *patchset)
{
pcmk__xml_log_patchset(log_level, patchset);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:00 PM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002430
Default Alt Text
(134 KB)

Event Timeline