Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index 73acb165f3..b5042a8bae 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -1,596 +1,597 @@
/*
* 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 <stdint.h> // uint32_t
#include <stdio.h>
#include <string.h>
#include <crm/crm.h> /* transitively imports qblog.h */
#include <crm/common/output_internal.h>
#include <crm/common/xml_io_internal.h>
#include <crm/common/xml_names_internal.h> // PCMK__XE_PROMOTABLE_LEGACY
#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),
// @COMPAT Can we start including text nodes unconditionally?
//! 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),
};
void pcmk__xml_init(void);
+void pcmk__xml_cleanup(void);
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(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_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 \
"[@" PCMK_XA_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 \
"[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \
"[@" PCMK_XA_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 "='" PCMK_VALUE_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_first_child(const xmlNode *parent, const char *node_name,
const char *attr_n, const char *attr_v);
void pcmk__xe_remove_attr(xmlNode *element, const char *name);
bool pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data);
void pcmk__xe_remove_matching_attrs(xmlNode *element,
bool (*match)(xmlAttrPtr, void *),
void *user_data);
int pcmk__xe_delete_match(xmlNode *xml, xmlNode *search);
int pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace);
int pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags);
GString *pcmk__element_xpath(const xmlNode *xml);
/*!
* \internal
* \enum pcmk__xml_escape_type
* \brief Indicators of which XML characters to escape
*
* XML allows the escaping of special characters by replacing them with entity
* references (for example, <tt>"&quot;"</tt>) or character references (for
* example, <tt>"&#13;"</tt>).
*
* The special characters <tt>'&'</tt> (except as the beginning of an entity
* reference) and <tt>'<'</tt> are not allowed in their literal forms in XML
* character data. Character data is non-markup text (for example, the content
* of a text node). <tt>'>'</tt> is allowed under most circumstances; we escape
* it for safety and symmetry.
*
* For more details, see the "Character Data and Markup" section of the XML
* spec, currently section 2.4:
* https://www.w3.org/TR/xml/#dt-markup
*
* Attribute values are handled specially.
* * If an attribute value is delimited by single quotes, then single quotes
* must be escaped within the value.
* * Similarly, if an attribute value is delimited by double quotes, then double
* quotes must be escaped within the value.
* * A conformant XML processor replaces a literal whitespace character (tab,
* newline, carriage return, space) in an attribute value with a space
* (\c '#x20') character. However, a reference to a whitespace character (for
* example, \c "&#x0A;" for \c '\n') does not get replaced.
* * For more details, see the "Attribute-Value Normalization" section of the
* XML spec, currently section 3.3.3. Note that the default attribute type
* is CDATA; we don't deal with NMTOKENS, etc.:
* https://www.w3.org/TR/xml/#AVNormalize
*
* Pacemaker always delimits attribute values with double quotes, so there's no
* need to escape single quotes.
*
* Newlines and tabs should be escaped in attribute values when XML is
* serialized to text, so that future parsing preserves them rather than
* normalizing them to spaces.
*
* We always escape carriage returns, so that they're not converted to spaces
* during attribute-value normalization and because displaying them as literals
* is messy.
*/
enum pcmk__xml_escape_type {
/*!
* For text nodes.
* * Escape \c '<', \c '>', and \c '&' using entity references.
* * Do not escape \c '\n' and \c '\t'.
* * Escape other non-printing characters using character references.
*/
pcmk__xml_escape_text,
/*!
* For attribute values.
* * Escape \c '<', \c '>', \c '&', and \c '"' using entity references.
* * Escape \c '\n', \c '\t', and other non-printing characters using
* character references.
*/
pcmk__xml_escape_attr,
/* @COMPAT Drop escaping of at least '\n' and '\t' for
* pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip,
* and openstack-virtual-ip resource agents no longer depend on it.
*
* At time of writing, openstack-info may set a multiline value for the
* openstack_ports node attribute. The other two agents query the value and
* require it to be on one line with no spaces.
*/
/*!
* For attribute values displayed in text output delimited by double quotes.
* * Escape \c '\n' as \c "\\n"
* * Escape \c '\r' as \c "\\r"
* * Escape \c '\t' as \c "\\t"
* * Escape \c '"' as \c "\\""
*/
pcmk__xml_escape_attr_pretty,
};
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type);
char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type);
/*!
* \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 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__xe_create(xmlNode *parent, const char *name);
xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src);
xmlNode *pcmk__xe_next_same(const xmlNode *node);
void pcmk__xe_set_content(xmlNode *node, const char *format, ...)
G_GNUC_PRINTF(2, 3);
/*!
* \internal
* \enum pcmk__xa_flags
* \brief Flags for operations affecting XML attributes
*/
enum pcmk__xa_flags {
//! Flag has no effect
pcmk__xaf_none = 0U,
//! Don't overwrite existing values
pcmk__xaf_no_overwrite = (1U << 0),
/*!
* Treat values as score updates where possible (see
* \c pcmk__xe_set_score())
*/
pcmk__xaf_score_update = (1U << 1),
};
int pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags);
/*!
* \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
* \brief Print an informational message if an xpath query returned multiple
* items with the same ID.
*
* \param[in,out] out The output object
* \param[in] search The xpath search result, most typically the result of
* calling cib->cmds->query().
* \param[in] name The name searched for
*/
void
pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search,
const char *name);
/* 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);
bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
void *user_data);
static inline const char *
pcmk__xml_attr_value(const xmlAttr *attr)
{
return ((attr == NULL) || (attr->children == NULL))? NULL
: (const char *) attr->children->content;
}
// @COMPAT Remove when v1 patchsets are removed
xmlNode *pcmk__diff_v1_xml_object(xmlNode *left, xmlNode *right, bool suppress);
// @COMPAT Drop when PCMK__XE_PROMOTABLE_LEGACY is removed
static inline const char *
pcmk__map_element_name(const xmlNode *xml)
{
if (xml == NULL) {
return NULL;
} else if (pcmk__xe_is(xml, PCMK__XE_PROMOTABLE_LEGACY)) {
return PCMK_XE_CLONE;
} else {
return (const char *) xml->name;
}
}
#endif // PCMK__XML_INTERNAL__H
diff --git a/lib/common/results.c b/lib/common/results.c
index 396ac0d2f3..67b02300be 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,1144 +1,1144 @@
/*
* 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>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <bzlib.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <qb/qbdefs.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
G_DEFINE_QUARK(pcmk-rc-error-quark, pcmk__rc_error)
G_DEFINE_QUARK(pcmk-exitc-error-quark, pcmk__exitc_error)
// General (all result code types)
/*!
* \brief Get the name and description of a given result code
*
* A result code can be interpreted as a member of any one of several families.
*
* \param[in] code The result code to look up
* \param[in] type How \p code should be interpreted
* \param[out] name Where to store the result code's name
* \param[out] desc Where to store the result code's description
*
* \return Standard Pacemaker return code
*/
int
pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name,
const char **desc)
{
const char *code_name = NULL;
const char *code_desc = NULL;
switch (type) {
case pcmk_result_legacy:
code_name = pcmk_errorname(code);
code_desc = pcmk_strerror(code);
break;
case pcmk_result_rc:
code_name = pcmk_rc_name(code);
code_desc = pcmk_rc_str(code);
break;
case pcmk_result_exitcode:
code_name = crm_exit_name(code);
code_desc = crm_exit_str((crm_exit_t) code);
break;
default:
return pcmk_rc_undetermined;
}
if (name != NULL) {
*name = code_name;
}
if (desc != NULL) {
*desc = code_desc;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Get the lower and upper bounds of a result code family
*
* \param[in] type Type of result code
* \param[out] lower Where to store the lower bound
* \param[out] upper Where to store the upper bound
*
* \return Standard Pacemaker return code
*
* \note There is no true upper bound on standard Pacemaker return codes or
* legacy return codes. All system \p errno values are valid members of
* these result code families, and there is no global upper limit nor a
* constant by which to refer to the highest \p errno value on a given
* system.
*/
int
pcmk__result_bounds(enum pcmk_result_type type, int *lower, int *upper)
{
CRM_ASSERT((lower != NULL) && (upper != NULL));
switch (type) {
case pcmk_result_legacy:
*lower = pcmk_ok;
*upper = 256; // should be enough for almost any system error code
break;
case pcmk_result_rc:
*lower = pcmk_rc_error - pcmk__n_rc + 1;
*upper = 256;
break;
case pcmk_result_exitcode:
*lower = CRM_EX_OK;
*upper = CRM_EX_MAX;
break;
default:
*lower = 0;
*upper = -1;
return pcmk_rc_undetermined;
}
return pcmk_rc_ok;
}
// @COMPAT Legacy function return codes
//! \deprecated Use standard return codes and pcmk_rc_name() instead
const char *
pcmk_errorname(int rc)
{
rc = abs(rc);
switch (rc) {
case pcmk_err_generic: return "pcmk_err_generic";
case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
case pcmk_err_old_data: return "pcmk_err_old_data";
case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
case pcmk_err_cib_save: return "pcmk_err_cib_save";
case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
case pcmk_err_multiple: return "pcmk_err_multiple";
case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
case pcmk_err_already: return "pcmk_err_already";
case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
default: return pcmk_rc_name(rc); // system errno
}
}
//! \deprecated Use standard return codes and pcmk_rc_str() instead
const char *
pcmk_strerror(int rc)
{
return pcmk_rc_str(pcmk_legacy2rc(rc));
}
// Standard Pacemaker API return codes
/* This array is used only for nonzero values of pcmk_rc_e. Its values must be
* kept in the exact reverse order of the enum value numbering (i.e. add new
* values to the end of the array).
*/
static const struct pcmk__rc_info {
const char *name;
const char *desc;
int legacy_rc;
} pcmk__rcs[] = {
{ "pcmk_rc_error",
"Error",
-pcmk_err_generic,
},
{ "pcmk_rc_unknown_format",
"Unknown output format",
-pcmk_err_unknown_format,
},
{ "pcmk_rc_bad_nvpair",
"Bad name/value pair given",
-pcmk_err_bad_nvpair,
},
{ "pcmk_rc_already",
"Already in requested state",
-pcmk_err_already,
},
{ "pcmk_rc_node_unknown",
"Node not found",
-pcmk_err_node_unknown,
},
{ "pcmk_rc_multiple",
"Resource active on multiple nodes",
-pcmk_err_multiple,
},
{ "pcmk_rc_cib_corrupt",
"Could not parse on-disk configuration",
-pcmk_err_cib_corrupt,
},
{ "pcmk_rc_cib_save",
"Could not save new configuration to disk",
-pcmk_err_cib_save,
},
{ "pcmk_rc_cib_backup",
"Could not archive previous configuration",
-pcmk_err_cib_backup,
},
{ "pcmk_rc_cib_modified",
"On-disk configuration was manually modified",
-pcmk_err_cib_modified,
},
{ "pcmk_rc_diff_resync",
"Application of update diff failed, requesting full refresh",
-pcmk_err_diff_resync,
},
{ "pcmk_rc_diff_failed",
"Application of update diff failed",
-pcmk_err_diff_failed,
},
{ "pcmk_rc_old_data",
"Update was older than existing configuration",
-pcmk_err_old_data,
},
{ "pcmk_rc_transform_failed",
"Schema transform failed",
-pcmk_err_transform_failed,
},
{ "pcmk_rc_schema_unchanged",
"Schema is already the latest available",
-pcmk_err_schema_unchanged,
},
{ "pcmk_rc_schema_validation",
"Update does not conform to the configured schema",
-pcmk_err_schema_validation,
},
{ "pcmk_rc_no_quorum",
"Operation requires quorum",
-pcmk_err_no_quorum,
},
{ "pcmk_rc_ipc_unauthorized",
"IPC server is blocked by unauthorized process",
-pcmk_err_generic,
},
{ "pcmk_rc_ipc_unresponsive",
"IPC server is unresponsive",
-pcmk_err_generic,
},
{ "pcmk_rc_ipc_pid_only",
"IPC server process is active but not accepting connections",
-pcmk_err_generic,
},
{ "pcmk_rc_op_unsatisfied",
"Not applicable under current conditions",
-pcmk_err_generic,
},
{ "pcmk_rc_undetermined",
"Result undetermined",
-pcmk_err_generic,
},
{ "pcmk_rc_before_range",
"Result occurs before given range",
-pcmk_err_generic,
},
{ "pcmk_rc_within_range",
"Result occurs within given range",
-pcmk_err_generic,
},
{ "pcmk_rc_after_range",
"Result occurs after given range",
-pcmk_err_generic,
},
{ "pcmk_rc_no_output",
"Output message produced no output",
-pcmk_err_generic,
},
{ "pcmk_rc_no_input",
"Input file not available",
-pcmk_err_generic,
},
{ "pcmk_rc_underflow",
"Value too small to be stored in data type",
-pcmk_err_generic,
},
{ "pcmk_rc_dot_error",
"Error writing dot(1) file",
-pcmk_err_generic,
},
{ "pcmk_rc_graph_error",
"Error writing graph file",
-pcmk_err_generic,
},
{ "pcmk_rc_invalid_transition",
"Cluster simulation produced invalid transition",
-pcmk_err_generic,
},
{ "pcmk_rc_unpack_error",
"Unable to parse CIB XML",
-pcmk_err_generic,
},
{ "pcmk_rc_duplicate_id",
"Two or more XML elements have the same ID",
-pcmk_err_generic,
},
{ "pcmk_rc_disabled",
"Disabled",
-pcmk_err_generic,
},
{ "pcmk_rc_bad_input",
"Bad input value provided",
-pcmk_err_generic,
},
{ "pcmk_rc_bad_xml_patch",
"Bad XML patch format",
-pcmk_err_generic,
},
{ "pcmk_rc_no_transaction",
"No active transaction found",
-pcmk_err_generic,
},
{ "pcmk_rc_ns_resolution",
"Nameserver resolution error",
-pcmk_err_generic,
},
{ "pcmk_rc_compression",
"Compression/decompression error",
-pcmk_err_generic,
},
};
/*!
* \internal
* \brief The number of <tt>enum pcmk_rc_e</tt> values, excluding \c pcmk_rc_ok
*
* This constant stores the number of negative standard Pacemaker return codes.
* These represent Pacemaker-custom error codes. The count does not include
* positive system error numbers, nor does it include \c pcmk_rc_ok (success).
*/
const size_t pcmk__n_rc = PCMK__NELEM(pcmk__rcs);
/*!
* \brief Get a return code constant name as a string
*
* \param[in] rc Integer return code to convert
*
* \return String of constant name corresponding to rc
*/
const char *
pcmk_rc_name(int rc)
{
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].name;
}
switch (rc) {
case pcmk_rc_ok: return "pcmk_rc_ok";
case E2BIG: return "E2BIG";
case EACCES: return "EACCES";
case EADDRINUSE: return "EADDRINUSE";
case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
case EAFNOSUPPORT: return "EAFNOSUPPORT";
case EAGAIN: return "EAGAIN";
case EALREADY: return "EALREADY";
case EBADF: return "EBADF";
case EBADMSG: return "EBADMSG";
case EBUSY: return "EBUSY";
case ECANCELED: return "ECANCELED";
case ECHILD: return "ECHILD";
case ECOMM: return "ECOMM";
case ECONNABORTED: return "ECONNABORTED";
case ECONNREFUSED: return "ECONNREFUSED";
case ECONNRESET: return "ECONNRESET";
/* case EDEADLK: return "EDEADLK"; */
case EDESTADDRREQ: return "EDESTADDRREQ";
case EDOM: return "EDOM";
case EDQUOT: return "EDQUOT";
case EEXIST: return "EEXIST";
case EFAULT: return "EFAULT";
case EFBIG: return "EFBIG";
case EHOSTDOWN: return "EHOSTDOWN";
case EHOSTUNREACH: return "EHOSTUNREACH";
case EIDRM: return "EIDRM";
case EILSEQ: return "EILSEQ";
case EINPROGRESS: return "EINPROGRESS";
case EINTR: return "EINTR";
case EINVAL: return "EINVAL";
case EIO: return "EIO";
case EISCONN: return "EISCONN";
case EISDIR: return "EISDIR";
case ELIBACC: return "ELIBACC";
case ELOOP: return "ELOOP";
case EMFILE: return "EMFILE";
case EMLINK: return "EMLINK";
case EMSGSIZE: return "EMSGSIZE";
#ifdef EMULTIHOP // Not available on OpenBSD
case EMULTIHOP: return "EMULTIHOP";
#endif
case ENAMETOOLONG: return "ENAMETOOLONG";
case ENETDOWN: return "ENETDOWN";
case ENETRESET: return "ENETRESET";
case ENETUNREACH: return "ENETUNREACH";
case ENFILE: return "ENFILE";
case ENOBUFS: return "ENOBUFS";
case ENODATA: return "ENODATA";
case ENODEV: return "ENODEV";
case ENOENT: return "ENOENT";
case ENOEXEC: return "ENOEXEC";
case ENOKEY: return "ENOKEY";
case ENOLCK: return "ENOLCK";
#ifdef ENOLINK // Not available on OpenBSD
case ENOLINK: return "ENOLINK";
#endif
case ENOMEM: return "ENOMEM";
case ENOMSG: return "ENOMSG";
case ENOPROTOOPT: return "ENOPROTOOPT";
case ENOSPC: return "ENOSPC";
#ifdef ENOSR
case ENOSR: return "ENOSR";
#endif
#ifdef ENOSTR
case ENOSTR: return "ENOSTR";
#endif
case ENOSYS: return "ENOSYS";
case ENOTBLK: return "ENOTBLK";
case ENOTCONN: return "ENOTCONN";
case ENOTDIR: return "ENOTDIR";
case ENOTEMPTY: return "ENOTEMPTY";
case ENOTSOCK: return "ENOTSOCK";
#if ENOTSUP != EOPNOTSUPP
case ENOTSUP: return "ENOTSUP";
#endif
case ENOTTY: return "ENOTTY";
case ENOTUNIQ: return "ENOTUNIQ";
case ENXIO: return "ENXIO";
case EOPNOTSUPP: return "EOPNOTSUPP";
case EOVERFLOW: return "EOVERFLOW";
case EPERM: return "EPERM";
case EPFNOSUPPORT: return "EPFNOSUPPORT";
case EPIPE: return "EPIPE";
case EPROTO: return "EPROTO";
case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
case EPROTOTYPE: return "EPROTOTYPE";
case ERANGE: return "ERANGE";
case EREMOTE: return "EREMOTE";
case EREMOTEIO: return "EREMOTEIO";
case EROFS: return "EROFS";
case ESHUTDOWN: return "ESHUTDOWN";
case ESPIPE: return "ESPIPE";
case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
case ESRCH: return "ESRCH";
case ESTALE: return "ESTALE";
case ETIME: return "ETIME";
case ETIMEDOUT: return "ETIMEDOUT";
case ETXTBSY: return "ETXTBSY";
#ifdef EUNATCH
case EUNATCH: return "EUNATCH";
#endif
case EUSERS: return "EUSERS";
/* case EWOULDBLOCK: return "EWOULDBLOCK"; */
case EXDEV: return "EXDEV";
#ifdef EBADE // Not available on OS X
case EBADE: return "EBADE";
case EBADFD: return "EBADFD";
case EBADSLT: return "EBADSLT";
case EDEADLOCK: return "EDEADLOCK";
case EBADR: return "EBADR";
case EBADRQC: return "EBADRQC";
case ECHRNG: return "ECHRNG";
#ifdef EISNAM // Not available on OS X, Illumos, Solaris
case EISNAM: return "EISNAM";
case EKEYEXPIRED: return "EKEYEXPIRED";
case EKEYREVOKED: return "EKEYREVOKED";
#endif
case EKEYREJECTED: return "EKEYREJECTED";
case EL2HLT: return "EL2HLT";
case EL2NSYNC: return "EL2NSYNC";
case EL3HLT: return "EL3HLT";
case EL3RST: return "EL3RST";
case ELIBBAD: return "ELIBBAD";
case ELIBMAX: return "ELIBMAX";
case ELIBSCN: return "ELIBSCN";
case ELIBEXEC: return "ELIBEXEC";
#ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
case ENOMEDIUM: return "ENOMEDIUM";
case EMEDIUMTYPE: return "EMEDIUMTYPE";
#endif
case ENONET: return "ENONET";
case ENOPKG: return "ENOPKG";
case EREMCHG: return "EREMCHG";
case ERESTART: return "ERESTART";
case ESTRPIPE: return "ESTRPIPE";
#ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
case EUCLEAN: return "EUCLEAN";
#endif
case EXFULL: return "EXFULL";
#endif // EBADE
default: return "Unknown";
}
}
/*!
* \brief Get a user-friendly description of a return code
*
* \param[in] rc Integer return code to convert
*
* \return String description of rc
*/
const char *
pcmk_rc_str(int rc)
{
if (rc == pcmk_rc_ok) {
return "OK";
}
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].desc;
}
if (rc < 0) {
return "Error";
}
// Handle values that could be defined by system or by portability.h
switch (rc) {
#ifdef PCMK__ENOTUNIQ
case ENOTUNIQ: return "Name not unique on network";
#endif
#ifdef PCMK__ECOMM
case ECOMM: return "Communication error on send";
#endif
#ifdef PCMK__ELIBACC
case ELIBACC: return "Can not access a needed shared library";
#endif
#ifdef PCMK__EREMOTEIO
case EREMOTEIO: return "Remote I/O error";
#endif
#ifdef PCMK__ENOKEY
case ENOKEY: return "Required key not available";
#endif
#ifdef PCMK__ENODATA
case ENODATA: return "No data available";
#endif
#ifdef PCMK__ETIME
case ETIME: return "Timer expired";
#endif
#ifdef PCMK__EKEYREJECTED
case EKEYREJECTED: return "Key was rejected by service";
#endif
default: return strerror(rc);
}
}
// This returns negative values for errors
//! \deprecated Use standard return codes instead
int
pcmk_rc2legacy(int rc)
{
if (rc >= 0) {
return -rc; // OK or system errno
}
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
}
return -pcmk_err_generic;
}
//! \deprecated Use standard return codes instead
int
pcmk_legacy2rc(int legacy_rc)
{
legacy_rc = abs(legacy_rc);
switch (legacy_rc) {
case pcmk_err_no_quorum: return pcmk_rc_no_quorum;
case pcmk_err_schema_validation: return pcmk_rc_schema_validation;
case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged;
case pcmk_err_transform_failed: return pcmk_rc_transform_failed;
case pcmk_err_old_data: return pcmk_rc_old_data;
case pcmk_err_diff_failed: return pcmk_rc_diff_failed;
case pcmk_err_diff_resync: return pcmk_rc_diff_resync;
case pcmk_err_cib_modified: return pcmk_rc_cib_modified;
case pcmk_err_cib_backup: return pcmk_rc_cib_backup;
case pcmk_err_cib_save: return pcmk_rc_cib_save;
case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt;
case pcmk_err_multiple: return pcmk_rc_multiple;
case pcmk_err_node_unknown: return pcmk_rc_node_unknown;
case pcmk_err_already: return pcmk_rc_already;
case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair;
case pcmk_err_unknown_format: return pcmk_rc_unknown_format;
case pcmk_err_generic: return pcmk_rc_error;
case pcmk_ok: return pcmk_rc_ok;
default: return legacy_rc; // system errno
}
}
// Exit status codes
const char *
crm_exit_name(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "CRM_EX_OK";
case CRM_EX_ERROR: return "CRM_EX_ERROR";
case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
case CRM_EX_PROMOTED: return "CRM_EX_PROMOTED";
case CRM_EX_FAILED_PROMOTED: return "CRM_EX_FAILED_PROMOTED";
case CRM_EX_USAGE: return "CRM_EX_USAGE";
case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
case CRM_EX_OSERR: return "CRM_EX_OSERR";
case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
case CRM_EX_IOERR: return "CRM_EX_IOERR";
case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
case CRM_EX_FATAL: return "CRM_EX_FATAL";
case CRM_EX_PANIC: return "CRM_EX_PANIC";
case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
case CRM_EX_OLD: return "CRM_EX_OLD";
case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED";
case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED";
case CRM_EX_NONE: return "CRM_EX_NONE";
case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
}
return "CRM_EX_UNKNOWN";
}
const char *
crm_exit_str(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "OK";
case CRM_EX_ERROR: return "Error occurred";
case CRM_EX_INVALID_PARAM: return "Invalid parameter";
case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
case CRM_EX_NOT_INSTALLED: return "Not installed";
case CRM_EX_NOT_CONFIGURED: return "Not configured";
case CRM_EX_NOT_RUNNING: return "Not running";
case CRM_EX_PROMOTED: return "Promoted";
case CRM_EX_FAILED_PROMOTED: return "Failed in promoted role";
case CRM_EX_USAGE: return "Incorrect usage";
case CRM_EX_DATAERR: return "Invalid data given";
case CRM_EX_NOINPUT: return "Input file not available";
case CRM_EX_NOUSER: return "User does not exist";
case CRM_EX_NOHOST: return "Host does not exist";
case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
case CRM_EX_SOFTWARE: return "Internal software bug";
case CRM_EX_OSERR: return "Operating system error occurred";
case CRM_EX_OSFILE: return "System file not available";
case CRM_EX_CANTCREAT: return "Cannot create output file";
case CRM_EX_IOERR: return "I/O error occurred";
case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
case CRM_EX_PROTOCOL: return "Protocol violated";
case CRM_EX_NOPERM: return "Insufficient privileges";
case CRM_EX_CONFIG: return "Invalid configuration";
case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
case CRM_EX_PANIC: return "System panic required";
case CRM_EX_DISCONNECT: return "Not connected";
case CRM_EX_DIGEST: return "Digest mismatch";
case CRM_EX_NOSUCH: return "No such object";
case CRM_EX_QUORUM: return "Quorum required";
case CRM_EX_UNSAFE: return "Operation not safe";
case CRM_EX_EXISTS: return "Requested item already exists";
case CRM_EX_MULTIPLE: return "Multiple items match request";
case CRM_EX_EXPIRED: return "Requested item has expired";
case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
case CRM_EX_INDETERMINATE: return "Could not determine status";
case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
case CRM_EX_OLD: return "Update was older than existing configuration";
case CRM_EX_TIMEOUT: return "Timeout occurred";
case CRM_EX_DEGRADED: return "Service is active but might fail soon";
case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon";
case CRM_EX_NONE: return "No exit status available";
case CRM_EX_MAX: return "Error occurred";
}
if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
return "Interrupted by signal";
}
return "Unknown exit status";
}
/*!
* \brief Map a function return code to the most similar exit code
*
* \param[in] rc Function return code
*
* \return Most similar exit code
*/
crm_exit_t
pcmk_rc2exitc(int rc)
{
switch (rc) {
case pcmk_rc_ok:
case pcmk_rc_no_output: // quiet mode, or nothing to output
return CRM_EX_OK;
case pcmk_rc_no_quorum:
return CRM_EX_QUORUM;
case pcmk_rc_old_data:
return CRM_EX_OLD;
case pcmk_rc_schema_validation:
case pcmk_rc_transform_failed:
case pcmk_rc_unpack_error:
return CRM_EX_CONFIG;
case pcmk_rc_bad_nvpair:
return CRM_EX_INVALID_PARAM;
case EACCES:
return CRM_EX_INSUFFICIENT_PRIV;
case EBADF:
case EINVAL:
case EFAULT:
case ENOSYS:
case EOVERFLOW:
case pcmk_rc_underflow:
case pcmk_rc_compression:
return CRM_EX_SOFTWARE;
case EBADMSG:
case EMSGSIZE:
case ENOMSG:
case ENOPROTOOPT:
case EPROTO:
case EPROTONOSUPPORT:
case EPROTOTYPE:
return CRM_EX_PROTOCOL;
case ECOMM:
case ENOMEM:
return CRM_EX_OSERR;
case ECONNABORTED:
case ECONNREFUSED:
case ECONNRESET:
case ENOTCONN:
return CRM_EX_DISCONNECT;
case EEXIST:
case pcmk_rc_already:
return CRM_EX_EXISTS;
case EIO:
case pcmk_rc_dot_error:
case pcmk_rc_graph_error:
return CRM_EX_IOERR;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return CRM_EX_UNIMPLEMENT_FEATURE;
case ENOTUNIQ:
case pcmk_rc_multiple:
return CRM_EX_MULTIPLE;
case ENODEV:
case ENOENT:
case ENXIO:
case pcmk_rc_no_transaction:
case pcmk_rc_unknown_format:
return CRM_EX_NOSUCH;
case pcmk_rc_node_unknown:
case pcmk_rc_ns_resolution:
return CRM_EX_NOHOST;
case ETIME:
case ETIMEDOUT:
return CRM_EX_TIMEOUT;
case EAGAIN:
case EBUSY:
return CRM_EX_UNSATISFIED;
case pcmk_rc_before_range:
return CRM_EX_NOT_YET_IN_EFFECT;
case pcmk_rc_after_range:
return CRM_EX_EXPIRED;
case pcmk_rc_undetermined:
return CRM_EX_INDETERMINATE;
case pcmk_rc_op_unsatisfied:
return CRM_EX_UNSATISFIED;
case pcmk_rc_within_range:
return CRM_EX_OK;
case pcmk_rc_no_input:
return CRM_EX_NOINPUT;
case pcmk_rc_duplicate_id:
return CRM_EX_MULTIPLE;
case pcmk_rc_bad_input:
case pcmk_rc_bad_xml_patch:
return CRM_EX_DATAERR;
default:
return CRM_EX_ERROR;
}
}
/*!
* \brief Map a function return code to the most similar OCF exit code
*
* \param[in] rc Function return code
*
* \return Most similar OCF exit code
*/
enum ocf_exitcode
pcmk_rc2ocf(int rc)
{
switch (rc) {
case pcmk_rc_ok:
return PCMK_OCF_OK;
case pcmk_rc_bad_nvpair:
return PCMK_OCF_INVALID_PARAM;
case EACCES:
return PCMK_OCF_INSUFFICIENT_PRIV;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return PCMK_OCF_UNIMPLEMENT_FEATURE;
default:
return PCMK_OCF_UNKNOWN_ERROR;
}
}
// Other functions
/*!
* \brief Map a getaddrinfo() return code to the most similar Pacemaker
* return code
*
* \param[in] gai getaddrinfo() return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__gaierror2rc(int gai)
{
switch (gai) {
case 0:
return pcmk_rc_ok;
case EAI_AGAIN:
return EAGAIN;
case EAI_BADFLAGS:
case EAI_SERVICE:
return EINVAL;
case EAI_FAMILY:
return EAFNOSUPPORT;
case EAI_MEMORY:
return ENOMEM;
case EAI_NONAME:
return pcmk_rc_node_unknown;
case EAI_SOCKTYPE:
return ESOCKTNOSUPPORT;
case EAI_SYSTEM:
return errno;
default:
return pcmk_rc_ns_resolution;
}
}
/*!
* \brief Map a bz2 return code to the most similar Pacemaker return code
*
* \param[in] bz2 bz2 return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__bzlib2rc(int bz2)
{
switch (bz2) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return pcmk_rc_ok;
case BZ_MEM_ERROR:
return ENOMEM;
case BZ_DATA_ERROR:
case BZ_DATA_ERROR_MAGIC:
case BZ_UNEXPECTED_EOF:
return pcmk_rc_bad_input;
case BZ_IO_ERROR:
return EIO;
case BZ_OUTBUFF_FULL:
return EFBIG;
default:
return pcmk_rc_compression;
}
}
crm_exit_t
crm_exit(crm_exit_t rc)
{
/* A compiler could theoretically use any type for crm_exit_t, but an int
* should always hold it, so cast to int to keep static analysis happy.
*/
if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
rc = CRM_EX_ERROR;
}
mainloop_cleanup();
- crm_xml_cleanup();
+ pcmk__xml_cleanup();
free(pcmk__our_nodename);
if (crm_system_name) {
crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
free(crm_system_name);
} else {
crm_trace("Exiting with status %d", rc);
}
pcmk__free_common_logger();
qb_log_fini(); // Don't log anything after this point
exit(rc);
}
/*
* External action results
*/
/*!
* \internal
* \brief Set the result of an action
*
* \param[out] result Where to set action result
* \param[in] exit_status OCF exit status to set
* \param[in] exec_status Execution status to set
* \param[in] exit_reason Human-friendly description of event to set
*/
void
pcmk__set_result(pcmk__action_result_t *result, int exit_status,
enum pcmk_exec_status exec_status, const char *exit_reason)
{
if (result == NULL) {
return;
}
result->exit_status = exit_status;
result->execution_status = exec_status;
if (!pcmk__str_eq(result->exit_reason, exit_reason, pcmk__str_none)) {
free(result->exit_reason);
result->exit_reason = (exit_reason == NULL)? NULL : strdup(exit_reason);
}
}
/*!
* \internal
* \brief Set the result of an action, with a formatted exit reason
*
* \param[out] result Where to set action result
* \param[in] exit_status OCF exit status to set
* \param[in] exec_status Execution status to set
* \param[in] format printf-style format for a human-friendly
* description of reason for result
* \param[in] ... arguments for \p format
*/
G_GNUC_PRINTF(4, 5)
void
pcmk__format_result(pcmk__action_result_t *result, int exit_status,
enum pcmk_exec_status exec_status,
const char *format, ...)
{
va_list ap;
int len = 0;
char *reason = NULL;
if (result == NULL) {
return;
}
result->exit_status = exit_status;
result->execution_status = exec_status;
if (format != NULL) {
va_start(ap, format);
len = vasprintf(&reason, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
}
free(result->exit_reason);
result->exit_reason = reason;
}
/*!
* \internal
* \brief Set the output of an action
*
* \param[out] result Action result to set output for
* \param[in] out Action output to set (must be dynamically
* allocated)
* \param[in] err Action error output to set (must be dynamically
* allocated)
*
* \note \p result will take ownership of \p out and \p err, so the caller
* should not free them.
*/
void
pcmk__set_result_output(pcmk__action_result_t *result, char *out, char *err)
{
if (result == NULL) {
return;
}
free(result->action_stdout);
result->action_stdout = out;
free(result->action_stderr);
result->action_stderr = err;
}
/*!
* \internal
* \brief Clear a result's exit reason, output, and error output
*
* \param[in,out] result Result to reset
*/
void
pcmk__reset_result(pcmk__action_result_t *result)
{
if (result == NULL) {
return;
}
free(result->exit_reason);
result->exit_reason = NULL;
free(result->action_stdout);
result->action_stdout = NULL;
free(result->action_stderr);
result->action_stderr = NULL;
}
/*!
* \internal
* \brief Copy the result of an action
*
* \param[in] src Result to copy
* \param[out] dst Where to copy \p src to
*/
void
pcmk__copy_result(const pcmk__action_result_t *src, pcmk__action_result_t *dst)
{
CRM_CHECK((src != NULL) && (dst != NULL), return);
dst->exit_status = src->exit_status;
dst->execution_status = src->execution_status;
dst->exit_reason = pcmk__str_copy(src->exit_reason);
dst->action_stdout = pcmk__str_copy(src->action_stdout);
dst->action_stderr = pcmk__str_copy(src->action_stderr);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/results_compat.h>
const char *
bz2_strerror(int rc)
{
// See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
switch (rc) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return "Ok";
case BZ_CONFIG_ERROR:
return "libbz2 has been improperly compiled on your platform";
case BZ_SEQUENCE_ERROR:
return "library functions called in the wrong order";
case BZ_PARAM_ERROR:
return "parameter is out of range or otherwise incorrect";
case BZ_MEM_ERROR:
return "memory allocation failed";
case BZ_DATA_ERROR:
return "data integrity error is detected during decompression";
case BZ_DATA_ERROR_MAGIC:
return "the compressed stream does not start with the correct magic bytes";
case BZ_IO_ERROR:
return "error reading or writing in the compressed file";
case BZ_UNEXPECTED_EOF:
return "compressed file finishes before the logical end of stream is detected";
case BZ_OUTBUFF_FULL:
return "output data will not fit into the buffer provided";
}
return "Data compression error";
}
crm_exit_t
crm_errno2exit(int rc)
{
return pcmk_rc2exitc(pcmk_legacy2rc(rc));
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/tests/xml/pcmk__xml_init_test.c b/lib/common/tests/xml/pcmk__xml_init_test.c
index d5e00b4f97..1c65647f0a 100644
--- a/lib/common/tests/xml/pcmk__xml_init_test.c
+++ b/lib/common/tests/xml/pcmk__xml_init_test.c
@@ -1,230 +1,230 @@
/*
* Copyright 2023-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/xml.h>
#include <crm/common/unittest_internal.h>
#include <crm/common/xml_internal.h>
#include "crmcommon_private.h"
/* Copied from lib/common/xml.c */
#define XML_DOC_PRIVATE_MAGIC 0x81726354UL
#define XML_NODE_PRIVATE_MAGIC 0x54637281UL
static int
setup(void **state) {
pcmk__xml_init();
return 0;
}
static int
teardown(void **state) {
- crm_xml_cleanup();
+ pcmk__xml_cleanup();
return 0;
}
static void
buffer_scheme_test(void **state) {
assert_int_equal(XML_BUFFER_ALLOC_DOUBLEIT, xmlGetBufferAllocationScheme());
}
/* These functions also serve as unit tests of the static new_private_data
* function. We can't test free_private_data because libxml will call that as
* part of freeing everything else. By the time we'd get back into a unit test
* where we could check that private members are NULL, the structure containing
* the private data would have been freed.
*
* This could probably be tested with a lot of function mocking, but that
* doesn't seem worth it.
*/
static void
create_document_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
/* Double check things */
assert_non_null(doc);
assert_int_equal(doc->type, XML_DOCUMENT_NODE);
/* Check that the private data is initialized correctly */
docpriv = doc->_private;
assert_non_null(docpriv);
assert_int_equal(docpriv->check, XML_DOC_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
xmlFreeDoc(doc);
}
static void
create_element_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNodePtr node = xmlNewDocNode(doc, NULL, (pcmkXmlStr) "test", NULL);
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(node);
assert_int_equal(node->type, XML_ELEMENT_NODE);
/* Check that the private data is initialized correctly */
priv = node->_private;
assert_non_null(priv);
assert_int_equal(priv->check, XML_NODE_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
xmlFreeNode(node);
xmlFreeDoc(doc);
}
static void
create_attr_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNodePtr node = xmlNewDocNode(doc, NULL, (pcmkXmlStr) "test", NULL);
xmlAttrPtr attr = xmlNewProp(node, (pcmkXmlStr) PCMK_XA_NAME,
(pcmkXmlStr) "dummy-value");
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(attr);
assert_int_equal(attr->type, XML_ATTRIBUTE_NODE);
/* Check that the private data is initialized correctly */
priv = attr->_private;
assert_non_null(priv);
assert_int_equal(priv->check, XML_NODE_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
xmlFreeNode(node);
xmlFreeDoc(doc);
}
static void
create_comment_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNodePtr node = xmlNewDocComment(doc, (pcmkXmlStr) "blahblah");
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(node);
assert_int_equal(node->type, XML_COMMENT_NODE);
/* Check that the private data is initialized correctly */
priv = node->_private;
assert_non_null(priv);
assert_int_equal(priv->check, XML_NODE_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
xmlFreeNode(node);
xmlFreeDoc(doc);
}
static void
create_text_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNodePtr node = xmlNewDocText(doc, (pcmkXmlStr) "blahblah");
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(node);
assert_int_equal(node->type, XML_TEXT_NODE);
/* Check that no private data was created */
priv = node->_private;
assert_null(priv);
/* Clean up */
xmlFreeNode(node);
xmlFreeDoc(doc);
}
static void
create_dtd_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlDtdPtr dtd = xmlNewDtd(doc, (pcmkXmlStr) PCMK_XA_NAME,
(pcmkXmlStr) "externalId",
(pcmkXmlStr) "systemId");
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(dtd);
assert_int_equal(dtd->type, XML_DTD_NODE);
/* Check that no private data was created */
priv = dtd->_private;
assert_null(priv);
/* Clean up */
/* If you call xmlFreeDtd before xmlFreeDoc, you get a segfault */
xmlFreeDoc(doc);
}
static void
create_cdata_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDocPtr doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNodePtr node = xmlNewCDataBlock(doc, (pcmkXmlStr) "blahblah", 8);
/* Adding a node to the document marks it as dirty */
docpriv = doc->_private;
assert_true(pcmk_all_flags_set(docpriv->flags, pcmk__xf_dirty));
/* Double check things */
assert_non_null(node);
assert_int_equal(node->type, XML_CDATA_SECTION_NODE);
/* Check that no private data was created */
priv = node->_private;
assert_null(priv);
/* Clean up */
xmlFreeNode(node);
xmlFreeDoc(doc);
}
PCMK__UNIT_TEST(setup, teardown,
cmocka_unit_test(buffer_scheme_test),
cmocka_unit_test(create_document_node),
cmocka_unit_test(create_element_node),
cmocka_unit_test(create_attr_node),
cmocka_unit_test(create_comment_node),
cmocka_unit_test(create_text_node),
cmocka_unit_test(create_dtd_node),
cmocka_unit_test(create_cdata_node));
diff --git a/lib/common/xml.c b/lib/common/xml.c
index 69f007b437..e368c406d8 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,2742 +1,2754 @@
/*
* 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 <stdarg.h>
#include <stdint.h> // uint32_t
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // stat(), S_ISREG, etc.
#include <sys/types.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
#include "crmcommon_private.h"
/*!
* \internal
* \brief Apply a function to each XML node in a tree (pre-order, depth-first)
*
* \param[in,out] xml XML tree to traverse
* \param[in,out] fn Function to call for each node (returns \c true to
* continue traversing the tree or \c false to stop)
* \param[in,out] user_data Argument to \p fn
*
* \return \c false if any \p fn call returned \c false, or \c true otherwise
*
* \note This function is recursive.
*/
bool
pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
void *user_data)
{
if (!fn(xml, user_data)) {
return false;
}
for (xml = pcmk__xml_first_child(xml); xml != NULL;
xml = pcmk__xml_next(xml)) {
if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
return false;
}
}
return true;
}
bool
pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
{
if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
return FALSE;
} else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
pcmk__xf_tracking)) {
return FALSE;
} else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
pcmk__xf_lazy)) {
return FALSE;
}
return TRUE;
}
static inline void
set_parent_flag(xmlNode *xml, long flag)
{
for(; xml; xml = xml->parent) {
xml_node_private_t *nodepriv = xml->_private;
if (nodepriv == NULL) {
/* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
} else {
pcmk__set_xml_flags(nodepriv, flag);
}
}
}
void
pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
{
if(xml && xml->doc && xml->doc->_private){
/* During calls to xmlDocCopyNode(), xml->doc may be unset */
xml_doc_private_t *docpriv = xml->doc->_private;
pcmk__set_xml_flags(docpriv, flag);
}
}
// Mark document, element, and all element's parents as changed
void
pcmk__mark_xml_node_dirty(xmlNode *xml)
{
pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
set_parent_flag(xml, pcmk__xf_dirty);
}
/*!
* \internal
* \brief Clear flags on an XML node
*
* \param[in,out] xml XML node whose flags to reset
* \param[in,out] user_data Ignored
*
* \return \c true (to continue traversing the tree)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
reset_xml_node_flags(xmlNode *xml, void *user_data)
{
xml_node_private_t *nodepriv = xml->_private;
if (nodepriv != NULL) {
nodepriv->flags = pcmk__xf_none;
}
return true;
}
/*!
* \internal
* \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
*
* \param[in,out] xml Node whose flags to set
* \param[in] user_data Ignored
*
* \return \c true (to continue traversing the tree)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
mark_xml_dirty_created(xmlNode *xml, void *user_data)
{
xml_node_private_t *nodepriv = xml->_private;
if (nodepriv != NULL) {
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
}
return true;
}
/*!
* \internal
* \brief Mark an XML tree as dirty and created, and mark its parents dirty
*
* Also mark the document dirty.
*
* \param[in,out] xml Tree to mark as dirty and created
*/
void
pcmk__xml_mark_created(xmlNode *xml)
{
CRM_ASSERT(xml != NULL);
if (!pcmk__tracking_xml_changes(xml, false)) {
// Tracking is disabled for entire document
return;
}
// Mark all parents and document dirty
pcmk__mark_xml_node_dirty(xml);
pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
}
#define XML_DOC_PRIVATE_MAGIC 0x81726354UL
#define XML_NODE_PRIVATE_MAGIC 0x54637281UL
// Free an XML object previously marked as deleted
static void
free_deleted_object(void *data)
{
if(data) {
pcmk__deleted_xml_t *deleted_obj = data;
g_free(deleted_obj->path);
free(deleted_obj);
}
}
// Free and NULL user, ACLs, and deleted objects in an XML node's private data
static void
reset_xml_private_data(xml_doc_private_t *docpriv)
{
if (docpriv != NULL) {
CRM_ASSERT(docpriv->check == XML_DOC_PRIVATE_MAGIC);
free(docpriv->user);
docpriv->user = NULL;
if (docpriv->acls != NULL) {
pcmk__free_acls(docpriv->acls);
docpriv->acls = NULL;
}
if(docpriv->deleted_objs) {
g_list_free_full(docpriv->deleted_objs, free_deleted_object);
docpriv->deleted_objs = NULL;
}
}
}
// Free all private data associated with an XML node
static void
free_private_data(xmlNode *node)
{
/* Note:
This function frees private data assosciated with an XML node,
unless the function is being called as a result of internal
XSLT cleanup.
That could happen through, for example, the following chain of
function calls:
xsltApplyStylesheetInternal
-> xsltFreeTransformContext
-> xsltFreeRVTs
-> xmlFreeDoc
And in that case, the node would fulfill three conditions:
1. It would be a standalone document (i.e. it wouldn't be
part of a document)
2. It would have a space-prefixed name (for reference, please
see xsltInternals.h: XSLT_MARK_RES_TREE_FRAG)
3. It would carry its own payload in the _private field.
We do not free data in this circumstance to avoid a failed
assertion on the XML_*_PRIVATE_MAGIC later.
*/
if (node->name == NULL || node->name[0] != ' ') {
if (node->_private) {
if (node->type == XML_DOCUMENT_NODE) {
reset_xml_private_data(node->_private);
} else {
CRM_ASSERT(((xml_node_private_t *) node->_private)->check
== XML_NODE_PRIVATE_MAGIC);
/* nothing dynamically allocated nested */
}
free(node->_private);
node->_private = NULL;
}
}
}
// Allocate and initialize private data for an XML node
static void
new_private_data(xmlNode *node)
{
switch (node->type) {
case XML_DOCUMENT_NODE: {
xml_doc_private_t *docpriv =
pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
docpriv->check = XML_DOC_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
node->_private = docpriv;
break;
}
case XML_ELEMENT_NODE:
case XML_ATTRIBUTE_NODE:
case XML_COMMENT_NODE: {
xml_node_private_t *nodepriv =
pcmk__assert_alloc(1, sizeof(xml_node_private_t));
nodepriv->check = XML_NODE_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
node->_private = nodepriv;
if (pcmk__tracking_xml_changes(node, FALSE)) {
/* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
* not hooked up at the point we are called
*/
pcmk__mark_xml_node_dirty(node);
}
break;
}
case XML_TEXT_NODE:
case XML_DTD_NODE:
case XML_CDATA_SECTION_NODE:
break;
default:
/* Ignore */
crm_trace("Ignoring %p %d", node, node->type);
CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
break;
}
}
void
xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
{
xml_accept_changes(xml);
crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
if(enforce_acls) {
if(acl_source == NULL) {
acl_source = xml;
}
pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
pcmk__unpack_acl(acl_source, xml, user);
pcmk__apply_acl(xml);
}
}
bool xml_tracking_changes(xmlNode * xml)
{
return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
&& pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
pcmk__xf_tracking);
}
bool xml_document_dirty(xmlNode *xml)
{
return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
&& pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
pcmk__xf_dirty);
}
/*!
* \internal
* \brief Return ordinal position of an XML node among its siblings
*
* \param[in] xml XML node to check
* \param[in] ignore_if_set Don't count siblings with this flag set
*
* \return Ordinal position of \p xml (starting with 0)
*/
int
pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
{
int position = 0;
for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
position++;
}
}
return position;
}
/*!
* \internal
* \brief Remove all attributes marked as deleted from an XML node
*
* \param[in,out] xml XML node whose deleted attributes to remove
* \param[in,out] user_data Ignored
*
* \return \c true (to continue traversing the tree)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
accept_attr_deletions(xmlNode *xml, void *user_data)
{
reset_xml_node_flags(xml, NULL);
pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
return true;
}
/*!
* \internal
* \brief Find first child XML node matching another given XML node
*
* \param[in] haystack XML whose children should be checked
* \param[in] needle XML to match (comment content or element name and ID)
* \param[in] exact If true and needle is a comment, position must match
*/
xmlNode *
pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
{
CRM_CHECK(needle != NULL, return NULL);
if (needle->type == XML_COMMENT_NODE) {
return pcmk__xc_match(haystack, needle, exact);
} else {
const char *id = pcmk__xe_id(needle);
const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
return pcmk__xe_first_child(haystack, (const char *) needle->name, attr,
id);
}
}
void
xml_accept_changes(xmlNode * xml)
{
xmlNode *top = NULL;
xml_doc_private_t *docpriv = NULL;
if(xml == NULL) {
return;
}
crm_trace("Accepting changes to %p", xml);
docpriv = xml->doc->_private;
top = xmlDocGetRootElement(xml->doc);
reset_xml_private_data(xml->doc->_private);
if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
docpriv->flags = pcmk__xf_none;
return;
}
docpriv->flags = pcmk__xf_none;
pcmk__xml_tree_foreach(top, accept_attr_deletions, NULL);
}
/*!
* \internal
* \brief Find first XML child element matching given criteria
*
* \param[in] parent XML element to search (can be \c NULL)
* \param[in] node_name If not \c NULL, only match children of this type
* \param[in] attr_n If not \c NULL, only match children with an attribute
* of this name.
* \param[in] attr_v If \p attr_n and this are not NULL, only match children
* with an attribute named \p attr_n and this value
*
* \return Matching XML child element, or \c NULL if none found
*/
xmlNode *
pcmk__xe_first_child(const xmlNode *parent, const char *node_name,
const char *attr_n, const char *attr_v)
{
xmlNode *child = NULL;
const char *parent_name = "<null>";
CRM_CHECK((attr_v == NULL) || (attr_n != NULL), return NULL);
if (parent != NULL) {
child = parent->children;
while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) {
child = child->next;
}
parent_name = (const char *) parent->name;
}
for (; child != NULL; child = pcmk__xe_next(child)) {
const char *value = NULL;
if ((node_name != NULL) && !pcmk__xe_is(child, node_name)) {
// Node name mismatch
continue;
}
if (attr_n == NULL) {
// No attribute match needed
return child;
}
value = crm_element_value(child, attr_n);
if ((attr_v == NULL) && (value != NULL)) {
// attr_v == NULL: Attribute attr_n must be set (to any value)
return child;
}
if ((attr_v != NULL) && (pcmk__str_eq(value, attr_v, pcmk__str_none))) {
// attr_v != NULL: Attribute attr_n must be set to value attr_v
return child;
}
}
if (node_name == NULL) {
node_name = "(any)"; // For logging
}
if (attr_n != NULL) {
crm_trace("XML child node <%s %s=%s> not found in %s",
node_name, attr_n, attr_v, parent_name);
} else {
crm_trace("XML child node <%s> not found in %s",
node_name, parent_name);
}
return NULL;
}
/*!
* \internal
* \brief Set an XML attribute, expanding \c ++ and \c += where appropriate
*
* If \p target already has an attribute named \p name set to an integer value
* and \p value is an addition assignment expression on \p name, then expand
* \p value to an integer and set attribute \p name to the expanded value in
* \p target.
*
* Otherwise, set attribute \p name on \p target using the literal \p value.
*
* The original attribute value in \p target and the number in an assignment
* expression in \p value are parsed and added as scores (that is, their values
* are capped at \c INFINITY and \c -INFINITY). For more details, refer to
* \c char2score().
*
* For example, suppose \p target has an attribute named \c "X" with value
* \c "5", and that \p name is \c "X".
* * If \p value is \c "X++", the new value of \c "X" in \p target is \c "6".
* * If \p value is \c "X+=3", the new value of \c "X" in \p target is \c "8".
* * If \p value is \c "val", the new value of \c "X" in \p target is \c "val".
* * If \p value is \c "Y++", the new value of \c "X" in \p target is \c "Y++".
*
* \param[in,out] target XML node whose attribute to set
* \param[in] name Name of the attribute to set
* \param[in] value New value of attribute to set
*
* \return Standard Pacemaker return code (specifically, \c EINVAL on invalid
* argument, or \c pcmk_rc_ok otherwise)
*/
int
pcmk__xe_set_score(xmlNode *target, const char *name, const char *value)
{
const char *old_value = NULL;
CRM_CHECK((target != NULL) && (name != NULL), return EINVAL);
if (value == NULL) {
return pcmk_rc_ok;
}
old_value = crm_element_value(target, name);
// If no previous value, skip to default case and set the value unexpanded.
if (old_value != NULL) {
const char *n = name;
const char *v = value;
// Stop at first character that differs between name and value
for (; (*n == *v) && (*n != '\0'); n++, v++);
// If value begins with name followed by a "++" or "+="
if ((*n == '\0')
&& (*v++ == '+')
&& ((*v == '+') || (*v == '='))) {
// If we're expanding ourselves, no previous value was set; use 0
int old_value_i = (old_value != value)? char2score(old_value) : 0;
/* value="X++": new value of X is old_value + 1
* value="X+=Y": new value of X is old_value + Y (for some number Y)
*/
int add = (*v == '+')? 1 : char2score(++v);
crm_xml_add_int(target, name, pcmk__add_scores(old_value_i, add));
return pcmk_rc_ok;
}
}
// Default case: set the attribute unexpanded (with value treated literally)
if (old_value != value) {
crm_xml_add(target, name, value);
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Copy XML attributes from a source element to a target element
*
* This is similar to \c xmlCopyPropList() except that attributes are marked
* as dirty for change tracking purposes.
*
* \param[in,out] target XML element to receive copied attributes from \p src
* \param[in] src XML element whose attributes to copy to \p target
* \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
*
* \return Standard Pacemaker return code
*/
int
pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags)
{
CRM_CHECK((src != NULL) && (target != NULL), return EINVAL);
for (xmlAttr *attr = pcmk__xe_first_attr(src); attr != NULL;
attr = attr->next) {
const char *name = (const char *) attr->name;
const char *value = pcmk__xml_attr_value(attr);
if (pcmk_is_set(flags, pcmk__xaf_no_overwrite)
&& (crm_element_value(target, name) != NULL)) {
continue;
}
if (pcmk_is_set(flags, pcmk__xaf_score_update)) {
pcmk__xe_set_score(target, name, value);
} else {
crm_xml_add(target, name, value);
}
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Remove an XML attribute from an element
*
* \param[in,out] element XML element that owns \p attr
* \param[in,out] attr XML attribute to remove from \p element
*
* \return Standard Pacemaker return code (\c EPERM if ACLs prevent removal of
* attributes from \p element, or \c pcmk_rc_ok otherwise)
*/
static int
remove_xe_attr(xmlNode *element, xmlAttr *attr)
{
if (attr == NULL) {
return pcmk_rc_ok;
}
if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
// ACLs apply to element, not to particular attributes
crm_trace("ACLs prevent removal of attributes from %s element",
(const char *) element->name);
return EPERM;
}
if (pcmk__tracking_xml_changes(element, false)) {
// Leave in place (marked for removal) until after diff is calculated
set_parent_flag(element, pcmk__xf_dirty);
pcmk__set_xml_flags((xml_node_private_t *) attr->_private,
pcmk__xf_deleted);
} else {
xmlRemoveProp(attr);
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Remove a named attribute from an XML element
*
* \param[in,out] element XML element to remove an attribute from
* \param[in] name Name of attribute to remove
*/
void
pcmk__xe_remove_attr(xmlNode *element, const char *name)
{
if (name != NULL) {
remove_xe_attr(element, xmlHasProp(element, (pcmkXmlStr) name));
}
}
/*!
* \internal
* \brief Remove a named attribute from an XML element
*
* This is a wrapper for \c pcmk__xe_remove_attr() for use with
* \c pcmk__xml_tree_foreach().
*
* \param[in,out] xml XML element to remove an attribute from
* \param[in] user_data Name of attribute to remove
*
* \return \c true (to continue traversing the tree)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
bool
pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data)
{
const char *name = user_data;
pcmk__xe_remove_attr(xml, name);
return true;
}
/*!
* \internal
* \brief Remove an XML element's attributes that match some criteria
*
* \param[in,out] element XML element to modify
* \param[in] match If not NULL, only remove attributes for which
* this function returns true
* \param[in,out] user_data Data to pass to \p match
*/
void
pcmk__xe_remove_matching_attrs(xmlNode *element,
bool (*match)(xmlAttrPtr, void *),
void *user_data)
{
xmlAttrPtr next = NULL;
for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
next = a->next; // Grab now because attribute might get removed
if ((match == NULL) || match(a, user_data)) {
if (remove_xe_attr(element, a) != pcmk_rc_ok) {
return;
}
}
}
}
/*!
* \internal
* \brief Create a new XML element under a given parent
*
* \param[in,out] parent XML element that will be the new element's parent
* (\c NULL to create a new XML document with the new
* node as root)
* \param[in] name Name of new element
*
* \return Newly created XML element (guaranteed not to be \c NULL)
*/
xmlNode *
pcmk__xe_create(xmlNode *parent, const char *name)
{
xmlNode *node = NULL;
CRM_ASSERT(!pcmk__str_empty(name));
if (parent == NULL) {
xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
pcmk__mem_assert(doc);
node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
pcmk__mem_assert(node);
xmlDocSetRootElement(doc, node);
} else {
node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
pcmk__mem_assert(node);
}
pcmk__xml_mark_created(node);
return node;
}
/*!
* \internal
* \brief Set a formatted string as an XML node's content
*
* \param[in,out] node Node whose content to set
* \param[in] format <tt>printf(3)</tt>-style format string
* \param[in] ... Arguments for \p format
*
* \note This function escapes special characters. \c xmlNodeSetContent() does
* not.
*/
G_GNUC_PRINTF(2, 3)
void
pcmk__xe_set_content(xmlNode *node, const char *format, ...)
{
if (node != NULL) {
const char *content = NULL;
char *buf = NULL;
if (strchr(format, '%') == NULL) {
// Nothing to format
content = format;
} else {
va_list ap;
va_start(ap, format);
if (pcmk__str_eq(format, "%s", pcmk__str_none)) {
// No need to make a copy
content = va_arg(ap, const char *);
} else {
CRM_ASSERT(vasprintf(&buf, format, ap) >= 0);
content = buf;
}
va_end(ap);
}
xmlNodeSetContent(node, (pcmkXmlStr) content);
free(buf);
}
}
/*!
* Free an XML element and all of its children, removing it from its parent
*
* \param[in,out] xml XML element to free
*/
void
pcmk_free_xml_subtree(xmlNode *xml)
{
xmlUnlinkNode(xml); // Detaches from parent and siblings
xmlFreeNode(xml); // Frees
}
static void
free_xml_with_position(xmlNode *child, int position)
{
xmlDoc *doc = NULL;
xml_node_private_t *nodepriv = NULL;
if (child == NULL) {
return;
}
doc = child->doc;
nodepriv = child->_private;
if ((doc != NULL) && (xmlDocGetRootElement(doc) == child)) {
// Free everything
xmlFreeDoc(doc);
return;
}
if (!pcmk__check_acl(child, NULL, pcmk__xf_acl_write)) {
GString *xpath = NULL;
pcmk__if_tracing({}, return);
xpath = pcmk__element_xpath(child);
qb_log_from_external_source(__func__, __FILE__,
"Cannot remove %s %x", LOG_TRACE,
__LINE__, 0, xpath->str, nodepriv->flags);
g_string_free(xpath, TRUE);
return;
}
if ((doc != NULL) && pcmk__tracking_xml_changes(child, false)
&& !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
xml_doc_private_t *docpriv = doc->_private;
GString *xpath = pcmk__element_xpath(child);
if (xpath != NULL) {
pcmk__deleted_xml_t *deleted_obj = NULL;
crm_trace("Deleting %s %p from %p", xpath->str, child, doc);
deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
deleted_obj->path = g_string_free(xpath, FALSE);
deleted_obj->position = -1;
// Record the position only for XML comments for now
if (child->type == XML_COMMENT_NODE) {
if (position >= 0) {
deleted_obj->position = position;
} else {
deleted_obj->position = pcmk__xml_position(child,
pcmk__xf_skip);
}
}
docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
deleted_obj);
pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
}
}
pcmk_free_xml_subtree(child);
}
void
free_xml(xmlNode * child)
{
free_xml_with_position(child, -1);
}
/*!
* \internal
* \brief Make a deep copy of an XML node under a given parent
*
* \param[in,out] parent XML element that will be the copy's parent (\c NULL
* to create a new XML document with the copy as root)
* \param[in] src XML node to copy
*
* \return Deep copy of \p src, or \c NULL if \p src is \c NULL
*/
xmlNode *
pcmk__xml_copy(xmlNode *parent, xmlNode *src)
{
xmlNode *copy = NULL;
if (src == NULL) {
return NULL;
}
if (parent == NULL) {
xmlDoc *doc = NULL;
// The copy will be the root element of a new document
CRM_ASSERT(src->type == XML_ELEMENT_NODE);
doc = xmlNewDoc(PCMK__XML_VERSION);
pcmk__mem_assert(doc);
copy = xmlDocCopyNode(src, doc, 1);
pcmk__mem_assert(copy);
xmlDocSetRootElement(doc, copy);
} else {
copy = xmlDocCopyNode(src, parent->doc, 1);
pcmk__mem_assert(copy);
xmlAddChild(parent, copy);
}
pcmk__xml_mark_created(copy);
return copy;
}
/*!
* \internal
* \brief Remove XML text nodes from specified XML and all its children
*
* \param[in,out] xml XML to strip text from
*/
void
pcmk__strip_xml_text(xmlNode *xml)
{
xmlNode *iter = xml->children;
while (iter) {
xmlNode *next = iter->next;
switch (iter->type) {
case XML_TEXT_NODE:
/* Remove it */
pcmk_free_xml_subtree(iter);
break;
case XML_ELEMENT_NODE:
/* Search it */
pcmk__strip_xml_text(iter);
break;
default:
/* Leave it */
break;
}
iter = next;
}
}
/*!
* \internal
* \brief Add a "last written" attribute to an XML element, set to current time
*
* \param[in,out] xe XML element to add attribute to
*
* \return Value that was set, or NULL on error
*/
const char *
pcmk__xe_add_last_written(xmlNode *xe)
{
char *now_s = pcmk__epoch2str(NULL, 0);
const char *result = NULL;
result = crm_xml_add(xe, PCMK_XA_CIB_LAST_WRITTEN,
pcmk__s(now_s, "Could not determine current time"));
free(now_s);
return result;
}
/*!
* \brief Sanitize a string so it is usable as an XML ID
*
* \param[in,out] id String to sanitize
*/
void
crm_xml_sanitize_id(char *id)
{
char *c;
for (c = id; *c; ++c) {
/* @TODO Sanitize more comprehensively */
switch (*c) {
case ':':
case '#':
*c = '.';
}
}
}
/*!
* \brief Set the ID of an XML element using a format
*
* \param[in,out] xml XML element
* \param[in] fmt printf-style format
* \param[in] ... any arguments required by format
*/
void
crm_xml_set_id(xmlNode *xml, const char *format, ...)
{
va_list ap;
int len = 0;
char *id = NULL;
/* equivalent to crm_strdup_printf() */
va_start(ap, format);
len = vasprintf(&id, format, ap);
va_end(ap);
CRM_ASSERT(len > 0);
crm_xml_sanitize_id(id);
crm_xml_add(xml, PCMK_XA_ID, id);
free(id);
}
/*!
* \internal
* \brief Check whether a string has XML special characters that must be escaped
*
* See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
*
* \param[in] text String to check
* \param[in] type Type of escaping
*
* \return \c true if \p text has special characters that need to be escaped, or
* \c false otherwise
*/
bool
pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
{
if (text == NULL) {
return false;
}
while (*text != '\0') {
switch (type) {
case pcmk__xml_escape_text:
switch (*text) {
case '<':
case '>':
case '&':
return true;
case '\n':
case '\t':
break;
default:
if (g_ascii_iscntrl(*text)) {
return true;
}
break;
}
break;
case pcmk__xml_escape_attr:
switch (*text) {
case '<':
case '>':
case '&':
case '"':
return true;
default:
if (g_ascii_iscntrl(*text)) {
return true;
}
break;
}
break;
case pcmk__xml_escape_attr_pretty:
switch (*text) {
case '\n':
case '\r':
case '\t':
case '"':
return true;
default:
break;
}
break;
default: // Invalid enum value
CRM_ASSERT(false);
break;
}
text = g_utf8_next_char(text);
}
return false;
}
/*!
* \internal
* \brief Replace special characters with their XML escape sequences
*
* \param[in] text Text to escape
* \param[in] type Type of escaping
*
* \return Newly allocated string equivalent to \p text but with special
* characters replaced with XML escape sequences (or \c NULL if \p text
* is \c NULL). If \p text is not \c NULL, the return value is
* guaranteed not to be \c NULL.
*
* \note There are libxml functions that purport to do this:
* \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
* However, their escaping is incomplete. See:
* https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
* \note The caller is responsible for freeing the return value using
* \c g_free().
*/
gchar *
pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
{
GString *copy = NULL;
if (text == NULL) {
return NULL;
}
copy = g_string_sized_new(strlen(text));
while (*text != '\0') {
// Don't escape any non-ASCII characters
if ((*text & 0x80) != 0) {
size_t bytes = g_utf8_next_char(text) - text;
g_string_append_len(copy, text, bytes);
text += bytes;
continue;
}
switch (type) {
case pcmk__xml_escape_text:
switch (*text) {
case '<':
g_string_append(copy, PCMK__XML_ENTITY_LT);
break;
case '>':
g_string_append(copy, PCMK__XML_ENTITY_GT);
break;
case '&':
g_string_append(copy, PCMK__XML_ENTITY_AMP);
break;
case '\n':
case '\t':
g_string_append_c(copy, *text);
break;
default:
if (g_ascii_iscntrl(*text)) {
g_string_append_printf(copy, "&#x%.2X;", *text);
} else {
g_string_append_c(copy, *text);
}
break;
}
break;
case pcmk__xml_escape_attr:
switch (*text) {
case '<':
g_string_append(copy, PCMK__XML_ENTITY_LT);
break;
case '>':
g_string_append(copy, PCMK__XML_ENTITY_GT);
break;
case '&':
g_string_append(copy, PCMK__XML_ENTITY_AMP);
break;
case '"':
g_string_append(copy, PCMK__XML_ENTITY_QUOT);
break;
default:
if (g_ascii_iscntrl(*text)) {
g_string_append_printf(copy, "&#x%.2X;", *text);
} else {
g_string_append_c(copy, *text);
}
break;
}
break;
case pcmk__xml_escape_attr_pretty:
switch (*text) {
case '"':
g_string_append(copy, "\\\"");
break;
case '\n':
g_string_append(copy, "\\n");
break;
case '\r':
g_string_append(copy, "\\r");
break;
case '\t':
g_string_append(copy, "\\t");
break;
default:
g_string_append_c(copy, *text);
break;
}
break;
default: // Invalid enum value
CRM_ASSERT(false);
break;
}
text = g_utf8_next_char(text);
}
return g_string_free(copy, FALSE);
}
/*!
* \internal
* \brief Set a flag on all attributes of an XML element
*
* \param[in,out] xml XML node to set flags on
* \param[in] flag XML private flag to set
*/
static void
set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
{
for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
}
}
/*!
* \internal
* \brief Add an XML attribute to a node, marked as deleted
*
* When calculating XML changes, we need to know when an attribute has been
* deleted. Add the attribute back to the new XML, so that we can check the
* removal against ACLs, and mark it as deleted for later removal after
* differences have been calculated.
*
* \param[in,out] new_xml XML to modify
* \param[in] element Name of XML element that changed (for logging)
* \param[in] attr_name Name of attribute that was deleted
* \param[in] old_value Value of attribute that was deleted
*/
static void
mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
const char *old_value)
{
xml_doc_private_t *docpriv = new_xml->doc->_private;
xmlAttr *attr = NULL;
xml_node_private_t *nodepriv;
// Prevent the dirty flag being set recursively upwards
pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
// Restore the old value (and the tracking flag)
attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
// Reset flags (so the attribute doesn't appear as newly created)
nodepriv = attr->_private;
nodepriv->flags = 0;
// Check ACLs and mark restored value for later removal
remove_xe_attr(new_xml, attr);
crm_trace("XML attribute %s=%s was removed from %s",
attr_name, old_value, element);
}
/*
* \internal
* \brief Check ACLs for a changed XML attribute
*/
static void
mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
const char *old_value)
{
char *vcopy = crm_element_value_copy(new_xml, attr_name);
crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
attr_name, old_value, vcopy, element);
// Restore the original value
xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
// Change it back to the new value, to check ACLs
crm_xml_add(new_xml, attr_name, vcopy);
free(vcopy);
}
/*!
* \internal
* \brief Mark an XML attribute as having changed position
*
* \param[in,out] new_xml XML to modify
* \param[in] element Name of XML element that changed (for logging)
* \param[in,out] old_attr Attribute that moved, in original XML
* \param[in,out] new_attr Attribute that moved, in \p new_xml
* \param[in] p_old Ordinal position of \p old_attr in original XML
* \param[in] p_new Ordinal position of \p new_attr in \p new_xml
*/
static void
mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
xmlAttr *new_attr, int p_old, int p_new)
{
xml_node_private_t *nodepriv = new_attr->_private;
crm_trace("XML attribute %s moved from position %d to %d in %s",
old_attr->name, p_old, p_new, element);
// Mark document, element, and all element's parents as changed
pcmk__mark_xml_node_dirty(new_xml);
// Mark attribute as changed
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
}
/*!
* \internal
* \brief Calculate differences in all previously existing XML attributes
*
* \param[in,out] old_xml Original XML to compare
* \param[in,out] new_xml New XML to compare
*/
static void
xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
{
xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
while (attr_iter != NULL) {
const char *name = (const char *) attr_iter->name;
xmlAttr *old_attr = attr_iter;
xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
const char *old_value = pcmk__xml_attr_value(attr_iter);
attr_iter = attr_iter->next;
if (new_attr == NULL) {
mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
old_value);
} else {
xml_node_private_t *nodepriv = new_attr->_private;
int new_pos = pcmk__xml_position((xmlNode*) new_attr,
pcmk__xf_skip);
int old_pos = pcmk__xml_position((xmlNode*) old_attr,
pcmk__xf_skip);
const char *new_value = crm_element_value(new_xml, name);
// This attribute isn't new
pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
if (strcmp(new_value, old_value) != 0) {
mark_attr_changed(new_xml, (const char *) old_xml->name, name,
old_value);
} else if ((old_pos != new_pos)
&& !pcmk__tracking_xml_changes(new_xml, TRUE)) {
mark_attr_moved(new_xml, (const char *) old_xml->name,
old_attr, new_attr, old_pos, new_pos);
}
}
}
}
/*!
* \internal
* \brief Check all attributes in new XML for creation
*
* For each of a given XML element's attributes marked as newly created, accept
* (and mark as dirty) or reject the creation according to ACLs.
*
* \param[in,out] new_xml XML to check
*/
static void
mark_created_attrs(xmlNode *new_xml)
{
xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
while (attr_iter != NULL) {
xmlAttr *new_attr = attr_iter;
xml_node_private_t *nodepriv = attr_iter->_private;
attr_iter = attr_iter->next;
if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
const char *attr_name = (const char *) new_attr->name;
crm_trace("Created new attribute %s=%s in %s",
attr_name, pcmk__xml_attr_value(new_attr),
new_xml->name);
/* Check ACLs (we can't use the remove-then-create trick because it
* would modify the attribute position).
*/
if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
pcmk__mark_xml_attr_dirty(new_attr);
} else {
// Creation was not allowed, so remove the attribute
xmlUnsetProp(new_xml, new_attr->name);
}
}
}
}
/*!
* \internal
* \brief Calculate differences in attributes between two XML nodes
*
* \param[in,out] old_xml Original XML to compare
* \param[in,out] new_xml New XML to compare
*/
static void
xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
{
set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
xml_diff_old_attrs(old_xml, new_xml);
mark_created_attrs(new_xml);
}
/*!
* \internal
* \brief Add an XML child element to a node, marked as deleted
*
* When calculating XML changes, we need to know when a child element has been
* deleted. Add the child back to the new XML, so that we can check the removal
* against ACLs, and mark it as deleted for later removal after differences have
* been calculated.
*
* \param[in,out] old_child Child element from original XML
* \param[in,out] new_parent New XML to add marked copy to
*/
static void
mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
{
// Re-create the child element so we can check ACLs
xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
// Clear flags on new child and its children
pcmk__xml_tree_foreach(candidate, reset_xml_node_flags, NULL);
// Check whether ACLs allow the deletion
pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
// Remove the child again (which will track it in document's deleted_objs)
free_xml_with_position(candidate,
pcmk__xml_position(old_child, pcmk__xf_skip));
if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
pcmk__xf_skip);
}
}
static void
mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
int p_old, int p_new)
{
xml_node_private_t *nodepriv = new_child->_private;
crm_trace("Child element %s with "
PCMK_XA_ID "='%s' moved from position %d to %d under %s",
new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
p_old, p_new, new_parent->name);
pcmk__mark_xml_node_dirty(new_parent);
pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
if (p_old > p_new) {
nodepriv = old_child->_private;
} else {
nodepriv = new_child->_private;
}
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
}
// Given original and new XML, mark new XML portions that have changed
static void
mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
{
xmlNode *old_child = NULL;
xmlNode *new_child = NULL;
xml_node_private_t *nodepriv = NULL;
CRM_CHECK(new_xml != NULL, return);
if (old_xml == NULL) {
pcmk__xml_mark_created(new_xml);
pcmk__apply_creation_acl(new_xml, check_top);
return;
}
nodepriv = new_xml->_private;
CRM_CHECK(nodepriv != NULL, return);
if(nodepriv->flags & pcmk__xf_processed) {
/* Avoid re-comparing nodes */
return;
}
pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
xml_diff_attrs(old_xml, new_xml);
// Check for differences in the original children
for (old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
old_child = pcmk__xml_next(old_child)) {
new_child = pcmk__xml_match(new_xml, old_child, true);
if (new_child != NULL) {
mark_xml_changes(old_child, new_child, true);
} else {
mark_child_deleted(old_child, new_xml);
}
}
// Check for moved or created children
new_child = pcmk__xml_first_child(new_xml);
while (new_child != NULL) {
xmlNode *next = pcmk__xml_next(new_child);
old_child = pcmk__xml_match(old_xml, new_child, true);
if (old_child == NULL) {
// This is a newly created child
nodepriv = new_child->_private;
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
// May free new_child
mark_xml_changes(old_child, new_child, true);
} else {
/* Check for movement, we already checked for differences */
int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
if(p_old != p_new) {
mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
}
}
new_child = next;
}
}
void
xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
{
pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
xml_calculate_changes(old_xml, new_xml);
}
// Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
void
xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
{
CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
&& pcmk__xe_is(old_xml, (const char *) new_xml->name)
&& pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
pcmk__str_none),
return);
if(xml_tracking_changes(new_xml) == FALSE) {
xml_track_changes(new_xml, NULL, NULL, FALSE);
}
mark_xml_changes(old_xml, new_xml, FALSE);
}
/*!
* \internal
* \brief Find a comment with matching content in specified XML
*
* \param[in] root XML to search
* \param[in] search_comment Comment whose content should be searched for
* \param[in] exact If true, comment must also be at same position
*/
xmlNode *
pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
{
xmlNode *a_child = NULL;
int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
for (a_child = pcmk__xml_first_child(root); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
if (exact) {
int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
xml_node_private_t *nodepriv = a_child->_private;
if (offset < search_offset) {
continue;
} else if (offset > search_offset) {
return NULL;
}
if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
continue;
}
}
if (a_child->type == XML_COMMENT_NODE
&& pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
return a_child;
} else if (exact) {
return NULL;
}
}
return NULL;
}
/*!
* \internal
* \brief Make one XML comment match another (in content)
*
* \param[in,out] parent If \p target is NULL and this is not, add or update
* comment child of this XML node that matches \p update
* \param[in,out] target If not NULL, update this XML comment node
* \param[in] update Make comment content match this (must not be NULL)
*
* \note At least one of \parent and \target must be non-NULL
*/
void
pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
{
CRM_CHECK(update != NULL, return);
CRM_CHECK(update->type == XML_COMMENT_NODE, return);
if (target == NULL) {
target = pcmk__xc_match(parent, update, false);
}
if (target == NULL) {
pcmk__xml_copy(parent, update);
} else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
xmlFree(target->content);
target->content = xmlStrdup(update->content);
}
}
/*!
* \internal
* \brief Merge one XML tree into another
*
* Here, "merge" means:
* 1. Copy attribute values from \p update to the target, overwriting in case of
* conflict.
* 2. Descend through \p update and the target in parallel. At each level, for
* each child of \p update, look for a matching child of the target.
* a. For each child, if a match is found, go to step 1, recursively merging
* the child of \p update into the child of the target.
* b. Otherwise, copy the child of \p update as a child of the target.
*
* A match is defined as the first child of the same type within the target,
* with:
* * the \c PCMK_XA_ID attribute matching, if set in \p update; otherwise,
* * the \c PCMK_XA_ID_REF attribute matching, if set in \p update
*
* This function does not delete any elements or attributes from the target. It
* may add elements or overwrite attributes, as described above.
*
* \param[in,out] parent If \p target is NULL and this is not, add or update
* child of this XML node that matches \p update
* \param[in,out] target If not NULL, update this XML
* \param[in] update Make the desired XML match this (must not be \c NULL)
* \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
* \param[in] as_diff If \c true, preserve order of attributes (deprecated
* since 2.0.5)
*
* \note At least one of \p parent and \p target must be non-<tt>NULL</tt>.
* \note This function is recursive. For the top-level call, \p parent is
* \c NULL and \p target is not \c NULL. For recursive calls, \p target is
* \c NULL and \p parent is not \c NULL.
*/
void
pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
uint32_t flags, bool as_diff)
{
/* @COMPAT Refactor further and staticize after v1 patchset deprecation.
*
* @COMPAT Drop as_diff argument when apply_xml_diff() is dropped.
*/
const char *update_name = NULL;
const char *update_id_attr = NULL;
const char *update_id_val = NULL;
char *trace_s = NULL;
crm_log_xml_trace(update, "update");
crm_log_xml_trace(target, "target");
CRM_CHECK(update != NULL, goto done);
if (update->type == XML_COMMENT_NODE) {
pcmk__xc_update(parent, target, update);
goto done;
}
update_name = (const char *) update->name;
CRM_CHECK(update_name != NULL, goto done);
CRM_CHECK((target != NULL) || (parent != NULL), goto done);
update_id_val = pcmk__xe_id(update);
if (update_id_val != NULL) {
update_id_attr = PCMK_XA_ID;
} else {
update_id_val = crm_element_value(update, PCMK_XA_ID_REF);
if (update_id_val != NULL) {
update_id_attr = PCMK_XA_ID_REF;
}
}
pcmk__if_tracing(
{
if (update_id_attr != NULL) {
trace_s = crm_strdup_printf("<%s %s=%s/>",
update_name, update_id_attr,
update_id_val);
} else {
trace_s = crm_strdup_printf("<%s/>", update_name);
}
},
{}
);
if (target == NULL) {
// Recursive call
target = pcmk__xe_first_child(parent, update_name, update_id_attr,
update_id_val);
}
if (target == NULL) {
// Recursive call with no existing matching child
target = pcmk__xe_create(parent, update_name);
crm_trace("Added %s", pcmk__s(trace_s, update_name));
} else {
// Either recursive call with match, or top-level call
crm_trace("Found node %s to update", pcmk__s(trace_s, update_name));
}
CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
if (!as_diff) {
pcmk__xe_copy_attrs(target, update, flags);
} else {
// Preserve order of attributes. Don't use pcmk__xe_copy_attrs().
for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
a = a->next) {
const char *p_value = pcmk__xml_attr_value(a);
/* Remove it first so the ordering of the update is preserved */
xmlUnsetProp(target, a->name);
xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
}
}
for (xmlNode *child = pcmk__xml_first_child(update); child != NULL;
child = pcmk__xml_next(child)) {
crm_trace("Updating child of %s", pcmk__s(trace_s, update_name));
pcmk__xml_update(target, NULL, child, flags, as_diff);
}
crm_trace("Finished with %s", pcmk__s(trace_s, update_name));
done:
free(trace_s);
}
/*!
* \internal
* \brief Delete an XML subtree if it matches a search element
*
* A match is defined as follows:
* * \p xml and \p user_data are both element nodes of the same type.
* * If \p user_data has attributes set, \p xml has those attributes set to the
* same values. (\p xml may have additional attributes set to arbitrary
* values.)
*
* \param[in,out] xml XML subtree to delete upon match
* \param[in] user_data Search element
*
* \return \c true to continue traversing the tree, or \c false to stop (because
* \p xml was deleted)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
delete_xe_if_matching(xmlNode *xml, void *user_data)
{
xmlNode *search = user_data;
if (!pcmk__xe_is(search, (const char *) xml->name)) {
// No match: either not both elements, or different element types
return true;
}
for (const xmlAttr *attr = pcmk__xe_first_attr(search); attr != NULL;
attr = attr->next) {
const char *search_val = pcmk__xml_attr_value(attr);
const char *xml_val = crm_element_value(xml, (const char *) attr->name);
if (!pcmk__str_eq(search_val, xml_val, pcmk__str_casei)) {
// No match: an attr in xml doesn't match the attr in search
return true;
}
}
crm_log_xml_trace(xml, "delete-match");
crm_log_xml_trace(search, "delete-search");
free_xml(xml);
// Found a match and deleted it; stop traversing tree
return false;
}
/*!
* \internal
* \brief Search an XML tree depth-first and delete the first matching element
*
* This function does not attempt to match the tree root (\p xml).
*
* A match with a node \c node is defined as follows:
* * \c node and \p search are both element nodes of the same type.
* * If \p search has attributes set, \c node has those attributes set to the
* same values. (\c node may have additional attributes set to arbitrary
* values.)
*
* \param[in,out] xml XML subtree to search
* \param[in] search Element to match against
*
* \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
* successful deletion and an error code otherwise)
*/
int
pcmk__xe_delete_match(xmlNode *xml, xmlNode *search)
{
// See @COMPAT comment in pcmk__xe_replace_match()
CRM_CHECK((xml != NULL) && (search != NULL), return EINVAL);
for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
xml = pcmk__xe_next(xml)) {
if (!pcmk__xml_tree_foreach(xml, delete_xe_if_matching, search)) {
// Found and deleted an element
return pcmk_rc_ok;
}
}
// No match found in this subtree
return ENXIO;
}
/*!
* \internal
* \brief Replace one XML node with a copy of another XML node
*
* This function handles change tracking and applies ACLs.
*
* \param[in,out] old XML node to replace
* \param[in] new XML node to copy as replacement for \p old
*
* \note This frees \p old.
*/
static void
replace_node(xmlNode *old, xmlNode *new)
{
new = xmlCopyNode(new, 1);
pcmk__mem_assert(new);
// May be unnecessary but avoids slight changes to some test outputs
pcmk__xml_tree_foreach(new, reset_xml_node_flags, NULL);
old = xmlReplaceNode(old, new);
if (xml_tracking_changes(new)) {
// Replaced sections may have included relevant ACLs
pcmk__apply_acl(new);
}
xml_calculate_changes(old, new);
xmlFreeNode(old);
}
/*!
* \internal
* \brief Replace one XML subtree with a copy of another if the two match
*
* A match is defined as follows:
* * \p xml and \p user_data are both element nodes of the same type.
* * If \p user_data has the \c PCMK_XA_ID attribute set, then \p xml has
* \c PCMK_XA_ID set to the same value.
*
* \param[in,out] xml XML subtree to replace with \p user_data upon match
* \param[in] user_data XML to replace \p xml with a copy of upon match
*
* \return \c true to continue traversing the tree, or \c false to stop (because
* \p xml was replaced by \p user_data)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
replace_xe_if_matching(xmlNode *xml, void *user_data)
{
xmlNode *replace = user_data;
const char *xml_id = NULL;
const char *replace_id = NULL;
xml_id = pcmk__xe_id(xml);
replace_id = pcmk__xe_id(replace);
if (!pcmk__xe_is(replace, (const char *) xml->name)) {
// No match: either not both elements, or different element types
return true;
}
if ((replace_id != NULL)
&& !pcmk__str_eq(replace_id, xml_id, pcmk__str_none)) {
// No match: ID was provided in replace and doesn't match xml's ID
return true;
}
crm_log_xml_trace(xml, "replace-match");
crm_log_xml_trace(replace, "replace-with");
replace_node(xml, replace);
// Found a match and replaced it; stop traversing tree
return false;
}
/*!
* \internal
* \brief Search an XML tree depth-first and replace the first matching element
*
* This function does not attempt to match the tree root (\p xml).
*
* A match with a node \c node is defined as follows:
* * \c node and \p replace are both element nodes of the same type.
* * If \p replace has the \c PCMK_XA_ID attribute set, then \c node has
* \c PCMK_XA_ID set to the same value.
*
* \param[in,out] xml XML tree to search
* \param[in] replace XML to replace a matching element with a copy of
*
* \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
* successful replacement and an error code otherwise)
*/
int
pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace)
{
/* @COMPAT Some of this behavior (like not matching the tree root, which is
* allowed by pcmk__xe_update_match()) is questionable for general use but
* required for backward compatibility by cib_process_replace() and
* cib_process_delete(). Behavior can change at a major version release if
* desired.
*/
CRM_CHECK((xml != NULL) && (replace != NULL), return EINVAL);
for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
xml = pcmk__xe_next(xml)) {
if (!pcmk__xml_tree_foreach(xml, replace_xe_if_matching, replace)) {
// Found and replaced an element
return pcmk_rc_ok;
}
}
// No match found in this subtree
return ENXIO;
}
//! User data for \c update_xe_if_matching()
struct update_data {
xmlNode *update; //!< Update source
uint32_t flags; //!< Group of <tt>enum pcmk__xa_flags</tt>
};
/*!
* \internal
* \brief Update one XML subtree with another if the two match
*
* "Update" means to merge a source subtree into a target subtree (see
* \c pcmk__xml_update()).
*
* A match is defined as follows:
* * \p xml and \p user_data->update are both element nodes of the same type.
* * \p xml and \p user_data->update have the same \c PCMK_XA_ID attribute
* value, or \c PCMK_XA_ID is unset in both
*
* \param[in,out] xml XML subtree to update with \p user_data->update
* upon match
* \param[in] user_data <tt>struct update_data</tt> object
*
* \return \c true to continue traversing the tree, or \c false to stop (because
* \p xml was updated by \p user_data->update)
*
* \note This is compatible with \c pcmk__xml_tree_foreach().
*/
static bool
update_xe_if_matching(xmlNode *xml, void *user_data)
{
struct update_data *data = user_data;
xmlNode *update = data->update;
if (!pcmk__xe_is(update, (const char *) xml->name)) {
// No match: either not both elements, or different element types
return true;
}
if (!pcmk__str_eq(pcmk__xe_id(xml), pcmk__xe_id(update), pcmk__str_none)) {
// No match: ID mismatch
return true;
}
crm_log_xml_trace(xml, "update-match");
crm_log_xml_trace(update, "update-with");
pcmk__xml_update(NULL, xml, update, data->flags, false);
// Found a match and replaced it; stop traversing tree
return false;
}
/*!
* \internal
* \brief Search an XML tree depth-first and update the first matching element
*
* "Update" means to merge a source subtree into a target subtree (see
* \c pcmk__xml_update()).
*
* A match with a node \c node is defined as follows:
* * \c node and \p update are both element nodes of the same type.
* * \c node and \p update have the same \c PCMK_XA_ID attribute value, or
* \c PCMK_XA_ID is unset in both
*
* \param[in,out] xml XML tree to search
* \param[in] update XML to update a matching element with
* \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
*
* \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
* successful update and an error code otherwise)
*/
int
pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags)
{
/* @COMPAT In pcmk__xe_delete_match() and pcmk__xe_replace_match(), we
* compare IDs only if the equivalent of the update argument has an ID.
* Here, we're stricter: we consider it a mismatch if only one element has
* an ID attribute, or if both elements have IDs but they don't match.
*
* Perhaps we should align the behavior at a major version release.
*/
struct update_data data = {
.update = update,
.flags = flags,
};
CRM_CHECK((xml != NULL) && (update != NULL), return EINVAL);
if (!pcmk__xml_tree_foreach(xml, update_xe_if_matching, &data)) {
// Found and updated an element
return pcmk_rc_ok;
}
// No match found in this subtree
return ENXIO;
}
xmlNode *
sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
{
xmlNode *child = NULL;
GSList *nvpairs = NULL;
xmlNode *result = NULL;
CRM_CHECK(input != NULL, return NULL);
result = pcmk__xe_create(parent, (const char *) input->name);
nvpairs = pcmk_xml_attrs2nvpairs(input);
nvpairs = pcmk_sort_nvpairs(nvpairs);
pcmk_nvpairs2xml_attrs(nvpairs, result);
pcmk_free_nvpairs(nvpairs);
for (child = pcmk__xe_first_child(input, NULL, NULL, NULL); child != NULL;
child = pcmk__xe_next(child)) {
if (recursive) {
sorted_xml(child, result, recursive);
} else {
pcmk__xml_copy(result, child);
}
}
return result;
}
/*!
* \internal
* \brief Get next sibling XML element with the same name as a given element
*
* \param[in] node XML element to start from
*
* \return Next sibling XML element with same name
*/
xmlNode *
pcmk__xe_next_same(const xmlNode *node)
{
for (xmlNode *match = pcmk__xe_next(node); match != NULL;
match = pcmk__xe_next(match)) {
if (pcmk__xe_is(match, (const char *) node->name)) {
return match;
}
}
return NULL;
}
/*!
* \internal
* \brief Initialize the Pacemaker XML environment
*
* Set an XML buffer allocation scheme, set XML node create and destroy
* callbacks, and load schemas into the cache.
*/
void
pcmk__xml_init(void)
{
// @TODO Try to find a better caller than crm_log_preinit()
static bool initialized = false;
if (!initialized) {
initialized = true;
/* Double the buffer size when the buffer needs to grow. The default
* allocator XML_BUFFER_ALLOC_EXACT was found to cause poor performance
* due to the number of reallocs.
*/
xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
// Initialize private data at node creation
xmlRegisterNodeDefault(new_private_data);
// Free private data at node destruction
xmlDeregisterNodeDefault(free_private_data);
// Load schemas into the cache
crm_schema_init();
}
}
+/*!
+ * \internal
+ * \brief Tear down the Pacemaker XML environment
+ *
+ * Destroy schema cache and clean up memory allocated by libxml2.
+ */
void
-crm_xml_cleanup(void)
+pcmk__xml_cleanup(void)
{
crm_schema_cleanup();
xmlCleanupParser();
}
+void
+crm_xml_cleanup(void)
+{
+ pcmk__xml_cleanup();
+}
+
#define XPATH_MAX 512
xmlNode *
expand_idref(xmlNode * input, xmlNode * top)
{
char *xpath = NULL;
const char *ref = NULL;
xmlNode *result = NULL;
if (input == NULL) {
return NULL;
}
ref = crm_element_value(input, PCMK_XA_ID_REF);
if (ref == NULL) {
return input;
}
if (top == NULL) {
top = input;
}
xpath = crm_strdup_printf("//%s[@" PCMK_XA_ID "='%s']", input->name, ref);
result = get_xpath_object(xpath, top, LOG_DEBUG);
if (result == NULL) { // Not possible with schema validation enabled
pcmk__config_err("Ignoring invalid %s configuration: "
PCMK_XA_ID_REF " '%s' does not reference "
"a valid object " CRM_XS " xpath=%s",
input->name, ref, xpath);
}
free(xpath);
return result;
}
char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
{
static const char *base = NULL;
char *ret = NULL;
if (base == NULL) {
base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
}
if (pcmk__str_empty(base)) {
base = CRM_SCHEMA_DIRECTORY;
}
switch (ns) {
case pcmk__xml_artefact_ns_legacy_rng:
case pcmk__xml_artefact_ns_legacy_xslt:
ret = strdup(base);
break;
case pcmk__xml_artefact_ns_base_rng:
case pcmk__xml_artefact_ns_base_xslt:
ret = crm_strdup_printf("%s/base", base);
break;
default:
crm_err("XML artefact family specified as %u not recognized", ns);
}
return ret;
}
static char *
find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
{
char *ret = NULL;
switch (ns) {
case pcmk__xml_artefact_ns_legacy_rng:
case pcmk__xml_artefact_ns_base_rng:
if (pcmk__ends_with(filespec, ".rng")) {
ret = crm_strdup_printf("%s/%s", path, filespec);
} else {
ret = crm_strdup_printf("%s/%s.rng", path, filespec);
}
break;
case pcmk__xml_artefact_ns_legacy_xslt:
case pcmk__xml_artefact_ns_base_xslt:
if (pcmk__ends_with(filespec, ".xsl")) {
ret = crm_strdup_printf("%s/%s", path, filespec);
} else {
ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
}
break;
default:
crm_err("XML artefact family specified as %u not recognized", ns);
}
return ret;
}
char *
pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
{
struct stat sb;
char *base = pcmk__xml_artefact_root(ns);
char *ret = NULL;
ret = find_artefact(ns, base, filespec);
free(base);
if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
const char *remote_schema_dir = pcmk__remote_schema_dir();
free(ret);
ret = find_artefact(ns, remote_schema_dir, filespec);
}
return ret;
}
void
pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
{
while (true) {
const char *name, *value;
name = va_arg(pairs, const char *);
if (name == NULL) {
return;
}
value = va_arg(pairs, const char *);
if (value != NULL) {
crm_xml_add(node, name, value);
}
}
}
void
pcmk__xe_set_props(xmlNodePtr node, ...)
{
va_list pairs;
va_start(pairs, node);
pcmk__xe_set_propv(node, pairs);
va_end(pairs);
}
int
pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
int (*handler)(xmlNode *xml, void *userdata),
void *userdata)
{
xmlNode *children = (xml? xml->children : NULL);
CRM_ASSERT(handler != NULL);
for (xmlNode *node = children; node != NULL; node = node->next) {
if ((node->type == XML_ELEMENT_NODE)
&& ((child_element_name == NULL)
|| pcmk__xe_is(node, child_element_name))) {
int rc = handler(node, userdata);
if (rc != pcmk_rc_ok) {
return rc;
}
}
}
return pcmk_rc_ok;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
xmlNode *
find_entity(xmlNode *parent, const char *node_name, const char *id)
{
return pcmk__xe_first_child(parent, node_name,
((id == NULL)? id : PCMK_XA_ID), id);
}
void
crm_destroy_xml(gpointer data)
{
free_xml(data);
}
xmlDoc *
getDocPtr(xmlNode *node)
{
xmlDoc *doc = NULL;
CRM_CHECK(node != NULL, return NULL);
doc = node->doc;
if (doc == NULL) {
doc = xmlNewDoc(PCMK__XML_VERSION);
xmlDocSetRootElement(doc, node);
}
return doc;
}
xmlNode *
add_node_copy(xmlNode *parent, xmlNode *src_node)
{
xmlNode *child = NULL;
CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
child = xmlDocCopyNode(src_node, parent->doc, 1);
if (child == NULL) {
return NULL;
}
xmlAddChild(parent, child);
pcmk__xml_mark_created(child);
return child;
}
int
add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
{
add_node_copy(parent, child);
free_xml(child);
return 1;
}
gboolean
xml_has_children(const xmlNode * xml_root)
{
if (xml_root != NULL && xml_root->children != NULL) {
return TRUE;
}
return FALSE;
}
static char *
replace_text(char *text, size_t *index, size_t *length, const char *replace)
{
// We have space for 1 char already
size_t offset = strlen(replace) - 1;
if (offset > 0) {
*length += offset;
text = pcmk__realloc(text, *length + 1);
// Shift characters to the right to make room for the replacement string
for (size_t i = *length; i > (*index + offset); i--) {
text[i] = text[i - offset];
}
}
// Replace the character at index by the replacement string
memcpy(text + *index, replace, offset + 1);
// Reset index to the end of replacement string
*index += offset;
return text;
}
char *
crm_xml_escape(const char *text)
{
size_t length = 0;
char *copy = NULL;
if (text == NULL) {
return NULL;
}
length = strlen(text);
copy = pcmk__str_copy(text);
for (size_t index = 0; index <= length; index++) {
if(copy[index] & 0x80 && copy[index+1] & 0x80){
index++;
continue;
}
switch (copy[index]) {
case 0:
// Sanity only; loop should stop at the last non-null byte
break;
case '<':
copy = replace_text(copy, &index, &length, "&lt;");
break;
case '>':
copy = replace_text(copy, &index, &length, "&gt;");
break;
case '"':
copy = replace_text(copy, &index, &length, "&quot;");
break;
case '\'':
copy = replace_text(copy, &index, &length, "&apos;");
break;
case '&':
copy = replace_text(copy, &index, &length, "&amp;");
break;
case '\t':
/* Might as well just expand to a few spaces... */
copy = replace_text(copy, &index, &length, " ");
break;
case '\n':
copy = replace_text(copy, &index, &length, "\\n");
break;
case '\r':
copy = replace_text(copy, &index, &length, "\\r");
break;
default:
/* Check for and replace non-printing characters with their octal equivalent */
if(copy[index] < ' ' || copy[index] > '~') {
char *replace = crm_strdup_printf("\\%.3o", copy[index]);
copy = replace_text(copy, &index, &length, replace);
free(replace);
}
}
}
return copy;
}
xmlNode *
copy_xml(xmlNode *src)
{
xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
xmlNode *copy = NULL;
pcmk__mem_assert(doc);
copy = xmlDocCopyNode(src, doc, 1);
pcmk__mem_assert(copy);
xmlDocSetRootElement(doc, copy);
return copy;
}
xmlNode *
create_xml_node(xmlNode *parent, const char *name)
{
// Like pcmk__xe_create(), but returns NULL on failure
xmlNode *node = NULL;
CRM_CHECK(!pcmk__str_empty(name), return NULL);
if (parent == NULL) {
xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
if (doc == NULL) {
return NULL;
}
node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
if (node == NULL) {
xmlFreeDoc(doc);
return NULL;
}
xmlDocSetRootElement(doc, node);
} else {
node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
if (node == NULL) {
return NULL;
}
}
pcmk__xml_mark_created(node);
return node;
}
xmlNode *
pcmk_create_xml_text_node(xmlNode *parent, const char *name,
const char *content)
{
xmlNode *node = pcmk__xe_create(parent, name);
pcmk__xe_set_content(node, "%s", content);
return node;
}
xmlNode *
pcmk_create_html_node(xmlNode *parent, const char *element_name, const char *id,
const char *class_name, const char *text)
{
xmlNode *node = pcmk__html_create(parent, element_name, id, class_name);
pcmk__xe_set_content(node, "%s", text);
return node;
}
xmlNode *
first_named_child(const xmlNode *parent, const char *name)
{
return pcmk__xe_first_child(parent, name, NULL, NULL);
}
xmlNode *
find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
{
xmlNode *result = NULL;
if (search_path == NULL) {
crm_warn("Will never find <NULL>");
return NULL;
}
result = pcmk__xe_first_child(root, search_path, NULL, NULL);
if (must_find && (result == NULL)) {
crm_warn("Could not find %s in %s",
search_path,
((root != NULL)? (const char *) root->name : "<NULL>"));
}
return result;
}
xmlNode *
crm_next_same_xml(const xmlNode *sibling)
{
return pcmk__xe_next_same(sibling);
}
void
xml_remove_prop(xmlNode * obj, const char *name)
{
pcmk__xe_remove_attr(obj, name);
}
gboolean
replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
{
bool is_match = false;
const char *child_id = NULL;
const char *update_id = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(update != NULL, return FALSE);
child_id = pcmk__xe_id(child);
update_id = pcmk__xe_id(update);
/* Match element name and (if provided in update XML) element ID. Don't
* match search root (child is search root if parent == NULL).
*/
is_match = (parent != NULL)
&& pcmk__xe_is(update, (const char *) child->name)
&& ((update_id == NULL)
|| pcmk__str_eq(update_id, child_id, pcmk__str_none));
/* For deletion, match all attributes provided in update. A matching node
* can have additional attributes, but values must match for provided ones.
*/
if (is_match && delete_only) {
for (xmlAttr *attr = pcmk__xe_first_attr(update); attr != NULL;
attr = attr->next) {
const char *name = (const char *) attr->name;
const char *update_val = pcmk__xml_attr_value(attr);
const char *child_val = crm_element_value(child, name);
if (!pcmk__str_eq(update_val, child_val, pcmk__str_casei)) {
is_match = false;
break;
}
}
}
if (is_match) {
if (delete_only) {
crm_log_xml_trace(child, "delete-match");
crm_log_xml_trace(update, "delete-search");
free_xml(child);
} else {
crm_log_xml_trace(child, "replace-match");
crm_log_xml_trace(update, "replace-with");
replace_node(child, update);
}
return TRUE;
}
// Current node not a match; search the rest of the subtree depth-first
parent = child;
for (child = pcmk__xml_first_child(parent); child != NULL;
child = pcmk__xml_next(child)) {
// Only delete/replace the first match
if (replace_xml_child(parent, child, update, delete_only)) {
return TRUE;
}
}
// No match found in this subtree
return FALSE;
}
gboolean
update_xml_child(xmlNode *child, xmlNode *to_update)
{
return pcmk__xe_update_match(child, to_update,
pcmk__xaf_score_update) == pcmk_rc_ok;
}
int
find_xml_children(xmlNode **children, xmlNode *root, const char *tag,
const char *field, const char *value, gboolean search_matches)
{
int match_found = 0;
CRM_CHECK(root != NULL, return FALSE);
CRM_CHECK(children != NULL, return FALSE);
if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
} else if ((value != NULL)
&& !pcmk__str_eq(value, crm_element_value(root, field),
pcmk__str_casei)) {
} else {
if (*children == NULL) {
*children = pcmk__xe_create(NULL, __func__);
}
pcmk__xml_copy(*children, root);
match_found = 1;
}
if (search_matches || match_found == 0) {
xmlNode *child = NULL;
for (child = pcmk__xml_first_child(root); child != NULL;
child = pcmk__xml_next(child)) {
match_found += find_xml_children(children, child, tag, field, value,
search_matches);
}
}
return match_found;
}
void
fix_plus_plus_recursive(xmlNode *target)
{
/* TODO: Remove recursion and use xpath searches for value++ */
xmlNode *child = NULL;
for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
const char *p_name = (const char *) a->name;
const char *p_value = pcmk__xml_attr_value(a);
expand_plus_plus(target, p_name, p_value);
}
for (child = pcmk__xe_first_child(target, NULL, NULL, NULL); child != NULL;
child = pcmk__xe_next(child)) {
fix_plus_plus_recursive(child);
}
}
void
copy_in_properties(xmlNode *target, const xmlNode *src)
{
if (src == NULL) {
crm_warn("No node to copy properties from");
} else if (target == NULL) {
crm_err("No node to copy properties into");
} else {
for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
const char *p_name = (const char *) a->name;
const char *p_value = pcmk__xml_attr_value(a);
expand_plus_plus(target, p_name, p_value);
if (xml_acl_denied(target)) {
crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
return;
}
}
}
}
void
expand_plus_plus(xmlNode * target, const char *name, const char *value)
{
pcmk__xe_set_score(target, name, value);
}
void
crm_xml_init(void)
{
pcmk__xml_init();
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/pengine/tests/native/native_find_rsc_test.c b/lib/pengine/tests/native/native_find_rsc_test.c
index 926473ebbd..a89b46ea4f 100644
--- a/lib/pengine/tests/native/native_find_rsc_test.c
+++ b/lib/pengine/tests/native/native_find_rsc_test.c
@@ -1,917 +1,917 @@
/*
* Copyright 2022-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/unittest_internal.h>
#include <crm/common/scheduler.h>
#include <crm/common/xml.h>
#include <crm/pengine/internal.h>
#include <crm/pengine/status.h>
xmlNode *input = NULL;
pcmk_scheduler_t *scheduler = NULL;
pcmk_node_t *cluster01, *cluster02, *httpd_bundle_0;
pcmk_resource_t *exim_group, *inactive_group;
pcmk_resource_t *promotable_clone, *inactive_clone;
pcmk_resource_t *httpd_bundle, *mysql_clone_group;
static int
setup(void **state) {
char *path = NULL;
pcmk__xml_init();
path = crm_strdup_printf("%s/crm_mon.xml", getenv("PCMK_CTS_CLI_DIR"));
input = pcmk__xml_read(path);
free(path);
if (input == NULL) {
return 1;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
return 1;
}
pcmk__set_scheduler_flags(scheduler,
pcmk_sched_no_counts|pcmk_sched_no_compat);
scheduler->input = input;
cluster_status(scheduler);
/* Get references to the cluster nodes so we don't have to find them repeatedly. */
cluster01 = pcmk_find_node(scheduler, "cluster01");
cluster02 = pcmk_find_node(scheduler, "cluster02");
httpd_bundle_0 = pcmk_find_node(scheduler, "httpd-bundle-0");
/* Get references to several resources we use frequently. */
for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "exim-group") == 0) {
exim_group = rsc;
} else if (strcmp(rsc->id, "httpd-bundle") == 0) {
httpd_bundle = rsc;
} else if (strcmp(rsc->id, "inactive-clone") == 0) {
inactive_clone = rsc;
} else if (strcmp(rsc->id, "inactive-group") == 0) {
inactive_group = rsc;
} else if (strcmp(rsc->id, "mysql-clone-group") == 0) {
mysql_clone_group = rsc;
} else if (strcmp(rsc->id, "promotable-clone") == 0) {
promotable_clone = rsc;
}
}
return 0;
}
static int
teardown(void **state) {
pe_free_working_set(scheduler);
-
+ pcmk__xml_cleanup();
return 0;
}
static void
bad_args(void **state) {
pcmk_resource_t *rsc = g_list_first(scheduler->resources)->data;
char *id = rsc->id;
char *name = NULL;
assert_non_null(rsc);
assert_null(native_find_rsc(NULL, "dummy", NULL, 0));
assert_null(native_find_rsc(rsc, NULL, NULL, 0));
/* No resources exist with these names. */
name = crm_strdup_printf("%sX", rsc->id);
assert_null(native_find_rsc(rsc, name, NULL, 0));
free(name);
name = crm_strdup_printf("x%s", rsc->id);
assert_null(native_find_rsc(rsc, name, NULL, 0));
free(name);
name = g_ascii_strup(rsc->id, -1);
assert_null(native_find_rsc(rsc, name, NULL, 0));
g_free(name);
/* Fails because resource ID is NULL. */
rsc->id = NULL;
assert_null(native_find_rsc(rsc, id, NULL, 0));
rsc->id = id;
}
static void
primitive_rsc(void **state) {
pcmk_resource_t *dummy = NULL;
/* Find the "dummy" resource, which is the only one with that ID in the set. */
for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "dummy") == 0) {
dummy = rsc;
break;
}
}
assert_non_null(dummy);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(dummy, native_find_rsc(dummy, "dummy", NULL, 0));
assert_ptr_equal(dummy,
native_find_rsc(dummy, "dummy", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(dummy, "dummy", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(dummy, "dummy", cluster02,
pcmk_rsc_match_clone_only));
/* Fails because dummy is not running on cluster01, even with the right flags. */
assert_null(native_find_rsc(dummy, "dummy", cluster01,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(dummy, "dummy", cluster02, 0));
/* Passes because dummy is running on cluster02. */
assert_ptr_equal(dummy,
native_find_rsc(dummy, "dummy", cluster02,
pcmk_rsc_match_current_node));
}
static void
group_rsc(void **state) {
assert_non_null(exim_group);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(exim_group, native_find_rsc(exim_group, "exim-group", NULL, 0));
assert_ptr_equal(exim_group,
native_find_rsc(exim_group, "exim-group", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(exim_group, "exim-group", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(exim_group, "exim-group", cluster01,
pcmk_rsc_match_clone_only));
/* Fails because none of exim-group's children are running on cluster01, even with the right flags. */
assert_null(native_find_rsc(exim_group, "exim-group", cluster01,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(exim_group, "exim-group", cluster01, 0));
/* Passes because one of exim-group's children is running on cluster02. */
assert_ptr_equal(exim_group,
native_find_rsc(exim_group, "exim-group", cluster02,
pcmk_rsc_match_current_node));
}
static void
inactive_group_rsc(void **state) {
assert_non_null(inactive_group);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(inactive_group, native_find_rsc(inactive_group, "inactive-group", NULL, 0));
assert_ptr_equal(inactive_group,
native_find_rsc(inactive_group, "inactive-group", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(inactive_group, "inactive-group", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(inactive_group, "inactive-group", cluster01,
pcmk_rsc_match_clone_only));
/* Fails because none of inactive-group's children are running. */
assert_null(native_find_rsc(inactive_group, "inactive-group", cluster01,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(inactive_group, "inactive-group", cluster02,
pcmk_rsc_match_current_node));
}
static void
group_member_rsc(void **state) {
pcmk_resource_t *public_ip = NULL;
/* Find the "Public-IP" resource, a member of "exim-group". */
for (GList *iter = exim_group->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "Public-IP") == 0) {
public_ip = rsc;
break;
}
}
assert_non_null(public_ip);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(public_ip, native_find_rsc(public_ip, "Public-IP", NULL, 0));
assert_ptr_equal(public_ip,
native_find_rsc(public_ip, "Public-IP", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(public_ip, "Public-IP", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(public_ip, "Public-IP", cluster02,
pcmk_rsc_match_clone_only));
/* Fails because Public-IP is not running on cluster01, even with the right flags. */
assert_null(native_find_rsc(public_ip, "Public-IP", cluster01,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(public_ip, "Public-IP", cluster02, 0));
/* Passes because Public-IP is running on cluster02. */
assert_ptr_equal(public_ip,
native_find_rsc(public_ip, "Public-IP", cluster02,
pcmk_rsc_match_current_node));
}
static void
inactive_group_member_rsc(void **state) {
pcmk_resource_t *inactive_dummy_1 = NULL;
/* Find the "inactive-dummy-1" resource, a member of "inactive-group". */
for (GList *iter = inactive_group->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "inactive-dummy-1") == 0) {
inactive_dummy_1 = rsc;
break;
}
}
assert_non_null(inactive_dummy_1);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(inactive_dummy_1, native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL, 0));
assert_ptr_equal(inactive_dummy_1,
native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster01,
pcmk_rsc_match_clone_only));
/* Fails because inactive-dummy-1 is not running. */
assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster01,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(inactive_dummy_1, "inactive-dummy-1", cluster02,
pcmk_rsc_match_current_node));
}
static void
clone_rsc(void **state) {
assert_non_null(promotable_clone);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(promotable_clone, native_find_rsc(promotable_clone, "promotable-clone", NULL, 0));
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone", NULL,
pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone", NULL,
pcmk_rsc_match_clone_only));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(promotable_clone, "promotable-clone", cluster01, 0));
/* Passes because one of ping-clone's children is running on cluster01. */
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone",
cluster01, pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(promotable_clone, "promotable-clone", cluster02, 0));
/* Passes because one of ping_clone's children is running on cluster02. */
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone",
cluster02, pcmk_rsc_match_current_node));
// Passes for previous reasons, plus includes pcmk_rsc_match_clone_only
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone",
cluster01,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_clone,
native_find_rsc(promotable_clone, "promotable-clone",
cluster02,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node));
}
static void
inactive_clone_rsc(void **state) {
assert_non_null(inactive_clone);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(inactive_clone, native_find_rsc(inactive_clone, "inactive-clone", NULL, 0));
assert_ptr_equal(inactive_clone,
native_find_rsc(inactive_clone, "inactive-clone", NULL,
pcmk_rsc_match_current_node));
assert_ptr_equal(inactive_clone,
native_find_rsc(inactive_clone, "inactive-clone", NULL,
pcmk_rsc_match_clone_only));
/* Fails because none of inactive-clone's children are running. */
assert_null(native_find_rsc(inactive_clone, "inactive-clone", cluster01,
pcmk_rsc_match_current_node
|pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(inactive_clone, "inactive-clone", cluster02,
pcmk_rsc_match_current_node
|pcmk_rsc_match_clone_only));
}
static void
clone_instance_rsc(void **state) {
pcmk_resource_t *promotable_0 = NULL;
pcmk_resource_t *promotable_1 = NULL;
/* Find the "promotable-rsc:0" and "promotable-rsc:1" resources, members of "promotable-clone". */
for (GList *iter = promotable_clone->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "promotable-rsc:0") == 0) {
promotable_0 = rsc;
} else if (strcmp(rsc->id, "promotable-rsc:1") == 0) {
promotable_1 = rsc;
}
}
assert_non_null(promotable_0);
assert_non_null(promotable_1);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(promotable_0, native_find_rsc(promotable_0, "promotable-rsc:0", NULL, 0));
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc:0", NULL,
pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1, native_find_rsc(promotable_1, "promotable-rsc:1", NULL, 0));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc:1", NULL,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(promotable_0, "promotable-rsc:0", cluster02, 0));
assert_null(native_find_rsc(promotable_1, "promotable-rsc:1", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc:0",
cluster02, pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_0, "promotable-rsc:0", cluster01,
pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc:1",
cluster01, pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_1, "promotable-rsc:1", cluster02,
pcmk_rsc_match_current_node));
/* Passes because NULL was passed for node and primitive name was given, with correct flags. */
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", NULL,
pcmk_rsc_match_clone_only));
// Passes because pcmk_rsc_match_basename matches any instance's base name
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", NULL,
pcmk_rsc_match_basename));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc", NULL,
pcmk_rsc_match_basename));
// Passes because pcmk_rsc_match_anon_basename matches
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", NULL,
pcmk_rsc_match_anon_basename));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc", NULL,
pcmk_rsc_match_anon_basename));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_0, "promotable-rsc", cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_0, "promotable-rsc", cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc", cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc", cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_1, "promotable-rsc", cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_1, "promotable-rsc", cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
/* Fails because incorrect flags were given along with primitive name. */
assert_null(native_find_rsc(promotable_0, "promotable-rsc", NULL,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(promotable_1, "promotable-rsc", NULL,
pcmk_rsc_match_current_node));
/* And then we check failure possibilities again, except passing promotable_clone
* instead of promotable_X as the first argument to native_find_rsc.
*/
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(promotable_clone, "promotable-rsc:0", cluster02, 0));
assert_null(native_find_rsc(promotable_clone, "promotable-rsc:1", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_clone, "promotable-rsc:0",
cluster02, pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_clone, "promotable-rsc",
cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_clone, "promotable-rsc",
cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_clone, "promotable-rsc:1",
cluster01, pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_clone, "promotable-rsc",
cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_clone, "promotable-rsc",
cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
}
static void
renamed_rsc(void **state) {
pcmk_resource_t *promotable_0 = NULL;
pcmk_resource_t *promotable_1 = NULL;
/* Find the "promotable-rsc:0" and "promotable-rsc:1" resources, members of "promotable-clone". */
for (GList *iter = promotable_clone->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "promotable-rsc:0") == 0) {
promotable_0 = rsc;
} else if (strcmp(rsc->id, "promotable-rsc:1") == 0) {
promotable_1 = rsc;
}
}
assert_non_null(promotable_0);
assert_non_null(promotable_1);
// Passes because pcmk_rsc_match_history means base name matches clone_name
assert_ptr_equal(promotable_0,
native_find_rsc(promotable_0, "promotable-rsc", NULL,
pcmk_rsc_match_history));
assert_ptr_equal(promotable_1,
native_find_rsc(promotable_1, "promotable-rsc", NULL,
pcmk_rsc_match_history));
}
static void
bundle_rsc(void **state) {
assert_non_null(httpd_bundle);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(httpd_bundle, native_find_rsc(httpd_bundle, "httpd-bundle", NULL, 0));
assert_ptr_equal(httpd_bundle,
native_find_rsc(httpd_bundle, "httpd-bundle", NULL,
pcmk_rsc_match_current_node));
/* Fails because resource is not a clone (nor cloned). */
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", NULL,
pcmk_rsc_match_clone_only));
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", cluster01,
pcmk_rsc_match_clone_only));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle", cluster01, 0));
/* Passes because one of httpd_bundle's children is running on cluster01. */
assert_ptr_equal(httpd_bundle,
native_find_rsc(httpd_bundle, "httpd-bundle", cluster01,
pcmk_rsc_match_current_node));
}
static bool
bundle_first_replica(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk_resource_t *ip_0 = replica->ip;
pcmk_resource_t *child_0 = replica->child;
pcmk_resource_t *container_0 = replica->container;
pcmk_resource_t *remote_0 = replica->remote;
assert_non_null(ip_0);
assert_non_null(child_0);
assert_non_null(container_0);
assert_non_null(remote_0);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(ip_0, native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", NULL, 0));
assert_ptr_equal(child_0, native_find_rsc(child_0, "httpd:0", NULL, 0));
assert_ptr_equal(container_0, native_find_rsc(container_0, "httpd-bundle-docker-0", NULL, 0));
assert_ptr_equal(remote_0, native_find_rsc(remote_0, "httpd-bundle-0", NULL, 0));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131", cluster01, 0));
assert_null(native_find_rsc(child_0, "httpd:0", httpd_bundle_0, 0));
assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0", cluster01, 0));
assert_null(native_find_rsc(remote_0, "httpd-bundle-0", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(ip_0,
native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131",
cluster01, pcmk_rsc_match_current_node));
assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131",
cluster02, pcmk_rsc_match_current_node));
assert_null(native_find_rsc(ip_0, "httpd-bundle-ip-192.168.122.131",
httpd_bundle_0, pcmk_rsc_match_current_node));
assert_ptr_equal(child_0,
native_find_rsc(child_0, "httpd:0", httpd_bundle_0,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd:0", cluster01,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd:0", cluster02,
pcmk_rsc_match_current_node));
assert_ptr_equal(container_0,
native_find_rsc(container_0, "httpd-bundle-docker-0",
cluster01, pcmk_rsc_match_current_node));
assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0", cluster02,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(container_0, "httpd-bundle-docker-0",
httpd_bundle_0, pcmk_rsc_match_current_node));
assert_ptr_equal(remote_0,
native_find_rsc(remote_0, "httpd-bundle-0", cluster01,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(remote_0, "httpd-bundle-0", cluster02,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(remote_0, "httpd-bundle-0", httpd_bundle_0,
pcmk_rsc_match_current_node));
// Passes because pcmk_rsc_match_basename matches any replica's base name
assert_ptr_equal(child_0,
native_find_rsc(child_0, "httpd", NULL,
pcmk_rsc_match_basename));
// Passes because pcmk_rsc_match_anon_basename matches
assert_ptr_equal(child_0,
native_find_rsc(child_0, "httpd", NULL,
pcmk_rsc_match_anon_basename));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(child_0,
native_find_rsc(child_0, "httpd", httpd_bundle_0,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(child_0,
native_find_rsc(child_0, "httpd", httpd_bundle_0,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd", cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd", cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd", cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(child_0, "httpd", cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
/* Fails because incorrect flags were given along with base name. */
assert_null(native_find_rsc(child_0, "httpd", NULL,
pcmk_rsc_match_current_node));
/* And then we check failure possibilities again, except passing httpd-bundle
* instead of X_0 as the first argument to native_find_rsc.
*/
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-ip-192.168.122.131", cluster01, 0));
assert_null(native_find_rsc(httpd_bundle, "httpd:0", httpd_bundle_0, 0));
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-docker-0", cluster01, 0));
assert_null(native_find_rsc(httpd_bundle, "httpd-bundle-0", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(ip_0,
native_find_rsc(httpd_bundle,
"httpd-bundle-ip-192.168.122.131",
cluster01, pcmk_rsc_match_current_node));
assert_ptr_equal(child_0,
native_find_rsc(httpd_bundle, "httpd:0", httpd_bundle_0,
pcmk_rsc_match_current_node));
assert_ptr_equal(container_0,
native_find_rsc(httpd_bundle, "httpd-bundle-docker-0",
cluster01, pcmk_rsc_match_current_node));
assert_ptr_equal(remote_0,
native_find_rsc(httpd_bundle, "httpd-bundle-0", cluster01,
pcmk_rsc_match_current_node));
return false; // Do not iterate through any further replicas
}
static void
bundle_replica_rsc(void **state)
{
pe__foreach_bundle_replica(httpd_bundle, bundle_first_replica, NULL);
}
static void
clone_group_rsc(void **rsc) {
assert_non_null(mysql_clone_group);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(mysql_clone_group, native_find_rsc(mysql_clone_group, "mysql-clone-group", NULL, 0));
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
NULL, pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
NULL, pcmk_rsc_match_clone_only));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster01, 0));
/* Passes because one of mysql-clone-group's children is running on cluster01. */
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
cluster01, pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(mysql_clone_group, "mysql-clone-group", cluster02, 0));
/* Passes because one of mysql-clone-group's children is running on cluster02. */
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
cluster02, pcmk_rsc_match_current_node));
// Passes for previous reasons, plus includes pcmk_rsc_match_clone_only
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
cluster01,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_clone_group,
native_find_rsc(mysql_clone_group, "mysql-clone-group",
cluster02,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node));
}
static void
clone_group_instance_rsc(void **rsc) {
pcmk_resource_t *mysql_group_0 = NULL;
pcmk_resource_t *mysql_group_1 = NULL;
/* Find the "mysql-group:0" and "mysql-group:1" resources, members of "mysql-clone-group". */
for (GList *iter = mysql_clone_group->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "mysql-group:0") == 0) {
mysql_group_0 = rsc;
} else if (strcmp(rsc->id, "mysql-group:1") == 0) {
mysql_group_1 = rsc;
}
}
assert_non_null(mysql_group_0);
assert_non_null(mysql_group_1);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(mysql_group_0, native_find_rsc(mysql_group_0, "mysql-group:0", NULL, 0));
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group:0", NULL,
pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1, native_find_rsc(mysql_group_1, "mysql-group:1", NULL, 0));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group:1", NULL,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(mysql_group_0, "mysql-group:0", cluster02, 0));
assert_null(native_find_rsc(mysql_group_1, "mysql-group:1", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group:0", cluster02,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_0, "mysql-group:0", cluster01,
pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group:1", cluster01,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_1, "mysql-group:1", cluster02,
pcmk_rsc_match_current_node));
/* Passes because NULL was passed for node and base name was given, with correct flags. */
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group" , NULL,
pcmk_rsc_match_clone_only));
// Passes because pcmk_rsc_match_basename matches any base name
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group" , NULL,
pcmk_rsc_match_basename));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group" , NULL,
pcmk_rsc_match_basename));
// Passes because pcmk_rsc_match_anon_basename matches
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group" , NULL,
pcmk_rsc_match_anon_basename));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group" , NULL,
pcmk_rsc_match_anon_basename));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group", cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_group_0, "mysql-group", cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_0, "mysql-group", cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_0, "mysql-group", cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group", cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_group_1, "mysql-group", cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_1, "mysql-group", cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_1, "mysql-group", cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
/* Fails because incorrect flags were given along with base name. */
assert_null(native_find_rsc(mysql_group_0, "mysql-group", NULL,
pcmk_rsc_match_current_node));
assert_null(native_find_rsc(mysql_group_1, "mysql-group", NULL,
pcmk_rsc_match_current_node));
/* And then we check failure possibilities again, except passing mysql_clone_group
* instead of mysql_group_X as the first argument to native_find_rsc.
*/
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(mysql_clone_group, "mysql-group:0", cluster02, 0));
assert_null(native_find_rsc(mysql_clone_group, "mysql-group:1", cluster01, 0));
/* Check that the resource is running on the node we expect. */
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_clone_group, "mysql-group:0",
cluster02, pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_clone_group, "mysql-group",
cluster02,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_0,
native_find_rsc(mysql_clone_group, "mysql-group",
cluster02,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_clone_group, "mysql-group:1",
cluster01, pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_clone_group, "mysql-group",
cluster01,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node));
assert_ptr_equal(mysql_group_1,
native_find_rsc(mysql_clone_group, "mysql-group",
cluster01,
pcmk_rsc_match_anon_basename
|pcmk_rsc_match_current_node));
}
static void
clone_group_member_rsc(void **state) {
pcmk_resource_t *mysql_proxy = NULL;
/* Find the "mysql-proxy" resource, a member of "mysql-group". */
for (GList *iter = mysql_clone_group->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "mysql-group:0") == 0) {
for (GList *iter2 = rsc->children; iter2 != NULL; iter2 = iter2->next) {
pcmk_resource_t *child = (pcmk_resource_t *) iter2->data;
if (strcmp(child->id, "mysql-proxy:0") == 0) {
mysql_proxy = child;
break;
}
}
break;
}
}
assert_non_null(mysql_proxy);
/* Passes because NULL was passed for node, regardless of flags. */
assert_ptr_equal(mysql_proxy, native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL, 0));
assert_ptr_equal(mysql_proxy,
native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL,
pcmk_rsc_match_current_node));
/* Passes because resource's parent is a clone. */
assert_ptr_equal(mysql_proxy,
native_find_rsc(mysql_proxy, "mysql-proxy:0", NULL,
pcmk_rsc_match_clone_only));
assert_ptr_equal(mysql_proxy,
native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node));
/* Fails because mysql-proxy:0 is not running on cluster01, even with the right flags. */
assert_null(native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster01,
pcmk_rsc_match_current_node));
// Fails because pcmk_rsc_match_current_node is required if a node is given
assert_null(native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02, 0));
/* Passes because mysql-proxy:0 is running on cluster02. */
assert_ptr_equal(mysql_proxy,
native_find_rsc(mysql_proxy, "mysql-proxy:0", cluster02,
pcmk_rsc_match_current_node));
}
/* TODO: Add tests for finding on assigned node (passing a node without
* pcmk_rsc_match_current_node, after scheduling, for a resource that is
* starting/stopping/moving.
*/
PCMK__UNIT_TEST(setup, teardown,
cmocka_unit_test(bad_args),
cmocka_unit_test(primitive_rsc),
cmocka_unit_test(group_rsc),
cmocka_unit_test(inactive_group_rsc),
cmocka_unit_test(group_member_rsc),
cmocka_unit_test(inactive_group_member_rsc),
cmocka_unit_test(clone_rsc),
cmocka_unit_test(inactive_clone_rsc),
cmocka_unit_test(clone_instance_rsc),
cmocka_unit_test(renamed_rsc),
cmocka_unit_test(bundle_rsc),
cmocka_unit_test(bundle_replica_rsc),
cmocka_unit_test(clone_group_rsc),
cmocka_unit_test(clone_group_instance_rsc),
cmocka_unit_test(clone_group_member_rsc))
diff --git a/lib/pengine/tests/native/pe_base_name_eq_test.c b/lib/pengine/tests/native/pe_base_name_eq_test.c
index 0bdbbb7438..e07972ad2a 100644
--- a/lib/pengine/tests/native/pe_base_name_eq_test.c
+++ b/lib/pengine/tests/native/pe_base_name_eq_test.c
@@ -1,150 +1,150 @@
/*
* Copyright 2022-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/unittest_internal.h>
#include <crm/common/xml.h>
#include <crm/common/scheduler.h>
#include <crm/pengine/internal.h>
#include <crm/pengine/status.h>
xmlNode *input = NULL;
pcmk_scheduler_t *scheduler = NULL;
pcmk_resource_t *exim_group, *promotable_0, *promotable_1, *dummy;
pcmk_resource_t *httpd_bundle, *mysql_group_0, *mysql_group_1;
static int
setup(void **state) {
char *path = NULL;
pcmk__xml_init();
path = crm_strdup_printf("%s/crm_mon.xml", getenv("PCMK_CTS_CLI_DIR"));
input = pcmk__xml_read(path);
free(path);
if (input == NULL) {
return 1;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
return 1;
}
pcmk__set_scheduler_flags(scheduler,
pcmk_sched_no_counts|pcmk_sched_no_compat);
scheduler->input = input;
cluster_status(scheduler);
/* Get references to several resources we use frequently. */
for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (strcmp(rsc->id, "dummy") == 0) {
dummy = rsc;
} else if (strcmp(rsc->id, "exim-group") == 0) {
exim_group = rsc;
} else if (strcmp(rsc->id, "httpd-bundle") == 0) {
httpd_bundle = rsc;
} else if (strcmp(rsc->id, "mysql-clone-group") == 0) {
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
if (strcmp(child->id, "mysql-group:0") == 0) {
mysql_group_0 = child;
} else if (strcmp(child->id, "mysql-group:1") == 0) {
mysql_group_1 = child;
}
}
} else if (strcmp(rsc->id, "promotable-clone") == 0) {
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
if (strcmp(child->id, "promotable-rsc:0") == 0) {
promotable_0 = child;
} else if (strcmp(child->id, "promotable-rsc:1") == 0) {
promotable_1 = child;
}
}
}
}
return 0;
}
static int
teardown(void **state) {
pe_free_working_set(scheduler);
-
+ pcmk__xml_cleanup();
return 0;
}
static void
bad_args(void **state) {
char *id = dummy->id;
assert_false(pe_base_name_eq(NULL, "dummy"));
assert_false(pe_base_name_eq(dummy, NULL));
dummy->id = NULL;
assert_false(pe_base_name_eq(dummy, "dummy"));
dummy->id = id;
}
static void
primitive_rsc(void **state) {
assert_true(pe_base_name_eq(dummy, "dummy"));
assert_false(pe_base_name_eq(dummy, "DUMMY"));
assert_false(pe_base_name_eq(dummy, "dUmMy"));
assert_false(pe_base_name_eq(dummy, "dummy0"));
assert_false(pe_base_name_eq(dummy, "dummy:0"));
}
static void
group_rsc(void **state) {
assert_true(pe_base_name_eq(exim_group, "exim-group"));
assert_false(pe_base_name_eq(exim_group, "EXIM-GROUP"));
assert_false(pe_base_name_eq(exim_group, "exim-group0"));
assert_false(pe_base_name_eq(exim_group, "exim-group:0"));
assert_false(pe_base_name_eq(exim_group, "Public-IP"));
}
static void
clone_rsc(void **state) {
assert_true(pe_base_name_eq(promotable_0, "promotable-rsc"));
assert_true(pe_base_name_eq(promotable_1, "promotable-rsc"));
assert_false(pe_base_name_eq(promotable_0, "promotable-rsc:0"));
assert_false(pe_base_name_eq(promotable_1, "promotable-rsc:1"));
assert_false(pe_base_name_eq(promotable_0, "PROMOTABLE-RSC"));
assert_false(pe_base_name_eq(promotable_1, "PROMOTABLE-RSC"));
assert_false(pe_base_name_eq(promotable_0, "Promotable-rsc"));
assert_false(pe_base_name_eq(promotable_1, "Promotable-rsc"));
}
static void
bundle_rsc(void **state) {
assert_true(pe_base_name_eq(httpd_bundle, "httpd-bundle"));
assert_false(pe_base_name_eq(httpd_bundle, "HTTPD-BUNDLE"));
assert_false(pe_base_name_eq(httpd_bundle, "httpd"));
assert_false(pe_base_name_eq(httpd_bundle, "httpd-docker-0"));
}
PCMK__UNIT_TEST(setup, teardown,
cmocka_unit_test(bad_args),
cmocka_unit_test(primitive_rsc),
cmocka_unit_test(group_rsc),
cmocka_unit_test(clone_rsc),
cmocka_unit_test(bundle_rsc))

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:04 PM (20 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665330
Default Alt Text
(198 KB)

Event Timeline