Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml_comment_internal.h b/include/crm/common/xml_comment_internal.h
new file mode 100644
index 0000000000..59f85591fe
--- /dev/null
+++ b/include/crm/common/xml_comment_internal.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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__CRM_COMMON_XML_COMMENT_INTERNAL__H
+#define PCMK__CRM_COMMON_XML_COMMENT_INTERNAL__H
+
+/*
+ * Internal-only wrappers for and extensions to libxml2 XML comment functions
+ */
+
+#include <libxml/tree.h> // xmlDoc, xmlNode
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+xmlNode *pcmk__xc_create(xmlDoc *doc, const char *content);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__XML_COMMENT_INTERNAL__H
diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index a20428f785..b2a2bd82f4 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -1,595 +1,596 @@
/*
* 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),
};
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__xc_create(xmlDoc *doc, const char *content);
void pcmk__xml_free_doc(xmlDoc *doc);
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/include/crm_internal.h b/include/crm_internal.h
index 18388b9fc2..035799e200 100644
--- a/include/crm_internal.h
+++ b/include/crm_internal.h
@@ -1,90 +1,91 @@
/*
* Copyright 2006-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 CRM_INTERNAL__H
# define CRM_INTERNAL__H
# ifndef PCMK__CONFIG_H
# define PCMK__CONFIG_H
# include <config.h>
# endif
# include <portability.h>
/* Our minimum glib dependency is 2.42. Define that as both the minimum and
* maximum glib APIs that are allowed (i.e. APIs that were already deprecated
* in 2.42, and APIs introduced after 2.42, cannot be used by Pacemaker code).
*/
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_42
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_42
# include <glib.h>
# include <stdbool.h>
# include <libxml/tree.h>
/* Public API headers can guard including deprecated API headers with this
* symbol, thus preventing internal code (which includes this header) from using
* deprecated APIs, while still allowing external code to use them by default.
*/
#define PCMK_ALLOW_DEPRECATED 0
# include <crm/lrmd.h>
# include <crm/cluster/internal.h>
# include <crm/common/logging.h>
# include <crm/common/logging_internal.h>
# include <crm/common/ipc_internal.h>
# include <crm/common/options_internal.h>
# include <crm/common/output_internal.h>
# include <crm/common/scheduler_internal.h>
# include <crm/common/schemas_internal.h>
+# include <crm/common/xml_comment_internal.h>
# include <crm/common/xml_internal.h>
# include <crm/common/xml_io_internal.h>
# include <crm/common/xml_names_internal.h>
# include <crm/common/internal.h>
# include <locale.h>
# include <gettext.h>
#define N_(String) (String)
#ifdef ENABLE_NLS
# define _(String) gettext(String)
#else
# define _(String) (String)
#endif
/*
* IPC service names that are only used internally
*/
# define PCMK__SERVER_BASED_RO "cib_ro"
# define PCMK__SERVER_BASED_RW "cib_rw"
# define PCMK__SERVER_BASED_SHM "cib_shm"
/*
* IPC commands that can be sent to Pacemaker daemons
*/
#define PCMK__ATTRD_CMD_PEER_REMOVE "peer-remove"
#define PCMK__ATTRD_CMD_UPDATE "update"
#define PCMK__ATTRD_CMD_UPDATE_BOTH "update-both"
#define PCMK__ATTRD_CMD_UPDATE_DELAY "update-delay"
#define PCMK__ATTRD_CMD_QUERY "query"
#define PCMK__ATTRD_CMD_REFRESH "refresh"
#define PCMK__ATTRD_CMD_FLUSH "flush"
#define PCMK__ATTRD_CMD_SYNC_RESPONSE "sync-response"
#define PCMK__ATTRD_CMD_CLEAR_FAILURE "clear-failure"
#define PCMK__ATTRD_CMD_CONFIRM "confirm"
#define PCMK__CONTROLD_CMD_NODES "list-nodes"
#define ST__LEVEL_MIN 1
#define ST__LEVEL_MAX 9
#endif // CRM_INTERNAL__H
diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am
index bfa5c1dd7b..06967ec828 100644
--- a/lib/common/Makefile.am
+++ b/lib/common/Makefile.am
@@ -1,143 +1,144 @@
#
# 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 General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
include $(top_srcdir)/mk/common.mk
AM_CPPFLAGS += -I$(top_builddir)/lib/gnu \
-I$(top_srcdir)/lib/gnu
## libraries
lib_LTLIBRARIES = libcrmcommon.la
check_LTLIBRARIES = libcrmcommon_test.la
# Disable -Wcast-qual if used, because we do some hacky casting,
# and because libxml2 has some signatures that should be const but aren't
# for backward compatibility reasons.
# s390 needs -fPIC
# s390-suse-linux/bin/ld: .libs/ipc.o: relocation R_390_PC32DBL against `__stack_chk_fail@@GLIBC_2.4' can not be used when making a shared object; recompile with -fPIC
CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC
# Without "." here, check-recursive will run through the subdirectories first
# and then run "make check" here. This will fail, because there's things in
# the subdirectories that need check_LTLIBRARIES built first. Adding "." here
# changes the order so the subdirectories are processed afterwards.
SUBDIRS = . tests
noinst_HEADERS = crmcommon_private.h \
mock_private.h
libcrmcommon_la_LDFLAGS = -version-info 47:0:13
libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
libcrmcommon_la_LIBADD = @LIBADD_DL@ \
$(top_builddir)/lib/gnu/libgnu.la
# If configured with --with-profiling or --with-coverage, BUILD_PROFILING will
# be set and -fno-builtin will be added to the CFLAGS. However, libcrmcommon
# uses the fabs() function which is normally supplied by gcc as one of its
# builtins. Therefore we need to explicitly link against libm here or the
# tests won't link.
if BUILD_PROFILING
libcrmcommon_la_LIBADD += -lm
endif
## Library sources (*must* use += format for bumplibs)
libcrmcommon_la_SOURCES =
libcrmcommon_la_SOURCES += acl.c
libcrmcommon_la_SOURCES += actions.c
libcrmcommon_la_SOURCES += agents.c
libcrmcommon_la_SOURCES += alerts.c
libcrmcommon_la_SOURCES += attrs.c
libcrmcommon_la_SOURCES += cib.c
if BUILD_CIBSECRETS
libcrmcommon_la_SOURCES += cib_secrets.c
endif
libcrmcommon_la_SOURCES += cmdline.c
libcrmcommon_la_SOURCES += digest.c
libcrmcommon_la_SOURCES += health.c
libcrmcommon_la_SOURCES += io.c
libcrmcommon_la_SOURCES += ipc_attrd.c
libcrmcommon_la_SOURCES += ipc_client.c
libcrmcommon_la_SOURCES += ipc_common.c
libcrmcommon_la_SOURCES += ipc_controld.c
libcrmcommon_la_SOURCES += ipc_pacemakerd.c
libcrmcommon_la_SOURCES += ipc_schedulerd.c
libcrmcommon_la_SOURCES += ipc_server.c
libcrmcommon_la_SOURCES += iso8601.c
libcrmcommon_la_SOURCES += lists.c
libcrmcommon_la_SOURCES += logging.c
libcrmcommon_la_SOURCES += mainloop.c
libcrmcommon_la_SOURCES += messages.c
libcrmcommon_la_SOURCES += nodes.c
libcrmcommon_la_SOURCES += nvpair.c
libcrmcommon_la_SOURCES += options.c
libcrmcommon_la_SOURCES += options_display.c
libcrmcommon_la_SOURCES += output.c
libcrmcommon_la_SOURCES += output_html.c
libcrmcommon_la_SOURCES += output_log.c
libcrmcommon_la_SOURCES += output_none.c
libcrmcommon_la_SOURCES += output_text.c
libcrmcommon_la_SOURCES += output_xml.c
libcrmcommon_la_SOURCES += patchset.c
libcrmcommon_la_SOURCES += patchset_display.c
libcrmcommon_la_SOURCES += pid.c
libcrmcommon_la_SOURCES += probes.c
libcrmcommon_la_SOURCES += procfs.c
libcrmcommon_la_SOURCES += remote.c
libcrmcommon_la_SOURCES += resources.c
libcrmcommon_la_SOURCES += results.c
libcrmcommon_la_SOURCES += roles.c
libcrmcommon_la_SOURCES += rules.c
libcrmcommon_la_SOURCES += scheduler.c
libcrmcommon_la_SOURCES += schemas.c
libcrmcommon_la_SOURCES += scores.c
libcrmcommon_la_SOURCES += strings.c
libcrmcommon_la_SOURCES += utils.c
libcrmcommon_la_SOURCES += watchdog.c
libcrmcommon_la_SOURCES += xml.c
libcrmcommon_la_SOURCES += xml_attr.c
+libcrmcommon_la_SOURCES += xml_comment.c
libcrmcommon_la_SOURCES += xml_display.c
libcrmcommon_la_SOURCES += xml_io.c
libcrmcommon_la_SOURCES += xpath.c
#
# libcrmcommon_test is used only with unit tests, so we can mock system calls.
# See mock.c for details.
#
include $(top_srcdir)/mk/tap.mk
libcrmcommon_test_la_SOURCES = $(libcrmcommon_la_SOURCES)
libcrmcommon_test_la_SOURCES += mock.c
libcrmcommon_test_la_SOURCES += unittest.c
libcrmcommon_test_la_LDFLAGS = $(libcrmcommon_la_LDFLAGS) \
-rpath $(libdir) \
$(LDFLAGS_WRAP)
# If GCC emits a builtin function in place of something we've mocked up, that will
# get used instead of the mocked version which leads to unexpected test results. So
# disable all builtins. Older versions of GCC (at least, on RHEL7) will still emit
# replacement code for strdup (and possibly other functions) unless -fno-inline is
# also added.
libcrmcommon_test_la_CFLAGS = $(libcrmcommon_la_CFLAGS) \
-DPCMK__UNIT_TESTING \
-fno-builtin \
-fno-inline
# If -fno-builtin is used, -lm also needs to be added. See the comment at
# BUILD_PROFILING above.
libcrmcommon_test_la_LIBADD = $(libcrmcommon_la_LIBADD)
if BUILD_COVERAGE
libcrmcommon_test_la_LIBADD += -lgcov
endif
libcrmcommon_test_la_LIBADD += -lcmocka
libcrmcommon_test_la_LIBADD += -lm
nodist_libcrmcommon_test_la_SOURCES = $(nodist_libcrmcommon_la_SOURCES)
diff --git a/lib/common/tests/xml/Makefile.am b/lib/common/tests/xml/Makefile.am
index 4bda1a3997..21873b9650 100644
--- a/lib/common/tests/xml/Makefile.am
+++ b/lib/common/tests/xml/Makefile.am
@@ -1,23 +1,24 @@
#
# 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 $(top_srcdir)/mk/tap.mk
include $(top_srcdir)/mk/unittest.mk
# Add "_test" to the end of all test program names to simplify .gitignore.
check_PROGRAMS = crm_xml_init_test \
+ pcmk__xc_create_test \
pcmk__xe_copy_attrs_test \
pcmk__xe_first_child_test \
pcmk__xe_foreach_child_test \
pcmk__xe_set_score_test \
pcmk__xml_escape_test \
pcmk__xml_needs_escape_test \
pcmk__xml_new_doc_test
TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/xml/crm_xml_init_test.c b/lib/common/tests/xml/crm_xml_init_test.c
index ad3bb59123..94560baecb 100644
--- a/lib/common/tests/xml/crm_xml_init_test.c
+++ b/lib/common/tests/xml/crm_xml_init_test.c
@@ -1,190 +1,164 @@
/*
* 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"
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_element_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDoc *doc = pcmk__xml_new_doc();
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, PCMK__XML_NODE_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
pcmk__xml_free_doc(doc);
}
static void
create_attr_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDoc *doc = pcmk__xml_new_doc();
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, PCMK__XML_NODE_PRIVATE_MAGIC);
assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
/* Clean up */
pcmk__xml_free_doc(doc);
}
-static void
-create_comment_node(void **state) {
- xml_doc_private_t *docpriv = NULL;
- xml_node_private_t *priv = NULL;
- xmlDoc *doc = pcmk__xml_new_doc();
- 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, PCMK__XML_NODE_PRIVATE_MAGIC);
- assert_true(pcmk_all_flags_set(priv->flags, pcmk__xf_dirty|pcmk__xf_created));
-
- /* Clean up */
- pcmk__xml_free_doc(doc);
-}
-
static void
create_text_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDoc *doc = pcmk__xml_new_doc();
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 */
pcmk__xml_free_doc(doc);
}
static void
create_dtd_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDoc *doc = pcmk__xml_new_doc();
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 pcmk__xml_free_doc(), you get a segfault
pcmk__xml_free_doc(doc);
}
static void
create_cdata_node(void **state) {
xml_doc_private_t *docpriv = NULL;
xml_node_private_t *priv = NULL;
xmlDoc *doc = pcmk__xml_new_doc();
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 */
pcmk__xml_free_doc(doc);
}
// The group setup/teardown functions call crm_xml_init()/crm_xml_cleanup()
PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group,
cmocka_unit_test(buffer_scheme_test),
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/tests/xml/pcmk__xc_create_test.c b/lib/common/tests/xml/pcmk__xc_create_test.c
new file mode 100644
index 0000000000..5fda3332be
--- /dev/null
+++ b/lib/common/tests/xml/pcmk__xc_create_test.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright 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 "crmcommon_private.h"
+
+/* This tests new_private_data() indirectly for comment nodes. Testing
+ * free_private_data() would be much less straightforward and is not worth the
+ * hassle.
+ */
+
+static void
+assert_comment(xmlDoc *doc, const char *content)
+{
+ xmlNode *node = NULL;
+ xml_node_private_t *nodepriv = NULL;
+ xml_doc_private_t *docpriv = doc->_private;
+
+ // Also clears existing doc flags
+ xml_track_changes((xmlNode *) doc, NULL, NULL, false);
+
+ node = pcmk__xc_create(doc, content);
+ assert_non_null(node);
+ assert_int_equal(node->type, XML_COMMENT_NODE);
+ assert_ptr_equal(node->doc, doc);
+
+ if (content == NULL) {
+ assert_null(node->content);
+ } else {
+ assert_non_null(node->content);
+ assert_string_equal((const char *) node->content, content);
+ }
+
+ nodepriv = node->_private;
+ assert_non_null(nodepriv);
+ assert_int_equal(nodepriv->check, PCMK__XML_NODE_PRIVATE_MAGIC);
+ assert_true(pcmk_all_flags_set(nodepriv->flags,
+ pcmk__xf_dirty|pcmk__xf_created));
+
+ assert_true(pcmk_is_set(docpriv->flags, pcmk__xf_dirty));
+
+ free_xml(node);
+}
+
+static void
+null_doc(void **state)
+{
+ pcmk__assert_asserts(pcmk__xc_create(NULL, NULL));
+ pcmk__assert_asserts(pcmk__xc_create(NULL, "some content"));
+}
+
+static void
+with_doc(void **state)
+{
+ xmlDoc *doc = pcmk__xml_new_doc();
+
+ assert_non_null(doc);
+ assert_non_null(doc->_private);
+
+ assert_comment(doc, NULL);
+ assert_comment(doc, "some content");
+
+ pcmk__xml_free_doc(doc);
+}
+
+PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group,
+ cmocka_unit_test(null_doc),
+ cmocka_unit_test(with_doc));
diff --git a/lib/common/xml_comment.c b/lib/common/xml_comment.c
new file mode 100644
index 0000000000..b0128e0e66
--- /dev/null
+++ b/lib/common/xml_comment.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h> // NULL
+
+#include <libxml/tree.h> // xmlDoc, xmlNode, etc.
+
+#include "crmcommon_private.h"
+
+/*!
+ * \internal
+ * \brief Create a new XML comment belonging to a given document
+ *
+ * \param[in] doc Document that new comment will belong to
+ * \param[in] content Comment content
+ *
+ * \return Newly created XML comment (guaranteed not to be \c NULL)
+ */
+xmlNode *
+pcmk__xc_create(xmlDoc *doc, const char *content)
+{
+ /* @TODO Allocate comment private data here when we drop
+ * new_private_data()/free_private_data()
+ */
+ xmlNode *node = NULL;
+
+ // Pacemaker typically assumes every xmlNode has a doc
+ CRM_ASSERT(doc != NULL);
+
+ node = xmlNewDocComment(doc, (pcmkXmlStr) content);
+ pcmk__mem_assert(node);
+ pcmk__xml_mark_created(node);
+ return node;
+}
diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c
index d137afd0af..e515a8c86e 100644
--- a/lib/pacemaker/pcmk_acl.c
+++ b/lib/pacemaker/pcmk_acl.c
@@ -1,399 +1,395 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxslt/transform.h>
#include <libxslt/variables.h>
#include <libxslt/xsltutils.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/common/internal.h>
#include <pacemaker-internal.h>
#define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
#define ACL_NS_Q_PREFIX "pcmk-access-"
#define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX "writable"
#define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX "readable"
#define ACL_NS_Q_DENIED (const xmlChar *) ACL_NS_Q_PREFIX "denied"
static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
/*!
* \brief This function takes a node and marks it with the namespace
* given in the ns parameter.
*
* \param[in,out] i_node
* \param[in] ns
* \param[in,out] ret
* \param[in,out] ns_recycle_writable
* \param[in,out] ns_recycle_readable
* \param[in,out] ns_recycle_denied
*/
static void
pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
xmlNs **ns_recycle_writable,
xmlNs **ns_recycle_readable,
xmlNs **ns_recycle_denied)
{
if (ns == NS_WRITABLE)
{
if (*ns_recycle_writable == NULL)
{
*ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_WRITABLE, ACL_NS_Q_WRITABLE);
}
xmlSetNs(i_node, *ns_recycle_writable);
*ret = pcmk_rc_ok;
}
else if (ns == NS_READABLE)
{
if (*ns_recycle_readable == NULL)
{
*ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_READABLE, ACL_NS_Q_READABLE);
}
xmlSetNs(i_node, *ns_recycle_readable);
*ret = pcmk_rc_ok;
}
else if (ns == NS_DENIED)
{
if (*ns_recycle_denied == NULL)
{
*ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_DENIED, ACL_NS_Q_DENIED);
};
xmlSetNs(i_node, *ns_recycle_denied);
*ret = pcmk_rc_ok;
}
}
/*!
* \brief Annotate a given XML element or property and its siblings with
* XML namespaces to indicate ACL permissions
*
* \param[in,out] xml_modify XML to annotate
*
* \return A standard Pacemaker return code
* Namely:
* - pcmk_rc_ok upon success,
* - pcmk_rc_already if ACLs were not applicable,
* - pcmk_rc_schema_validation if the validation schema version
* is unsupported (see note), or
* - EINVAL or ENOMEM as appropriate;
*
* \note This function is recursive
*/
static int
annotate_with_siblings(xmlNode *xml_modify)
{
static xmlNs *ns_recycle_writable = NULL,
*ns_recycle_readable = NULL,
*ns_recycle_denied = NULL;
static const xmlDoc *prev_doc = NULL;
xmlNode *i_node = NULL;
const xmlChar *ns;
int ret = EINVAL; // nodes have not been processed yet
if (prev_doc == NULL || prev_doc != xml_modify->doc) {
prev_doc = xml_modify->doc;
ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
}
for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
switch (i_node->type) {
case XML_ELEMENT_NODE:
pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking);
if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
ns = NS_DENIED;
} else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
ns = NS_READABLE;
} else {
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
&ns_recycle_writable,
&ns_recycle_readable,
&ns_recycle_denied);
// @TODO Could replace recursion with iteration to save stack
if (i_node->properties != NULL) {
/* This is not entirely clear, but relies on the very same
* class-hierarchy emulation that libxml2 has firmly baked
* in its API/ABI
*/
ret |= annotate_with_siblings((xmlNodePtr)
i_node->properties);
}
if (i_node->children != NULL) {
ret |= annotate_with_siblings(i_node->children);
}
break;
case XML_ATTRIBUTE_NODE:
// We can utilize that parent has already been assigned the ns
if (!pcmk__check_acl(i_node->parent,
(const char *) i_node->name,
pcmk__xf_acl_read)) {
ns = NS_DENIED;
} else if (!pcmk__check_acl(i_node,
(const char *) i_node->name,
pcmk__xf_acl_write)) {
ns = NS_READABLE;
} else {
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
&ns_recycle_writable,
&ns_recycle_readable,
&ns_recycle_denied);
break;
case XML_COMMENT_NODE:
// We can utilize that parent has already been assigned the ns
if (!pcmk__check_acl(i_node->parent,
(const char *) i_node->name,
pcmk__xf_acl_read)) {
ns = NS_DENIED;
} else if (!pcmk__check_acl(i_node->parent,
(const char *) i_node->name,
pcmk__xf_acl_write)) {
ns = NS_READABLE;
} else {
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
&ns_recycle_writable,
&ns_recycle_readable,
&ns_recycle_denied);
break;
default:
break;
}
}
return ret;
}
int
pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
xmlDoc **acl_evaled_doc)
{
int ret;
xmlNode *target, *comment;
const char *validation;
CRM_CHECK(cred != NULL, return EINVAL);
CRM_CHECK(cib_doc != NULL, return EINVAL);
CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
/* avoid trivial accidental XML injection */
if (strpbrk(cred, "<>&") != NULL) {
return EINVAL;
}
if (!pcmk_acl_required(cred)) {
/* nothing to evaluate */
return pcmk_rc_already;
}
// @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
PCMK_XA_VALIDATE_WITH);
if (pcmk__cmp_schemas_by_name(PCMK__COMPAT_ACL_2_MIN_INCL,
validation) > 0) {
return pcmk_rc_schema_validation;
}
target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
if (target == NULL) {
return EINVAL;
}
pcmk__enable_acl(target, target, cred);
ret = annotate_with_siblings(target);
if (ret == pcmk_rc_ok) {
- char *credentials = crm_strdup_printf("ACLs as evaluated for user %s",
- cred);
-
- comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
- free(credentials);
- if (comment == NULL) {
- free_xml(target);
- return EINVAL;
- }
+ char *content = crm_strdup_printf("ACLs as evaluated for user %s",
+ cred);
+
+ comment = pcmk__xc_create(target->doc, content);
xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
*acl_evaled_doc = target->doc;
- return pcmk_rc_ok;
+ free(content);
+
} else {
free_xml(target);
- return ret; //for now, it should be some kind of error
}
+ return ret;
}
int
pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
xmlChar **doc_txt_ptr)
{
xmlDoc *xslt_doc;
xsltStylesheet *xslt;
xsltTransformContext *xslt_ctxt;
xmlDoc *res;
char *sfile;
static const char *params_namespace[] = {
"accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
"accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
"accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_useansi[] = {
/* start with hard-coded defaults, then adapt per the template ones */
"accessrendercfg:c-writable", "\x1b[32m",
"accessrendercfg:c-readable", "\x1b[34m",
"accessrendercfg:c-denied", "\x1b[31m",
"accessrendercfg:c-reset", "\x1b[0m",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_noansi[] = {
"accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
"accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
"accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "yes",
"accessrender:self-reproducing-prefix", "",
NULL
};
const char **params;
int rc = pcmk_rc_ok;
xmlParserCtxtPtr parser_ctxt;
/* unfortunately, the input (coming from CIB originally) was parsed with
blanks ignored, and since the output is a conversion of XML to text
format (we would be covered otherwise thanks to implicit
pretty-printing), we need to dump the tree to string output first,
only to subsequently reparse it -- this time with blanks honoured */
xmlChar *annotated_dump;
int dump_size;
CRM_ASSERT(how != pcmk__acl_render_none);
// Color is the default render mode for terminals; text is default otherwise
if (how == pcmk__acl_render_default) {
if (isatty(STDOUT_FILENO)) {
how = pcmk__acl_render_color;
} else {
how = pcmk__acl_render_text;
}
}
xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
XML_PARSE_NONET);
CRM_ASSERT(res != NULL);
xmlFree(annotated_dump);
pcmk__xml_free_doc(annotated_doc);
annotated_doc = res;
sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
"access-render-2");
parser_ctxt = xmlNewParserCtxt();
CRM_ASSERT(sfile != NULL);
pcmk__mem_assert(parser_ctxt);
xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
if (xslt == NULL) {
crm_crit("Problem in parsing %s", sfile);
rc = EINVAL;
goto done;
}
xmlFreeParserCtxt(parser_ctxt);
xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
pcmk__mem_assert(xslt_ctxt);
switch (how) {
case pcmk__acl_render_namespace:
params = params_namespace;
break;
case pcmk__acl_render_text:
params = params_noansi;
break;
default:
/* pcmk__acl_render_color is the only remaining option.
* The compiler complains about params possibly uninitialized if we
* don't use default here.
*/
params = params_useansi;
break;
}
xsltQuoteUserParams(xslt_ctxt, params);
res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
NULL, NULL, xslt_ctxt);
pcmk__xml_free_doc(annotated_doc);
annotated_doc = NULL;
xsltFreeTransformContext(xslt_ctxt);
xslt_ctxt = NULL;
if (how == pcmk__acl_render_color && params != params_useansi) {
char **param_i = (char **) params;
do {
free(*param_i);
} while (*param_i++ != NULL);
free(params);
}
if (res == NULL) {
rc = EINVAL;
} else {
int doc_txt_len;
int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
pcmk__xml_free_doc(res);
if (temp != 0) {
rc = EINVAL;
}
}
done:
if (xslt != NULL) {
xsltFreeStylesheet(xslt);
}
free(sfile);
return rc;
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 6:02 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665023
Default Alt Text
(58 KB)

Event Timeline