Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index cb50c4e1b4..6ef0fc7c5c 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -1,474 +1,477 @@
/*
* Copyright 2017-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__XML_INTERNAL__H
# define PCMK__XML_INTERNAL__H
/*
* Internal-only wrappers for and extensions to libxml2 (libxslt)
*/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <crm/crm.h> /* transitively imports qblog.h */
# include <crm/common/output_internal.h>
# include <libxml/relaxng.h>
/*!
* \brief Base for directing lib{xml2,xslt} log into standard libqb backend
*
* This macro implements the core of what can be needed for directing
* libxml2 or libxslt error messaging into standard, preconfigured
* libqb-backed log stream.
*
* It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
* emits a single message by chunks (location is emitted separatedly from
* the message itself), so we have to take the effort to combine these
* chunks back to single message. Whether to do this or not is driven
* with \p dechunk toggle.
*
* The form of a macro was chosen for implicit deriving of __FILE__, etc.
* and also because static dechunking buffer should be differentiated per
* library (here we assume different functions referring to this macro
* will not ever be using both at once), preferably also per-library
* context of use to avoid clashes altogether.
*
* Note that we cannot use qb_logt, because callsite data have to be known
* at the moment of compilation, which it is not always the case -- xml_log
* (and unfortunately there's no clear explanation of the fail to compile).
*
* Also note that there's no explicit guard against said libraries producing
* never-newline-terminated chunks (which would just keep consuming memory),
* as it's quite improbable. Termination of the program in between the
* same-message chunks will raise a flag with valgrind and the likes, though.
*
* And lastly, regarding how dechunking combines with other non-message
* parameters -- for \p priority, most important running specification
* wins (possibly elevated to LOG_ERR in case of nonconformance with the
* newline-termination "protocol"), \p dechunk is expected to always be
* on once it was at the start, and the rest (\p postemit and \p prefix)
* are picked directly from the last chunk entry finalizing the message
* (also reasonable to always have it the same with all related entries).
*
* \param[in] priority Syslog priority for the message to be logged
* \param[in] dechunk Whether to dechunk new-line terminated message
* \param[in] postemit Code to be executed once message is sent out
* \param[in] prefix How to prefix the message or NULL for raw passing
* \param[in] fmt Format string as with printf-like functions
* \param[in] ap Variable argument list to supplement \p fmt format string
*/
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
do { \
if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
(priority), __LINE__, 0, (ap)); \
(void) (postemit); \
} else { \
int CXLB_len = 0; \
char *CXLB_buf = NULL; \
static int CXLB_buffer_len = 0; \
static char *CXLB_buffer = NULL; \
static uint8_t CXLB_priority = 0; \
\
CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
\
if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
if (CXLB_len < 0) { \
CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
} else if (CXLB_len > 0 /* && (dechunk) */ \
&& CXLB_buf[CXLB_len - 1] == '\n') { \
CXLB_buf[CXLB_len - 1] = '\0'; \
} \
if (CXLB_buffer) { \
qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
CXLB_priority, __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buffer, CXLB_buf); \
free(CXLB_buffer); \
} else { \
qb_log_from_external_source(__func__, __FILE__, "%s%s", \
(priority), __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buf); \
} \
if (CXLB_len < 0) { \
CXLB_buf = NULL; /* restore temporary override */ \
} \
CXLB_buffer = NULL; \
CXLB_buffer_len = 0; \
(void) (postemit); \
\
} else if (CXLB_buffer == NULL) { \
CXLB_buffer_len = CXLB_len; \
CXLB_buffer = CXLB_buf; \
CXLB_buf = NULL; \
CXLB_priority = (priority); /* remember as a running severest */ \
\
} else { \
CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
CXLB_buffer_len += CXLB_len; \
CXLB_buffer[CXLB_buffer_len] = '\0'; \
CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
} \
free(CXLB_buf); \
} \
} while (0)
/*
* \enum pcmk__xml_fmt_options
* \brief Bit flags to control format in XML logs and dumps
*/
enum pcmk__xml_fmt_options {
//! Exclude certain XML attributes (for calculating digests)
pcmk__xml_fmt_filtered = (1 << 0),
//! Include indentation and newlines
pcmk__xml_fmt_pretty = (1 << 1),
//! Include the opening tag of an XML element, and include XML comments
pcmk__xml_fmt_open = (1 << 3),
//! Include the children of an XML element
pcmk__xml_fmt_children = (1 << 4),
//! Include the closing tag of an XML element
pcmk__xml_fmt_close = (1 << 5),
//! Include XML text nodes
pcmk__xml_fmt_text = (1 << 6),
// @COMPAT Remove when v1 patchsets are removed
//! Log a created XML subtree
pcmk__xml_fmt_diff_plus = (1 << 7),
// @COMPAT Remove when v1 patchsets are removed
//! Log a removed XML subtree
pcmk__xml_fmt_diff_minus = (1 << 8),
// @COMPAT Remove when v1 patchsets are removed
//! Log a minimal version of an XML diff (only showing the changes)
pcmk__xml_fmt_diff_short = (1 << 9),
};
int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
int depth, uint32_t options);
int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml);
/* XML search strings for guest, remote and pacemaker_remote nodes */
/* search string to find CIB resources entries for cluster nodes */
#define PCMK__XP_MEMBER_NODE_CONFIG \
"//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \
"/" PCMK_XE_NODE "[not(@type) or @type='member']"
/* search string to find CIB resources entries for guest nodes */
#define PCMK__XP_GUEST_NODE_CONFIG \
"//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \
"//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \
"[@name='" PCMK_META_REMOTE_NODE "']"
/* search string to find CIB resources entries for remote nodes */
#define PCMK__XP_REMOTE_NODE_CONFIG \
"//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \
"[@type='remote'][@provider='pacemaker']"
/* search string to find CIB node status entries for pacemaker_remote nodes */
#define PCMK__XP_REMOTE_NODE_STATUS \
"//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \
"[@" PCMK_XA_REMOTE_NODE "='true']"
/*!
* \internal
* \brief Serialize XML (using libxml) into provided descriptor
*
* \param[in] fd File descriptor to (piece-wise) write to
* \param[in] cur XML subtree to proceed
*
* \return a standard Pacemaker return code
*/
int pcmk__xml2fd(int fd, xmlNode *cur);
enum pcmk__xml_artefact_ns {
pcmk__xml_artefact_ns_legacy_rng = 1,
pcmk__xml_artefact_ns_legacy_xslt,
pcmk__xml_artefact_ns_base_rng,
pcmk__xml_artefact_ns_base_xslt,
};
void pcmk__strip_xml_text(xmlNode *xml);
const char *pcmk__xe_add_last_written(xmlNode *xe);
xmlNode *pcmk__xe_match(const xmlNode *parent, const char *node_name,
const char *attr_n, const char *attr_v);
void pcmk__xe_remove_matching_attrs(xmlNode *element,
bool (*match)(xmlAttrPtr, void *),
void *user_data);
GString *pcmk__element_xpath(const xmlNode *xml);
bool pcmk__xml_needs_escape(const char *text, bool escape_quote);
char *pcmk__xml_escape(const char *text, bool escape_quote);
/*!
* \internal
* \brief Get the root directory to scan XML artefacts of given kind for
*
* \param[in] ns governs the hierarchy nesting against the inherent root dir
*
* \return root directory to scan XML artefacts of given kind for
*/
char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
/*!
* \internal
* \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
*
* \param[in] ns denotes path forming details (parent dir, suffix)
* \param[in] filespec symbolic file specification to be combined with
* #artefact_ns to form the final path
* \return unwrapped path to particular XML artifact (RNG/XSLT)
*/
char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
const char *filespec);
/*!
* \internal
* \brief Retrieve the value of the \c PCMK_XA_ID XML attribute
*
* \param[in] xml XML element to check
*
* \return Value of the \c PCMK_XA_ID attribute (may be \c NULL)
*/
static inline const char *
pcmk__xe_id(const xmlNode *xml)
{
return crm_element_value(xml, PCMK_XA_ID);
}
/*!
* \internal
* \brief Check whether an XML element is of a particular type
*
* \param[in] xml XML element to compare
* \param[in] name XML element name to compare
*
* \return \c true if \p xml is of type \p name, otherwise \c false
*/
static inline bool
pcmk__xe_is(const xmlNode *xml, const char *name)
{
return (xml != NULL) && (xml->name != NULL) && (name != NULL)
&& (strcmp((const char *) xml->name, name) == 0);
}
/*!
* \internal
* \brief Return first non-text child node of an XML node
*
* \param[in] parent XML node to check
*
* \return First non-text child node of \p parent (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_first_child(const xmlNode *parent)
{
xmlNode *child = (parent? parent->children : NULL);
while (child && (child->type == XML_TEXT_NODE)) {
child = child->next;
}
return child;
}
/*!
* \internal
* \brief Return next non-text sibling node of an XML node
*
* \param[in] child XML node to check
*
* \return Next non-text sibling of \p child (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_next(const xmlNode *child)
{
xmlNode *next = (child? child->next : NULL);
while (next && (next->type == XML_TEXT_NODE)) {
next = next->next;
}
return next;
}
/*!
* \internal
* \brief Return first non-text child element of an XML node
*
* \param[in] parent XML node to check
*
* \return First child element of \p parent (or NULL if none)
*/
static inline xmlNode *
pcmk__xe_first_child(const xmlNode *parent)
{
xmlNode *child = (parent? parent->children : NULL);
while (child && (child->type != XML_ELEMENT_NODE)) {
child = child->next;
}
return child;
}
/*!
* \internal
* \brief Return next non-text sibling element of an XML element
*
* \param[in] child XML element to check
*
* \return Next sibling element of \p child (or NULL if none)
*/
static inline xmlNode *
pcmk__xe_next(const xmlNode *child)
{
xmlNode *next = child? child->next : NULL;
while (next && (next->type != XML_ELEMENT_NODE)) {
next = next->next;
}
return next;
}
xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src);
void pcmk__xe_set_content(xmlNode *node, const char *content);
/*!
* \internal
* \brief Like pcmk__xe_set_props, but takes a va_list instead of
* arguments directly.
*
* \param[in,out] node XML to add attributes to
* \param[in] pairs NULL-terminated list of name/value pairs to add
*/
void
pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
/*!
* \internal
* \brief Add a NULL-terminated list of name/value pairs to the given
* XML node as properties.
*
* \param[in,out] node XML node to add properties to
* \param[in] ... NULL-terminated list of name/value pairs
*
* \note A NULL name terminates the arguments; a NULL value will be skipped.
*/
void
pcmk__xe_set_props(xmlNodePtr node, ...)
G_GNUC_NULL_TERMINATED;
/*!
* \internal
* \brief Get first attribute of an XML element
*
* \param[in] xe XML element to check
*
* \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
*/
static inline xmlAttr *
pcmk__xe_first_attr(const xmlNode *xe)
{
return (xe == NULL)? NULL : xe->properties;
}
/*!
* \internal
* \brief Extract the ID attribute from an XML element
*
* \param[in] xpath String to search
* \param[in] node Node to get the ID for
*
* \return ID attribute of \p node in xpath string \p xpath
*/
char *
pcmk__xpath_node_id(const char *xpath, const char *node);
/* internal XML-related utilities */
enum xml_private_flags {
pcmk__xf_none = 0x0000,
pcmk__xf_dirty = 0x0001,
pcmk__xf_deleted = 0x0002,
pcmk__xf_created = 0x0004,
pcmk__xf_modified = 0x0008,
pcmk__xf_tracking = 0x0010,
pcmk__xf_processed = 0x0020,
pcmk__xf_skip = 0x0040,
pcmk__xf_moved = 0x0080,
pcmk__xf_acl_enabled = 0x0100,
pcmk__xf_acl_read = 0x0200,
pcmk__xf_acl_write = 0x0400,
pcmk__xf_acl_deny = 0x0800,
pcmk__xf_acl_create = 0x1000,
pcmk__xf_acl_denied = 0x2000,
pcmk__xf_lazy = 0x4000,
};
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag);
/*!
* \internal
* \brief Iterate over child elements of \p xml
*
* This function iterates over the children of \p xml, performing the
* callback function \p handler on each node. If the callback returns
* a value other than pcmk_rc_ok, the iteration stops and the value is
* returned. It is therefore possible that not all children will be
* visited.
*
* \param[in,out] xml The starting XML node. Can be NULL.
* \param[in] child_element_name The name that the node must match in order
* for \p handler to be run. If NULL, all
* child elements will match.
* \param[in] handler The callback function.
* \param[in,out] userdata User data to pass to the callback function.
* Can be NULL.
*
* \return Standard Pacemaker return code
*/
int
pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
int (*handler)(xmlNode *xml, void *userdata),
void *userdata);
static inline const char *
pcmk__xml_attr_value(const xmlAttr *attr)
{
return ((attr == NULL) || (attr->children == NULL))? NULL
: (const char *) attr->children->content;
}
gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
xmlRelaxNGValidityErrorFunc error_handler,
void *error_handler_context);
void pcmk__log_known_schemas(void);
const char *pcmk__remote_schema_dir(void);
void pcmk__sort_schemas(void);
+// @COMPAT Remove when v1 patchsets are removed
+xmlNode *pcmk__diff_v1_xml_object(xmlNode *left, xmlNode *right, bool suppress);
+
#endif // PCMK__XML_INTERNAL__H
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
index c129bd86e0..a4fd4d7f1d 100644
--- a/lib/cib/cib_ops.c
+++ b/lib/cib/cib_ops.c
@@ -1,1010 +1,1010 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
// @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon
static GHashTable *operation_table = NULL;
static const cib__operation_t cib_ops[] = {
{
PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete,
cib__op_attr_modifies|cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_BUMP, cib__op_bump,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_writes_through
},
{
PCMK__CIB_REQUEST_CREATE, cib__op_create,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_DELETE, cib__op_delete,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_ERASE, cib__op_erase,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary,
cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_MODIFY, cib__op_modify,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none
},
{
CRM_OP_PING, cib__op_ping, cib__op_attr_none
},
{
// @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support
PCMK__CIB_REQUEST_PRIMARY, cib__op_primary,
cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local
},
{
PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none
},
{
PCMK__CIB_REQUEST_REPLACE, cib__op_replace,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_writes_through
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary,
cib__op_attr_privileged|cib__op_attr_local
},
{
PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_writes_through
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local
}
};
/*!
* \internal
* \brief Get the \c cib__operation_t object for a given CIB operation name
*
* \param[in] op CIB operation name
* \param[out] operation Where to store CIB operation object
*
* \return Standard Pacemaker return code
*/
int
cib__get_operation(const char *op, const cib__operation_t **operation)
{
CRM_ASSERT((op != NULL) && (operation != NULL));
if (operation_table == NULL) {
operation_table = pcmk__strkey_table(NULL, NULL);
for (int lpc = 0; lpc < PCMK__NELEM(cib_ops); lpc++) {
const cib__operation_t *oper = &(cib_ops[lpc]);
g_hash_table_insert(operation_table, (gpointer) oper->name,
(gpointer) oper);
}
}
*operation = g_hash_table_lookup(operation_table, op);
if (*operation == NULL) {
crm_err("Operation %s is invalid", op);
return EINVAL;
}
return pcmk_rc_ok;
}
int
cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
CRM_CHECK(*answer == NULL, free_xml(*answer));
*answer = NULL;
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
}
obj_root = pcmk_find_cib_element(existing_cib, section);
if (obj_root == NULL) {
result = -ENXIO;
} else if (options & cib_no_children) {
xmlNode *shallow = create_xml_node(*answer,
(const char *) obj_root->name);
copy_in_properties(shallow, obj_root);
*answer = shallow;
} else {
*answer = obj_root;
}
if (result == pcmk_ok && *answer == NULL) {
crm_err("Error creating query response");
result = -ENOMSG;
}
return result;
}
static int
update_counter(xmlNode *xml_obj, const char *field, bool reset)
{
char *new_value = NULL;
char *old_value = NULL;
int int_value = -1;
if (!reset && crm_element_value(xml_obj, field) != NULL) {
old_value = crm_element_value_copy(xml_obj, field);
}
if (old_value != NULL) {
int_value = atoi(old_value);
new_value = pcmk__itoa(++int_value);
} else {
new_value = strdup("1");
CRM_ASSERT(new_value != NULL);
}
crm_trace("Update %s from %s to %s",
field, pcmk__s(old_value, "unset"), new_value);
crm_xml_add(xml_obj, field, new_value);
free(new_value);
free(old_value);
return pcmk_ok;
}
int
cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing \"%s\" event", op);
if (*result_cib != existing_cib) {
free_xml(*result_cib);
}
*result_cib = createEmptyCib(0);
copy_in_properties(*result_cib, existing_cib);
update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false);
*answer = NULL;
return result;
}
int
cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int rc = 0;
int new_version = 0;
int current_version = 0;
int max_version = 0;
const char *max = crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX);
const char *value = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH);
*answer = NULL;
crm_trace("Processing \"%s\" event with max=%s", op, max);
if (value != NULL) {
current_version = get_schema_version(value);
}
if (max) {
max_version = get_schema_version(max);
}
rc = update_validation(result_cib, &new_version, max_version, TRUE,
!(options & cib_verbose));
if (new_version > current_version) {
update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false);
update_counter(*result_cib, PCMK_XA_EPOCH, true);
update_counter(*result_cib, PCMK_XA_NUM_UPDATES, true);
return pcmk_ok;
}
return rc;
}
int
cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing %s for epoch='%s'", op,
pcmk__s(crm_element_value(existing_cib, PCMK_XA_EPOCH), ""));
*answer = NULL;
update_counter(*result_cib, PCMK_XA_EPOCH, false);
return result;
}
int
cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
*answer = NULL;
if (input == NULL) {
return -EINVAL;
}
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, section)) {
section = NULL;
}
if (pcmk__xe_is(input, PCMK_XE_CIB)) {
int updates = 0;
int epoch = 0;
int admin_epoch = 0;
int replace_updates = 0;
int replace_epoch = 0;
int replace_admin_epoch = 0;
const char *reason = NULL;
const char *peer = crm_element_value(req, PCMK__XA_SRC);
const char *digest = crm_element_value(req, PCMK__XA_DIGEST);
if (digest) {
const char *version = crm_element_value(req,
PCMK_XA_CRM_FEATURE_SET);
char *digest_verify = calculate_xml_versioned_digest(input, FALSE, TRUE,
version ? version :
CRM_FEATURE_SET);
if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) {
crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer,
digest_verify, digest);
reason = "digest mismatch";
} else {
crm_info("Digest matched on replace from %s: %s", peer, digest);
}
free(digest_verify);
} else {
crm_trace("No digest to verify");
}
cib_version_details(existing_cib, &admin_epoch, &epoch, &updates);
cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates);
if (replace_admin_epoch < admin_epoch) {
reason = PCMK_XA_ADMIN_EPOCH;
} else if (replace_admin_epoch > admin_epoch) {
/* no more checks */
} else if (replace_epoch < epoch) {
reason = PCMK_XA_EPOCH;
} else if (replace_epoch > epoch) {
/* no more checks */
} else if (replace_updates < updates) {
reason = PCMK_XA_NUM_UPDATES;
}
if (reason != NULL) {
crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:"
" current %s is greater than the replacement",
replace_admin_epoch, replace_epoch,
replace_updates, peer, admin_epoch, epoch, updates, reason);
result = -pcmk_err_old_data;
} else {
crm_info("Replaced %d.%d.%d with %d.%d.%d from %s",
admin_epoch, epoch, updates,
replace_admin_epoch, replace_epoch, replace_updates, peer);
}
if (*result_cib != existing_cib) {
free_xml(*result_cib);
}
*result_cib = pcmk__xml_copy(NULL, input);
} else {
xmlNode *obj_root = NULL;
gboolean ok = TRUE;
obj_root = pcmk_find_cib_element(*result_cib, section);
ok = replace_xml_child(NULL, obj_root, input, FALSE);
if (ok == FALSE) {
crm_trace("No matching object to replace");
result = -ENXIO;
}
}
return result;
}
int
cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if (pcmk__xe_is(input, section)) {
xmlNode *child = NULL;
for (child = pcmk__xml_first_child(input); child;
child = pcmk__xml_next(child)) {
if (replace_xml_child(NULL, obj_root, child, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s",
child->name, pcmk__xe_id(child));
}
}
} else if (replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s",
input->name, pcmk__xe_id(input));
}
return pcmk_ok;
}
int
cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if (obj_root == NULL) {
xmlNode *tmp_section = NULL;
const char *path = pcmk_cib_parent_name_for(section);
if (path == NULL) {
return -EINVAL;
}
tmp_section = create_xml_node(NULL, section);
cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section,
NULL, result_cib, answer);
free_xml(tmp_section);
obj_root = pcmk_find_cib_element(*result_cib, section);
}
CRM_CHECK(obj_root != NULL, return -EINVAL);
if (update_xml_child(obj_root, input) == FALSE) {
if (options & cib_can_create) {
pcmk__xml_copy(obj_root, input);
} else {
return -ENXIO;
}
}
// @COMPAT cib_mixed_update is deprecated as of 2.1.7
if (pcmk_is_set(options, cib_mixed_update)) {
int max = 0, lpc;
xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__");
if (xpathObj) {
max = numXpathResults(xpathObj);
crm_log_xml_trace(*result_cib, "Mixed result");
}
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
xmlChar *match_path = xmlGetNodePath(match);
crm_debug("Destroying %s", match_path);
free(match_path);
free_xml(match);
}
freeXpathObject(xpathObj);
}
return pcmk_ok;
}
static int
update_cib_object(xmlNode * parent, xmlNode * update)
{
int result = pcmk_ok;
xmlNode *target = NULL;
xmlNode *a_child = NULL;
const char *replace = NULL;
const char *object_id = NULL;
const char *object_name = NULL;
CRM_CHECK(update != NULL, return -EINVAL);
CRM_CHECK(parent != NULL, return -EINVAL);
object_name = (const char *) update->name;
CRM_CHECK(object_name != NULL, return -EINVAL);
object_id = pcmk__xe_id(update);
crm_trace("Processing update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " PCMK_XA_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
/* placeholder object */
target = find_xml_node(parent, object_name, FALSE);
} else {
target = pcmk__xe_match(parent, object_name, PCMK_XA_ID, object_id);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
}
crm_trace("Found node <%s%s%s%s> to update", object_name,
((object_id == NULL)? "" : " " PCMK_XA_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
// @COMPAT PCMK__XA__REPLACE is deprecated since 2.1.6
replace = crm_element_value(update, PCMK__XA_REPLACE);
if (replace != NULL) {
int last = 0;
int len = strlen(replace);
for (int lpc = 0; lpc <= len; ++lpc) {
if (replace[lpc] == ',' || replace[lpc] == 0) {
if (last != lpc) {
char *replace_item = strndup(replace + last, lpc - last);
xmlNode *remove = find_xml_node(target, replace_item,
FALSE);
if (remove != NULL) {
crm_trace("Replacing node <%s> in <%s>",
replace_item, target->name);
free_xml(remove);
}
free(replace_item);
}
last = lpc + 1;
}
}
xml_remove_prop(update, PCMK__XA_REPLACE);
xml_remove_prop(target, PCMK__XA_REPLACE);
}
copy_in_properties(target, update);
if (xml_acl_denied(target)) {
crm_notice("Cannot update <%s " PCMK_XA_ID "=%s>",
pcmk__s(object_name, "<null>"),
pcmk__s(object_id, "<null>"));
return -EACCES;
}
crm_trace("Processing children of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " PCMK_XA_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
for (a_child = pcmk__xml_first_child(update); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
const char *child_id = pcmk__xe_id(a_child);
int tmp_result = 0;
if (child_id != NULL) {
crm_trace("Updating child <%s " PCMK_XA_ID "='%s'>",
a_child->name, child_id);
} else {
crm_trace("Updating child <%s>", a_child->name);
}
tmp_result = update_cib_object(target, a_child);
/* only the first error is likely to be interesting */
if (tmp_result != pcmk_ok) {
if (child_id != NULL) {
crm_err("Error updating child <%s " PCMK_XA_ID "='%s'>",
a_child->name, child_id);
} else {
crm_err("Error updating child <%s>", a_child->name);
}
if (result == pcmk_ok) {
result = tmp_result;
}
}
}
crm_trace("Finished handling update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " PCMK_XA_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
return result;
}
static int
add_cib_object(xmlNode * parent, xmlNode * new_obj)
{
const char *object_name = NULL;
const char *object_id = NULL;
xmlNode *equiv_node = NULL;
if ((parent == NULL) || (new_obj == NULL)) {
return -EINVAL;
}
object_name = (const char *) new_obj->name;
if (object_name == NULL) {
return -EINVAL;
}
object_id = pcmk__xe_id(new_obj);
crm_trace("Processing creation of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " PCMK_XA_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
equiv_node = find_xml_node(parent, object_name, FALSE);
} else {
equiv_node = pcmk__xe_match(parent, object_name, PCMK_XA_ID, object_id);
}
if (equiv_node != NULL) {
return -EEXIST;
}
return update_cib_object(parent, new_obj);
}
static bool
update_results(xmlNode *failed, xmlNode *target, const char *operation,
int return_code)
{
xmlNode *xml_node = NULL;
bool was_error = false;
const char *error_msg = NULL;
if (return_code != pcmk_ok) {
error_msg = pcmk_strerror(return_code);
was_error = true;
xml_node = create_xml_node(failed, PCMK__XE_FAILED_UPDATE);
pcmk__xml_copy(xml_node, target);
crm_xml_add(xml_node, PCMK_XA_ID, pcmk__xe_id(target));
crm_xml_add(xml_node, PCMK_XA_OBJECT_TYPE, (const char *) target->name);
crm_xml_add(xml_node, PCMK_XA_OPERATION, operation);
crm_xml_add(xml_node, PCMK_XA_REASON, error_msg);
crm_warn("Action %s failed: %s (cde=%d)",
operation, error_msg, return_code);
}
return was_error;
}
int
cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *failed = NULL;
int result = pcmk_ok;
xmlNode *update_section = NULL;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, PCMK_XE_CIB)) {
section = NULL;
}
CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL);
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
if (section == NULL) {
return cib_process_modify(op, options, section, req, input, existing_cib, result_cib,
answer);
}
failed = create_xml_node(NULL, PCMK__XE_FAILED);
update_section = pcmk_find_cib_element(*result_cib, section);
if (pcmk__xe_is(input, section)) {
xmlNode *a_child = NULL;
for (a_child = pcmk__xml_first_child(input); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
result = add_cib_object(update_section, a_child);
if (update_results(failed, a_child, op, result)) {
break;
}
}
} else {
result = add_cib_object(update_section, input);
update_results(failed, input, op, result);
}
if ((result == pcmk_ok) && (failed->children != NULL)) {
result = -EINVAL;
}
if (result != pcmk_ok) {
crm_log_xml_err(failed, "CIB Update failures");
*answer = failed;
} else {
free_xml(failed);
}
return result;
}
int
cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
const char *originator = NULL;
if (req != NULL) {
originator = crm_element_value(req, PCMK__XA_SRC);
}
crm_trace("Processing \"%s\" event from %s%s",
op, originator,
(pcmk_is_set(options, cib_force_diff)? " (global update)" : ""));
if (*result_cib != existing_cib) {
free_xml(*result_cib);
}
*result_cib = pcmk__xml_copy(NULL, existing_cib);
return xml_apply_patchset(*result_cib, input, TRUE);
}
// @COMPAT: v1-only
bool
cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff)
{
int lpc = 0, max = 0;
bool config_changes = false;
xmlXPathObject *xpathObj = NULL;
int format = 1;
CRM_ASSERT(diff != NULL);
if (*diff == NULL && last != NULL && next != NULL) {
- *diff = diff_xml_object(last, next, FALSE);
+ *diff = pcmk__diff_v1_xml_object(last, next, false);
}
if (*diff == NULL) {
goto done;
}
crm_element_value_int(*diff, PCMK_XA_FORMAT, &format);
CRM_LOG_ASSERT(format == 1);
xpathObj = xpath_search(*diff, "//" PCMK_XE_CONFIGURATION);
if (numXpathResults(xpathObj) > 0) {
config_changes = true;
goto done;
}
freeXpathObject(xpathObj);
/*
* Do not check PCMK__XE_DIFF_ADDED "//" PCMK_XE_CIB
* This always contains every field and would produce a false positive
* every time if the checked value existed
*/
xpathObj = xpath_search(*diff, "//" PCMK__XE_DIFF_REMOVED "//" PCMK_XE_CIB);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *top = getXpathResult(xpathObj, lpc);
if (crm_element_value(top, PCMK_XA_EPOCH) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, PCMK_XA_ADMIN_EPOCH) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, PCMK_XA_VALIDATE_WITH) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, PCMK_XA_CRM_FEATURE_SET) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, PCMK_XA_REMOTE_CLEAR_PORT) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, PCMK_XA_REMOTE_TLS_PORT) != NULL) {
config_changes = true;
goto done;
}
}
done:
freeXpathObject(xpathObj);
return config_changes;
}
int
cib_process_xpath(const char *op, int options, const char *section,
const xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
int lpc = 0;
int max = 0;
int rc = pcmk_ok;
bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none);
xmlXPathObjectPtr xpathObj = NULL;
crm_trace("Processing \"%s\" event", op);
if (is_query) {
xpathObj = xpath_search(existing_cib, section);
} else {
xpathObj = xpath_search(*result_cib, section);
}
max = numXpathResults(xpathObj);
if ((max < 1)
&& pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
crm_debug("%s was already removed", section);
} else if (max < 1) {
crm_debug("%s: %s does not exist", op, section);
rc = -ENXIO;
} else if (is_query) {
if (max > 1) {
*answer = create_xml_node(NULL, PCMK__XE_XPATH_QUERY);
}
}
if (pcmk_is_set(options, cib_multiple)
&& pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
dedupXpathResults(xpathObj);
}
for (lpc = 0; lpc < max; lpc++) {
xmlChar *path = NULL;
xmlNode *match = getXpathResult(xpathObj, lpc);
if (match == NULL) {
continue;
}
path = xmlGetNodePath(match);
crm_debug("Processing %s op for %s with %s", op, section, path);
free(path);
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
if (match == *result_cib) {
/* Attempting to delete the whole "/cib" */
crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section);
rc = -EINVAL;
break;
}
free_xml(match);
if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) {
if (update_xml_child(match, input) == FALSE) {
rc = -ENXIO;
} else if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) {
pcmk__xml_copy(match, input);
break;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
if (options & cib_no_children) {
xmlNode *shallow = create_xml_node(*answer,
(const char *) match->name);
copy_in_properties(shallow, match);
if (*answer == NULL) {
*answer = shallow;
}
} else if (options & cib_xpath_address) {
char *path = NULL;
xmlNode *parent = match;
while (parent && parent->type == XML_ELEMENT_NODE) {
const char *id = crm_element_value(parent, PCMK_XA_ID);
char *new_path = NULL;
if (id) {
new_path = crm_strdup_printf("/%s[@" PCMK_XA_ID "='%s']"
"%s",
parent->name, id,
pcmk__s(path, ""));
} else {
new_path = crm_strdup_printf("/%s%s", parent->name,
pcmk__s(path, ""));
}
free(path);
path = new_path;
parent = parent->parent;
}
crm_trace("Got: %s", path);
if (*answer == NULL) {
*answer = create_xml_node(NULL, PCMK__XE_XPATH_QUERY);
}
parent = create_xml_node(*answer, PCMK__XE_XPATH_QUERY_PATH);
crm_xml_add(parent, PCMK_XA_ID, path);
free(path);
} else if (*answer) {
pcmk__xml_copy(*answer, match);
} else {
*answer = match;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE,
pcmk__str_none)) {
xmlNode *parent = match->parent;
free_xml(match);
pcmk__xml_copy(parent, input);
if ((options & cib_multiple) == 0) {
break;
}
}
}
freeXpathObject(xpathObj);
return rc;
}
diff --git a/lib/common/patchset.c b/lib/common/patchset.c
index 9b7894ba43..3f26bf4ac6 100644
--- a/lib/common/patchset.c
+++ b/lib/common/patchset.c
@@ -1,1517 +1,1523 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <bzlib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
#include "crmcommon_private.h"
static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
xmlNode *right, gboolean *changed);
/* Add changes for specified XML to patchset.
* For patchset format, refer to diff schema.
*/
static void
add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xmlNode *change = NULL;
xml_node_private_t *nodepriv = xml->_private;
const char *value = NULL;
if (nodepriv == NULL) {
/* Elements that shouldn't occur in a CIB don't have _private set. They
* should be stripped out, ignored, or have an error thrown by any code
* that processes their parent, so we ignore any changes to them.
*/
return;
}
// If this XML node is new, just report that
if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
GString *xpath = pcmk__element_xpath(xml->parent);
if (xpath != NULL) {
int position = pcmk__xml_position(xml, pcmk__xf_deleted);
change = create_xml_node(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
crm_xml_add_int(change, PCMK_XE_POSITION, position);
pcmk__xml_copy(change, xml);
g_string_free(xpath, TRUE);
}
return;
}
// Check each of the XML node's attributes for changes
for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
pIter = pIter->next) {
xmlNode *attr = NULL;
nodepriv = pIter->_private;
if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
continue;
}
if (change == NULL) {
GString *xpath = pcmk__element_xpath(xml);
if (xpath != NULL) {
change = create_xml_node(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
change = create_xml_node(change, PCMK_XE_CHANGE_LIST);
g_string_free(xpath, TRUE);
}
}
attr = create_xml_node(change, PCMK_XE_CHANGE_ATTR);
crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
if (nodepriv->flags & pcmk__xf_deleted) {
crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
} else {
crm_xml_add(attr, PCMK_XA_OPERATION, "set");
value = pcmk__xml_attr_value(pIter);
crm_xml_add(attr, PCMK_XA_VALUE, value);
}
}
if (change) {
xmlNode *result = NULL;
change = create_xml_node(change->parent, PCMK_XE_CHANGE_RESULT);
result = create_xml_node(change, (const char *)xml->name);
for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
pIter = pIter->next) {
nodepriv = pIter->_private;
if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
value = crm_element_value(xml, (const char *) pIter->name);
crm_xml_add(result, (const char *)pIter->name, value);
}
}
}
// Now recursively do the same for each child node of this node
for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
add_xml_changes_to_patchset(cIter, patchset);
}
nodepriv = xml->_private;
if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
GString *xpath = pcmk__element_xpath(xml);
crm_trace("%s.%s moved to position %d",
xml->name, pcmk__xe_id(xml),
pcmk__xml_position(xml, pcmk__xf_skip));
if (xpath != NULL) {
change = create_xml_node(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
crm_xml_add_int(change, PCMK_XE_POSITION,
pcmk__xml_position(xml, pcmk__xf_deleted));
g_string_free(xpath, TRUE);
}
}
}
static bool
is_config_change(xmlNode *xml)
{
GList *gIter = NULL;
xml_node_private_t *nodepriv = NULL;
xml_doc_private_t *docpriv;
xmlNode *config = first_named_child(xml, PCMK_XE_CONFIGURATION);
if (config) {
nodepriv = config->_private;
}
if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
return TRUE;
}
if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
docpriv = xml->doc->_private;
for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
pcmk__deleted_xml_t *deleted_obj = gIter->data;
if (strstr(deleted_obj->path,
"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
return TRUE;
}
}
}
return FALSE;
}
static void
xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
gboolean changed)
{
int lpc = 0;
xmlNode *cib = NULL;
xmlNode *diff_child = NULL;
const char *tag = NULL;
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
if (local_diff == NULL) {
crm_trace("Nothing to do");
return;
}
tag = PCMK__XE_DIFF_REMOVED;
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = PCMK_XE_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
const char *value = crm_element_value(last, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
if (changed || lpc == 2) {
crm_xml_add(cib, vfields[lpc], value);
}
}
tag = PCMK__XE_DIFF_ADDED;
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = PCMK_XE_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
const char *value = crm_element_value(next, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
}
for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
const char *p_value = pcmk__xml_attr_value(a);
xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
}
crm_log_xml_explicit(local_diff, "Repaired-diff");
}
static xmlNode *
xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
bool suppress)
{
- xmlNode *patchset = diff_xml_object(source, target, suppress);
+ xmlNode *patchset = pcmk__diff_v1_xml_object(source, target, suppress);
if (patchset) {
CRM_LOG_ASSERT(xml_document_dirty(target));
xml_repair_v1_diff(source, target, patchset, config);
crm_xml_add(patchset, PCMK_XA_FORMAT, "1");
}
return patchset;
}
static xmlNode *
xml_create_patchset_v2(xmlNode *source, xmlNode *target)
{
int lpc = 0;
GList *gIter = NULL;
xml_doc_private_t *docpriv;
xmlNode *v = NULL;
xmlNode *version = NULL;
xmlNode *patchset = NULL;
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
CRM_ASSERT(target);
if (!xml_document_dirty(target)) {
return NULL;
}
CRM_ASSERT(target->doc);
docpriv = target->doc->_private;
patchset = create_xml_node(NULL, PCMK_XE_DIFF);
crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
version = create_xml_node(patchset, PCMK_XE_VERSION);
v = create_xml_node(version, PCMK_XE_SOURCE);
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
const char *value = crm_element_value(source, vfields[lpc]);
if (value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
v = create_xml_node(version, PCMK_XE_TARGET);
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
const char *value = crm_element_value(target, vfields[lpc]);
if (value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
pcmk__deleted_xml_t *deleted_obj = gIter->data;
xmlNode *change = create_xml_node(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE);
crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
if (deleted_obj->position >= 0) {
crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
}
}
add_xml_changes_to_patchset(target, patchset);
return patchset;
}
xmlNode *
xml_create_patchset(int format, xmlNode *source, xmlNode *target,
bool *config_changed, bool manage_version)
{
int counter = 0;
bool config = FALSE;
xmlNode *patch = NULL;
const char *version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET);
xml_acl_disable(target);
if (!xml_document_dirty(target)) {
crm_trace("No change %d", format);
return NULL; /* No change */
}
config = is_config_change(target);
if (config_changed) {
*config_changed = config;
}
if (manage_version && config) {
crm_trace("Config changed %d", format);
crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0");
crm_element_value_int(target, PCMK_XA_EPOCH, &counter);
crm_xml_add_int(target, PCMK_XA_EPOCH, counter+1);
} else if (manage_version) {
crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter);
crm_trace("Status changed %d - %d %s", format, counter,
crm_element_value(source, PCMK_XA_NUM_UPDATES));
crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, (counter + 1));
}
if (format == 0) {
if (compare_version("3.0.8", version) < 0) {
format = 2;
} else {
format = 1;
}
crm_trace("Using patch format %d for version: %s", format, version);
}
switch (format) {
case 1:
patch = xml_create_patchset_v1(source, target, config, FALSE);
break;
case 2:
patch = xml_create_patchset_v2(source, target);
break;
default:
crm_err("Unknown patch format: %d", format);
return NULL;
}
return patch;
}
void
patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
bool with_digest)
{
int format = 1;
const char *version = NULL;
char *digest = NULL;
if ((patch == NULL) || (source == NULL) || (target == NULL)) {
return;
}
/* We should always call xml_accept_changes() before calculating a digest.
* Otherwise, with an on-tracking dirty target, we could get a wrong digest.
*/
CRM_LOG_ASSERT(!xml_document_dirty(target));
crm_element_value_int(patch, PCMK_XA_FORMAT, &format);
if ((format > 1) && !with_digest) {
return;
}
version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET);
digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
crm_xml_add(patch, PCMK__XA_DIGEST, digest);
free(digest);
return;
}
// Return true if attribute name is not \c PCMK_XML_ID
static bool
not_id(xmlAttrPtr attr, void *user_data)
{
return strcmp((const char *) attr->name, PCMK_XA_ID) != 0;
}
// Apply the removals section of an v1 patchset to an XML node
static void
process_v1_removals(xmlNode *target, xmlNode *patch)
{
xmlNode *patch_child = NULL;
xmlNode *cIter = NULL;
char *id = NULL;
const char *value = NULL;
if ((target == NULL) || (patch == NULL)) {
return;
}
if (target->type == XML_COMMENT_NODE) {
gboolean dummy;
subtract_xml_comment(target->parent, target, patch, &dummy);
}
CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return);
CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
pcmk__str_none),
return);
// Check for PCMK__XA_CRM_DIFF_MARKER in a child
id = crm_element_value_copy(target, PCMK_XA_ID);
value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER);
if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
crm_trace("We are the root of the deletion: %s.id=%s",
target->name, id);
free_xml(target);
free(id);
return;
}
// Removing then restoring id would change ordering of properties
pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
// Changes to child objects
cIter = pcmk__xml_first_child(target);
while (cIter) {
xmlNode *target_child = cIter;
cIter = pcmk__xml_next(cIter);
patch_child = pcmk__xml_match(patch, target_child, false);
process_v1_removals(target_child, patch_child);
}
free(id);
}
// Apply the additions section of an v1 patchset to an XML node
static void
process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
{
xmlNode *patch_child = NULL;
xmlNode *target_child = NULL;
xmlAttrPtr xIter = NULL;
const char *id = NULL;
const char *name = NULL;
const char *value = NULL;
if (patch == NULL) {
return;
} else if ((parent == NULL) && (target == NULL)) {
return;
}
// Check for PCMK__XA_CRM_DIFF_MARKER in a child
name = (const char *) patch->name;
value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER);
if ((target == NULL) && (value != NULL)
&& (strcmp(value, "added:top") == 0)) {
id = pcmk__xe_id(patch);
crm_trace("We are the root of the addition: %s.id=%s", name, id);
pcmk__xml_copy(parent, patch);
return;
} else if (target == NULL) {
id = pcmk__xe_id(patch);
crm_err("Could not locate: %s.id=%s", name, id);
return;
}
if (target->type == XML_COMMENT_NODE) {
pcmk__xc_update(parent, target, patch);
}
CRM_CHECK(pcmk__xe_is(target, name), return);
CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
pcmk__str_none),
return);
for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
xIter = xIter->next) {
const char *p_name = (const char *) xIter->name;
const char *p_value = pcmk__xml_attr_value(xIter);
xml_remove_prop(target, p_name); // Preserve patch order
crm_xml_add(target, p_name, p_value);
}
// Changes to child objects
for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
patch_child = pcmk__xml_next(patch_child)) {
target_child = pcmk__xml_match(target, patch_child, false);
process_v1_additions(target, target_child, patch_child);
}
}
/*!
* \internal
* \brief Find additions or removals in a patch set
*
* \param[in] patchset XML of patch
* \param[in] format Patch version
* \param[in] added TRUE if looking for additions, FALSE if removals
* \param[in,out] patch_node Will be set to node if found
*
* \return TRUE if format is valid, FALSE if invalid
*/
static bool
find_patch_xml_node(const xmlNode *patchset, int format, bool added,
xmlNode **patch_node)
{
xmlNode *cib_node;
const char *label;
switch (format) {
case 1:
label = added? PCMK__XE_DIFF_ADDED : PCMK__XE_DIFF_REMOVED;
*patch_node = find_xml_node(patchset, label, FALSE);
cib_node = find_xml_node(*patch_node, PCMK_XE_CIB, FALSE);
if (cib_node != NULL) {
*patch_node = cib_node;
}
break;
case 2:
label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE;
*patch_node = find_xml_node(patchset, PCMK_XE_VERSION, FALSE);
*patch_node = find_xml_node(*patch_node, label, FALSE);
break;
default:
crm_warn("Unknown patch format: %d", format);
*patch_node = NULL;
return FALSE;
}
return TRUE;
}
// Get CIB versions used for additions and deletions in a patchset
bool
xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
{
int lpc = 0;
int format = 1;
xmlNode *tmp = NULL;
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
/* Process removals */
if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
return -EINVAL;
}
if (tmp != NULL) {
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
}
}
/* Process additions */
if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
return -EINVAL;
}
if (tmp != NULL) {
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
}
}
return pcmk_ok;
}
/*!
* \internal
* \brief Check whether patchset can be applied to current CIB
*
* \param[in] xml Root of current CIB
* \param[in] patchset Patchset to check
*
* \return Standard Pacemaker return code
*/
static int
xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
{
int lpc = 0;
bool changed = FALSE;
int this[] = { 0, 0, 0 };
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
if (this[lpc] < 0) {
this[lpc] = 0;
}
}
/* Set some defaults in case nothing is present */
add[0] = this[0];
add[1] = this[1];
add[2] = this[2] + 1;
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
del[lpc] = this[lpc];
}
xml_patch_versions(patchset, add, del);
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
if (this[lpc] < del[lpc]) {
crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
vfields[lpc], this[0], this[1], this[2],
del[0], del[1], del[2], add[0], add[1], add[2]);
return pcmk_rc_diff_resync;
} else if (this[lpc] > del[lpc]) {
crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
vfields[lpc], this[0], this[1], this[2],
del[0], del[1], del[2], add[0], add[1], add[2], patchset);
crm_log_xml_info(patchset, "OldPatch");
return pcmk_rc_old_data;
}
}
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
if (add[lpc] > del[lpc]) {
changed = TRUE;
}
}
if (!changed) {
crm_notice("Versions did not change in patch %d.%d.%d",
add[0], add[1], add[2]);
return pcmk_rc_old_data;
}
crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
add[0], add[1], add[2], this[0], this[1], this[2]);
return pcmk_rc_ok;
}
// @COMPAT Remove when v1 patchsets are removed
static void
purge_v1_diff_markers(xmlNode *node)
{
xmlNode *child = NULL;
CRM_CHECK(node != NULL, return);
xml_remove_prop(node, PCMK__XA_CRM_DIFF_MARKER);
for (child = pcmk__xml_first_child(node); child != NULL;
child = pcmk__xml_next(child)) {
purge_v1_diff_markers(child);
}
}
/*!
* \internal
* \brief Apply a version 1 patchset to an XML node
*
* \param[in,out] xml XML to apply patchset to
* \param[in] patchset Patchset to apply
*
* \return Standard Pacemaker return code
*/
static int
apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
{
int rc = pcmk_rc_ok;
int root_nodes_seen = 0;
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(patchset, PCMK__XE_DIFF_ADDED, FALSE);
xmlNode *removed = find_xml_node(patchset, PCMK__XE_DIFF_REMOVED, FALSE);
xmlNode *old = pcmk__xml_copy(NULL, 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_v1_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 = pcmk__xe_id(cIter);
if ((cid == NULL) || (strcmp(cid, id) != 0)) {
continue;
}
}
// "position" makes sense only for XML comments for now
if ((cIter->type == XML_COMMENT_NODE)
&& (position >= 0)
&& (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
continue;
}
return cIter;
}
return NULL;
}
/*!
* \internal
* \brief Simplified, more efficient alternative to get_xpath_object()
*
* \param[in] top Root of XML to search
* \param[in] key Search xpath
* \param[in] target_position If deleting, where to delete
*
* \return XML child matching xpath if found, NULL otherwise
*
* \note This only works on simplified xpaths found in v2 patchset diffs,
* i.e. the only allowed search predicate is [@id='XXX'].
*/
static xmlNode *
search_v2_xpath(const xmlNode *top, const char *key, int target_position)
{
xmlNode *target = (xmlNode *) top->doc;
const char *current = key;
char *section;
char *remainder;
char *id;
char *tag;
char *path = NULL;
int rc;
size_t key_len;
CRM_CHECK(key != NULL, return NULL);
key_len = strlen(key);
/* These are scanned from key after a slash, so they can't be bigger
* than key_len - 1 characters plus a null terminator.
*/
remainder = calloc(key_len, sizeof(char));
CRM_ASSERT(remainder != NULL);
section = calloc(key_len, sizeof(char));
CRM_ASSERT(section != NULL);
id = calloc(key_len, sizeof(char));
CRM_ASSERT(id != NULL);
tag = calloc(key_len, sizeof(char));
CRM_ASSERT(tag != NULL);
do {
// Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
rc = sscanf(current, "/%[^/]%s", section, remainder);
if (rc > 0) {
// Separate FIRST_COMPONENT into TAG[@id='ID']
int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
int current_position = -1;
/* The target position is for the final component tag, so only use
* it if there is nothing left to search after this component.
*/
if ((rc == 1) && (target_position >= 0)) {
current_position = target_position;
}
switch (f) {
case 1:
target = first_matching_xml_child(target, tag, NULL,
current_position);
break;
case 2:
target = first_matching_xml_child(target, tag, id,
current_position);
break;
default:
// This should not be possible
target = NULL;
break;
}
current = remainder;
}
// Continue if something remains to search, and we've matched so far
} while ((rc == 2) && target);
if (target) {
crm_trace("Found %s for %s",
(path = (char *) xmlGetNodePath(target)), key);
free(path);
} else {
crm_debug("No match for %s", key);
}
free(remainder);
free(section);
free(tag);
free(id);
return target;
}
typedef struct xml_change_obj_s {
const xmlNode *change;
xmlNode *match;
} xml_change_obj_t;
static gint
sort_change_obj_by_position(gconstpointer a, gconstpointer b)
{
const xml_change_obj_t *change_obj_a = a;
const xml_change_obj_t *change_obj_b = b;
int position_a = -1;
int position_b = -1;
crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
if (position_a < position_b) {
return -1;
} else if (position_a > position_b) {
return 1;
}
return 0;
}
/*!
* \internal
* \brief Apply a version 2 patchset to an XML node
*
* \param[in,out] xml XML to apply patchset to
* \param[in] patchset Patchset to apply
*
* \return Standard Pacemaker return code
*/
static int
apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
{
int rc = pcmk_rc_ok;
const xmlNode *change = NULL;
GList *change_objs = NULL;
GList *gIter = NULL;
for (change = pcmk__xml_first_child(patchset); change != NULL;
change = pcmk__xml_next(change)) {
xmlNode *match = NULL;
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
int position = -1;
if (op == NULL) {
continue;
}
crm_trace("Processing %s %s", change->name, op);
/* PCMK_VALUE_DELETE changes for XML comments are generated with
* PCMK_XE_POSITION
*/
if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
crm_element_value_int(change, PCMK_XE_POSITION, &position);
}
match = search_v2_xpath(xml, xpath, position);
crm_trace("Performing %s on %s with %p", op, xpath, match);
if ((match == NULL) && (strcmp(op, PCMK_VALUE_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 (pcmk__str_any_of(op,
PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) {
// Delay the adding of a PCMK_VALUE_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, PCMK_VALUE_MOVE) == 0) {
// Temporarily put the PCMK_VALUE_MOVE object after the last sibling
if ((match->parent != NULL) && (match->parent->last != NULL)) {
xmlAddNextSibling(match->parent->last, match);
}
}
} else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
free_xml(match);
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
const xmlNode *child = first_named_child(change,
PCMK_XE_CHANGE_RESULT);
const xmlNode *attrs = pcmk__xml_first_child(child);
if (attrs == NULL) {
rc = ENOMSG;
continue;
}
pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
pIter = pIter->next) {
const char *name = (const char *) pIter->name;
const char *value = pcmk__xml_attr_value(pIter);
crm_xml_add(match, name, value);
}
} else {
crm_err("Unknown operation: %s", op);
rc = pcmk_rc_diff_failed;
}
}
// Changes should be generated in the right order. Double checking.
change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
for (gIter = change_objs; gIter; gIter = gIter->next) {
xml_change_obj_t *change_obj = gIter->data;
xmlNode *match = change_obj->match;
const char *op = NULL;
const char *xpath = NULL;
change = change_obj->change;
op = crm_element_value(change, PCMK_XA_OPERATION);
xpath = crm_element_value(change, PCMK_XA_PATH);
crm_trace("Continue performing %s on %s with %p", op, xpath, match);
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
int position = 0;
xmlNode *child = NULL;
xmlNode *match_child = NULL;
match_child = match->children;
crm_element_value_int(change, PCMK_XE_POSITION, &position);
while ((match_child != NULL)
&& (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
child = xmlDocCopyNode(change->children, match->doc, 1);
if (child == NULL) {
return ENOMEM;
}
if (match_child) {
crm_trace("Adding %s at position %d", child->name, position);
xmlAddPrevSibling(match_child, child);
} else if (match->last) {
crm_trace("Adding %s at position %d (end)",
child->name, position);
xmlAddNextSibling(match->last, child);
} else {
crm_trace("Adding %s at position %d (first)",
child->name, position);
CRM_LOG_ASSERT(position == 0);
xmlAddChild(match, child);
}
pcmk__mark_xml_created(child);
} else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
int position = 0;
crm_element_value_int(change, PCMK_XE_POSITION, &position);
if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
xmlNode *match_child = NULL;
int p = position;
if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
p++; // Skip ourselves
}
CRM_ASSERT(match->parent != NULL);
match_child = match->parent->children;
while ((match_child != NULL)
&& (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
match->name, position,
pcmk__xml_position(match, pcmk__xf_skip),
match->prev, (match_child? "next":"last"),
(match_child? match_child : match->parent->last));
if (match_child) {
xmlAddPrevSibling(match_child, match);
} else {
CRM_ASSERT(match->parent->last != NULL);
xmlAddNextSibling(match->parent->last, match);
}
} else {
crm_trace("%s is already in position %d",
match->name, position);
}
if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
crm_err("Moved %s.%s to position %d instead of %d (%p)",
match->name, pcmk__xe_id(match),
pcmk__xml_position(match, pcmk__xf_skip),
position, match->prev);
rc = pcmk_rc_diff_failed;
}
}
}
g_list_free_full(change_objs, free);
return rc;
}
int
xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int format = 1;
int rc = pcmk_ok;
xmlNode *old = NULL;
const char *digest = NULL;
if (patchset == NULL) {
return rc;
}
pcmk__log_xml_patchset(LOG_TRACE, patchset);
if (check_version) {
rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
if (rc != pcmk_ok) {
return rc;
}
}
digest = crm_element_value(patchset, PCMK__XA_DIGEST);
if (digest != NULL) {
/* Make original XML available for logging in case result doesn't have
* expected digest
*/
pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
}
if (rc == pcmk_ok) {
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
break;
case 2:
rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
break;
default:
crm_err("Unknown patch format: %d", format);
rc = -EINVAL;
}
}
if ((rc == pcmk_ok) && (digest != NULL)) {
char *new_digest = NULL;
char *version = crm_element_value_copy(xml, PCMK_XA_CRM_FEATURE_SET);
new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
crm_info("v%d digest mis-match: expected %s, calculated %s",
format, digest, new_digest);
rc = -pcmk_err_diff_failed;
pcmk__if_tracing(
{
save_xml_to_file(old, "PatchDigest:input", NULL);
save_xml_to_file(xml, "PatchDigest:result", NULL);
save_xml_to_file(patchset, "PatchDigest:diff", NULL);
},
{}
);
} else {
crm_trace("v%d digest matched: expected %s, calculated %s",
format, digest, new_digest);
}
free(new_digest);
free(version);
}
free_xml(old);
return rc;
}
xmlNode *
-diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
+pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
{
xmlNode *tmp1 = NULL;
xmlNode *diff = create_xml_node(NULL, PCMK_XE_DIFF);
xmlNode *removed = create_xml_node(diff, PCMK__XE_DIFF_REMOVED);
xmlNode *added = create_xml_node(diff, PCMK__XE_DIFF_ADDED);
crm_xml_add(diff, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
- tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
+ 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");
+ 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;
}
+xmlNode *
+diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
+{
+ return pcmk__diff_v1_xml_object(old, new, suppress);
+}
+
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 = pcmk__xml_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 = pcmk__xe_id(left);
name = (const char *) left->name;
if (right == NULL) {
xmlNode *deleted = NULL;
crm_trace("Processing <%s " PCMK_XA_ID "=%s> (complete copy)",
name, id);
deleted = pcmk__xml_copy(parent, left);
crm_xml_add(deleted, PCMK__XA_CRM_DIFF_MARKER, marker);
*changed = TRUE;
return deleted;
}
CRM_CHECK(name != NULL, return NULL);
CRM_CHECK(pcmk__xe_is(left, (const char *) right->name), return NULL);
// Check for PCMK__XA_CRM_DIFF_MARKER in a child
value = crm_element_value(right, PCMK__XA_CRM_DIFF_MARKER);
if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
crm_trace("We are the root of the deletion: %s.id=%s", name, id);
*changed = TRUE;
return NULL;
}
// @TODO Avoiding creating the full hierarchy would save work here
diff = create_xml_node(parent, name);
// Changes to child objects
for (left_child = pcmk__xml_first_child(left); left_child != NULL;
left_child = pcmk__xml_next(left_child)) {
gboolean child_changed = FALSE;
right_child = pcmk__xml_match(right, left_child, false);
subtract_xml_object(diff, left_child, right_child, full, &child_changed,
marker);
if (child_changed) {
*changed = TRUE;
}
}
if (!*changed) {
/* Nothing to do */
} else if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = pcmk__xml_attr_value(pIter);
xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
}
// We have everything we need
goto done;
}
// Changes to name/value pairs
for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
xIter = xIter->next) {
const char *prop_name = (const char *) xIter->name;
xmlAttrPtr right_attr = NULL;
xml_node_private_t *nodepriv = NULL;
if (strcmp(prop_name, PCMK_XA_ID) == 0) {
// id already obtained when present ~ this case, so just reuse
xmlSetProp(diff, (pcmkXmlStr) PCMK_XA_ID, (pcmkXmlStr) id);
continue;
}
if (pcmk__xa_filterable(prop_name)) {
continue;
}
right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
if (right_attr) {
nodepriv = right_attr->_private;
}
right_val = crm_element_value(right, prop_name);
if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) {
/* new */
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
pIter = pIter->next) {
const char *p_name = (const char *) pIter->name;
const char *p_value = pcmk__xml_attr_value(pIter);
xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
}
break;
} else {
const char *left_value = pcmk__xml_attr_value(xIter);
xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
crm_xml_add(diff, prop_name, left_value);
}
} else {
/* Only now do we need the left value */
const char *left_value = pcmk__xml_attr_value(xIter);
if (strcmp(left_value, right_val) == 0) {
/* unchanged */
} else {
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
crm_trace("Changes detected to %s in "
"<%s " PCMK_XA_ID "=%s>", prop_name, name, id);
for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
pIter = pIter->next) {
const char *p_name = (const char *) pIter->name;
const char *p_value = pcmk__xml_attr_value(pIter);
xmlSetProp(diff, (pcmkXmlStr) p_name,
(pcmkXmlStr) p_value);
}
break;
} else {
crm_trace("Changes detected to %s (%s -> %s) in "
"<%s " PCMK_XA_ID "=%s>",
prop_name, left_value, right_val, name, id);
crm_xml_add(diff, prop_name, left_value);
}
}
}
}
if (!*changed) {
free_xml(diff);
return NULL;
} else if (!full && (id != NULL)) {
crm_xml_add(diff, PCMK_XA_ID, id);
}
done:
return diff;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
gboolean
apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
{
gboolean result = TRUE;
int root_nodes_seen = 0;
const char *digest = crm_element_value(diff, PCMK__XA_DIGEST);
const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(diff, PCMK__XE_DIFF_ADDED, FALSE);
xmlNode *removed = find_xml_node(diff, PCMK__XE_DIFF_REMOVED, FALSE);
CRM_CHECK(new_xml != NULL, return FALSE);
crm_trace("Subtraction Phase");
for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
child_diff = pcmk__xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, result = FALSE);
if (root_nodes_seen == 0) {
*new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
NULL, NULL);
}
root_nodes_seen++;
}
if (root_nodes_seen == 0) {
*new_xml = pcmk__xml_copy(NULL, 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_v1_diff_markers(*new_xml); // Purge now so diff is ok
new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
version);
if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
crm_info("Digest mis-match: expected %s, calculated %s",
digest, new_digest);
result = FALSE;
pcmk__if_tracing(
{
save_xml_to_file(old_xml, "diff:original", NULL);
save_xml_to_file(diff, "diff:input", NULL);
save_xml_to_file(*new_xml, "diff:new", NULL);
},
{}
);
} else {
crm_trace("Digest matched: expected %s, calculated %s",
digest, new_digest);
}
free(new_digest);
} else if (result) {
purge_v1_diff_markers(*new_xml); // Purge now so diff is ok
}
return result;
}
void
purge_diff_markers(xmlNode *a_node)
{
purge_v1_diff_markers(a_node);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 12:49 AM (5 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009353
Default Alt Text
(97 KB)

Event Timeline