diff --git a/cts/cli/regression.acls.exp b/cts/cli/regression.acls.exp index 353edeb53c3..521e2d52204 100644 --- a/cts/cli/regression.acls.exp +++ b/cts/cli/regression.acls.exp @@ -537,7 +537,7 @@ crm_attribute: Error performing operation: Permission denied * Passed: crm_attribute - unknownguy: Set fencing-enabled =#=#=#= Begin test: unknownguy: Create a resource =#=#=#= pcmk__check_acl trace: Lack of ACL denies user 'unknownguy' read/write access to /cib/configuration/resources/primitive[@id='dummy'] -pcmk__apply_creation_acl trace: ACLs disallow creation of with id="dummy" +check_creation_disallowed trace: ACLs disallow creation of with id="dummy" cibadmin: CIB API call failed: Permission denied =#=#=#= End test: unknownguy: Create a resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - unknownguy: Create a resource @@ -555,7 +555,7 @@ crm_attribute: Error performing operation: Permission denied * Passed: crm_attribute - l33t-haxor: Set fencing-enabled =#=#=#= Begin test: l33t-haxor: Create a resource =#=#=#= pcmk__check_acl trace: Parent ACL denies user 'l33t-haxor' read/write access to /cib/configuration/resources/primitive[@id='dummy'] -pcmk__apply_creation_acl trace: ACLs disallow creation of with id="dummy" +check_creation_disallowed trace: ACLs disallow creation of with id="dummy" cibadmin: CIB API call failed: Permission denied =#=#=#= End test: l33t-haxor: Create a resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - l33t-haxor: Create a resource @@ -639,7 +639,7 @@ crm_attribute: Error performing operation: Permission denied =#=#=#= End test: niceguy: Set enable-acl - Insufficient privileges (4) =#=#=#= * Passed: crm_attribute - niceguy: Set enable-acl =#=#=#= Begin test: niceguy: Set fencing-enabled =#=#=#= -pcmk__apply_creation_acl trace: ACLs allow creation of with id="cib-bootstrap-options-fencing-enabled" +check_creation_disallowed trace: ACLs allow creation of with id="cib-bootstrap-options-fencing-enabled" =#=#=#= Current cib after: niceguy: Set fencing-enabled =#=#=#= @@ -716,7 +716,7 @@ pcmk__apply_creation_acl trace: ACLs allow creation of with id="cib-bo * Passed: crm_attribute - niceguy: Set fencing-enabled =#=#=#= Begin test: niceguy: Create a resource =#=#=#= pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to /cib/configuration/resources/primitive[@id='dummy'] -pcmk__apply_creation_acl trace: ACLs disallow creation of with id="dummy" +check_creation_disallowed trace: ACLs disallow creation of with id="dummy" cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Create a resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Create a resource @@ -1041,8 +1041,8 @@ crm_resource: Error performing operation: Insufficient privileges * Passed: crm_resource - l33t-haxor: Remove a resource meta attribute =#=#=#= Begin test: niceguy: Create a resource meta attribute =#=#=#= unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. -pcmk__apply_creation_acl trace: Creation of scaffolding with id="dummy-meta_attributes" is implicitly allowed -pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" +check_creation_disallowed trace: Creation of scaffolding with id="dummy-meta_attributes" is implicitly allowed +check_creation_disallowed trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attributes name=target-role value=Stopped =#=#=#= Current cib after: niceguy: Create a resource meta attribute =#=#=#= @@ -1293,7 +1293,7 @@ Deleted 'dummy' option: id=dummy-meta_attributes-target-role name=target-role * Passed: crm_resource - niceguy: Remove a resource meta attribute =#=#=#= Begin test: niceguy: Create a resource meta attribute =#=#=#= unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. -pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" +check_creation_disallowed trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attributes name=target-role value=Started =#=#=#= Current cib after: niceguy: Create a resource meta attribute =#=#=#= @@ -1514,7 +1514,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: niceguy: Replace - create resource =#=#=#= pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to /cib[@epoch] pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to /cib/configuration/resources/primitive[@id='dummy2'] -pcmk__apply_creation_acl trace: ACLs disallow creation of with id="dummy2" +check_creation_disallowed trace: ACLs disallow creation of with id="dummy2" cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - create resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - create resource @@ -2546,7 +2546,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: mike: Create another resource =#=#=#= -pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy2" +check_creation_disallowed trace: ACLs allow creation of with id="dummy2" =#=#=#= Current cib after: mike: Create another resource =#=#=#= diff --git a/cts/cts-cli.in b/cts/cts-cli.in index eadb6a69ba1..3125f2d642e 100644 --- a/cts/cts-cli.in +++ b/cts/cts-cli.in @@ -249,6 +249,7 @@ def sanitize_output(s): (r'(name, PCMK__XA_ATTR_SYNC_POINT, pcmk__str_none); + return pcmk__str_eq((const char *) attr->name, PCMK__XA_ATTR_SYNC_POINT, + pcmk__str_none); } static int diff --git a/daemons/controld/controld_join_dc.c b/daemons/controld/controld_join_dc.c index 8e2bf7c1d0b..8ed1b29b3ad 100644 --- a/daemons/controld/controld_join_dc.c +++ b/daemons/controld/controld_join_dc.c @@ -962,11 +962,9 @@ finalize_join_for(gpointer key, gpointer value, gpointer user_data) } remote = pcmk__xe_create(remotes, PCMK_XE_NODE); - pcmk__xe_set_props(remote, - PCMK_XA_ID, node->name, - PCMK__XA_NODE_STATE, node->state, - PCMK__XA_CONNECTION_HOST, node->conn_host, - NULL); + pcmk__xe_set(remote, PCMK_XA_ID, node->name); + pcmk__xe_set(remote, PCMK__XA_NODE_STATE, node->state); + pcmk__xe_set(remote, PCMK__XA_CONNECTION_HOST, node->conn_host); } } } diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 8c5da82a325..9f33f795806 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -153,6 +153,8 @@ fenced_has_watchdog_device(void) void fenced_foreach_device(GHFunc fn, gpointer user_data) { + pcmk__assert(fn != NULL); + if (device_table == NULL) { return; } @@ -170,6 +172,8 @@ fenced_foreach_device(GHFunc fn, gpointer user_data) void fenced_foreach_device_remove(GHRFunc fn) { + pcmk__assert(fn != NULL); + if (device_table == NULL) { return; } diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c index 98f38cc5fca..ff901eb1f2b 100644 --- a/daemons/pacemakerd/pacemakerd.c +++ b/daemons/pacemakerd/pacemakerd.c @@ -67,12 +67,12 @@ PCMK__OUTPUT_ARGS("features") static int pacemakerd_features_xml(pcmk__output_t *out, va_list args) { gchar **feature_list = g_strsplit(CRM_FEATURES, " ", 0); + xmlNode *xml = pcmk__output_xml_create_parent(out, PCMK_XE_PACEMAKERD); + + pcmk__xe_set(xml, PCMK_XA_VERSION, PACEMAKER_VERSION); + pcmk__xe_set(xml, PCMK_XA_BUILD, BUILD_VERSION); + pcmk__xe_set(xml, PCMK_XA_FEATURE_SET, CRM_FEATURE_SET); - pcmk__output_xml_create_parent(out, PCMK_XE_PACEMAKERD, - PCMK_XA_VERSION, PACEMAKER_VERSION, - PCMK_XA_BUILD, BUILD_VERSION, - PCMK_XA_FEATURE_SET, CRM_FEATURE_SET, - NULL); out->begin_list(out, NULL, NULL, PCMK_XE_FEATURES); for (char **s = feature_list; *s != NULL; s++) { diff --git a/devel/Makefile.am b/devel/Makefile.am index 11acfd6eafb..d7e045f43d7 100644 --- a/devel/Makefile.am +++ b/devel/Makefile.am @@ -70,12 +70,15 @@ coverity: $(COVTAR) # of them are designed so that things execute in the proper order (which is # not the same as GNU make's order-only prerequisites). +# @COMPAT Prior to GLib 2.58, the implementation of g_clear_pointer() +# triggers the INCONSISTENT_UNION_ACCESS warning .PHONY: coverity-analyze coverity-analyze: $(COVERITY_DIR) @echo "" @echo "Analyzing (waiting for coverity license if necessary) ..." cd $(top_builddir) && cov-analyze --dir "$<" --wait-for-license \ - --security --aggressiveness-level "$(COVLEVEL)" + --security --aggressiveness-level "$(COVLEVEL)" \ + --disable INCONSISTENT_UNION_ACCESS .PHONY: $(COVEMACS) $(COVEMACS): coverity-analyze diff --git a/include/crm/common/acl.h b/include/crm/common/acl.h index bff52f63417..708b3639a84 100644 --- a/include/crm/common/acl.h +++ b/include/crm/common/acl.h @@ -24,11 +24,6 @@ extern "C" { */ void xml_acl_disable(xmlNode *xml); -bool xml_acl_denied(const xmlNode *xml); -bool xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml, - xmlNode **result); - -bool pcmk_acl_required(const char *user); #ifdef __cplusplus } diff --git a/include/crm/common/acl_compat.h b/include/crm/common/acl_compat.h index a87db5ca26f..14fe5237c30 100644 --- a/include/crm/common/acl_compat.h +++ b/include/crm/common/acl_compat.h @@ -30,6 +30,16 @@ extern "C" { //! \deprecated Do not use bool xml_acl_enabled(const xmlNode *xml); +//! \deprecated Do not use +bool xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, + xmlNode **result); + +//! \deprecated Do not use +bool xml_acl_denied(const xmlNode *xml); + +//! \deprecated Do not use +bool pcmk_acl_required(const char *user); + #ifdef __cplusplus } #endif diff --git a/include/crm/common/acl_internal.h b/include/crm/common/acl_internal.h index cb4930bfaa1..4faab34152a 100644 --- a/include/crm/common/acl_internal.h +++ b/include/crm/common/acl_internal.h @@ -36,8 +36,26 @@ pcmk__is_privileged(const char *user) return user && (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")); } +/*! + * \internal + * \brief Check whether an ACL is required for a given user to access the CIB + * + * \param[in] user User name + * + * \return \c true if \p user requires an ACL to access the CIB, or \c false + * otherwise + */ +static inline bool +pcmk__acl_required(const char *user) +{ + return !pcmk__str_empty(user) && !pcmk__is_privileged(user); +} + void pcmk__enable_acls(xmlDoc *source, xmlDoc *target, const char *user); +xmlNode *pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, + xmlNode *xml); + bool pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode); diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index 25f9731870a..88bdac5d242 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -60,6 +60,7 @@ #include #include #include +// xml_attr_internal.h intentionally left out // xml_comment_internal.h intentionally left out // xml_element_internal.h intentionally left out // xml_idref_internal.h intentionally left out diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h index bdc0837bfb9..2f99732a857 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -757,11 +757,9 @@ void pcmk__output_set_log_filter(pcmk__output_t *out, const char *file, * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. - * \param[in] ... Name/value pairs to set as XML properties. */ -xmlNodePtr -pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) -G_GNUC_NULL_TERMINATED; +xmlNode * +pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name); /*! * \internal @@ -781,11 +779,9 @@ pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node); * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. - * \param[in] ... Name/value pairs to set as XML properties. */ -xmlNodePtr -pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) -G_GNUC_NULL_TERMINATED; +xmlNode * +pcmk__output_create_xml_node(pcmk__output_t *out, const char *name); /*! * \internal diff --git a/include/crm/common/xml_attr_internal.h b/include/crm/common/xml_attr_internal.h new file mode 100644 index 00000000000..e9820d698c3 --- /dev/null +++ b/include/crm/common/xml_attr_internal.h @@ -0,0 +1,32 @@ +/* + * Copyright 2025 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_ATTR_INTERNAL__H +#define PCMK__CRM_COMMON_XML_ATTR_INTERNAL__H + +/* + * Internal-only wrappers for and extensions to libxml2 for processing XML + * attributes + */ + +#include // bool + +#include // xmlAttr + +#ifdef __cplusplus +extern "C" { +#endif + +bool pcmk__xa_insert_dup(const xmlAttr *attr, void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__XML_ATTR_INTERNAL__H diff --git a/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index ec2964c2504..2114e942556 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -37,13 +37,19 @@ extern "C" { const char *pcmk__xe_add_last_written(xmlNode *xe); +bool pcmk__xe_foreach_attr(xmlNode *xml, bool (*fn)(xmlAttr *, void *), + void *user_data); +bool pcmk__xe_foreach_const_attr(const xmlNode *xml, + bool (*fn)(const xmlAttr *, void *), + void *user_data); + 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 force, - bool (*match)(xmlAttrPtr, void *), + bool (*match)(const xmlAttr *, void *), void *user_data); int pcmk__xe_delete_match(xmlNode *xml, xmlNode *search); int pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace); @@ -80,31 +86,6 @@ void pcmk__xe_sort_attrs(xmlNode *xml); void pcmk__xe_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3); -/*! - * \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 diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 8fd2148354d..97e00de10dd 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -28,11 +28,13 @@ #include // PCMK_XA_ID, PCMK_XE_CLONE // This file is a wrapper for other {xml_*,xpath}_internal.h headers +#include #include #include #include #include #include +#include #include #include @@ -259,7 +261,6 @@ enum pcmk__xml_escape_type { 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); /*! @@ -426,17 +427,28 @@ enum pcmk__xml_flags { void pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags); bool pcmk__xml_doc_all_flags_set(const xmlDoc *xml, uint32_t flags); -void pcmk__xml_commit_changes(xmlDoc *doc); -void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml); - +bool pcmk__xml_foreach_child(xmlNode *xml, bool (*fn)(xmlNode *, void *), + void *user_data); bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void *user_data); +/*! + * \internal + * \brief Get an XML attribute's value + * + * \param[in] attr XML attribute + * + * \return Value of \p attr, or \c NULL if \p attr is \c NULL or its value is + * unset + */ static inline const char * pcmk__xml_attr_value(const xmlAttr *attr) { - return ((attr == NULL) || (attr->children == NULL))? NULL - : (const char *) attr->children->content; + if ((attr == NULL) || (attr->children == NULL)) { + return NULL; + } + + return (const char *) attr->children->content; } void pcmk__xml_patchset_add_digest(xmlNode *patchset, const xmlNode *target); diff --git a/include/crm/common/xml_tracking_internal.h b/include/crm/common/xml_tracking_internal.h new file mode 100644 index 00000000000..016dc1ecb3f --- /dev/null +++ b/include/crm/common/xml_tracking_internal.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017-2025 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_TRACKING_INTERNAL__H +#define PCMK__CRM_COMMON_XML_TRACKING_INTERNAL__H + +/* + * Internal-only functions for tracking, calculating, and committing XML changes + */ + +#include // xmlDoc, xmlNode + +#ifdef __cplusplus +extern "C" { +#endif + +void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml); +void pcmk__xml_commit_changes(xmlDoc *doc); + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMMON_XML_TRACKING_INTERNAL__H diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index e27d41cc0cd..0ca301cd3a3 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -77,8 +77,6 @@ gchar *pcmk__native_output_string(const pcmk_resource_t *rsc, const char *name, const pcmk_node_t *node, uint32_t show_opts, const char *target_role, bool show_nodes); -int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name, - ...) G_GNUC_NULL_TERMINATED; char *pe__node_display_name(pcmk_node_t *node, bool print_detail); @@ -246,7 +244,7 @@ pe_base_name_eq(const pcmk_resource_t *rsc, const char *id) // Number of characters in rsc->id before any clone suffix size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1; - return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len); + return (strlen(id) == base_len) && g_str_has_prefix(rsc->id, id); } return false; } diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c index c2d0fc63e74..ce5333282ad 100644 --- a/lib/cib/cib_remote.c +++ b/lib/cib/cib_remote.c @@ -508,12 +508,10 @@ cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel) /* login to server */ login = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND); - pcmk__xe_set_props(login, - PCMK_XA_OP, "authenticate", - PCMK_XA_USER, private->user, - PCMK__XA_PASSWORD, private->passwd, - PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD, - NULL); + pcmk__xe_set(login, PCMK_XA_OP, "authenticate"); + pcmk__xe_set(login, PCMK_XA_USER, private->user); + pcmk__xe_set(login, PCMK__XA_PASSWORD, private->passwd); + pcmk__xe_set(login, PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD); pcmk__remote_send_xml(connection, login); pcmk__xml_free(login); diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 835f46ff7b7..c9d25e2cb3d 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -156,7 +156,7 @@ cib_acl_enabled(xmlNode *xml, const char *user) GHashTable *options = NULL; bool rc = false; - if ((xml == NULL) || !pcmk_acl_required(user)) { + if ((xml == NULL) || !pcmk__acl_required(user)) { return false; } @@ -212,9 +212,9 @@ cib__perform_query(cib__op_fn_t fn, xmlNode *req, xmlNode **current_cib, input = get_op_input(req); cib = *current_cib; - if (cib_acl_enabled(*current_cib, user) - && xml_acl_filtered_copy(user, *current_cib, *current_cib, - &cib_filtered)) { + if (cib_acl_enabled(*current_cib, user)) { + cib_filtered = pcmk__acl_filtered_copy(user, (*current_cib)->doc, + *current_cib); if (cib_filtered == NULL) { pcmk__debug("Pre-filtered the entire cib"); @@ -570,7 +570,7 @@ cib_perform_op(enum cib_variant variant, cib__op_fn_t fn, xmlNode *req, // Allow ourselves to make any additional necessary changes xml_acl_disable(*cib); - if (xml_acl_denied(*cib)) { + if (pcmk__xml_doc_all_flags_set((*cib)->doc, pcmk__xf_acl_denied)) { pcmk__trace("ACL rejected part or all of the proposed changes"); rc = EACCES; goto done; @@ -647,12 +647,12 @@ cib_perform_op(enum cib_variant variant, cib__op_fn_t fn, xmlNode *req, if ((rc != pcmk_rc_ok) && cib_acl_enabled(old_versions, user)) { xmlNode *saved_cib = *cib; - if (xml_acl_filtered_copy(user, old_versions, *cib, cib)) { - if (*cib == NULL) { - pcmk__debug("Pre-filtered the entire cib result"); - } - pcmk__xml_free(saved_cib); + *cib = pcmk__acl_filtered_copy(user, old_versions->doc, *cib); + if (*cib == NULL) { + pcmk__debug("Pre-filtered the entire cib result"); } + + pcmk__xml_free(saved_cib); } pcmk__xml_free(top); diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am index 96631723dac..fba4ac07bbe 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -107,6 +107,7 @@ libcrmcommon_la_SOURCES += xml_display.c libcrmcommon_la_SOURCES += xml_element.c libcrmcommon_la_SOURCES += xml_idref.c libcrmcommon_la_SOURCES += xml_io.c +libcrmcommon_la_SOURCES += xml_tracking.c libcrmcommon_la_SOURCES += xpath.c # diff --git a/lib/common/acl.c b/lib/common/acl.c index ce34b93d6cc..bf43df476af 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -590,14 +590,14 @@ unpack_acl_target_or_group(xmlNode *xml, void *user_data) * \param[in,out] target XML document private data whose \c acls field to set * \param[in] user User whose ACLs to unpack */ -void -pcmk__unpack_acls(xmlDoc *source, xml_doc_private_t *target, const char *user) +static void +unpack_acls(xmlDoc *source, xml_doc_private_t *target, const char *user) { xmlNode *acls = NULL; pcmk__assert(target != NULL); - if ((target->acls != NULL) || !pcmk_acl_required(user)) { + if ((target->acls != NULL) || !pcmk__acl_required(user)) { return; } @@ -733,7 +733,7 @@ pcmk__enable_acls(xmlDoc *source, xmlDoc *target, const char *user) if (target == NULL) { return; } - pcmk__unpack_acls(source, target->_private, user); + unpack_acls(source, target->_private, user); pcmk__xml_doc_set_flags(target, pcmk__xf_acl_enabled); pcmk__apply_acls(target); } @@ -764,30 +764,46 @@ is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode) return false; } - if (pcmk__is_set(flags, mode)) { - // The access we requested is explicitly allowed - return true; - } - - if ((mode == pcmk__xf_acl_read) - && pcmk__is_set(flags, pcmk__xf_acl_write)) { + switch (mode) { + case pcmk__xf_acl_read: + // Write access provides read access + return pcmk__any_flags_set(flags, + pcmk__xf_acl_read|pcmk__xf_acl_write); - // Write access provides read access - return true; - } + case pcmk__xf_acl_write: + return pcmk__is_set(flags, pcmk__xf_acl_write); - if ((mode == pcmk__xf_acl_create) - && pcmk__any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_created)) { + case pcmk__xf_acl_create: + /* Write access provides create access. + * + * @TODO Why does the \c pcmk__xf_created flag provide create + * access? This was introduced by commit e2ed85fe. + */ + return pcmk__any_flags_set(flags, + pcmk__xf_acl_write|pcmk__xf_created); - /* Write access provides create access. - * - * @TODO Why does the \c pcmk__xf_created flag provide create access? - * This was introduced by commit e2ed85fe. - */ - return true; + default: + // Invalid mode + return false; } +} - return false; +/*! + * \internal + * \brief Check whether an XML attribute's name is \c PCMK_XA_ID + * + * \param[in] attr Attribute to check + * \param[in] user_data Ignored + * + * \return \c true if the attribute's name is \c PCMK_XA_ID, or \c false + * otherwise + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +attr_is_id(const xmlAttr *attr, void *user_data) +{ + return pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none); } /*! @@ -803,142 +819,171 @@ is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode) * \note This is compatible with \c pcmk__xe_remove_matching_attrs(). */ static bool -attr_is_not_id(xmlAttr *attr, void *user_data) +attr_is_not_id(const xmlAttr *attr, void *user_data) { - return !pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none); + return !attr_is_id(attr, user_data); } /*! * \internal - * \brief Rid XML tree of all unreadable nodes and node properties + * \brief Filter out nodes that are unreadable by the current ACL user + * + * Access or denial via ACLs is inherited, with more specific ACLs (those that + * match the node directly or match a more recent ancestor) taking precedence + * over less specific ones (those that match a less recent ancestor). Access is + * denied by default, if no ACL matches a node or any of its ancestors. * - * \param[in,out] xml Root XML node to be purged of attributes + * For each node in the tree, check whether any ACL matched the node. If so, + * then use that ACL for the current node. If not, then the current node + * inherits read access or denial from its parent. * - * \return true if this node or any of its children are readable - * if false is returned, xml will be freed + * Filter each child. Each child inherits read access or denial from the current + * node, unless an ACL matched the child directly. * - * \note This function is recursive + * If the current node is readable, don't modify it. If it's unreadable but has + * at least one readable descendant, then remove all of its attributes other + * than \c PCMK_XA_ID. If it's unreadable and has no readable descendants, + * remove it. + * + * \param[in,out] xml XML tree (will be set to \c NULL if + * freed) + * \param[in] in_readable_context If \c true, the parent of \p xml is + * readable + * + * \note This function assumes that \c pcmk__apply_acls() has already been + * called for \p *xml->doc. + * \note This function is recursive. */ -static bool -purge_xml_attributes(xmlNode *xml) +static void +filter_unreadable_nodes(xmlNode **xml, bool in_readable_context) { + const xml_node_private_t *nodepriv = (*xml)->_private; + bool direct = false; + const char *how = NULL; + const char *name = (const char *) (*xml)->name; + const char *id = pcmk__s(pcmk__xe_id(*xml), "(unset)"); xmlNode *child = NULL; - bool readable_children = false; - xml_node_private_t *nodepriv = xml->_private; + /* If an ACL matched xml directly, update the context for xml and its + * descendants. Otherwise, keep the access that we inherited. + */ if (is_mode_allowed(nodepriv->flags, pcmk__xf_acl_read)) { - pcmk__trace("%s[@" PCMK_XA_ID "=%s] is readable", xml->name, - pcmk__xe_id(xml)); - return true; + in_readable_context = true; + direct = true; + + } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) { + in_readable_context = false; + direct = true; } - pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL); + if (direct) { + how = "directly"; - child = pcmk__xe_first_child(xml, NULL, NULL, NULL); + } else if (*xml == xmlDocGetRootElement((*xml)->doc)) { + how = "by default"; + + } else { + how = "by inheritance"; + } + + pcmk__trace("ACLs %s read access to %s[@" PCMK_XA_ID "='%s'] %s", + (in_readable_context? "allow" : "deny"), name, id, how); + + // Filter children recursively + child = pcmk__xml_first_child(*xml); while (child != NULL) { - xmlNode *tmp = child; + xmlNode *next = pcmk__xml_next(child); - child = pcmk__xe_next(child, NULL); + filter_unreadable_nodes(&child, in_readable_context); + child = next; + } - if (purge_xml_attributes(tmp)) { - readable_children = true; - } + if (in_readable_context) { + // This node is readable, either directly or by inheritance + return; } - if (!readable_children) { - // Nothing readable under here, so purge completely - pcmk__xml_free(xml); + if ((*xml)->children != NULL) { + /* At least one descendant is readable, but this node is not. + * + * Remove all attributes except PCMK_XA_ID. Keep this node as a bare + * "scaffolding" element for structure, so that the path from the root + * to any readable descendant remains intact. Removing this node would + * also remove its readable descendants. + */ + pcmk__xe_remove_matching_attrs(*xml, true, attr_is_not_id, NULL); + return; } - return readable_children; + + // This node and all its descendants are unreadable, so remove it + g_clear_pointer(xml, pcmk__xml_free); } /*! - * \brief Copy ACL-allowed portions of specified XML + * \internal + * \brief Copy XML, filtering out portions that are unreadable based on ACLs * - * \param[in] user Username whose ACLs should be used - * \param[in] acl_source XML containing ACLs - * \param[in] xml XML to be copied - * \param[out] result Copy of XML portions readable via ACLs + * Access or denial via ACLs is inherited, with more specific ACLs (those that + * match the node directly or match a more recent ancestor) taking precedence + * over less specific ones (those that match a less recent ancestor). Access is + * denied by default, if no ACL matches a node or any of its ancestors. * - * \return \c true if \p acl_source and \p xml are non-NULL and ACLs - * are required for \p user, or \c false otherwise + * If the user's ACLs grant read access to a node (either directly or through + * the most recent ancestor node matched by an ACL), then that node is kept + * intact. * - * \note If this returns true, caller should use \p result rather than \p xml + * If the user's ACLs deny read access to a node (either directly, through the + * most recent ancestor node matched by an ACL, or by default), then: + * - If the current node has at least one readable descendant, then all of the + * current node's attributes are removed other than \c PCMK_XA_ID. + * - Otherwise, the current node is removed. + * + * \param[in] user User whose ACLs to use + * \param[in] acl_source XML document whose ACL definitions to use + * \param[in] xml XML to copy + * + * \return Newly allocated ACL-filtered copy of \p xml, or \c NULL if the entire + * document is filtered out + * + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free(). */ -bool -xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, - xmlNode **result) +xmlNode * +pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, xmlNode *xml) { - xmlNode *target = NULL; + xmlNode *result = NULL; xml_doc_private_t *docpriv = NULL; - *result = NULL; - if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) - || !pcmk_acl_required(user)) { + pcmk__assert((acl_source != NULL) && (xml != NULL)); - return false; - } - - target = pcmk__xml_copy(NULL, xml); - docpriv = target->doc->_private; - - pcmk__enable_acls(acl_source->doc, target->doc, user); - - pcmk__trace("Filtering XML copy using user '%s' ACLs", user); - - for (const GList *iter = docpriv->acls; iter != NULL; iter = iter->next) { - const xml_acl_t *acl = iter->data; - xmlXPathObject *xpath_obj = NULL; - int num_results = 0; - - if ((acl->mode != pcmk__xf_acl_deny) || (acl->xpath == NULL)) { - continue; - } - - xpath_obj = pcmk__xpath_search(target->doc, acl->xpath); - num_results = pcmk__xpath_num_results(xpath_obj); - - for (int i = 0; i < num_results; i++) { - xmlNode *match = pcmk__xpath_result(xpath_obj, i); + result = pcmk__xml_copy(NULL, xml); - if (match == NULL) { - continue; - } - - // @COMPAT See COMPAT comment in pcmk__apply_acls() - match = pcmk__xpath_match_element(match); - if (match == NULL) { - continue; - } - - if (!purge_xml_attributes(match) && (match == target)) { - pcmk__trace("ACLs deny user '%s' access to entire XML document", - user); - xmlXPathFreeObject(xpath_obj); - return true; - } - } - pcmk__trace("ACLs deny user '%s' access to %s (%d match%s)", user, - acl->xpath, num_results, - pcmk__plural_alt(num_results, "", "es")); - xmlXPathFreeObject(xpath_obj); + if (!pcmk__acl_required(user)) { + // Return an unfiltered copy + return result; } - if (!purge_xml_attributes(target)) { - pcmk__trace("ACLs deny user '%s' access to entire XML document", user); - return true; - } + pcmk__enable_acls(acl_source->doc, result->doc, user); + docpriv = result->doc->_private; if (docpriv->acls == NULL) { pcmk__trace("User '%s' without ACLs denied access to entire XML " "document", user); - pcmk__xml_free(target); - return true; + pcmk__xml_free(result); + return NULL; + } + + pcmk__trace("Filtering XML copy using user '%s' ACLs", user); + filter_unreadable_nodes(&result, false); + + if (result == NULL) { + // Entire document was freed, so don't free docpriv->acls here + pcmk__trace("ACLs deny user '%s' access to entire XML document", user); + return NULL; } g_clear_pointer(&docpriv->acls, pcmk__free_acls); - *result = target; - return true; + return result; } /*! @@ -951,134 +996,113 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, * * \param[in] xml XML element to check * - * \return true if XML element is implicitly allowed, false otherwise + * \return \c true if XML element is implicitly allowed, or \c false otherwise */ static bool implicitly_allowed(const xmlNode *xml) { - GString *path = NULL; + if (!pcmk__xe_foreach_const_attr(xml, attr_is_id, NULL)) { + return false; + } - for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) { - if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) { + /* Creation is not implicitly allowed for a descendant of PCMK_XE_ACLS, but + * it may be for PCMK_XE_ACLS itself. Start checking at xml->parent and walk + * up the tree. + */ + for (xml = xml->parent; xml != NULL; xml = xml->parent) { + if (pcmk__xe_is(xml, PCMK_XE_ACLS)) { return false; } } - path = pcmk__element_xpath(xml); - pcmk__assert(path != NULL); - - if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) { - g_string_free(path, TRUE); - return false; - } - - g_string_free(path, TRUE); return true; } -#define display_id(xml) pcmk__s(pcmk__xe_id(xml), "") - /*! * \internal - * \brief Drop XML nodes created in violation of ACLs + * \brief Check whether ACLs allow creation of an XML node * - * Given an XML element, free all of its descendant nodes created in violation - * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those - * that aren't in the ACL section and don't have any attributes other than - * \c PCMK_XA_ID). + * "Scaffolding" elements (those that aren't in the ACLs section and don't have + * any attributes other than \c PCMK_XA_ID) are always allowed. * - * \param[in,out] xml XML to check - * \param[in] check_top Whether to apply checks to argument itself - * (if true, xml might get freed) + * \param[in,out] xml XML node * - * \note This function is recursive + * \return \c true \p xml is newly created and ACLs disallow its creation, or + * \c false otherwise */ -void -pcmk__apply_creation_acl(xmlNode *xml, bool check_top) +static bool +check_creation_disallowed(xmlNode *xml) { + const char *type = (const char *) xml->name; + const char *id = pcmk__s(pcmk__xe_id(xml), ""); xml_node_private_t *nodepriv = xml->_private; - if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - if (implicitly_allowed(xml)) { - pcmk__trace("Creation of <%s> scaffolding with " - PCMK_XA_ID "=\"%s\" is implicitly allowed", - xml->name, display_id(xml)); - - } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) { - pcmk__trace("ACLs allow creation of <%s> with " - PCMK_XA_ID "=\"%s\"", - xml->name, display_id(xml)); + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + return false; + } - } else if (check_top) { - /* is_root=true should be impossible with check_top=true, but check - * for sanity - */ - bool is_root = (xmlDocGetRootElement(xml->doc) == xml); - xml_doc_private_t *docpriv = xml->doc->_private; - - pcmk__trace("ACLs disallow creation of %s<%s> with " - PCMK_XA_ID "=\"%s\"", - (is_root? "root element " : ""), xml->name, - display_id(xml)); - - // pcmk__xml_free() checks ACLs if enabled, which would fail - pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); - pcmk__xml_free(xml); - - if (!is_root) { - // If root, the document was freed. Otherwise re-enable ACLs. - pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled); - } - return; - - } else { - const bool is_root = (xml == xmlDocGetRootElement(xml->doc)); - - pcmk__notice("ACLs would disallow creation of %s<%s> with " - PCMK_XA_ID "=\"%s\"", - (is_root? "root element " : ""), xml->name, - display_id(xml)); - } + if (implicitly_allowed(xml)) { + pcmk__trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\" " + "is implicitly allowed", type, id); + return false; } - for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) { - xmlNode *child = cIter; - cIter = pcmk__xml_next(cIter); /* In case it is free'd */ - pcmk__apply_creation_acl(child, true); + if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) { + pcmk__trace("ACLs allow creation of <%s> with " PCMK_XA_ID "=\"%s\"", + type, id); + return false; } + + pcmk__trace("ACLs disallow creation of <%s> with " PCMK_XA_ID "=\"%s\"", + type, id); + return true; } /*! - * \brief Check whether or not an XML node is ACL-denied + * \internal + * \brief Remove XML nodes created in violation of ACLs * - * \param[in] xml node to check + * Given an XML tree, free all nodes created in violation of ACLs, with the + * exception of allowing "scaffolding" elements (those that aren't in the ACLs + * section and don't have any attributes other than \c PCMK_XA_ID). * - * \return true if XML node exists and is ACL-denied, false otherwise + * \param[in,out] xml XML tree */ -bool -xml_acl_denied(const xmlNode *xml) +void +pcmk__check_creation_acls(xmlNode *xml) { - if (xml && xml->doc && xml->doc->_private){ - xml_doc_private_t *docpriv = xml->doc->_private; - - return pcmk__is_set(docpriv->flags, pcmk__xf_acl_denied); - } - return false; + pcmk__xml_tree_foreach_remove(xml, check_creation_disallowed); } void xml_acl_disable(xmlNode *xml) { - if ((xml != NULL) - && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) { + xmlNode *child = NULL; - xml_doc_private_t *docpriv = xml->doc->_private; + if ((xml == NULL) + || !pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) { - /* Catch anything that was created but shouldn't have been */ - pcmk__apply_acls(xml->doc); - pcmk__apply_creation_acl(xml, false); - pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); + return; } + + // Catch anything that was created but shouldn't have been + pcmk__apply_acls(xml->doc); + + /* Be sure not to free xml itself. + * + * @TODO Maybe we should free xml if it's newly created and the creation + * is disallowed, but we would need a way to inform the caller. This is + * public API. + */ + child = pcmk__xml_first_child(xml); + while (child != NULL) { + xmlNode *next = pcmk__xml_next(child); + + pcmk__check_creation_acls(child); + child = next; + } + + pcmk__xml_doc_clear_flags(xml->doc, pcmk__xf_acl_enabled); } /*! @@ -1170,28 +1194,6 @@ pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode) return false; } -/*! - * \brief Check whether ACLs are required for a given user - * - * \param[in] User name to check - * - * \return true if the user requires ACLs, false otherwise - */ -bool -pcmk_acl_required(const char *user) -{ - if (pcmk__str_empty(user)) { - pcmk__trace("ACLs not required because no user set"); - return false; - - } else if (pcmk__is_privileged(user)) { - pcmk__trace("ACLs not required for privileged user %s", user); - return false; - } - pcmk__trace("ACLs required for %s", user); - return true; -} - char * pcmk__uid2username(uid_t uid) { @@ -1318,5 +1320,32 @@ xml_acl_enabled(const xmlNode *xml) return false; } +bool +xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, + xmlNode **result) +{ + if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) + || !pcmk__acl_required(user)) { + + return false; + } + + *result = pcmk__acl_filtered_copy(user, acl_source->doc, xml); + return true; +} + +bool +xml_acl_denied(const xmlNode *xml) +{ + return (xml != NULL) + && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_denied); +} + +bool +pcmk_acl_required(const char *user) +{ + return pcmk__acl_required(user); +} + // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/actions.c b/lib/common/actions.c index b4a4ee04c19..b7b4e5afdff 100644 --- a/lib/common/actions.c +++ b/lib/common/actions.c @@ -264,7 +264,8 @@ match_before(const char *key, size_t position, const char **matches) const size_t possible = position - match_len - 1; if ((key[possible] == '_') - && (strncmp(key + possible + 1, matches[i], match_len) == 0)) { + && g_str_has_prefix(key + possible + 1, matches[i])) { + return possible; } } diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 58add461801..4f02d55bb33 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -113,21 +113,33 @@ typedef struct { G_GNUC_INTERNAL const char *pcmk__xml_element_type_text(xmlElementType type); +G_GNUC_INTERNAL +void pcmk__xml_tree_foreach_remove(xmlNode *xml, bool (*fn)(xmlNode *)); + G_GNUC_INTERNAL bool pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data); G_GNUC_INTERNAL void pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags); +G_GNUC_INTERNAL +void pcmk__xml_doc_clear_flags(xmlDoc *doc, uint32_t flags); + G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml); +G_GNUC_INTERNAL +void pcmk__xml_reset_doc_private_data(xml_doc_private_t *docpriv); + G_GNUC_INTERNAL void pcmk__xml_free_private_data(xmlNode *xml); G_GNUC_INTERNAL void pcmk__xml_free_node(xmlNode *xml); +G_GNUC_INTERNAL +int pcmk__xml_free_position(xmlNode *node, int position); + G_GNUC_INTERNAL xmlDoc *pcmk__xml_new_doc(void); @@ -143,10 +155,6 @@ void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update); G_GNUC_INTERNAL void pcmk__free_acls(GList *acls); -G_GNUC_INTERNAL -void pcmk__unpack_acls(xmlDoc *source, xml_doc_private_t *target, - const char *user); - G_GNUC_INTERNAL bool pcmk__is_user_in_group(const char *user, const char *group); @@ -154,7 +162,7 @@ G_GNUC_INTERNAL void pcmk__apply_acls(xmlDoc *doc); G_GNUC_INTERNAL -void pcmk__apply_creation_acl(xmlNode *xml, bool check_top); +void pcmk__check_creation_acls(xmlNode *xml); G_GNUC_INTERNAL int pcmk__xa_remove(xmlAttr *attr, bool force); @@ -173,10 +181,7 @@ G_GNUC_INTERNAL void pcmk__mark_xml_node_dirty(xmlNode *xml); G_GNUC_INTERNAL -bool pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data); - -G_GNUC_INTERNAL -void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer); +bool pcmk__dump_xml_attr(const xmlAttr *attr, void *user_data); G_GNUC_INTERNAL int pcmk__xe_set_score(xmlNode *target, const char *name, const char *value); diff --git a/lib/common/digest.c b/lib/common/digest.c index 211cfe35218..53a6f4417a6 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -298,12 +298,12 @@ pcmk__xa_filterable(const char *name) // Return true if a is an attribute that should be filtered static bool -should_filter_for_digest(xmlAttrPtr a, void *user_data) +should_filter_for_digest(const xmlAttr *a, void *user_data) { - if (strncmp((const char *) a->name, CRM_META "_", - sizeof(CRM_META " ") - 1) == 0) { + if (g_str_has_prefix((const char *) a->name, CRM_META "_")) { return true; } + return pcmk__str_any_of((const char *) a->name, PCMK_XA_ID, PCMK_XA_CRM_FEATURE_SET, diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c index 1c9cbe00c73..52cae01f46a 100644 --- a/lib/common/ipc_client.c +++ b/lib/common/ipc_client.c @@ -1741,8 +1741,9 @@ pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid, } rc = pcmk_rc_ok; - if ((found_uid != refuid || found_gid != refgid) - && strncmp(last_asked_name, name, sizeof(last_asked_name))) { + if (((found_uid != refuid) || (found_gid != refgid)) + && !pcmk__str_eq(name, last_asked_name, pcmk__str_none)) { + if ((found_uid == 0) && (refuid != 0)) { pcmk__warn("Daemon (IPC %s) runs as root, whereas the expected " "credentials are %lld:%lld, hazard of violating the " diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c index 76e16dc6f77..6cdb067c29a 100644 --- a/lib/common/ipc_server.c +++ b/lib/common/ipc_server.c @@ -50,7 +50,9 @@ pcmk__ipc_client_count(void) void pcmk__foreach_ipc_client(GHFunc func, gpointer user_data) { - if ((func != NULL) && (client_connections != NULL)) { + pcmk__assert(func != NULL); + + if (client_connections != NULL) { g_hash_table_foreach(client_connections, func, user_data); } } diff --git a/lib/common/messages.c b/lib/common/messages.c index 479a8df82d0..d60328f2a71 100644 --- a/lib/common/messages.c +++ b/lib/common/messages.c @@ -79,17 +79,16 @@ pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server, } message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE); - pcmk__xe_set_props(message, - PCMK_XA_ORIGIN, origin, - PCMK__XA_T, pcmk__server_message_type(server), - PCMK__XA_SUBT, subtype, - PCMK_XA_VERSION, CRM_FEATURE_SET, - PCMK_XA_REFERENCE, reply_to, - PCMK__XA_CRM_SYS_FROM, sender_system, - PCMK__XA_CRM_HOST_TO, recipient_node, - PCMK__XA_CRM_SYS_TO, recipient_system, - PCMK__XA_CRM_TASK, task, - NULL); + pcmk__xe_set(message, PCMK_XA_ORIGIN, origin); + pcmk__xe_set(message, PCMK__XA_T, pcmk__server_message_type(server)); + pcmk__xe_set(message, PCMK__XA_SUBT, subtype); + pcmk__xe_set(message, PCMK_XA_VERSION, CRM_FEATURE_SET); + pcmk__xe_set(message, PCMK_XA_REFERENCE, reply_to); + pcmk__xe_set(message, PCMK__XA_CRM_SYS_FROM, sender_system); + pcmk__xe_set(message, PCMK__XA_CRM_HOST_TO, recipient_node); + pcmk__xe_set(message, PCMK__XA_CRM_SYS_TO, recipient_system); + pcmk__xe_set(message, PCMK__XA_CRM_TASK, task); + if (data != NULL) { xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML); diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index b1510b93e12..8d817e675d4 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -340,7 +340,6 @@ GHashTable * xml2list(const xmlNode *parent) { xmlNode *child = NULL; - xmlAttrPtr pIter = NULL; xmlNode *nvpair_list = NULL; GHashTable *nvpair_hash = pcmk__strkey_table(free, free); @@ -354,16 +353,7 @@ xml2list(const xmlNode *parent) pcmk__log_xml_trace(nvpair_list, "Unpacking"); - for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL; - pIter = pIter->next) { - - const char *p_name = (const char *)pIter->name; - const char *p_value = pcmk__xml_attr_value(pIter); - - pcmk__trace("Added %s=%s", p_name, p_value); - - pcmk__insert_dup(nvpair_hash, p_name, p_value); - } + pcmk__xe_foreach_const_attr(nvpair_list, pcmk__xa_insert_dup, nvpair_hash); for (child = pcmk__xe_first_child(nvpair_list, PCMK__XE_PARAM, NULL, NULL); child != NULL; child = pcmk__xe_next(child, PCMK__XE_PARAM)) { diff --git a/lib/common/options_display.c b/lib/common/options_display.c index 40cc5de75ef..1c66869d83e 100644 --- a/lib/common/options_display.c +++ b/lib/common/options_display.c @@ -298,9 +298,9 @@ add_possible_values_xml(pcmk__output_t *out, values = g_strsplit(option->values, ", ", 0); for (gchar **value = values; *value != NULL; value++) { - pcmk__output_create_xml_node(out, PCMK_XE_OPTION, - PCMK_XA_VALUE, *value, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_OPTION); + + pcmk__xe_set(xml, PCMK_XA_VALUE, *value); } g_strfreev(values); @@ -348,6 +348,7 @@ add_option_metadata_xml(pcmk__output_t *out, const pcmk__cluster_option_t *option, const char *replaced_with) { + xmlNode *xml = NULL; const char *type = option->type; const char *desc_long = option->description_long; const char *desc_short = option->description_short; @@ -427,29 +428,26 @@ add_option_metadata_xml(pcmk__output_t *out, generated_s = NULL; } - pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER, - PCMK_XA_NAME, option->name, - PCMK_XA_ADVANCED, advanced_s, - PCMK_XA_GENERATED, generated_s, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER); + pcmk__xe_set(xml, PCMK_XA_NAME, option->name); + pcmk__xe_set(xml, PCMK_XA_ADVANCED, advanced_s); + pcmk__xe_set(xml, PCMK_XA_GENERATED, generated_s); if (deprecated && !legacy) { - pcmk__output_xml_create_parent(out, PCMK_XE_DEPRECATED, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_DEPRECATED); if (replaced_with != NULL) { - pcmk__output_create_xml_node(out, PCMK_XE_REPLACED_WITH, - PCMK_XA_NAME, replaced_with, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_REPLACED_WITH); + pcmk__xe_set(xml, PCMK_XA_NAME, replaced_with); } pcmk__output_xml_pop_parent(out); } add_desc_xml(out, true, desc_long); add_desc_xml(out, false, desc_short); - pcmk__output_xml_create_parent(out, PCMK_XE_CONTENT, - PCMK_XA_TYPE, type, - PCMK_XA_DEFAULT, option->default_value, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_CONTENT); + pcmk__xe_set(xml, PCMK_XA_TYPE, type); + pcmk__xe_set(xml, PCMK_XA_DEFAULT, option->default_value); add_possible_values_xml(out, option); @@ -485,6 +483,7 @@ PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *", static int option_list_xml(pcmk__output_t *out, va_list args) { + xmlNode *xml = NULL; const char *name = va_arg(args, const char *); const char *desc_short = va_arg(args, const char *); const char *desc_long = va_arg(args, const char *); @@ -495,16 +494,15 @@ option_list_xml(pcmk__output_t *out, va_list args) pcmk__assert((out != NULL) && (name != NULL) && (desc_short != NULL) && (desc_long != NULL) && (option_list != NULL)); - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT, - PCMK_XA_NAME, name, - PCMK_XA_VERSION, PACEMAKER_VERSION, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT); + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_VERSION, PACEMAKER_VERSION); pcmk__output_create_xml_text_node(out, PCMK_XE_VERSION, PCMK_OCF_VERSION); add_desc_xml(out, true, desc_long); add_desc_xml(out, false, desc_short); - pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETERS, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETERS); for (const pcmk__cluster_option_t *option = option_list; option->name != NULL; option++) { diff --git a/lib/common/output_html.c b/lib/common/output_html.c index a2fbb7d7867..861edea9099 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -124,7 +124,7 @@ html_init(pcmk__output_t *out) { g_queue_push_tail(priv->parent_q, priv->root); priv->errors = NULL; - pcmk__output_xml_create_parent(out, "body", NULL); + pcmk__output_xml_create_parent(out, "body"); return true; } @@ -192,9 +192,9 @@ html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy if (stylesheet_link != NULL) { htmlNodePtr link_node = pcmk__xe_create(head_node, "link"); - pcmk__xe_set_props(link_node, "rel", "stylesheet", - "href", stylesheet_link, - NULL); + + pcmk__xe_set(link_node, "rel", "stylesheet"); + pcmk__xe_set(link_node, "href", stylesheet_link); } if (g_slist_length(priv->errors) > 0) { @@ -323,7 +323,7 @@ html_begin_list(pcmk__output_t *out, const char *singular_noun, */ q_len = g_queue_get_length(priv->parent_q); if (q_len > 2) { - pcmk__output_xml_create_parent(out, "li", NULL); + pcmk__output_xml_create_parent(out, "li"); } if (format != NULL) { @@ -345,7 +345,9 @@ html_begin_list(pcmk__output_t *out, const char *singular_noun, free(buf); } - node = pcmk__output_xml_create_parent(out, "ul", NULL); + node = pcmk__output_xml_create_parent(out, "ul"); + + // @FIXME This looks like an incorrect double-push; check this g_queue_push_tail(priv->parent_q, node); } @@ -404,7 +406,7 @@ html_is_quiet(pcmk__output_t *out) { static void html_spacer(pcmk__output_t *out) { pcmk__assert(out != NULL); - pcmk__output_create_xml_node(out, "br", NULL); + pcmk__output_create_xml_node(out, "br"); } static void @@ -491,10 +493,8 @@ pcmk__html_create(xmlNode *parent, const char *name, const char *id, { xmlNode *node = pcmk__xe_create(parent, name); - pcmk__xe_set_props(node, - PCMK_XA_CLASS, class_name, - PCMK_XA_ID, id, - NULL); + pcmk__xe_set(node, PCMK_XA_CLASS, class_name); + pcmk__xe_set(node, PCMK_XA_ID, id); return node; } diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index fb8141f0068..6ff4e5f74c7 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -207,20 +207,14 @@ xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_ node = node->next; } } else { - char *rc_as_str = pcmk__itoa(exit_status); - node = pcmk__xe_create(priv->root, PCMK_XE_STATUS); - pcmk__xe_set_props(node, - PCMK_XA_CODE, rc_as_str, - PCMK_XA_MESSAGE, crm_exit_str(exit_status), - NULL); + pcmk__xe_set_int(node, PCMK_XA_CODE, exit_status); + pcmk__xe_set(node, PCMK_XA_MESSAGE, crm_exit_str(exit_status)); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = pcmk__xe_create(node, PCMK_XE_ERRORS); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } - - free(rc_as_str); } if (print) { @@ -247,15 +241,11 @@ static void xml_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { xmlNodePtr node, child_node; - char *rc_as_str = NULL; pcmk__assert(out != NULL); - rc_as_str = pcmk__itoa(exit_status); - - node = pcmk__output_xml_create_parent(out, PCMK_XE_COMMAND, - PCMK_XA_CODE, rc_as_str, - NULL); + node = pcmk__output_xml_create_parent(out, PCMK_XE_COMMAND); + pcmk__xe_set_int(node, PCMK_XA_CODE, exit_status); if (proc_stdout != NULL) { child_node = pcmk__xe_create(node, PCMK_XE_OUTPUT); @@ -268,24 +258,22 @@ xml_subprocess_output(pcmk__output_t *out, int exit_status, pcmk__xe_set_content(child_node, "%s", proc_stderr); pcmk__xe_set(child_node, PCMK_XA_SOURCE, "stderr"); } - - free(rc_as_str); } static void xml_version(pcmk__output_t *out) { - const char *author = "Andrew Beekhof and the Pacemaker project " - "contributors"; + xmlNode *xml = NULL; + pcmk__assert(out != NULL); - pcmk__output_create_xml_node(out, PCMK_XE_VERSION, - PCMK_XA_PROGRAM, "Pacemaker", - PCMK_XA_VERSION, PACEMAKER_VERSION, - PCMK_XA_AUTHOR, author, - PCMK_XA_BUILD, BUILD_VERSION, - PCMK_XA_FEATURES, CRM_FEATURES, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_VERSION); + pcmk__xe_set(xml, PCMK_XA_PROGRAM, "Pacemaker"); + pcmk__xe_set(xml, PCMK_XA_VERSION, PACEMAKER_VERSION); + pcmk__xe_set(xml, PCMK_XA_AUTHOR, + "Andrew Beekhof and the Pacemaker project contributors"); + pcmk__xe_set(xml, PCMK_XA_BUILD, BUILD_VERSION); + pcmk__xe_set(xml, PCMK_XA_FEATURES, CRM_FEATURES); } G_GNUC_PRINTF(2, 3) @@ -322,7 +310,7 @@ xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { pcmk__assert(out != NULL); - parent = pcmk__output_create_xml_node(out, name, NULL); + parent = pcmk__output_create_xml_node(out, name); if (parent == NULL) { return; } @@ -361,11 +349,12 @@ xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plura } if (priv->list_element) { - pcmk__output_xml_create_parent(out, PCMK_XE_LIST, - PCMK_XA_NAME, name, - NULL); + xmlNode *xml = pcmk__output_xml_create_parent(out, PCMK_XE_LIST); + + pcmk__xe_set(xml, PCMK_XA_NAME, name); + } else { - pcmk__output_xml_create_parent(out, name, NULL); + pcmk__output_xml_create_parent(out, name); } g_free(name); @@ -477,19 +466,15 @@ pcmk__mk_xml_output(char **argv) { return retval; } -xmlNodePtr -pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) { - va_list args; +xmlNode * +pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name) +{ xmlNodePtr node = NULL; pcmk__assert(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); - node = pcmk__output_create_xml_node(out, name, NULL); - - va_start(args, name); - pcmk__xe_set_propv(node, args); - va_end(args); + node = pcmk__output_create_xml_node(out, name); pcmk__output_xml_push_parent(out, node); return node; @@ -514,11 +499,10 @@ pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node) { pcmk__xml_copy(parent, node); } -xmlNodePtr -pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) { - xmlNodePtr node = NULL; +xmlNode * +pcmk__output_create_xml_node(pcmk__output_t *out, const char *name) +{ private_data_t *priv = NULL; - va_list args; pcmk__assert((out != NULL) && (out->priv != NULL)); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); @@ -527,12 +511,7 @@ pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) { priv = out->priv; - node = pcmk__xe_create(g_queue_peek_tail(priv->parent_q), name); - va_start(args, name); - pcmk__xe_set_propv(node, args); - va_end(args); - - return node; + return pcmk__xe_create(g_queue_peek_tail(priv->parent_q), name); } xmlNodePtr @@ -542,7 +521,7 @@ pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const c pcmk__assert(out != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); - node = pcmk__output_create_xml_node(out, name, NULL); + node = pcmk__output_create_xml_node(out, name); pcmk__xe_set_content(node, "%s", content); return node; } diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 311478717b1..71a52fd7301 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -31,168 +31,356 @@ static const char *const vfields[] = { PCMK_XA_NUM_UPDATES, }; -/* Add changes for specified XML to patchset. - * For patchset format, refer to diff schema. +/*! + * \internal + * \brief Set patchset version fields for source or target XML + * + * Create a child of \p version with name \p name and add version numbers from + * \p from. + * + * \param[in,out] version \c PCMK_XE_VERSION child of patchset + * \param[in] from XML to get version numbers from + * \param[in] name Name for new child of \p version to add fields to */ static void -add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) +set_version_fields(xmlNode *version, const xmlNode *from, const char *name) +{ + xmlNode *child = pcmk__xe_create(version, name); + + for (int i = 0; i < PCMK__NELEM(vfields); i++) { + const char *value = pcmk__xe_get(from, vfields[i]); + + if (value == NULL) { + value = "1"; + } + pcmk__xe_set(child, vfields[i], value); + } +} + +/*! + * \internal + * \brief Add a \c PCMK_VALUE_DELETE change to a patchset + * + * \param[in] data Deleted object + * (const pcmk__deleted_xml_t *) + * \param[in,out] user_data XML patchset (xmlNode *) + * + * \note This is a \c GFunc compatible with \c g_list_foreach(). + */ +static void +add_delete_change(gpointer data, gpointer user_data) +{ + const pcmk__deleted_xml_t *deleted_obj = data; + xmlNode *patchset = user_data; + + xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + + pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE); + pcmk__xe_set(change, PCMK_XA_PATH, deleted_obj->path); + + if (deleted_obj->position >= 0) { + pcmk__xe_set_int(change, PCMK_XE_POSITION, deleted_obj->position); + } +} + +/*! + * \internal + * \brief Add a \c PCMK_VALUE_CREATE change to a patchset + * + * Given \p xml with the \c pcmk__xf_created flag set, create a + * \c PCMK_XE_CHANGE child of \p patchset, with \c PCMK_XA_OPERATION set to + * \c PCMK_VALUE_CREATE and with a child copy of the created node. + * + * \param[in] xml Newly created XML to add to \p patchset + * \param[in,out] patchset XML patchset + */ +static void +add_create_change(xmlNode *xml, xmlNode *patchset) { - xmlNode *cIter = NULL; - xmlAttr *pIter = NULL; xmlNode *change = NULL; - xml_node_private_t *nodepriv = xml->_private; - const char *value = NULL; + GString *xpath = pcmk__element_xpath(xml->parent); - if (nodepriv == NULL) { - /* Elements that shouldn't occur in a CIB don't have _private set. They - * should be stripped out, ignored, or have an error thrown by any code - * that processes their parent, so we ignore any changes to them. - */ + if (xpath == NULL) { + // @TODO This can happen only if xml->parent == NULL. Is that possible? return; } - // If this XML node is new, just report that - if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - GString *xpath = pcmk__element_xpath(xml->parent); + change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); - if (xpath != NULL) { - int position = pcmk__xml_position(xml, pcmk__xf_deleted); + pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE); + pcmk__xe_set(change, PCMK_XA_PATH, xpath->str); + pcmk__xe_set_int(change, PCMK_XE_POSITION, + pcmk__xml_position(xml, pcmk__xf_deleted)); + pcmk__xml_copy(change, xml); - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + g_string_free(xpath, TRUE); +} - pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE); - pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str); - pcmk__xe_set_int(change, PCMK_XE_POSITION, position); - pcmk__xml_copy(change, xml); - g_string_free(xpath, TRUE); - } +/*! + * \internal + * \brief Append an attribute to a list if it has been deleted or modified + * + * \param[in] attr XML attribute + * \param[in,out] user_data List of changed attributes (GSList **) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +append_attr_if_changed(const xmlAttr *attr, void *user_data) +{ + GSList **changed_attrs = user_data; + const xml_node_private_t *nodepriv = attr->_private; - return; + if (pcmk__any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) { + *changed_attrs = g_slist_append(*changed_attrs, (gpointer) attr); } - // Check each of the XML node's attributes for changes - for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; - pIter = pIter->next) { - xmlNode *attr = NULL; + return true; +} - nodepriv = pIter->_private; - if (!pcmk__any_flags_set(nodepriv->flags, - pcmk__xf_deleted|pcmk__xf_dirty)) { - continue; - } +/*! + * \internal + * \brief Add a \c PCMK_XE_CHANGE_ATTR to a \c PCMK_XE_CHANGE_LIST + * + * Create a new \c PCMK_XE_CHANGE_ATTR child of a \c PCMK_XE_CHANGE_LIST and set + * its content based on a deleted or modified XML attribute. + * + * \param[in] data XML attribute + * \param[out] user_data \c PCMK_XE_CHANGE_LIST element + * + * \note This is a \c GFunc compatible with \c g_slist_foreach(). + */ +static void +add_change_attr(gpointer data, gpointer user_data) +{ + const xmlAttr *attr = data; + xmlNode *change_list = user_data; - if (change == NULL) { - GString *xpath = pcmk__element_xpath(xml); + const xml_node_private_t *nodepriv = attr->_private; + xmlNode *change_attr = pcmk__xe_create(change_list, PCMK_XE_CHANGE_ATTR); - if (xpath != NULL) { - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + pcmk__xe_set(change_attr, PCMK_XA_NAME, (const char *) attr->name); - pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY); - pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str); + if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + pcmk__xe_set(change_attr, PCMK_XA_OPERATION, "unset"); - change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); - g_string_free(xpath, TRUE); - } - } + } else { + // pcmk__xf_dirty is set + pcmk__xe_set(change_attr, PCMK_XA_OPERATION, "set"); + pcmk__xe_set(change_attr, PCMK_XA_VALUE, pcmk__xml_attr_value(attr)); + } +} - attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR); +/*! + * \internal + * \brief Copy an attribute to a target element if the deleted flag is not set + * + * \param[in] attr XML attribute + * \param[in,out] user_data Target element + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +copy_attr_if_not_deleted(const xmlAttr *attr, void *user_data) +{ + xmlNode *target = user_data; + const xml_node_private_t *nodepriv = attr->_private; - pcmk__xe_set(attr, PCMK_XA_NAME, (const char *) pIter->name); - if (nodepriv->flags & pcmk__xf_deleted) { - pcmk__xe_set(attr, PCMK_XA_OPERATION, "unset"); + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + pcmk__xe_set(target, (const char *) attr->name, + pcmk__xml_attr_value(attr)); + } - } else { - pcmk__xe_set(attr, PCMK_XA_OPERATION, "set"); + return true; +} - value = pcmk__xml_attr_value(pIter); - pcmk__xe_set(attr, PCMK_XA_VALUE, value); - } +/*! + * \internal + * \brief Add a \c PCMK_VALUE_MODIFY change to a patchset if appropriate + * + * If any attributes of \p xml were deleted or modified, create a + * \c PCMK_XE_CHANGE child of \p patchset, with \c PCMK_XA_OPERATION set to + * \c PCMK_VALUE_MODIFY and with the following children: + * - \c PCMK_XE_CHANGE_LIST, with a \c PCMK_XE_CHANGE_ATTR child for each + * deleted or modified attribute + * - \c PCMK_XE_CHANGE_RESULT, with a child of the same type as \p xml whose + * attributes are set to the post-change values. Deleted attributes are not + * added. + * + * \param[in] xml XML whose changes to add to \p patchset + * \param[in,out] patchset XML patchset + */ +static void +add_modify_change(const xmlNode *xml, xmlNode *patchset) +{ + GSList *changed_attrs = NULL; + GString *xpath = NULL; + xmlNode *change = NULL; + xmlNode *change_list = NULL; + xmlNode *result = NULL; + + // Check each of the XML node's attributes for changes + pcmk__xe_foreach_const_attr(xml, append_attr_if_changed, &changed_attrs); + + if (changed_attrs == NULL) { + return; } - if (change) { - xmlNode *result = NULL; + xpath = pcmk__element_xpath(xml); - change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT); - result = pcmk__xe_create(change, (const char *)xml->name); + change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY); + pcmk__xe_set(change, PCMK_XA_PATH, xpath->str); - for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; - pIter = pIter->next) { - nodepriv = pIter->_private; - if (!pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - value = pcmk__xe_get(xml, (const char *) pIter->name); - pcmk__xe_set(result, (const char *)pIter->name, value); - } - } + change_list = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); + g_slist_foreach(changed_attrs, add_change_attr, change_list); + + result = pcmk__xe_create(change, PCMK_XE_CHANGE_RESULT); + result = pcmk__xe_create(result, (const char *) xml->name); + pcmk__xe_foreach_const_attr(xml, copy_attr_if_not_deleted, result); + + g_string_free(xpath, TRUE); + g_slist_free(changed_attrs); +} + +/*! + * \internal + * \brief Add a \c PCMK_VALUE_MOVE change to a patchset + * + * Given \p xml with the \c pcmk__xf_move flag set, create a \c PCMK_XE_CHANGE + * child of \p patchset, with \c PCMK_XA_OPERATION set to \c PCMK_VALUE_MOVE. + * + * \param[in] xml XML whose move to add to \p patchset + * \param[in,out] patchset XML patchset + */ +static void +add_move_change(const xmlNode *xml, xmlNode *patchset) +{ + xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + GString *xpath = pcmk__element_xpath(xml); + + pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE); + pcmk__xe_set(change, PCMK_XA_PATH, xpath->str); + pcmk__xe_set_int(change, PCMK_XE_POSITION, + pcmk__xml_position(xml, pcmk__xf_deleted)); + + pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), + pcmk__xml_position(xml, pcmk__xf_skip)); + + g_string_free(xpath, TRUE); +} + +/* Add changes for specified XML to patchset. + * For patchset format, refer to diff schema. + */ +static void +add_changes_to_patchset(xmlNode *xml, xmlNode *patchset) +{ + xml_node_private_t *nodepriv = xml->_private; + + if (nodepriv == NULL) { + /* Elements that shouldn't occur in a CIB don't have _private set. They + * should be stripped out, ignored, or have an error thrown by any code + * that processes their parent, so we ignore any changes to them. + */ + return; + } + + // If this XML node is new, just report that + if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + add_create_change(xml, patchset); + return; + } + + if (pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) { + add_modify_change(xml, patchset); } // Now recursively do the same for each child node of this node - for (cIter = pcmk__xml_first_child(xml); cIter != NULL; - cIter = pcmk__xml_next(cIter)) { - add_xml_changes_to_patchset(cIter, patchset); + for (xmlNode *child = pcmk__xml_first_child(xml); child != NULL; + child = pcmk__xml_next(child)) { + + add_changes_to_patchset(child, patchset); } - nodepriv = xml->_private; - if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { - GString *xpath = pcmk__element_xpath(xml); + if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { + add_move_change(xml, patchset); + } +} - pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), - pcmk__xml_position(xml, pcmk__xf_skip)); +/*! + * \internal + * \brief Check whether a deleted object path contains the configuration element + * + * \param[in] data Deleted object (const pcmk__deleted_xml_t *) + * \param[in] ignored Ignored + * + * \retval 0 if \p data->path contains + * "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION + * \retval 1 otherwise + * + * \note This is a \c GCompareFunc. + */ +static gint +config_in_deleted_obj_path(gconstpointer data, gconstpointer ignored) +{ + const pcmk__deleted_xml_t *deleted_obj = data; - if (xpath != NULL) { - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + if (strstr(deleted_obj->path, + "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) { - pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE); - pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str); - pcmk__xe_set_int(change, PCMK_XE_POSITION, - pcmk__xml_position(xml, pcmk__xf_deleted)); - g_string_free(xpath, TRUE); - } + return 0; } + + return 1; } +/*! + * \internal + * \brief Check whether a CIB XML tree contains a configuration change + * + * \param[in] xml XML tree + * + * \return \c true if \p xml contains a dirty or deleted configuration element, + * or \c false otherwise + */ static bool -is_config_change(xmlNode *xml) +is_config_change(const xmlNode *xml) { - GList *gIter = NULL; xml_node_private_t *nodepriv = NULL; - xml_doc_private_t *docpriv; + xml_doc_private_t *docpriv = xml->doc->_private; xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL, NULL); - if (config) { + if (config != NULL) { nodepriv = config->_private; } + + // Arbitrary xml may come from the public API, so NULL-check nodepriv if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) { - return TRUE; + return true; } - if ((xml->doc != NULL) && (xml->doc->_private != NULL)) { - docpriv = xml->doc->_private; - for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { - pcmk__deleted_xml_t *deleted_obj = gIter->data; - - if (strstr(deleted_obj->path, - "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) { - return TRUE; - } - } - } - return FALSE; + return (g_list_find_custom(docpriv->deleted_objs, NULL, + config_in_deleted_obj_path) != NULL); } // Guaranteed to return non-NULL static xmlNode * xml_create_patchset_v2(const xmlNode *source, xmlNode *target) { - int lpc = 0; - GList *gIter = NULL; - xml_doc_private_t *docpriv; - - xmlNode *v = NULL; - xmlNode *version = NULL; + xml_doc_private_t *docpriv = NULL; xmlNode *patchset = NULL; + xmlNode *version = NULL; + + if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) { + return NULL; + } - pcmk__assert(target != NULL); pcmk__assert(target->doc != NULL); docpriv = target->doc->_private; @@ -200,39 +388,16 @@ xml_create_patchset_v2(const xmlNode *source, xmlNode *target) pcmk__xe_set_int(patchset, PCMK_XA_FORMAT, 2); version = pcmk__xe_create(patchset, PCMK_XE_VERSION); + set_version_fields(version, source, PCMK_XE_SOURCE); + set_version_fields(version, target, PCMK_XE_TARGET); - v = pcmk__xe_create(version, PCMK_XE_SOURCE); - for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { - const char *value = pcmk__xe_get(source, vfields[lpc]); - - if (value == NULL) { - value = "1"; - } - pcmk__xe_set(v, vfields[lpc], value); - } - - v = pcmk__xe_create(version, PCMK_XE_TARGET); - for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { - const char *value = pcmk__xe_get(target, vfields[lpc]); - - if (value == NULL) { - value = "1"; - } - pcmk__xe_set(v, vfields[lpc], value); - } - - for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { - pcmk__deleted_xml_t *deleted_obj = gIter->data; - xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); - - pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE); - pcmk__xe_set(change, PCMK_XA_PATH, deleted_obj->path); - if (deleted_obj->position >= 0) { - pcmk__xe_set_int(change, PCMK_XE_POSITION, deleted_obj->position); - } - } + /* Call this outside of add_changes_to_patchset(). That function is + * recursive and all calls will use the same XML document. We don't want to + * add duplicate delete changes to the patchset. + */ + g_list_foreach(docpriv->deleted_objs, add_delete_change, patchset); - add_xml_changes_to_patchset(target, patchset); + add_changes_to_patchset(target, patchset); return patchset; } @@ -730,13 +895,8 @@ apply_v2_patchset(xmlNode *xml, const xmlNode *patchset) */ pcmk__xe_remove_matching_attrs(match, false, NULL, NULL); - for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL; - pIter = pIter->next) { - const char *name = (const char *) pIter->name; - const char *value = pcmk__xml_attr_value(pIter); - - pcmk__xe_set(match, name, value); - } + // Copy the ones from attrs + pcmk__xe_copy_attrs(match, attrs, pcmk__xaf_none); } else { pcmk__err("Unknown operation: %s", op); diff --git a/lib/common/scheduler.c b/lib/common/scheduler.c index 44d14df455e..e9ecfd78d1b 100644 --- a/lib/common/scheduler.c +++ b/lib/common/scheduler.c @@ -369,7 +369,8 @@ pcmk__foreach_param_check(pcmk_scheduler_t *scheduler, const xmlNode*, enum pcmk__check_parameters)) { - CRM_CHECK((scheduler != NULL) && (cb != NULL), return); + pcmk__assert(cb != NULL); + CRM_CHECK(scheduler != NULL, return); for (GList *item = scheduler->priv->param_check; item != NULL; item = item->next) { diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index e8887ff7851..f54e9748976 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -13,8 +13,6 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__is_user_in_group_test -check_PROGRAMS += pcmk_acl_required_test -check_PROGRAMS += xml_acl_denied_test +check_PROGRAMS = pcmk__acl_required_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/acl/pcmk_acl_required_test.c b/lib/common/tests/acl/pcmk__acl_required_test.c similarity index 56% rename from lib/common/tests/acl/pcmk_acl_required_test.c rename to lib/common/tests/acl/pcmk__acl_required_test.c index bd5b922d64f..783e2b2959a 100644 --- a/lib/common/tests/acl/pcmk_acl_required_test.c +++ b/lib/common/tests/acl/pcmk__acl_required_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the Pacemaker project contributors + * Copyright 2020-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,16 +10,15 @@ #include #include -#include static void is_pcmk_acl_required(void **state) { - assert_false(pcmk_acl_required(NULL)); - assert_false(pcmk_acl_required("")); - assert_true(pcmk_acl_required("123")); - assert_false(pcmk_acl_required(CRM_DAEMON_USER)); - assert_false(pcmk_acl_required("root")); + assert_false(pcmk__acl_required(NULL)); + assert_false(pcmk__acl_required("")); + assert_true(pcmk__acl_required("123")); + assert_false(pcmk__acl_required(CRM_DAEMON_USER)); + assert_false(pcmk__acl_required("root")); } PCMK__UNIT_TEST(NULL, NULL, diff --git a/lib/common/tests/acl/xml_acl_denied_test.c b/lib/common/tests/acl/xml_acl_denied_test.c deleted file mode 100644 index 04054ac9ee6..00000000000 --- a/lib/common/tests/acl/xml_acl_denied_test.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020-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 - -#include -#include - -#include "../../crmcommon_private.h" - -static void -is_xml_acl_denied_without_node(void **state) -{ - xmlNode *test_xml = pcmk__xe_create(NULL, "test_xml"); - assert_false(xml_acl_denied(test_xml)); - - test_xml->doc->_private = NULL; - assert_false(xml_acl_denied(test_xml)); - - test_xml->doc = NULL; - assert_false(xml_acl_denied(test_xml)); - - test_xml = NULL; - assert_false(xml_acl_denied(test_xml)); -} - -static void -is_xml_acl_denied_with_node(void **state) -{ - xml_doc_private_t *docpriv; - - xmlNode *test_xml = pcmk__xe_create(NULL, "test_xml"); - - // allocate memory for _private, which is NULL by default - test_xml->doc->_private = pcmk__assert_alloc(1, sizeof(xml_doc_private_t)); - - assert_false(xml_acl_denied(test_xml)); - - // cast _private from void* to xml_doc_private_t* - docpriv = test_xml->doc->_private; - - // enable an irrelevant flag - docpriv->flags |= pcmk__xf_acl_enabled; - - assert_false(xml_acl_denied(test_xml)); - - // enable pcmk__xf_acl_denied - docpriv->flags |= pcmk__xf_acl_denied; - - assert_true(xml_acl_denied(test_xml)); -} - -PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, - cmocka_unit_test(is_xml_acl_denied_without_node), - cmocka_unit_test(is_xml_acl_denied_with_node)) diff --git a/lib/common/tests/utils/Makefile.am b/lib/common/tests/utils/Makefile.am index fc99b307958..266593bfd9a 100644 --- a/lib/common/tests/utils/Makefile.am +++ b/lib/common/tests/utils/Makefile.am @@ -17,6 +17,7 @@ check_PROGRAMS += pcmk__daemon_user_test check_PROGRAMS += pcmk__fail_attr_name_test check_PROGRAMS += pcmk__failcount_name_test check_PROGRAMS += pcmk__getpid_s_test +check_PROGRAMS += pcmk__is_user_in_group_test check_PROGRAMS += pcmk__lastfailure_name_test check_PROGRAMS += pcmk__lookup_user_test check_PROGRAMS += pcmk__realloc_test diff --git a/lib/common/tests/acl/pcmk__is_user_in_group_test.c b/lib/common/tests/utils/pcmk__is_user_in_group_test.c similarity index 100% rename from lib/common/tests/acl/pcmk__is_user_in_group_test.c rename to lib/common/tests/utils/pcmk__is_user_in_group_test.c diff --git a/lib/common/tests/xml/Makefile.am b/lib/common/tests/xml/Makefile.am index 4a26e9f767b..9ab573b84b4 100644 --- a/lib/common/tests/xml/Makefile.am +++ b/lib/common/tests/xml/Makefile.am @@ -15,7 +15,6 @@ include $(top_srcdir)/mk/unittest.mk check_PROGRAMS = pcmk__xml_escape_test check_PROGRAMS += pcmk__xml_is_name_char_test check_PROGRAMS += pcmk__xml_is_name_start_char_test -check_PROGRAMS += pcmk__xml_needs_escape_test check_PROGRAMS += pcmk__xml_new_doc_test check_PROGRAMS += pcmk__xml_sanitize_id_test diff --git a/lib/common/tests/xml/pcmk__xml_needs_escape_test.c b/lib/common/tests/xml/pcmk__xml_needs_escape_test.c deleted file mode 100644 index 7366f9ba4e3..00000000000 --- a/lib/common/tests/xml/pcmk__xml_needs_escape_test.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * 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 - -#include - -#include "crmcommon_private.h" - -static void -null_empty(void **state) -{ - assert_false(pcmk__xml_needs_escape(NULL, pcmk__xml_escape_text)); - assert_false(pcmk__xml_needs_escape(NULL, pcmk__xml_escape_attr)); - assert_false(pcmk__xml_needs_escape(NULL, pcmk__xml_escape_attr_pretty)); - - assert_false(pcmk__xml_needs_escape("", pcmk__xml_escape_text)); - assert_false(pcmk__xml_needs_escape("", pcmk__xml_escape_attr)); - assert_false(pcmk__xml_needs_escape("", pcmk__xml_escape_attr_pretty)); -} - -static void -invalid_type(void **state) -{ - const enum pcmk__xml_escape_type type = (enum pcmk__xml_escape_type) -1; - - // Easier to ignore invalid type for NULL or empty string - assert_false(pcmk__xml_needs_escape(NULL, type)); - assert_false(pcmk__xml_needs_escape("", type)); - - // Otherwise, assert if we somehow passed an invalid type - pcmk__assert_asserts(pcmk__xml_needs_escape("he<>llo", type)); -} - -static void -escape_unchanged(void **state) -{ - // No escaped characters (note: this string includes single quote at end) - const char *unchanged = "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789" - "`~!@#$%^*()-_=+/|\\[]{}?.,'"; - - assert_false(pcmk__xml_needs_escape(unchanged, pcmk__xml_escape_text)); - assert_false(pcmk__xml_needs_escape(unchanged, pcmk__xml_escape_attr)); - assert_false(pcmk__xml_needs_escape(unchanged, - pcmk__xml_escape_attr_pretty)); -} - -// Ensure special characters get escaped at start, middle, and end - -static void -escape_left_angle(void **state) -{ - const char *l_angle_left = "GHashTable *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +add_attr_to_flags_table(const xmlAttr *attr, void *user_data) +{ + GHashTable *attr_flags = user_data; + + const xml_node_private_t *nodepriv = attr->_private; + uint32_t flags = ((nodepriv != NULL)? nodepriv->flags : pcmk__xf_none); + + g_hash_table_insert(attr_flags, pcmk__str_copy((const char *) attr->name), + GUINT_TO_POINTER((guint) flags)); + return true; +} + /*! * \internal * \brief Sort an XML element's attributes and compare against a reference @@ -33,20 +57,11 @@ static void assert_order(xmlNode *test_xml, const xmlNode *reference_xml) { GHashTable *attr_flags = pcmk__strkey_table(free, NULL); - xmlAttr *test_attr = NULL; - xmlAttr *ref_attr = NULL; + const xmlAttr *test_attr = NULL; + const xmlAttr *ref_attr = NULL; // Save original flags - for (xmlAttr *attr = pcmk__xe_first_attr(test_xml); attr != NULL; - attr = attr->next) { - - xml_node_private_t *nodepriv = attr->_private; - uint32_t flags = (nodepriv != NULL)? nodepriv->flags : pcmk__xf_none; - - g_hash_table_insert(attr_flags, - pcmk__str_copy((const char *) attr->name), - GUINT_TO_POINTER((guint) flags)); - } + pcmk__xe_foreach_const_attr(test_xml, add_attr_to_flags_table, attr_flags); pcmk__xe_sort_attrs(test_xml); @@ -141,11 +156,9 @@ already_sorted(void **state) } } - pcmk__xe_set_props(reference_xml, - "admin", "john", - "dummy", "value", - "location", "usa", - NULL); + pcmk__xe_set(reference_xml, "admin", "john"); + pcmk__xe_set(reference_xml, "dummy", "value"); + pcmk__xe_set(reference_xml, "location", "usa"); assert_order(test_xml, reference_xml); @@ -184,11 +197,9 @@ need_sort(void **state) } } - pcmk__xe_set_props(reference_xml, - "admin", "john", - "dummy", "value", - "location", "usa", - NULL); + pcmk__xe_set(reference_xml, "admin", "john"); + pcmk__xe_set(reference_xml, "dummy", "value"); + pcmk__xe_set(reference_xml, "location", "usa"); assert_order(test_xml, reference_xml); diff --git a/lib/common/xml.c b/lib/common/xml.c index 6769c938cbf..1bb64b2b1d0 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -72,21 +72,61 @@ pcmk__xml_element_type_text(xmlElementType type) /*! * \internal - * \brief Apply a function to each XML node in a tree (pre-order, depth-first) + * \brief Call a function for each of an XML node's non-text children + * + * \param[in,out] xml XML element + * \param[in] fn Function to call for each attribute (returns + * \c true to continue iterating over children or + * \c false to stop) + * \param[in,out] user_data User data argument for \p fn + * + * \return \c false if any \p fn call returned \c false, or \c true otherwise + * + * \note \p fn may remove its XML node argument. + */ +bool +pcmk__xml_foreach_child(xmlNode *xml, bool (*fn)(xmlNode *, void *), + void *user_data) +{ + xmlNode *child = pcmk__xml_first_child(xml); + + pcmk__assert(fn != NULL); + + while (child != NULL) { + xmlNode *next = pcmk__xml_next(child); + + if (!fn(child, user_data)) { + return false; + } + + child = next; + } + + return true; +} + +/*! + * \internal + * \brief Call a function for 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 + * \param[in] 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. + * \note \c fn may not free or unlink its XML argument or any of that node's + * ancestors. \c fn may unlink the descendants of that node, and it may + * free them as long as it also unlinks them. */ bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void *user_data) { + pcmk__assert(fn != NULL); + if (xml == NULL) { return true; } @@ -105,6 +145,52 @@ pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), return true; } +/*! + * \internal + * \brief Remove XML nodes for which a given function returns \c true + * + * Call a function for each XML node in a tree (pre-order, depth-first). If the + * function returns true, remove the node. This means to free the entire + * document if the node is the document root, or to unlink and free the node and + * its subtree otherwise. ACLs and change tracking are ignored. + * + * \param[in,out] xml XML tree to traverse + * \param[in] fn Function to call for each node (returns \c true to remove + * its argument or \c false to recurse down its argument's + * subtree) + * + * \note This function is recursive. + */ +void +pcmk__xml_tree_foreach_remove(xmlNode *xml, bool (*fn)(xmlNode *)) +{ + pcmk__assert(fn != NULL); + + if (xml == NULL) { + return; + } + + if (fn(xml)) { + if (xml == xmlDocGetRootElement(xml->doc)) { + pcmk__xml_free_doc(xml->doc); + + } else { + pcmk__xml_free_node(xml); + } + + return; + } + + xml = pcmk__xml_first_child(xml); + + while (xml != NULL) { + xmlNode *next = pcmk__xml_next(xml); + + pcmk__xml_tree_foreach_remove(xml, fn); + xml = next; + } +} + void pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags) { @@ -127,11 +213,32 @@ pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags) void pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags) { - if (doc != NULL) { - xml_doc_private_t *docpriv = doc->_private; + xml_doc_private_t *docpriv = NULL; - pcmk__set_xml_flags(docpriv, flags); + if (doc == NULL) { + return; } + docpriv = doc->_private; + pcmk__set_xml_flags(docpriv, flags); +} + +/*! + * \internal + * \brief Clear flags for an XML document + * + * \param[in,out] doc XML document + * \param[in] flags Group of enum pcmk__xml_flags + */ +void +pcmk__xml_doc_clear_flags(xmlDoc *doc, uint32_t flags) +{ + xml_doc_private_t *docpriv = NULL; + + if (doc == NULL) { + return; + } + docpriv = doc->_private; + pcmk__clear_xml_flags(docpriv, flags); } /*! @@ -187,77 +294,85 @@ pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data) return true; } +// 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); + } +} + /*! * \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) + * \brief Allocate and initialize private data for an XML document * - * \note This is compatible with \c pcmk__xml_tree_foreach(). + * \param[in,out] doc XML document */ -static bool -mark_xml_dirty_created(xmlNode *xml, void *user_data) +static void +new_doc_private_data(xmlDoc *doc) { - xml_node_private_t *nodepriv = xml->_private; + xml_doc_private_t *priv = pcmk__assert_alloc(1, sizeof(xml_doc_private_t)); - if (nodepriv != NULL) { - pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created); - } - return true; + priv->check = PCMK__XML_DOC_PRIVATE_MAGIC; + doc->_private = priv; } /*! * \internal - * \brief Mark an XML tree as dirty and created, and mark its parents dirty - * - * Also mark the document dirty. + * \brief Allocate and initialize private data for a non-document XML node * - * \param[in,out] xml Tree to mark as dirty and created + * \param[in,out] xml XML node */ static void -mark_xml_tree_dirty_created(xmlNode *xml) +new_node_private_data(xmlNode *xml) { - pcmk__assert(xml != NULL); + const bool tracking = pcmk__xml_doc_all_flags_set(xml->doc, + pcmk__xf_tracking); + xml_node_private_t *priv = pcmk__assert_alloc(1, + sizeof(xml_node_private_t)); - if (!pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking)) { - // Tracking is disabled for entire document - return; - } + priv->check = PCMK__XML_NODE_PRIVATE_MAGIC; + xml->_private = priv; - // Mark all parents and document dirty - pcmk__mark_xml_node_dirty(xml); - - pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL); + if (tracking) { + pcmk__set_xml_flags(priv, pcmk__xf_created); + pcmk__mark_xml_node_dirty(xml); + } } -// Free an XML object previously marked as deleted -static void -free_deleted_object(void *data) +/*! + * \internal + * \brief Allocate and initialize private data for an XML attribute + * + * \param[in,out] attr XML attribute + * \param[in] user_data Ignored + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +new_attr_private_data(xmlAttr *attr, void *user_data) { - if(data) { - pcmk__deleted_xml_t *deleted_obj = data; - - g_free(deleted_obj->path); - free(deleted_obj); - } + new_node_private_data((xmlNode *) attr); + return true; } -// Free and NULL user, ACLs, and deleted objects in an XML node's private data +/*! + * \internal + * \brief Allocate and initialize private data for an XML element + * + * \param[in,out] xml XML element + */ static void -reset_xml_private_data(xml_doc_private_t *docpriv) +new_element_private_data(xmlNode *xml) { - if (docpriv != NULL) { - pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC); - - g_clear_pointer(&docpriv->acl_user, free); - g_clear_pointer(&docpriv->acls, pcmk__free_acls); - - g_list_free_full(docpriv->deleted_objs, free_deleted_object); - docpriv->deleted_objs = NULL; - } + new_node_private_data(xml); + pcmk__xe_foreach_attr(xml, new_attr_private_data, NULL); } /*! @@ -274,47 +389,25 @@ reset_xml_private_data(xml_doc_private_t *docpriv) static bool new_private_data(xmlNode *node, void *user_data) { - bool tracking = false; - CRM_CHECK(node != NULL, return true); if (node->_private != NULL) { return true; } - tracking = pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking); - switch (node->type) { case XML_DOCUMENT_NODE: - { - xml_doc_private_t *docpriv = - pcmk__assert_alloc(1, sizeof(xml_doc_private_t)); - - docpriv->check = PCMK__XML_DOC_PRIVATE_MAGIC; - node->_private = docpriv; - } - break; + new_doc_private_data((xmlDoc *) node); + return true; - 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 = PCMK__XML_NODE_PRIVATE_MAGIC; - node->_private = nodepriv; - if (tracking) { - pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created); - } - - for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL; - iter = iter->next) { + new_node_private_data(node); + return true; - new_private_data((xmlNode *) iter, user_data); - } - } - break; + case XML_ELEMENT_NODE: + new_element_private_data(node); + return true; case XML_TEXT_NODE: case XML_DTD_NODE: @@ -325,13 +418,95 @@ new_private_data(xmlNode *node, void *user_data) CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE); return true; } +} - if (tracking) { - pcmk__mark_xml_node_dirty(node); +/*! + * \internal + * \brief Free and zero all data fields of an XML document's private data + * + * This function does not clear the \c check field or free the private data + * object itself. + * + * \param[in,out] docpriv XML document private data + */ +void +pcmk__xml_reset_doc_private_data(xml_doc_private_t *docpriv) +{ + if (docpriv == NULL) { + return; } + + pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC); + + docpriv->flags = pcmk__xf_none; + + g_clear_pointer(&docpriv->acl_user, free); + g_clear_pointer(&docpriv->acls, pcmk__free_acls); + + g_list_free_full(docpriv->deleted_objs, free_deleted_object); + docpriv->deleted_objs = NULL; +} + +/*! + * \internal + * \brief Free and clear private data for an XML document + * + * \param[in,out] doc XML document + */ +static void +free_doc_private_data(xmlDoc *doc) +{ + pcmk__xml_reset_doc_private_data(doc->_private); + g_clear_pointer(&doc->_private, free); +} + +/*! + * \internal + * \brief Free and clear private data for a non-document XML node + * + * \param[in,out] xml XML node + */ +static void +free_node_private_data(xmlNode *xml) +{ + xml_node_private_t *nodepriv = xml->_private; + + pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC); + + g_clear_pointer(&xml->_private, free); +} + +/*! + * \internal + * \brief Free and clear private data for an XML attribute + * + * \param[in,out] attr XML attribute + * \param[in] user_data Ignored + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +free_attr_private_data(xmlAttr *xml, void *user_data) +{ + free_node_private_data((xmlNode *) xml); return true; } +/*! + * \internal + * \brief Free and clear private data for an XML element and its attributes + * + * \param[in,out] xml XML element + */ +static void +free_element_private_data(xmlNode *xml) +{ + free_node_private_data(xml); + pcmk__xe_foreach_attr(xml, free_attr_private_data, NULL); +} + /*! * \internal * \brief Free private data for an XML node @@ -352,23 +527,19 @@ free_private_data(xmlNode *node, void *user_data) return true; } - if (node->type == XML_DOCUMENT_NODE) { - reset_xml_private_data((xml_doc_private_t *) node->_private); - - } else { - xml_node_private_t *nodepriv = node->_private; - - pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC); + switch (node->type) { + case XML_DOCUMENT_NODE: + free_doc_private_data((xmlDoc *) node); + return true; - for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL; - iter = iter->next) { + case XML_ELEMENT_NODE: + free_element_private_data(node); + return true; - free_private_data((xmlNode *) iter, user_data); - } + default: + free_node_private_data(node); + return true; } - - g_clear_pointer(&node->_private, free); - return true; } /*! @@ -420,66 +591,6 @@ pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set) 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 -commit_attr_deletions(xmlNode *xml, void *user_data) -{ - pcmk__xml_reset_node_flags(xml, NULL); - pcmk__xe_remove_matching_attrs(xml, true, pcmk__marked_as_deleted, NULL); - return true; -} - -/*! - * \internal - * \brief Finalize all pending changes to an XML document and reset private data - * - * Clear the ACL user and all flags, unpacked ACLs, and deleted node records for - * the document; clear all flags on each node in the tree; and delete any - * attributes that are marked for deletion. - * - * \param[in,out] doc XML document - * - * \note When change tracking is enabled, "deleting" an attribute simply marks - * it for deletion (using \c pcmk__xf_deleted) until changes are - * committed. Freeing a node (using \c pcmk__xml_free()) adds a deleted - * node record (\c pcmk__deleted_xml_t) to the node's document before - * freeing it. - * \note This function clears all flags, not just flags that indicate changes. - * In particular, note that it clears the \c pcmk__xf_tracking flag, thus - * disabling tracking. - */ -void -pcmk__xml_commit_changes(xmlDoc *doc) -{ - xml_doc_private_t *docpriv = NULL; - - if (doc == NULL) { - return; - } - - docpriv = doc->_private; - if (docpriv == NULL) { - return; - } - - if (pcmk__is_set(docpriv->flags, pcmk__xf_dirty)) { - pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions, - NULL); - } - reset_xml_private_data(docpriv); - docpriv->flags = pcmk__xf_none; -} - /*! * \internal * \brief Create a new XML document @@ -727,8 +838,8 @@ pcmk__xml_free_node(xmlNode *xml) * * \return Standard Pacemaker return code */ -static int -free_xml_with_position(xmlNode *node, int position) +int +pcmk__xml_free_position(xmlNode *node, int position) { xmlDoc *doc = NULL; xml_node_private_t *nodepriv = NULL; @@ -815,7 +926,7 @@ free_xml_with_position(xmlNode *node, int position) void pcmk__xml_free(xmlNode *xml) { - free_xml_with_position(xml, -1); + pcmk__xml_free_position(xml, -1); } /*! @@ -940,744 +1051,193 @@ pcmk__strip_xml_text(xmlNode *xml) /*! * \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 - pcmk__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 + * \brief Append an XML-escaped character to a buffer (text 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. + * This appends an escaped character in \c pcmk__xml_escape_text mode. * - * \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 - pcmk__assert(false); - break; - } - - text = g_utf8_next_char(text); - } - return g_string_free(copy, FALSE); -} - -/*! - * \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; - - /* Restore the old value (without setting dirty flag recursively upwards or - * checking ACLs) - */ - pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking); - pcmk__xe_set(new_xml, attr_name, old_value); - pcmk__set_xml_flags(docpriv, pcmk__xf_tracking); - - // Reset flags (so the attribute doesn't appear as newly created) - attr = xmlHasProp(new_xml, (const xmlChar *) attr_name); - nodepriv = attr->_private; - nodepriv->flags = 0; - - // Check ACLs and mark restored value for later removal - pcmk__xa_remove(attr, false); - - pcmk__trace("XML attribute %s=%s was removed from %s", attr_name, old_value, - element); -} - -/* - * \internal - * \brief Check ACLs for a changed XML attribute + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer */ static void -mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name, - const char *old_value) +append_xml_escaped_char_text(char current_char, GString *buffer) { - xml_doc_private_t *docpriv = new_xml->doc->_private; - char *vcopy = pcmk__xe_get_copy(new_xml, attr_name); - - pcmk__trace("XML attribute %s was changed from '%s' to '%s' in %s", - attr_name, old_value, vcopy, element); + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; - // Restore the original value (without checking ACLs) - pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking); - pcmk__xe_set(new_xml, attr_name, old_value); - pcmk__set_xml_flags(docpriv, pcmk__xf_tracking); - - // Change it back to the new value, to check ACLs - pcmk__xe_set(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; + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; - pcmk__trace("XML attribute %s moved from position %d to %d in %s", - old_attr->name, p_old, p_new, element); + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; - // Mark document, element, and all element's parents as changed - pcmk__mark_xml_node_dirty(new_xml); + case '\n': + case '\t': + g_string_append_c(buffer, current_char); + return; - // 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 = pcmk__xe_get(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__xml_doc_all_flags_set(new_xml->doc, - pcmk__xf_ignore_attr_pos - |pcmk__xf_tracking)) { - /* pcmk__xf_tracking is always set by pcmk__xml_mark_changes() - * before this function is called, so only the - * pcmk__xf_ignore_attr_pos check is truly relevant. - */ - mark_attr_moved(new_xml, (const char *) old_xml->name, - old_attr, new_attr, old_pos, new_pos); + default: + if (g_ascii_iscntrl(current_char)) { + g_string_append_printf(buffer, "&#x%.2X;", current_char); + } else { + g_string_append_c(buffer, current_char); } - } + return; } } /*! * \internal - * \brief Check all attributes in new XML for creation + * \brief Append an XML-escaped character to a buffer (attribute escaping) * - * 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. + * This appends an escaped character in \c pcmk__xml_escape_attr mode. * - * \param[in,out] new_xml XML to check + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer */ static void -mark_created_attrs(xmlNode *new_xml) +append_xml_escaped_char_attr(char current_char, GString *buffer) { - xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml); + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; - while (attr_iter != NULL) { - xmlAttr *new_attr = attr_iter; - xml_node_private_t *nodepriv = attr_iter->_private; + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; - attr_iter = attr_iter->next; - if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - const char *attr_name = (const char *) new_attr->name; + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; - pcmk__trace("Created new attribute %s=%s in %s", attr_name, - pcmk__xml_attr_value(new_attr), new_xml->name); + case '"': + g_string_append(buffer, PCMK__XML_ENTITY_QUOT); + return; - /* 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); + default: + if (g_ascii_iscntrl(current_char)) { + g_string_append_printf(buffer, "&#x%.2X;", current_char); } else { - // Creation was not allowed, so remove the attribute - pcmk__xa_remove(new_attr, true); + g_string_append_c(buffer, current_char); } - } + return; } } /*! * \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) -{ - // Cleared later if attributes are not really new - for (xmlAttr *attr = pcmk__xe_first_attr(new_xml); attr != NULL; - attr = attr->next) { - xml_node_private_t *nodepriv = attr->_private; - - pcmk__set_xml_flags(nodepriv, pcmk__xf_created); - } - - xml_diff_old_attrs(old_xml, new_xml); - mark_created_attrs(new_xml); -} - -/*! - * \internal - * \brief Add a deleted object record for an old XML child if ACLs allow - * - * This is intended to be called for a child of an old XML element that is not - * present as a child of a new XML element. - * - * Add a temporary copy of the old child to the new XML. Then check whether ACLs - * would have allowed the deletion of that element. If so, add a deleted object - * record for it to the new XML's document, and set the \c pcmk__xf_skip flag on - * the old child. + * \brief Append an XML-escaped character to a buffer (pretty escaping) * - * The temporary copy is removed before returning. The new XML and all of its - * ancestors will have the \c pcmk__xf_dirty flag set because of the creation, - * however. + * This appends an escaped character in \c pcmk__xml_escape_attr_pretty mode. * - * \param[in,out] old_child Child of old XML - * \param[in,out] new_parent New XML that does not contain \p old_child - * - * \note The deletion is checked using the new XML's ACLs. The ACLs may have - * also changed between the old and new XML trees. Callers should take - * reasonable action if there were ACL changes that themselves would have - * been denied. + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer */ static void -mark_child_deleted(xmlNode *old_child, xmlNode *new_parent) +append_xml_escaped_char_pretty(char current_char, GString *buffer) { - int pos = pcmk__xml_position(old_child, pcmk__xf_skip); - - // 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, pcmk__xml_reset_node_flags, NULL); - - // free_xml_with_position() will check whether ACLs allow the deletion - pcmk__apply_acls(candidate->doc); + switch (current_char) { + case '"': + g_string_append(buffer, "\\\""); + return; - /* Try to remove the child again (which will track it in document's - * deleted_objs on success) - */ - if (free_xml_with_position(candidate, pos) != pcmk_rc_ok) { - // ACLs denied deletion in free_xml_with_position. Free candidate here. - pcmk__xml_free_node(candidate); - } - - pcmk__set_xml_flags((xml_node_private_t *) old_child->_private, - pcmk__xf_skip); -} + case '\n': + g_string_append(buffer, "\\n"); + return; -/*! - * \internal - * \brief Mark a new child as moved and set \c pcmk__xf_skip as appropriate - * - * \param[in,out] old_child Child of old XML - * \param[in,out] new_child Child of new XML that matches \p old_child - * \param[in] old_pos Position of \p old_child among its siblings - * \param[in] new_pos Position of \p new_child among its siblings - */ -static void -mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos, - int new_pos) -{ - const char *id_s = pcmk__s(pcmk__xe_id(new_child), ""); - xmlNode *new_parent = new_child->parent; - xml_node_private_t *nodepriv = new_child->_private; - - pcmk__trace("Child element %s with " PCMK_XA_ID "='%s' moved from position " - "%d to %d under %s", - new_child->name, id_s, old_pos, new_pos, new_parent->name); - pcmk__mark_xml_node_dirty(new_parent); - pcmk__set_xml_flags(nodepriv, pcmk__xf_moved); - - /* @TODO Figure out and document why we skip the old child in future - * position calculations if the old position is higher, and skip the new - * child in future position calculations if the new position is higher. This - * goes back to d028b52, and there's no explanation in the commit message. - */ - if (old_pos > new_pos) { - nodepriv = old_child->_private; - } - pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); -} + case '\r': + g_string_append(buffer, "\\r"); + return; -/*! - * \internal - * \brief Check whether a new XML child comment matches an old XML child comment - * - * Two comments match if they have the same position among their siblings and - * the same contents. - * - * If \p new_comment has the \c pcmk__xf_skip flag set, then it is automatically - * considered not to match. - * - * \param[in] old_comment Old XML child element - * \param[in] new_comment New XML child element - * - * \retval \c true if \p new_comment matches \p old_comment - * \retval \c false otherwise - */ -static bool -new_comment_matches(const xmlNode *old_comment, const xmlNode *new_comment) -{ - xml_node_private_t *nodepriv = new_comment->_private; + case '\t': + g_string_append(buffer, "\\t"); + return; - if (pcmk__is_set(nodepriv->flags, pcmk__xf_skip)) { - /* @TODO Should we also return false if old_comment has pcmk__xf_skip - * set? This preserves existing behavior at time of writing. - */ - return false; - } - if (pcmk__xml_position(old_comment, pcmk__xf_skip) - != pcmk__xml_position(new_comment, pcmk__xf_skip)) { - return false; - } - return pcmk__xc_matches(old_comment, new_comment); -} - -/*! - * \internal - * \brief Check whether a new XML child element matches an old XML child element - * - * Two elements match if they have the same name and the same ID. (Both IDs can - * be \c NULL.) - * - * For XML attributes other than \c PCMK_XA_ID, we can treat a value change as - * an in-place modification. However, when Pacemaker applies a patchset, it uses - * the \c PCMK_XA_ID attribute to find the node to update (modify, delete, or - * move). If we treat two nodes with different \c PCMK_XA_ID attributes as - * matching and then mark that attribute as changed, it can cause this lookup to - * fail. - * - * There's unlikely to ever be much practical reason to treat elements with - * different IDs as a change. Unless that changes, we'll treat them as a - * mismatch. - * - * \param[in] old_element Old XML child element - * \param[in] new_element New XML child element - * - * \retval \c true if \p new_element matches \p old_element - * \retval \c false otherwise - */ -static bool -new_element_matches(const xmlNode *old_element, const xmlNode *new_element) -{ - return pcmk__xe_is(new_element, (const char *) old_element->name) - && pcmk__str_eq(pcmk__xe_id(old_element), pcmk__xe_id(new_element), - pcmk__str_none); -} - -/*! - * \internal - * \brief Check whether a new XML child node matches an old XML child node - * - * Node types must be the same in order to match. - * - * For comments, a match is a comment at the same position with the same - * content. - * - * For elements, a match is an element with the same name and the same ID. (Both - * IDs can be \c NULL.) - * - * For other node types, there is no match. - * - * \param[in] old_child Child of old XML - * \param[in] new_child Child of new XML - * - * \retval \c true if \p new_child matches \p old_child - * \retval \c false otherwise - */ -static bool -new_child_matches(const xmlNode *old_child, const xmlNode *new_child) -{ - if (old_child->type != new_child->type) { - return false; - } - - switch (old_child->type) { - case XML_COMMENT_NODE: - return new_comment_matches(old_child, new_child); - case XML_ELEMENT_NODE: - return new_element_matches(old_child, new_child); default: - return false; + g_string_append_c(buffer, current_char); + return; } } /*! * \internal - * \brief Find matching XML node pairs between old and new XML's children - * - * A node that is part of a matching pair gets its _private:match - * member set to the matching node. + * \brief Append an XML-escaped character to a buffer * - * \param[in,out] old_xml Old XML - * \param[in,out] new_xml New XML + * \param[in] current_char Character to escape + * \param[in] type Type of escaping + * \param[in,out] buffer Buffer */ static void -find_matching_children(xmlNode *old_xml, xmlNode *new_xml) +append_xml_escaped_char(char current_char, enum pcmk__xml_escape_type type, + GString *buffer) { - for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL; - old_child = pcmk__xml_next(old_child)) { + switch (type) { + case pcmk__xml_escape_text: + append_xml_escaped_char_text(current_char, buffer); + return; - xml_node_private_t *old_nodepriv = old_child->_private; + case pcmk__xml_escape_attr: + append_xml_escaped_char_attr(current_char, buffer); + return; - if ((old_nodepriv == NULL) || (old_nodepriv->match != NULL)) { - // Can't process, or we already found a match for this old child - continue; - } - - for (xmlNode *new_child = pcmk__xml_first_child(new_xml); - new_child != NULL; new_child = pcmk__xml_next(new_child)) { - - xml_node_private_t *new_nodepriv = new_child->_private; + case pcmk__xml_escape_attr_pretty: + append_xml_escaped_char_pretty(current_char, buffer); + return; - if ((new_nodepriv == NULL) || (new_nodepriv->match != NULL)) { - /* Can't process, or this new child already matched some old - * child - */ - continue; - } - - if (new_child_matches(old_child, new_child)) { - old_nodepriv->match = new_child; - new_nodepriv->match = old_child; - break; - } - } + default: // Invalid enum value + pcmk__assert(false); + return; } } /*! * \internal - * \brief Mark changes between two XML trees + * \brief Replace special characters with their XML escape sequences * - * Set flags in a new XML tree to indicate changes relative to an old XML tree. + * \param[in] text Text to escape + * \param[in] type Type of escaping * - * \param[in,out] old_xml XML before changes - * \param[in,out] new_xml XML after changes + * \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 This may set \c pcmk__xf_skip on parts of \p old_xml. + * \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(). */ -void -pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml) +gchar * +pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type) { - /* This function may set the xml_node_private_t:match member on children of - * old_xml and new_xml, but it clears that member before returning. - * - * @TODO Ensure we handle (for example, by copying) or reject user-created - * XML that is missing xml_node_private_t at top level or in any children. - * Similarly, check handling of node types for which we don't create private - * data. For now, we'll skip them in the loops below. - */ - CRM_CHECK((old_xml != NULL) && (new_xml != NULL), return); - if ((old_xml->_private == NULL) || (new_xml->_private == NULL)) { - return; - } - - pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking); - xml_diff_attrs(old_xml, new_xml); - - find_matching_children(old_xml, new_xml); - - // Process matches (changed children) and deletions - for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL; - old_child = pcmk__xml_next(old_child)) { - - xml_node_private_t *nodepriv = old_child->_private; - xmlNode *new_child = NULL; - - if (nodepriv == NULL) { - continue; - } - - if (nodepriv->match == NULL) { - // No match in new XML means the old child was deleted - mark_child_deleted(old_child, new_xml); - continue; - } - - /* Fetch the match and clear old_child->_private's match member. - * new_child->_private's match member is handled in the new_xml loop. - */ - new_child = nodepriv->match; - nodepriv->match = NULL; - - pcmk__assert(old_child->type == new_child->type); - - if (old_child->type == XML_COMMENT_NODE) { - // Comments match only if their positions and contents match - continue; - } + GString *copy = NULL; - pcmk__xml_mark_changes(old_child, new_child); + if (text == NULL) { + return NULL; } + copy = g_string_sized_new(strlen(text)); - /* Mark unmatched new children as created, and mark matched new children as - * moved if their positions changed. Grab the next new child in advance, - * since new_child may get freed in the loop body. - */ - for (xmlNode *new_child = pcmk__xml_first_child(new_xml), - *next = pcmk__xml_next(new_child); - new_child != NULL; - new_child = next, next = pcmk__xml_next(new_child)) { - - xml_node_private_t *nodepriv = new_child->_private; - - if (nodepriv == NULL) { - continue; - } + while (*text != '\0') { + // Don't escape any non-ASCII characters + if ((*text & 0x80) != 0) { + size_t bytes = g_utf8_next_char(text) - text; - if (nodepriv->match != NULL) { - /* Fetch the match and clear new_child->_private's match member. Any - * changes were marked in the old_xml loop. Mark the move. - * - * We might be able to mark the move earlier, when we mark changes - * for matches in the old_xml loop, consolidating both actions. We'd - * have to think about whether the timing of setting the - * pcmk__xf_skip flag makes any difference. - */ - xmlNode *old_child = nodepriv->match; - int old_pos = pcmk__xml_position(old_child, pcmk__xf_skip); - int new_pos = pcmk__xml_position(new_child, pcmk__xf_skip); - - if (old_pos != new_pos) { - mark_child_moved(old_child, new_child, old_pos, new_pos); - } - nodepriv->match = NULL; + g_string_append_len(copy, text, bytes); + text += bytes; continue; } - // No match in old XML means the new child is newly created - pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); - mark_xml_tree_dirty_created(new_child); + append_xml_escaped_char(*text, type, copy); - // Check whether creation was allowed (may free new_child) - pcmk__apply_creation_acl(new_child, true); + text = g_utf8_next_char(text); } + return g_string_free(copy, FALSE); } char * @@ -1855,9 +1415,7 @@ xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source, if (acl_source == NULL) { acl_source = xml; } - pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_acl_enabled); - pcmk__unpack_acls(acl_source->doc, xml->doc->_private, user); - pcmk__apply_acls(xml->doc); + pcmk__enable_acls(acl_source->doc, xml->doc, user); } } diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index c7993bbab2f..561b65b6fe9 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -50,29 +50,58 @@ pcmk__xa_remove(xmlAttr *attr, bool force) return pcmk_rc_ok; } + if (force) { + goto remove; + } + element = attr->parent; - if (!force && !pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) { + if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) { // ACLs apply to element, not to particular attributes pcmk__trace("ACLs prevent removal of attributes from %s element", element->name); return EPERM; } - if (!force && (element != NULL) - && pcmk__xml_doc_all_flags_set(element->doc, pcmk__xf_tracking)) { - + if (pcmk__xml_doc_all_flags_set(element->doc, pcmk__xf_tracking)) { // Leave in place (marked for removal) until after diff is calculated pcmk__xml_set_parent_flags(element, pcmk__xf_dirty); pcmk__set_xml_flags((xml_node_private_t *) attr->_private, pcmk__xf_deleted); - } else { - pcmk__xml_free_private_data((xmlNode *) attr); - xmlRemoveProp(attr); + return pcmk_rc_ok; } + +remove: + pcmk__xml_free_private_data((xmlNode *) attr); + xmlRemoveProp(attr); return pcmk_rc_ok; } +/*! + * \internal + * \brief Add an attribute to a hash table of name-value pairs + * + * Insert a copy of the attribute's name as the key and a copy of the + * attribute's value as the value, using \c pcmk__insert_dup(). + * + * \param[in] attr XML attribute + * \param[in,out] user_data Name-value pair table (GHashTable *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +bool +pcmk__xa_insert_dup(const xmlAttr *attr, void *user_data) +{ + GHashTable *table = user_data; + const char *name = (const char *) attr->name; + const char *value = pcmk__xml_attr_value(attr); + + pcmk__insert_dup(table, name, value); + return true; +} + void pcmk__mark_xml_attr_dirty(xmlAttr *a) { @@ -84,58 +113,50 @@ pcmk__mark_xml_attr_dirty(xmlAttr *a) pcmk__mark_xml_node_dirty(parent); } -// This also clears attribute's flags if not marked as deleted -bool -pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data) -{ - xml_node_private_t *nodepriv = a->_private; - - if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - return true; - } - nodepriv->flags = pcmk__xf_none; - return false; -} - /*! * \internal * \brief Append an XML attribute to a buffer * - * \param[in] attr Attribute to append - * \param[in,out] buffer Where to append the content (must not be \p NULL) + * Append the attribute in the form " NAME=\"VALUE\"", where any XML- + * special characters in the value are escaped. + * + * \param[in] attr XML attribute + * \param[in,out] user_data Buffer (GString *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). */ -void -pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) +bool +pcmk__dump_xml_attr(const xmlAttr *attr, void *user_data) { - const char *name = NULL; + GString *buffer = user_data; const char *value = NULL; gchar *value_esc = NULL; - xml_node_private_t *nodepriv = NULL; + const xml_node_private_t *nodepriv = NULL; - if (attr == NULL || attr->children == NULL) { - return; + if (attr == NULL) { + return true; } nodepriv = attr->_private; if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - return; + return true; } - name = (const char *) attr->name; - value = (const char *) attr->children->content; + value = pcmk__xml_attr_value(attr); if (value == NULL) { /* Don't print anything for unset attribute. Any null-indicator value, * including the empty string, could also be a real value that needs to - * be treated differently from "unset". + * be treated differently from an unset value. */ - return; + return true; } - if (pcmk__xml_needs_escape(value, pcmk__xml_escape_attr)) { - value_esc = pcmk__xml_escape(value, pcmk__xml_escape_attr); - value = value_esc; - } + value_esc = pcmk__xml_escape(value, pcmk__xml_escape_attr); - pcmk__g_strcat(buffer, " ", name, "=\"", value, "\"", NULL); + pcmk__g_strcat(buffer, " ", (const char *) attr->name, + "=\"", value_esc, "\"", NULL); g_free(value_esc); + return true; } diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 26dfb7f44ca..d049520ebc3 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -70,6 +70,81 @@ show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth, return pcmk_rc_no_output; } +/*! + * \internal + * \brief Check whether an XML attribute is hidden + * + * If the \c PCMK__XA_HIDDEN attribute is set for an XML element, then it is + * treated as a comma-separated list of sibling attribute names whose values + * should be hidden. + * + * Parse the value of \c PCMK__XA_HIDDEN (if present) in the parent of \p attr, + * and check whether \p attr is in the list. + * + * \param[in] attr XML attribute + * + * \return \c true if \p attr is hidden, or \c false otherwise + */ +static bool +is_attr_hidden(const xmlAttr *attr) +{ + const char *hidden = pcmk__xe_get(attr->parent, PCMK__XA_HIDDEN); + gchar **hidden_names = NULL; + bool rc = false; + + if (hidden == NULL) { + return false; + } + + hidden_names = g_strsplit(hidden, ",", 0); + rc = pcmk__g_strv_contains(hidden_names, (const char *) attr->name); + + g_strfreev(hidden_names); + return rc; +} + +/*! + * \internal + * \brief Append an attribute to a buffer, respecting hidden attributes + * + * If the attribute has the \c pcmk__xf_deleted flag set, don't append it. + * Otherwise, append the attribute in the form " NAME=\"VALUE\"". + * + * If the attribute's name is in the parent element's \c PCMK__XA_HIDDEN + * attribute, hide it by showing \c "*****" for the value. + * + * \param[in] attr XML attribute + * \param[in,out] user_data Buffer (GString *) + * + * \return \c true (to continue iterating) + * + * \note This is just like \c pcmk__dump_xml_attr() except for the handling of + * hidden attributes. + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +show_xml_attribute(const xmlAttr *attr, void *user_data) +{ + GString *buffer = user_data; + + const xml_node_private_t *nodepriv = attr->_private; + + /* NULL-check nodepriv because, at the time of writing, the argument can be + * arbitrary XML from the public API + */ + if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + return true; + } + + if (is_attr_hidden(attr)) { + g_string_append_printf(buffer, " %s=\"*****\"", + (const char *) attr->name); + return true; + } + + return pcmk__dump_xml_attr(attr, buffer); +} + /*! * \internal * \brief Output an XML element in a formatted way @@ -97,8 +172,6 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, int rc = pcmk_rc_no_output; if (pcmk__is_set(options, pcmk__xml_fmt_open)) { - const char *hidden = pcmk__xe_get(data, PCMK__XA_HIDDEN); - g_string_truncate(buffer, 0); for (int lpc = 0; lpc < spaces; lpc++) { @@ -106,33 +179,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, } pcmk__g_strcat(buffer, "<", data->name, NULL); - for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; - attr = attr->next) { - xml_node_private_t *nodepriv = attr->_private; - const char *p_name = (const char *) attr->name; - const char *p_value = pcmk__xml_attr_value(attr); - gchar *p_value_disp = NULL; - - if ((nodepriv == NULL) - || pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - continue; - } - - if ((hidden != NULL) && !pcmk__str_empty(p_name)) { - gchar **hidden_names = g_strsplit(hidden, ",", 0); - - if (pcmk__g_strv_contains(hidden_names, p_name)) { - p_value = "*****"; - } - g_strfreev(hidden_names); - } - - p_value_disp = pcmk__xml_escape(p_value, pcmk__xml_escape_attr); - - pcmk__g_strcat(buffer, " ", p_name, "=\"", - pcmk__s(p_value_disp, ""), "\"", NULL); - g_free(p_value_disp); - } + pcmk__xe_foreach_const_attr(data, show_xml_attribute, buffer); if ((data->children != NULL) && pcmk__is_set(options, pcmk__xml_fmt_children)) { @@ -245,27 +292,86 @@ pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, return rc; } +/*! + * \internal + * \brief User data for \c show_attr_change() + */ +struct show_attr_change_data { + pcmk__output_t *out; //!< Output object + int rc; //!< Standard Pacemaker return code + int spaces; //!< Number of indentation spaces to output +}; + +/*! + * \internal + * \brief Output an attribute change + * + * Do nothing if the attribute has not been deleted or changed. + * + * \param[in] attr XML attribute + * \param[in,out] user_data User data (struct show_attr_change_data *) + * + * \return \c true (to continue iterating) + * + * \note This currently produces output only for text-like output objects. + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +show_attr_change(const xmlAttr *attr, void *user_data) +{ + struct show_attr_change_data *data = user_data; + pcmk__output_t *out = data->out; + + int rc = pcmk_rc_no_output; + const char *prefix = PCMK__XML_PREFIX_MODIFIED; + const xml_node_private_t *nodepriv = attr->_private; + + if (!pcmk__any_flags_set(nodepriv->flags, + pcmk__xf_deleted|pcmk__xf_dirty)) { + return true; + } + + if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + prefix = PCMK__XML_PREFIX_DELETED; + + } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + prefix = PCMK__XML_PREFIX_CREATED; + + } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_modified)) { + prefix = PCMK__XML_PREFIX_MODIFIED; + + } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { + prefix = PCMK__XML_PREFIX_MOVED; + } + + rc = out->info(out, "%s %*s @%s=%s", prefix, data->spaces, "", + (const char *) attr->name, pcmk__xml_attr_value(attr)); + data->rc = pcmk__output_select_rc(data->rc, rc); + + return true; +} + /*! * \internal * \brief Output XML portions that have been marked as changed * * \param[in,out] out Output object - * \param[in] data XML node to output + * \param[in] xml XML node to output * \param[in] depth Current indentation level * \param[in] options Group of \p pcmk__xml_fmt_options flags * - * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing - * changes to \p data and its children. + * \note This is a recursive helper for \c pcmk__xml_show_changes(), showing + * changes to \p xml and its children. * \note This currently produces output only for text-like output objects. */ static int -show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, +show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *xml, int depth, uint32_t options) { /* @COMPAT: When log_data_element() is removed, we can remove the options * argument here and instead hard-code pcmk__xml_log_pretty. */ - xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private; + const xml_node_private_t *nodepriv = xml->_private; int rc = pcmk_rc_no_output; int temp_rc = pcmk_rc_no_output; @@ -275,7 +381,7 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, if (pcmk__all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) { // Newly created - return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth, + return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, xml, depth, options |pcmk__xml_fmt_open |pcmk__xml_fmt_children @@ -288,52 +394,25 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, int spaces = pretty? (2 * depth) : 0; const char *prefix = PCMK__XML_PREFIX_MODIFIED; + struct show_attr_change_data data = { + .out = out, + .rc = rc, + .spaces = spaces, + }; + if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { prefix = PCMK__XML_PREFIX_MOVED; } // Log opening tag - rc = pcmk__xml_show(out, prefix, data, depth, + rc = pcmk__xml_show(out, prefix, xml, depth, options|pcmk__xml_fmt_open); // Log changes to attributes - for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; - attr = attr->next) { - const char *name = (const char *) attr->name; - - nodepriv = attr->_private; - - if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - const char *value = pcmk__xml_attr_value(attr); - - temp_rc = out->info(out, "%s %*s @%s=%s", - PCMK__XML_PREFIX_DELETED, spaces, "", name, - value); - - } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) { - const char *value = pcmk__xml_attr_value(attr); - - if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - prefix = PCMK__XML_PREFIX_CREATED; - - } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_modified)) { - prefix = PCMK__XML_PREFIX_MODIFIED; - - } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { - prefix = PCMK__XML_PREFIX_MOVED; - - } else { - prefix = PCMK__XML_PREFIX_MODIFIED; - } - - temp_rc = out->info(out, "%s %*s @%s=%s", - prefix, spaces, "", name, value); - } - rc = pcmk__output_select_rc(rc, temp_rc); - } + pcmk__xe_foreach_const_attr(xml, show_attr_change, &data); // Log changes to children - for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; + for (const xmlNode *child = pcmk__xml_first_child(xml); child != NULL; child = pcmk__xml_next(child)) { temp_rc = show_xml_changes_recursive(out, child, depth + 1, options); @@ -341,13 +420,13 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, } // Log closing tag - temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth, + temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, xml, depth, options|pcmk__xml_fmt_close); return pcmk__output_select_rc(rc, temp_rc); } // This node hasn't changed, but check its children - for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; + for (const xmlNode *child = pcmk__xml_first_child(xml); child != NULL; child = pcmk__xml_next(child)) { temp_rc = show_xml_changes_recursive(out, child, depth + 1, options); rc = pcmk__output_select_rc(rc, temp_rc); diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 91adf14ff80..7d969e4faa9 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -26,6 +26,71 @@ #include #include "crmcommon_private.h" +/*! + * \internal + * \brief Call a function for each of an XML element's attributes + * + * \param[in,out] xml XML element + * \param[in] fn Function to call for each attribute (returns + * \c true to continue iterating over attributes or + * \c false to stop) + * \param[in,out] user_data User data argument for \p fn + * + * \return \c false if any \p fn call returned \c false, or \c true otherwise + * + * \note \p fn may remove its attribute argument. + */ +bool +pcmk__xe_foreach_attr(xmlNode *xml, bool (*fn)(xmlAttr *, void *), + void *user_data) +{ + xmlAttr *attr = pcmk__xe_first_attr(xml); + + pcmk__assert(fn != NULL); + + while (attr != NULL) { + xmlAttr *next = attr->next; + + if (!fn(attr, user_data)) { + return false; + } + + attr = next; + } + + return true; +} + +/*! + * \internal + * \brief Call a function for each of a \c const XML element's attributes + * + * \param[in] xml XML element + * \param[in] fn Function to call for each attribute (returns + * \c true to continue iterating over attributes or + * \c false to stop) + * \param[in,out] user_data User data argument for \p fn + * + * \return \c false if any \p fn call returned \c false, or \c true otherwise + */ +bool +pcmk__xe_foreach_const_attr(const xmlNode *xml, + bool (*fn)(const xmlAttr *, void *), + void *user_data) +{ + pcmk__assert(fn != NULL); + + for (const xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL; + attr = attr->next) { + + if (!fn(attr, user_data)) { + return false; + } + } + + return true; +} + /*! * \internal * \brief Find first XML child element matching given criteria @@ -234,6 +299,48 @@ pcmk__xe_set_score(xmlNode *target, const char *name, const char *value) return pcmk_rc_ok; } +/*! + * \internal + * \brief User data for \c copy_attr() + */ +struct copy_attr_data { + xmlNode *target; //!< Element to copy the attribute to + uint32_t flags; //!< Group of enum pcmk__xa_flags +}; + +/*! + * \internal + * \brief Copy an attribute to a target element + * + * \param[in] attr XML attribute + * \param[in,out] user_data User data (struct copy_attr_data *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +copy_attr(const xmlAttr *attr, void *user_data) +{ + struct copy_attr_data *data = user_data; + const char *name = (const char *) attr->name; + const char *value = pcmk__xml_attr_value(attr); + + if (pcmk__is_set(data->flags, pcmk__xaf_no_overwrite) + && (pcmk__xe_get(data->target, name) != NULL)) { + + return true; + } + + if (pcmk__is_set(data->flags, pcmk__xaf_score_update)) { + pcmk__xe_set_score(data->target, name, value); + return true; + } + + pcmk__xe_set(data->target, name, value); + return true; +} + /*! * \internal * \brief Copy XML attributes from a source element to a target element @@ -250,26 +357,14 @@ pcmk__xe_set_score(xmlNode *target, const char *name, const char *value) 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) - && (pcmk__xe_get(target, name) != NULL)) { - continue; - } + struct copy_attr_data data = { + .target = target, + .flags = flags, + }; - if (pcmk__is_set(flags, pcmk__xaf_score_update)) { - pcmk__xe_set_score(target, name, value); - } else { - pcmk__xe_set(target, name, value); - } - } + CRM_CHECK((src != NULL) && (target != NULL), return EINVAL); + pcmk__xe_foreach_const_attr(src, copy_attr, &data); return pcmk_rc_ok; } @@ -296,6 +391,53 @@ compare_xml_attr(gconstpointer a, gconstpointer b) (const char *) attr_b->name, pcmk__str_none); } +/*! + * \internal + * \brief Prepend an attribute to a list + * + * \param[in] attr XML attribute + * \param[in,out] user_data List of attributes (GSList **) + * + * \return \c true (to continue iterating) + * + * \note The argument is const because it isn't modified here and it gets added + * as \c gpointer anyway. However, it will be used as non-const when we + * process the list later. + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +prepend_attr(const xmlAttr *attr, void *user_data) +{ + GSList **attr_list = user_data; + + *attr_list = g_slist_prepend(*attr_list, (gpointer) attr); + return true; +} + +/*! + * \internal + * \brief Unlink an attribute and then re-add it to its parent element + * + * This moves the attribute to the end of its parent's attribute list. + * + * \param[in,out] data XML attribute (xmlNode *) + * \param[in,out] user_data Parent element of \p data (xmlNode *) + * + * \note This is a \c GFunc compatible with \c g_slist_foreach(). + */ +static void +unlink_and_add_attr(gpointer data, gpointer user_data) +{ + /* attr was added to the list as an xmlAttr *, but we need to cast it to + * xmlNode * for xmlUnlinkNode() and xmlAddChild() + */ + xmlNode *attr = data; + xmlNode *xml = user_data; + + xmlUnlinkNode(attr); + xmlAddChild(xml, attr); +} + /*! * \internal * \brief Sort an XML element's attributes by name @@ -312,18 +454,12 @@ pcmk__xe_sort_attrs(xmlNode *xml) { GSList *attr_list = NULL; - for (xmlAttr *iter = pcmk__xe_first_attr(xml); iter != NULL; - iter = iter->next) { - attr_list = g_slist_prepend(attr_list, iter); - } + pcmk__xe_foreach_const_attr(xml, prepend_attr, &attr_list); + attr_list = g_slist_sort(attr_list, compare_xml_attr); - for (GSList *iter = attr_list; iter != NULL; iter = iter->next) { - xmlNode *attr = iter->data; + g_slist_foreach(attr_list, unlink_and_add_attr, xml); - xmlUnlinkNode(attr); - xmlAddChild(xml, attr); - } g_slist_free(attr_list); } @@ -365,6 +501,50 @@ pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data) return true; } +/*! + * \internal + * \brief User data for \c remove_xa_if_matching() + */ +struct remove_xa_if_matching_data { + //! \c force argument for \c pcmk__xa_remove() + bool force; + + //! Match function to call for each attribute + bool (*match)(const xmlAttr *, void *); + + //! User data argument for match function + void *match_data; +}; + +/*! + * \internal + * \brief Remove an attribute if a match function returns true for it + * + * Do nothing if any previous removal has failed. + * + * \param[in,out] attr XML attribute + * \param[in,out] user_data User data + * (struct remove_xa_if_matching_data *) + * + * \return \c true (to continue iterating) if \p attr was removed successfully + * or if we did not attempt to remove it; or \c false if removal failed + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +remove_xa_if_matching(xmlAttr *attr, void *user_data) +{ + struct remove_xa_if_matching_data *data = user_data; + + if ((data->match != NULL) && !data->match(attr, data->match_data)) { + // attr is not a match + return true; + } + + // @TODO Why do we stop removing attributes if one removal fails? + return (pcmk__xa_remove(attr, data->force) == pcmk_rc_ok); +} + /*! * \internal * \brief Remove an XML element's attributes that match some criteria @@ -378,19 +558,16 @@ pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data) */ void pcmk__xe_remove_matching_attrs(xmlNode *element, bool force, - bool (*match)(xmlAttrPtr, void *), + bool (*match)(const xmlAttr *, void *), void *user_data) { - xmlAttrPtr next = NULL; + struct remove_xa_if_matching_data data = { + .force = force, + .match = match, + .match_data = user_data, + }; - 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 (pcmk__xa_remove(a, force) != pcmk_rc_ok) { - return; - } - } - } + pcmk__xe_foreach_attr(element, remove_xa_if_matching, &data); } /*! @@ -647,6 +824,29 @@ update_xe(xmlNode *parent, xmlNode *target, xmlNode *update, uint32_t flags) free(trace_s); } +/*! + * \internal + * \brief Check whether an element's attribute value matches a reference value + * + * \param[in] attr XML attribute + * \param[in] user_data XML element to match against (const xmlNode *) + * + * \return \c true (to continue iterating) if \p user_data has an attribute with + * the same name and value as \p attr, or \c false otherwise + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr() + */ +static bool +match_attr_value(const xmlAttr *attr, void *user_data) +{ + const xmlNode *xml = user_data; + const char *ref_val = pcmk__xml_attr_value(attr); + const char *xml_val = pcmk__xe_get(xml, (const char *) attr->name); + + // Check whether attr's value matches the corresponding value in xml + return pcmk__str_eq(ref_val, xml_val, pcmk__str_casei); +} + /*! * \internal * \brief Delete an XML subtree if it matches a search element @@ -668,23 +868,16 @@ update_xe(xmlNode *parent, xmlNode *target, xmlNode *update, uint32_t flags) static bool delete_xe_if_matching(xmlNode *xml, void *user_data) { - xmlNode *search = user_data; + const 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 = pcmk__xe_get(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; - } + if (!pcmk__xe_foreach_const_attr(search, match_attr_value, xml)) { + // No match: mismatched attribute values + return true; } pcmk__log_xml_trace(xml, "delete-match"); @@ -916,31 +1109,6 @@ pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags) return ENXIO; } -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 *); - pcmk__xe_set(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), diff --git a/lib/common/xml_io.c b/lib/common/xml_io.c index 62f11f2de6b..889bab1f7a9 100644 --- a/lib/common/xml_io.c +++ b/lib/common/xml_io.c @@ -201,6 +201,27 @@ pcmk__xml_parse(const char *input) return xml; } +/*! + * \internal + * \brief Append an XML attribute to a buffer if it's not filterable + * + * \param[in] attr XML attribute + * \param[in,out] user_data Buffer (GString *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +dump_xa_if_not_filterable(const xmlAttr *attr, void *user_data) +{ + if (!pcmk__xa_filterable((const char *) attr->name)) { + pcmk__dump_xml_attr(attr, user_data); + } + + return true; +} + /*! * \internal * \brief Append a string representation of an XML element to a buffer @@ -218,18 +239,17 @@ dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer, const bool filtered = pcmk__is_set(options, pcmk__xml_fmt_filtered); const int spaces = pretty? (2 * depth) : 0; - for (int lpc = 0; lpc < spaces; lpc++) { + for (int i = 0; i < spaces; i++) { g_string_append_c(buffer, ' '); } pcmk__g_strcat(buffer, "<", data->name, NULL); - for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; - attr = attr->next) { + if (!filtered) { + pcmk__xe_foreach_const_attr(data, pcmk__dump_xml_attr, buffer); - if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) { - pcmk__dump_xml_attr(attr, buffer); - } + } else { + pcmk__xe_foreach_const_attr(data, dump_xa_if_not_filterable, buffer); } if (data->children == NULL) { @@ -277,18 +297,13 @@ dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer, const bool pretty = pcmk__is_set(options, pcmk__xml_fmt_pretty); const int spaces = pretty? (2 * depth) : 0; const char *content = (const char *) data->content; - gchar *content_esc = NULL; - - if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) { - content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text); - content = content_esc; - } + gchar *content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text); for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } - g_string_append(buffer, content); + g_string_append(buffer, content_esc); if (pretty) { g_string_append_c(buffer, '\n'); diff --git a/lib/common/xml_tracking.c b/lib/common/xml_tracking.c new file mode 100644 index 00000000000..5afa58ad3a4 --- /dev/null +++ b/lib/common/xml_tracking.c @@ -0,0 +1,791 @@ +/* + * Copyright 2004-2025 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 + +#include +#include // NULL +#include // free() + +#include // xmlNode, etc. +#include // xmlChar + +#include "crmcommon_private.h" + +/*! + * \internal + * \brief Set the \c pcmk__xf_created flag on an attribute + * + * \param[in,out] attr XML attribute + * \param[in] user_data Ignored + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +mark_attr_created(xmlAttr *attr, void *user_data) +{ + xml_node_private_t *nodepriv = attr->_private; + + pcmk__set_xml_flags(nodepriv, pcmk__xf_created); + return true; +} + +/*! + * \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] 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 *attr_name, + const char *old_value) +{ + xmlAttr *attr = NULL; + xml_node_private_t *nodepriv; + + /* Restore the old value (without setting dirty flag recursively upwards or + * checking ACLs) + */ + pcmk__xml_doc_clear_flags(new_xml->doc, pcmk__xf_tracking); + pcmk__xe_set(new_xml, attr_name, old_value); + pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking); + + // Reset flags (so the attribute doesn't appear as newly created) + attr = xmlHasProp(new_xml, (const xmlChar *) attr_name); + nodepriv = attr->_private; + nodepriv->flags = 0; + + // Check ACLs and mark restored value for later removal + pcmk__xa_remove(attr, false); + + pcmk__trace("XML attribute %s=%s was removed from %s", attr_name, old_value, + (const char *) new_xml->name); +} + +/* + * \internal + * \brief Check ACLs for a changed XML attribute + */ +static void +mark_attr_changed(xmlNode *new_xml, const char *attr_name, + const char *old_value) +{ + char *vcopy = pcmk__xe_get_copy(new_xml, attr_name); + + pcmk__trace("XML attribute %s was changed from '%s' to '%s' in %s", + attr_name, old_value, vcopy, (const char *) new_xml->name); + + // Restore the original value (without checking ACLs) + pcmk__xml_doc_clear_flags(new_xml->doc, pcmk__xf_tracking); + pcmk__xe_set(new_xml, attr_name, old_value); + pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking); + + // Change it back to the new value, to check ACLs + pcmk__xe_set(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,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, xmlAttr *old_attr, xmlAttr *new_attr, + int p_old, int p_new) +{ + xml_node_private_t *nodepriv = new_attr->_private; + + pcmk__trace("XML attribute %s moved from position %d to %d in %s", + old_attr->name, p_old, p_new, (const char *) new_xml->name); + + // 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 Mark an XML attribute as deleted, changed, or moved if appropriate + * + * Given an attribute (from an old XML element) and a new XML element, check + * whether the attribute has been deleted, changed, or moved between the old and + * new elements. If so, mark the new XML element to indicate what changed. + * + * \param[in,out] old_attr XML attribute from old element + * \param[in,out] user_data New XML element + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +mark_attr_diff(xmlAttr *old_attr, void *user_data) +{ + xmlNode *new_xml = user_data; + + const char *name = (const char *) old_attr->name; + + xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name); + xml_node_private_t *new_priv = NULL; + + const char *old_value = pcmk__xml_attr_value(old_attr); + const char *new_value = NULL; + + int old_pos = 0; + int new_pos = 0; + + if (new_attr == NULL) { + mark_attr_deleted(new_xml, name, old_value); + return true; + } + + new_priv = new_attr->_private; + new_value = pcmk__xe_get(new_xml, name); + + // This attribute isn't new + pcmk__clear_xml_flags(new_priv, pcmk__xf_created); + + if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) { + mark_attr_changed(new_xml, name, old_value); + return true; + } + + old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); + new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); + + if ((old_pos == new_pos) + || pcmk__xml_doc_all_flags_set(new_xml->doc, + pcmk__xf_ignore_attr_pos)) { + return true; + } + + mark_attr_moved(new_xml, old_attr, new_attr, old_pos, new_pos); + return true; +} + +/*! + * \internal + * \brief Mark a new attribute dirty if ACLs allow creation, or remove otherwise + * + * We set the \c pcmk__xf_created flag on all attributes in the new XML at an + * earlier stage of change calculation. Then we checked whether each attribute + * was present in the old XML, and we cleared the flag if so. If the flag is + * still set, then the attribute is truly new. + * + * Now we check whether ACLs allow the attribute's creation. If so, we "accept" + * it: we mark the attribute as dirty and modified, and we mark all of its + * parents as dirty. Otherwise, we reject it by removing the attribute (ignoring + * ACLs and change tracking for the removal). + * + * \param[in,out] attr XML attribute to mark dirty or remove + * \param[in] user_data Ignored + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_attr(). + */ +static bool +check_new_attr_acls(xmlAttr *attr, void *user_data) +{ + const char *name = (const char *) attr->name; + const char *value = pcmk__xml_attr_value(attr); + const xml_node_private_t *nodepriv = attr->_private; + xmlNode *new_xml = attr->parent; + const char *new_xml_id = pcmk__s(pcmk__xe_id(new_xml), "without ID"); + + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + return true; + } + + /* Check ACLs (we can't use the remove-then-create trick because it + * would modify the attribute position). + */ + if (!pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) { + pcmk__trace("ACLs prevent creation of attribute %s=%s in %s %s", name, + value, (const char *) new_xml->name, new_xml_id); + pcmk__xa_remove(attr, true); + return true; + } + + pcmk__trace("Created new attribute %s=%s in %s %s", name, value, + (const char *) new_xml->name, new_xml_id); + pcmk__mark_xml_attr_dirty(attr); + return true; +} + +/*! + * \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) +{ + // Cleared later if attributes are not really new + pcmk__xe_foreach_attr(new_xml, mark_attr_created, NULL); + + pcmk__xe_foreach_attr(old_xml, mark_attr_diff, new_xml); + pcmk__xe_foreach_attr(new_xml, check_new_attr_acls, NULL); +} + +/*! + * \internal + * \brief Add a deleted object record for an old XML child if ACLs allow + * + * This is intended to be called for a child of an old XML element that is not + * present as a child of a new XML element. + * + * Add a temporary copy of the old child to the new XML. Then check whether ACLs + * would have allowed the deletion of that element. If so, add a deleted object + * record for it to the new XML's document, and set the \c pcmk__xf_skip flag on + * the old child. + * + * The temporary copy is removed before returning. The new XML and all of its + * ancestors will have the \c pcmk__xf_dirty flag set because of the creation, + * however. + * + * \param[in,out] old_child Child of old XML + * \param[in,out] new_parent New XML that does not contain \p old_child + * + * \note The deletion is checked using the new XML's ACLs. The ACLs may have + * also changed between the old and new XML trees. Callers should take + * reasonable action if there were ACL changes that themselves would have + * been denied. + */ +static void +mark_child_deleted(xmlNode *old_child, xmlNode *new_parent) +{ + int pos = pcmk__xml_position(old_child, pcmk__xf_skip); + + // 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, pcmk__xml_reset_node_flags, NULL); + + // pcmk__xml_free_position() will check whether ACLs allow the deletion + pcmk__apply_acls(candidate->doc); + + /* Try to remove the child again (which will track it in document's + * deleted_objs on success) + */ + if (pcmk__xml_free_position(candidate, pos) != pcmk_rc_ok) { + // ACLs denied deletion in pcmk__xml_free_position(), so free here + pcmk__xml_free_node(candidate); + } + + pcmk__set_xml_flags((xml_node_private_t *) old_child->_private, + pcmk__xf_skip); +} + +/*! + * \internal + * \brief Mark a new child as moved and set \c pcmk__xf_skip as appropriate + * + * \param[in,out] old_child Child of old XML + * \param[in,out] new_child Child of new XML that matches \p old_child + * \param[in] old_pos Position of \p old_child among its siblings + * \param[in] new_pos Position of \p new_child among its siblings + */ +static void +mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos, + int new_pos) +{ + const char *id_s = pcmk__s(pcmk__xe_id(new_child), ""); + xmlNode *new_parent = new_child->parent; + xml_node_private_t *nodepriv = new_child->_private; + + pcmk__trace("Child element %s with " PCMK_XA_ID "='%s' moved from position " + "%d to %d under %s", + new_child->name, id_s, old_pos, new_pos, new_parent->name); + pcmk__mark_xml_node_dirty(new_parent); + pcmk__set_xml_flags(nodepriv, pcmk__xf_moved); + + /* @TODO Figure out and document why we skip the old child in future + * position calculations if the old position is higher, and skip the new + * child in future position calculations if the new position is higher. This + * goes back to d028b52, and there's no explanation in the commit message. + */ + if (old_pos > new_pos) { + nodepriv = old_child->_private; + } + pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); +} + +/*! + * \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 a child as created, and free it if ACLs disallow its creation + * + * This sets the \c pcmk__xf_skip, \c pcmk__xf_dirty, and \c pcmk__xf_created + * flags on \p new_child, and it sets dirty flags on all ancestor nodes and the + * document. + * + * \param[in,out] new_child Newly created child of new XML node + */ +static void +mark_child_created(xmlNode *new_child) +{ + xml_node_private_t *nodepriv = new_child->_private; + + /* Setting all these flags first seems like wasted work (albeit not much) if + * pcmk__check_creation_acls() ends up freeing new_child. It also sets dirty + * flags on the ancestors and document even if new_child ends up getting + * freed. We do these steps first because: + * - Currently pcmk__check_creation_acls() does nothing for nodes that don't + * have the pcmk__xf_created flag set. + * - Otherwise we have a use-after-free if new_child gets freed. + * + * @TODO Create a way to call pcmk__check_creation_acls() first. + */ + + // @TODO Why do we set pcmk__xf_skip here? + pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); + + // Mark all ancestors and document dirty + pcmk__mark_xml_node_dirty(new_child); + + // Mark new_child and all descendants dirty and created + pcmk__xml_tree_foreach(new_child, mark_xml_dirty_created, NULL); + + // Check whether creation was allowed (may free new_child) + pcmk__check_creation_acls(new_child); +} + +/*! + * \internal + * \brief Check whether a new XML child comment matches an old XML child comment + * + * Two comments match if they have the same position among their siblings and + * the same contents. + * + * If \p new_comment has the \c pcmk__xf_skip flag set, then it is automatically + * considered not to match. + * + * \param[in] old_comment Old XML child element + * \param[in] new_comment New XML child element + * + * \retval \c true if \p new_comment matches \p old_comment + * \retval \c false otherwise + */ +static bool +new_comment_matches(const xmlNode *old_comment, const xmlNode *new_comment) +{ + xml_node_private_t *nodepriv = new_comment->_private; + + if (pcmk__is_set(nodepriv->flags, pcmk__xf_skip)) { + /* @TODO Should we also return false if old_comment has pcmk__xf_skip + * set? This preserves existing behavior at time of writing. + */ + return false; + } + if (pcmk__xml_position(old_comment, pcmk__xf_skip) + != pcmk__xml_position(new_comment, pcmk__xf_skip)) { + return false; + } + return pcmk__xc_matches(old_comment, new_comment); +} + +/*! + * \internal + * \brief Check whether a new XML child element matches an old XML child element + * + * Two elements match if they have the same name and the same ID. (Both IDs can + * be \c NULL.) + * + * For XML attributes other than \c PCMK_XA_ID, we can treat a value change as + * an in-place modification. However, when Pacemaker applies a patchset, it uses + * the \c PCMK_XA_ID attribute to find the node to update (modify, delete, or + * move). If we treat two nodes with different \c PCMK_XA_ID attributes as + * matching and then mark that attribute as changed, it can cause this lookup to + * fail. + * + * There's unlikely to ever be much practical reason to treat elements with + * different IDs as a change. Unless that changes, we'll treat them as a + * mismatch. + * + * \param[in] old_element Old XML child element + * \param[in] new_element New XML child element + * + * \retval \c true if \p new_element matches \p old_element + * \retval \c false otherwise + */ +static bool +new_element_matches(const xmlNode *old_element, const xmlNode *new_element) +{ + return pcmk__xe_is(new_element, (const char *) old_element->name) + && pcmk__str_eq(pcmk__xe_id(old_element), pcmk__xe_id(new_element), + pcmk__str_none); +} + +/*! + * \internal + * \brief Check whether a new XML child node matches an old XML child node + * + * Node types must be the same in order to match. + * + * For comments, a match is a comment at the same position with the same + * content. + * + * For elements, a match is an element with the same name and the same ID. (Both + * IDs can be \c NULL.) + * + * For other node types, there is no match. + * + * \param[in] old_child Child of old XML + * \param[in] new_child Child of new XML + * + * \retval \c true if \p new_child matches \p old_child + * \retval \c false otherwise + */ +static bool +new_child_matches(const xmlNode *old_child, const xmlNode *new_child) +{ + if (old_child->type != new_child->type) { + return false; + } + + switch (old_child->type) { + case XML_COMMENT_NODE: + return new_comment_matches(old_child, new_child); + case XML_ELEMENT_NODE: + return new_element_matches(old_child, new_child); + default: + return false; + } +} + +/*! + * \internal + * \brief Set old and new child's \c match pointers to each other if they match + * + * A node that is part of a matching pair gets its _private:match + * member set to the matching node. + * + * \param[in,out] new_child New child + * \param[in,out] user_data Old child (xmlNode *) + * + * \return \c true (to continue iterating over new children) if the nodes don't + * match, or \c false (to stop iterating) if they do + */ +static bool +set_match_if_matching(xmlNode *new_child, void *user_data) +{ + xmlNode *old_child = user_data; + xml_node_private_t *old_nodepriv = old_child->_private; + xml_node_private_t *new_nodepriv = new_child->_private; + + if ((new_nodepriv == NULL) || (new_nodepriv->match != NULL)) { + // Can't process, or this new child already matched some old child + return true; + } + + if (!new_child_matches(old_child, new_child)) { + return true; + } + + old_nodepriv->match = new_child; + new_nodepriv->match = old_child; + return false; +} + +/*! + * \internal + * \brief Find a child of a new XML node that matches a child of an old node + * + * If a match is found, set the _private:child pointers in the matching + * old and new children to each other. + * + * \param[in,out] old_child Child of old XML node + * \param[in,out] user_data New XML node (xmlNode *) + * + * \return \c true (to continue iterating over old children) + */ +static bool +find_and_set_match(xmlNode *old_child, void *user_data) +{ + xmlNode *new_xml = user_data; + xml_node_private_t *old_nodepriv = old_child->_private; + + if ((old_nodepriv == NULL) || (old_nodepriv->match != NULL)) { + // Can't process, or we already found a match for this old child + return true; + } + + pcmk__xml_foreach_child(new_xml, set_match_if_matching, old_child); + return true; +} + +/*! + * \internal + * \brief Mark a child node as changed or deleted if appropriate + * + * If the old child has its \c match pointer set, then it's present in the new + * XML. It may or may not have changed. We make a recursive call to + * \c pcmk__xml_mark_changes() to mark any changes that may be present. + * + * Otherwise, the old child is absent from the new node, so we mark it as + * deleted. + * + * \param[in,out] old_child Child of old XML node + * \param[in,out] user_data New XML node (xmlNode *) + * + * \return \c true (to continue iterating over old children) + */ +static bool +mark_child_changed_or_deleted(xmlNode *old_child, void *user_data) +{ + xmlNode *new_xml = user_data; + xmlNode *new_child = NULL; + xml_node_private_t *nodepriv = old_child->_private; + + if (nodepriv == NULL) { + return true; + } + + if (nodepriv->match == NULL) { + // No match in new XML means the old child was deleted + mark_child_deleted(old_child, new_xml); + return true; + } + + /* Fetch the match and clear old_child->_private's match member. + * new_child->_private's match member is handled in + * mark_child_moved_or_created(). + */ + new_child = nodepriv->match; + nodepriv->match = NULL; + + pcmk__assert(old_child->type == new_child->type); + + if (old_child->type == XML_COMMENT_NODE) { + // Comments match only if their positions and contents match + return true; + } + + pcmk__xml_mark_changes(old_child, new_child); + return true; +} + +/*! + * \internal + * \brief Mark a child node as moved or created if appropriate + * + * If the new child has its \c match pointer set, then it's present in the old + * XML. Any changes within the child were marked in + * \c mark_child_changed_or_moved(). It may or may not have moved. We check for + * that and mark the move here if so. + * + * Otherwise, the new child is absent from the old node, so we mark it as + * created. + * + * \param[in,out] new_child Child of new XML node + * \param[in] user_data Ignored + * + * \return \c true (to continue iterating over new children) + * + * \note This frees \p new_child if it's newly created and ACLs disallow the + * creation. + */ +static bool +mark_child_moved_or_created(xmlNode *new_child, void *user_data) +{ + xmlNode *old_child = NULL; + int old_pos = 0; + int new_pos = 0; + xml_node_private_t *nodepriv = new_child->_private; + + if (nodepriv == NULL) { + return true; + } + + if (nodepriv->match == NULL) { + // No match in old XML means the new child is newly created + mark_child_created(new_child); + return true; + } + + /* Fetch the match and clear new_child->_private's match member. Any changes + * within the child were marked by mark_child_changed_or_deleted(). If the + * child was moved, mark the move now. + * + * We might be able to mark the move in mark_child_changed_or_deleted(), + * consolidating both actions. We'd have to think about whether the timing + * of setting the pcmk__xf_skip flag makes any difference. + */ + old_child = nodepriv->match; + nodepriv->match = NULL; + + old_pos = pcmk__xml_position(old_child, pcmk__xf_skip); + new_pos = pcmk__xml_position(new_child, pcmk__xf_skip); + + if (old_pos != new_pos) { + mark_child_moved(old_child, new_child, old_pos, new_pos); + } + + return true; +} + +/*! + * \internal + * \brief Mark changes between two XML trees + * + * Set flags in a new XML tree to indicate changes relative to an old XML tree. + * + * \param[in,out] old_xml XML before changes + * \param[in,out] new_xml XML after changes + * + * \note This may set \c pcmk__xf_skip on parts of \p old_xml. + * \note This function is recursive via \c mark_child_changed_or_deleted(). + */ +void +pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml) +{ + /* This function may set the xml_node_private_t:match member on children of + * old_xml and new_xml, but it clears that member before returning. + * + * @TODO Ensure we handle (for example, by copying) or reject user-created + * XML that is missing xml_node_private_t at top level or in any children. + * Similarly, check handling of node types for which we don't create private + * data. For now, we'll skip them in the loops below. + */ + CRM_CHECK((old_xml != NULL) && (new_xml != NULL), return); + if ((old_xml->_private == NULL) || (new_xml->_private == NULL)) { + return; + } + + pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking); + xml_diff_attrs(old_xml, new_xml); + + pcmk__xml_foreach_child(old_xml, find_and_set_match, new_xml); + pcmk__xml_foreach_child(old_xml, mark_child_changed_or_deleted, new_xml); + pcmk__xml_foreach_child(new_xml, mark_child_moved_or_created, NULL); +} + +/*! + * \internal + * \brief Check whether an attribute is marked as deleted + * + * \param[in] attr XML attribute + * \param[in] user_data Ignored + * + * \return \c true if \c pcmk__xf_deleted is set for \p attr, or \c false + * otherwise + * + * \note This is compatible with \c pcmk__xe_remove_matching_attrs(). + */ +static bool +marked_as_deleted(const xmlAttr *attr, void *user_data) +{ + const xml_node_private_t *nodepriv = attr->_private; + + return pcmk__is_set(nodepriv->flags, pcmk__xf_deleted); +} + +/*! + * \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 +commit_attr_deletions(xmlNode *xml, void *user_data) +{ + pcmk__xml_reset_node_flags(xml, NULL); + pcmk__xe_remove_matching_attrs(xml, true, marked_as_deleted, NULL); + return true; +} + +/*! + * \internal + * \brief Finalize all pending changes to an XML document and reset private data + * + * Clear the ACL user and all flags, unpacked ACLs, and deleted node records for + * the document; clear all flags on each node in the tree; and delete any + * attributes that are marked for deletion. + * + * \param[in,out] doc XML document + * + * \note When change tracking is enabled, "deleting" an attribute simply marks + * it for deletion (using \c pcmk__xf_deleted) until changes are + * committed. Freeing a node (using \c pcmk__xml_free()) adds a deleted + * node record (\c pcmk__deleted_xml_t) to the node's document before + * freeing it. + * \note This function clears all flags, not just flags that indicate changes. + * In particular, note that it clears the \c pcmk__xf_tracking flag, thus + * disabling tracking. + */ +void +pcmk__xml_commit_changes(xmlDoc *doc) +{ + xml_doc_private_t *docpriv = NULL; + + if (doc == NULL) { + return; + } + + docpriv = doc->_private; + if (docpriv == NULL) { + return; + } + + if (pcmk__is_set(docpriv->flags, pcmk__xf_dirty)) { + pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions, + NULL); + } + pcmk__xml_reset_doc_private_data(docpriv); +} diff --git a/lib/common/xpath.c b/lib/common/xpath.c index 96de7b01c42..1c852609dd4 100644 --- a/lib/common/xpath.c +++ b/lib/common/xpath.c @@ -173,7 +173,8 @@ pcmk__xpath_foreach_result(xmlDoc *doc, const char *path, xmlXPathObject *xpath_obj = NULL; int num_results = 0; - CRM_CHECK((doc != NULL) && !pcmk__str_empty(path) && (fn != NULL), return); + pcmk__assert(fn != NULL); + CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return); xpath_obj = pcmk__xpath_search(doc, path); num_results = pcmk__xpath_num_results(xpath_obj); @@ -272,10 +273,11 @@ pcmk__xpath_find_one(xmlDoc *doc, const char *path, uint8_t level) * * \param[in] xml The XML element for which to build an XPath string * - * \return A \p GString that matches \p xml, or \p NULL if \p xml is \p NULL. + * \return \c GString that matches \p xml, or \c NULL if \p xml is \c NULL + * (guaranteed not to be \c NULL if \p xml is not \c NULL) * * \note The caller is responsible for freeing the string using - * \p g_string_free(). + * \c g_string_free(). */ GString * pcmk__element_xpath(const xmlNode *xml) diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c index 349f79526fd..50915476dba 100644 --- a/lib/fencing/st_client.c +++ b/lib/fencing/st_client.c @@ -252,6 +252,8 @@ foreach_notify_entry (stonith_private_t *private, GFunc func, gpointer user_data) { + pcmk__assert(func != NULL); + private->notify_refcnt++; g_list_foreach(private->notify_list, func, user_data); private->notify_refcnt--; diff --git a/lib/fencing/st_lha.c b/lib/fencing/st_lha.c index 788e8d3d672..4e5ccdc2f49 100644 --- a/lib/fencing/st_lha.c +++ b/lib/fencing/st_lha.c @@ -204,8 +204,8 @@ stonith__lha_metadata(const char *agent, int timeout, char **output) } if (lha_agents_lib && st_new_fn && st_del_fn && st_info_fn && st_log_fn) { - char *meta_longdesc = NULL; - char *meta_shortdesc = NULL; + gchar *meta_longdesc = NULL; + gchar *meta_shortdesc = NULL; char *meta_param = NULL; const char *timeout_str = NULL; @@ -219,18 +219,22 @@ stonith__lha_metadata(const char *agent, int timeout, char **output) /* A st_info_fn() may free any existing output buffer every time * when it's called. Copy the output every time. */ - meta_longdesc = pcmk__str_copy(st_info_fn(stonith_obj, - ST_DEVICEDESCR)); + meta_longdesc = pcmk__xml_escape(st_info_fn(stonith_obj, + ST_DEVICEDESCR), + pcmk__xml_escape_text); if (meta_longdesc == NULL) { pcmk__warn("No long description in %s's metadata", agent); - meta_longdesc = pcmk__str_copy(no_parameter_info); + meta_longdesc = pcmk__xml_escape(no_parameter_info, + pcmk__xml_escape_text); } - meta_shortdesc = pcmk__str_copy(st_info_fn(stonith_obj, - ST_DEVICEID)); + meta_shortdesc = pcmk__xml_escape(st_info_fn(stonith_obj, + ST_DEVICEID), + pcmk__xml_escape_text); if (meta_shortdesc == NULL) { pcmk__warn("No short description in %s's metadata", agent); - meta_shortdesc = pcmk__str_copy(no_parameter_info); + meta_shortdesc = pcmk__xml_escape(no_parameter_info, + pcmk__xml_escape_text); } meta_param = pcmk__str_copy(st_info_fn(stonith_obj, @@ -248,33 +252,17 @@ stonith__lha_metadata(const char *agent, int timeout, char **output) return -EINVAL; } - if (pcmk__xml_needs_escape(meta_longdesc, pcmk__xml_escape_text)) { - meta_longdesc_esc = pcmk__xml_escape(meta_longdesc, - pcmk__xml_escape_text); - } - if (pcmk__xml_needs_escape(meta_shortdesc, pcmk__xml_escape_text)) { - meta_shortdesc_esc = pcmk__xml_escape(meta_shortdesc, - pcmk__xml_escape_text); - } - /* @TODO This needs a string that's parsable by pcmk__parse_ms(). In * general, pcmk__readable_interval() doesn't provide that. It works * here because PCMK_DEFAULT_ACTION_TIMEOUT_MS is 20000 -> "20s". */ timeout_str = pcmk__readable_interval(PCMK_DEFAULT_ACTION_TIMEOUT_MS); - buffer = pcmk__assert_asprintf(META_TEMPLATE, agent, - pcmk__s(meta_longdesc_esc, - meta_longdesc), - pcmk__s(meta_shortdesc_esc, - meta_shortdesc), - meta_param, timeout_str, timeout_str, - timeout_str); - - g_free(meta_longdesc_esc); - g_free(meta_shortdesc_esc); - - free(meta_longdesc); - free(meta_shortdesc); + buffer = pcmk__assert_asprintf(META_TEMPLATE, agent, meta_longdesc, + meta_shortdesc, meta_param, timeout_str, + timeout_str, timeout_str); + + g_free(meta_longdesc); + g_free(meta_shortdesc); free(meta_param); } if (output) { diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c index 0f68ed2ad74..a22c2027f13 100644 --- a/lib/fencing/st_output.c +++ b/lib/fencing/st_output.c @@ -311,13 +311,9 @@ full_history_xml(pcmk__output_t *out, va_list args) PCMK__OUTPUT_LIST_FOOTER(out, rc); } else { - char *rc_s = pcmk__itoa(history_rc); - - pcmk__output_create_xml_node(out, PCMK_XE_FENCE_HISTORY, - PCMK_XA_STATUS, rc_s, - NULL); - free(rc_s); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_FENCE_HISTORY); + pcmk__xe_set_int(xml, PCMK_XA_STATUS, history_rc); rc = pcmk_rc_ok; } @@ -364,11 +360,10 @@ last_fenced_xml(pcmk__output_t *out, va_list args) { if (when) { char *buf = timespec_string(when, 0, false); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_LAST_FENCED); - pcmk__output_create_xml_node(out, PCMK_XE_LAST_FENCED, - PCMK_XA_TARGET, target, - PCMK_XA_WHEN, buf, - NULL); + pcmk__xe_set(xml, PCMK_XA_TARGET, target); + pcmk__xe_set(xml, PCMK_XA_WHEN, buf); free(buf); return pcmk_rc_ok; @@ -478,47 +473,39 @@ stonith_event_xml(pcmk__output_t *out, va_list args) const char *succeeded G_GNUC_UNUSED = va_arg(args, const char *); uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); - xmlNodePtr node = NULL; + xmlNode *xml = NULL; - node = pcmk__output_create_xml_node(out, PCMK_XE_FENCE_EVENT, - PCMK_XA_ACTION, event->action, - PCMK_XA_TARGET, event->target, - PCMK_XA_CLIENT, event->client, - PCMK_XA_ORIGIN, event->origin, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_FENCE_EVENT); + pcmk__xe_set(xml, PCMK_XA_ACTION, event->action); + pcmk__xe_set(xml, PCMK_XA_TARGET, event->target); + pcmk__xe_set(xml, PCMK_XA_CLIENT, event->client); + pcmk__xe_set(xml, PCMK_XA_ORIGIN, event->origin); switch (event->state) { case st_failed: - pcmk__xe_set_props(node, - PCMK_XA_STATUS, PCMK_VALUE_FAILED, - PCMK_XA_EXIT_REASON, event->exit_reason, - NULL); + pcmk__xe_set(xml, PCMK_XA_STATUS, PCMK_VALUE_FAILED); + pcmk__xe_set(xml, PCMK_XA_EXIT_REASON, event->exit_reason); break; case st_done: - pcmk__xe_set(node, PCMK_XA_STATUS, PCMK_VALUE_SUCCESS); + pcmk__xe_set(xml, PCMK_XA_STATUS, PCMK_VALUE_SUCCESS); break; - default: { - char *state = pcmk__itoa(event->state); - pcmk__xe_set_props(node, - PCMK_XA_STATUS, PCMK_VALUE_PENDING, - PCMK_XA_EXTENDED_STATUS, state, - NULL); - free(state); + default: + pcmk__xe_set(xml, PCMK_XA_STATUS, PCMK_VALUE_PENDING); + pcmk__xe_set_int(xml, PCMK_XA_EXTENDED_STATUS, event->state); break; - } } if (event->delegate != NULL) { - pcmk__xe_set(node, PCMK_XA_DELEGATE, event->delegate); + pcmk__xe_set(xml, PCMK_XA_DELEGATE, event->delegate); } if ((event->state == st_failed) || (event->state == st_done)) { char *time_s = timespec_string(event->completed, event->completed_nsec, true); - pcmk__xe_set(node, PCMK_XA_COMPLETED, time_s); + pcmk__xe_set(xml, PCMK_XA_COMPLETED, time_s); free(time_s); } @@ -585,17 +572,13 @@ validate_agent_xml(pcmk__output_t *out, va_list args) { const char *error_output = va_arg(args, const char *); int rc = va_arg(args, int); - const char *valid = pcmk__btoa(rc == pcmk_ok); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_VALIDATE, - PCMK_XA_AGENT, agent, - PCMK_XA_VALID, valid, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_VALIDATE); - if (device != NULL) { - pcmk__xe_set(node, PCMK_XA_DEVICE, device); - } + pcmk__xe_set(xml, PCMK_XA_AGENT, agent); + pcmk__xe_set_bool(xml, PCMK_XA_VALID, (rc == pcmk_ok)); + pcmk__xe_set(xml, PCMK_XA_DEVICE, device); - pcmk__output_xml_push_parent(out, node); + pcmk__output_xml_push_parent(out, xml); out->subprocess_output(out, rc, output, error_output); pcmk__output_xml_pop_parent(out); diff --git a/lib/lrmd/lrmd_output.c b/lib/lrmd/lrmd_output.c index daa54e661c1..9bcb5a7038d 100644 --- a/lib/lrmd/lrmd_output.c +++ b/lib/lrmd/lrmd_output.c @@ -46,11 +46,12 @@ lrmd__alternatives_list_xml(pcmk__output_t *out, va_list args) { lrmd_list_t *list = va_arg(args, lrmd_list_t *); const char *agent_spec = va_arg(args, const char *); + xmlNode *xml = NULL; int rc = pcmk_rc_ok; - pcmk__output_xml_create_parent(out, PCMK_XE_PROVIDERS, - PCMK_XA_FOR, agent_spec, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_PROVIDERS); + pcmk__xe_set(xml, PCMK_XA_FOR, agent_spec); + rc = xml_list(out, list, PCMK_XE_PROVIDER); pcmk__output_xml_pop_parent(out); return rc; @@ -72,15 +73,14 @@ lrmd__agents_list_xml(pcmk__output_t *out, va_list args) { const char *agent_spec = va_arg(args, const char *); const char *provider = va_arg(args, const char *); + xmlNode *xml = NULL; int rc = pcmk_rc_ok; - xmlNodePtr node = NULL; - node = pcmk__output_xml_create_parent(out, PCMK_XE_AGENTS, - PCMK_XA_STANDARD, agent_spec, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_AGENTS); + pcmk__xe_set(xml, PCMK_XA_STANDARD, agent_spec); if (!pcmk__str_empty(provider)) { - pcmk__xe_set(node, PCMK_XA_PROVIDER, provider); + pcmk__xe_set(xml, PCMK_XA_PROVIDER, provider); } rc = xml_list(out, list, PCMK_XE_AGENT); @@ -110,14 +110,12 @@ lrmd__providers_list_xml(pcmk__output_t *out, va_list args) { lrmd_list_t *list = va_arg(args, lrmd_list_t *); const char *agent_spec = va_arg(args, const char *); + xmlNode *xml = NULL; int rc = pcmk_rc_ok; - xmlNodePtr node = pcmk__output_xml_create_parent(out, PCMK_XE_PROVIDERS, - PCMK_XA_STANDARD, "ocf", - NULL); - if (agent_spec != NULL) { - pcmk__xe_set(node, PCMK_XA_AGENT, agent_spec); - } + xml = pcmk__output_xml_create_parent(out, PCMK_XE_PROVIDERS); + pcmk__xe_set(xml, PCMK_XA_STANDARD, "ocf"); + pcmk__xe_set(xml, PCMK_XA_AGENT, agent_spec); rc = xml_list(out, list, PCMK_XE_PROVIDER); pcmk__output_xml_pop_parent(out); diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c index 5f47e76e2ee..e0fd8974e30 100644 --- a/lib/pacemaker/pcmk_acl.c +++ b/lib/pacemaker/pcmk_acl.c @@ -215,7 +215,7 @@ pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc, return EINVAL; } - if (!pcmk_acl_required(cred)) { + if (!pcmk__acl_required(cred)) { /* nothing to evaluate */ return pcmk_rc_already; } diff --git a/lib/pacemaker/pcmk_injections.c b/lib/pacemaker/pcmk_injections.c index 4fb742f49d6..061bfd2b0b5 100644 --- a/lib/pacemaker/pcmk_injections.c +++ b/lib/pacemaker/pcmk_injections.c @@ -362,20 +362,18 @@ pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up) xmlNode *cib_node = pcmk__inject_node(cib_conn, node, NULL); if (up) { - pcmk__xe_set_props(cib_node, - PCMK__XA_IN_CCM, PCMK_VALUE_TRUE, - PCMK_XA_CRMD, PCMK_VALUE_ONLINE, - PCMK__XA_JOIN, CRMD_JOINSTATE_MEMBER, - PCMK_XA_EXPECTED, CRMD_JOINSTATE_MEMBER, - NULL); + pcmk__xe_set(cib_node, PCMK__XA_IN_CCM, PCMK_VALUE_TRUE); + pcmk__xe_set(cib_node, PCMK_XA_CRMD, PCMK_VALUE_ONLINE); + pcmk__xe_set(cib_node, PCMK__XA_JOIN, CRMD_JOINSTATE_MEMBER); + pcmk__xe_set(cib_node, PCMK_XA_EXPECTED, CRMD_JOINSTATE_MEMBER); + } else { - pcmk__xe_set_props(cib_node, - PCMK__XA_IN_CCM, PCMK_VALUE_FALSE, - PCMK_XA_CRMD, PCMK_VALUE_OFFLINE, - PCMK__XA_JOIN, CRMD_JOINSTATE_DOWN, - PCMK_XA_EXPECTED, CRMD_JOINSTATE_DOWN, - NULL); + pcmk__xe_set(cib_node, PCMK__XA_IN_CCM, PCMK_VALUE_FALSE); + pcmk__xe_set(cib_node, PCMK_XA_CRMD, PCMK_VALUE_OFFLINE); + pcmk__xe_set(cib_node, PCMK__XA_JOIN, CRMD_JOINSTATE_DOWN); + pcmk__xe_set(cib_node, PCMK_XA_EXPECTED, CRMD_JOINSTATE_DOWN); } + pcmk__xe_set(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, crm_system_name); return cib_node; } diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 48e13538eb8..c20e630059f 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -46,27 +46,25 @@ colocations_header(pcmk_resource_t *rsc, pcmk__colocation_t *cons, static void colocations_xml_node(pcmk__output_t *out, pcmk_resource_t *rsc, - pcmk__colocation_t *cons) { - const char *score = pcmk_readable_score(cons->score); - const char *dependent_role = NULL; - const char *primary_role = NULL; + pcmk__colocation_t *cons) +{ + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_RSC_COLOCATION); + + pcmk__xe_set(xml, PCMK_XA_ID, cons->id); + pcmk__xe_set(xml, PCMK_XA_RSC, cons->dependent->id); + pcmk__xe_set(xml, PCMK_XA_WITH_RSC, cons->primary->id); + pcmk__xe_set(xml, PCMK_XA_SCORE, pcmk_readable_score(cons->score)); + pcmk__xe_set(xml, PCMK_XA_NODE_ATTRIBUTE, cons->node_attribute); if (cons->dependent_role != pcmk_role_unknown) { - dependent_role = pcmk_role_text(cons->dependent_role); + pcmk__xe_set(xml, PCMK_XA_RSC_ROLE, + pcmk_role_text(cons->dependent_role)); } + if (cons->primary_role != pcmk_role_unknown) { - primary_role = pcmk_role_text(cons->primary_role); + pcmk__xe_set(xml, PCMK_XA_WITH_RSC_ROLE, + pcmk_role_text(cons->primary_role)); } - - pcmk__output_create_xml_node(out, PCMK_XE_RSC_COLOCATION, - PCMK_XA_ID, cons->id, - PCMK_XA_RSC, cons->dependent->id, - PCMK_XA_WITH_RSC, cons->primary->id, - PCMK_XA_SCORE, score, - PCMK_XA_NODE_ATTRIBUTE, cons->node_attribute, - PCMK_XA_RSC_ROLE, dependent_role, - PCMK_XA_WITH_RSC_ROLE, primary_role, - NULL); } static int @@ -84,18 +82,18 @@ do_locations_list_xml(pcmk__output_t *out, pcmk_resource_t *rsc, for (lpc2 = cons->nodes; lpc2 != NULL; lpc2 = lpc2->next) { pcmk_node_t *node = (pcmk_node_t *) lpc2->data; + xmlNode *xml = NULL; if (add_header) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "locations"); } - pcmk__output_create_xml_node(out, PCMK_XE_RSC_LOCATION, - PCMK_XA_NODE, node->priv->name, - PCMK_XA_RSC, rsc->id, - PCMK_XA_ID, cons->id, - PCMK_XA_SCORE, - pcmk_readable_score(node->assign->score), - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_RSC_LOCATION); + pcmk__xe_set(xml, PCMK_XA_NODE, node->priv->name); + pcmk__xe_set(xml, PCMK_XA_RSC, rsc->id); + pcmk__xe_set(xml, PCMK_XA_ID, cons->id); + pcmk__xe_set(xml, PCMK_XA_SCORE, + pcmk_readable_score(node->assign->score)); } } @@ -279,21 +277,17 @@ rsc_action_item_xml(pcmk__output_t *out, va_list args) } change_str = g_ascii_strdown(change, -1); - xml = pcmk__output_create_xml_node(out, PCMK_XE_RSC_ACTION, - PCMK_XA_ACTION, change_str, - PCMK_XA_RESOURCE, rsc->id, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_RSC_ACTION); + pcmk__xe_set(xml, PCMK_XA_ACTION, change_str); + pcmk__xe_set(xml, PCMK_XA_RESOURCE, rsc->id); g_free(change_str); if (need_role && (origin == NULL)) { /* Starting and promoting a promotable clone instance */ - pcmk__xe_set_props(xml, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - PCMK_XA_NEXT_ROLE, - pcmk_role_text(rsc->priv->next_role), - PCMK_XA_DEST, destination->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); + pcmk__xe_set(xml, PCMK_XA_NEXT_ROLE, + pcmk_role_text(rsc->priv->next_role)); + pcmk__xe_set(xml, PCMK_XA_DEST, destination->priv->name); } else if (origin == NULL) { /* Starting a resource */ @@ -301,11 +295,8 @@ rsc_action_item_xml(pcmk__output_t *out, va_list args) } else if (need_role && (destination == NULL)) { /* Stopping a promotable clone instance */ - pcmk__xe_set_props(xml, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - PCMK_XA_NODE, origin->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); + pcmk__xe_set(xml, PCMK_XA_NODE, origin->priv->name); } else if (destination == NULL) { /* Stopping a resource */ @@ -313,11 +304,8 @@ rsc_action_item_xml(pcmk__output_t *out, va_list args) } else if (need_role && same_role && same_host) { /* Recovering, restarting or re-promoting a promotable clone instance */ - pcmk__xe_set_props(xml, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - PCMK_XA_SOURCE, origin->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); + pcmk__xe_set(xml, PCMK_XA_SOURCE, origin->priv->name); } else if (same_role && same_host) { /* Recovering or Restarting a normal resource */ @@ -325,48 +313,36 @@ rsc_action_item_xml(pcmk__output_t *out, va_list args) } else if (need_role && same_role) { /* Moving a promotable clone instance */ - pcmk__xe_set_props(xml, - PCMK_XA_SOURCE, origin->priv->name, - PCMK_XA_DEST, destination->priv->name, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - NULL); + pcmk__xe_set(xml, PCMK_XA_SOURCE, origin->priv->name); + pcmk__xe_set(xml, PCMK_XA_DEST, destination->priv->name); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); } else if (same_role) { /* Moving a normal resource */ - pcmk__xe_set_props(xml, - PCMK_XA_SOURCE, origin->priv->name, - PCMK_XA_DEST, destination->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_SOURCE, origin->priv->name); + pcmk__xe_set(xml, PCMK_XA_DEST, destination->priv->name); } else if (same_host) { /* Promoting or demoting a promotable clone instance */ - pcmk__xe_set_props(xml, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - PCMK_XA_NEXT_ROLE, - pcmk_role_text(rsc->priv->next_role), - PCMK_XA_SOURCE, origin->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); + pcmk__xe_set(xml, PCMK_XA_NEXT_ROLE, + pcmk_role_text(rsc->priv->next_role)); + pcmk__xe_set(xml, PCMK_XA_SOURCE, origin->priv->name); } else { /* Moving and promoting/demoting */ - pcmk__xe_set_props(xml, - PCMK_XA_ROLE, - pcmk_role_text(rsc->priv->orig_role), - PCMK_XA_SOURCE, origin->priv->name, - PCMK_XA_NEXT_ROLE, - pcmk_role_text(rsc->priv->next_role), - PCMK_XA_DEST, destination->priv->name, - NULL); + pcmk__xe_set(xml, PCMK_XA_ROLE, pcmk_role_text(rsc->priv->orig_role)); + pcmk__xe_set(xml, PCMK_XA_SOURCE, origin->priv->name); + pcmk__xe_set(xml, PCMK_XA_NEXT_ROLE, + pcmk_role_text(rsc->priv->next_role)); + pcmk__xe_set(xml, PCMK_XA_DEST, destination->priv->name); } if ((source->reason != NULL) && !pcmk__is_set(action->flags, pcmk__action_runnable)) { - pcmk__xe_set_props(xml, - PCMK_XA_REASON, source->reason, - PCMK_XA_BLOCKED, PCMK_VALUE_TRUE, - NULL); + + pcmk__xe_set(xml,PCMK_XA_REASON, source->reason); + pcmk__xe_set(xml, PCMK_XA_BLOCKED, PCMK_VALUE_TRUE); } else if (source->reason != NULL) { pcmk__xe_set(xml, PCMK_XA_REASON, source->reason); @@ -637,7 +613,7 @@ locations_and_colocations_xml(pcmk__output_t *out, va_list args) rsc = uber_parent(rsc); } - pcmk__output_xml_create_parent(out, PCMK_XE_CONSTRAINTS, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_CONSTRAINTS); do_locations_list_xml(out, rsc, false); pe__clear_resource_flags_on_all(rsc->priv->scheduler, @@ -699,12 +675,14 @@ health_xml(pcmk__output_t *out, va_list args) const char *host_from = va_arg(args, const char *); const char *fsa_state = va_arg(args, const char *); const char *result = va_arg(args, const char *); + xmlNode *xml = NULL; + + // @FIXME An empty XML ID is invalid + xml = pcmk__output_create_xml_node(out, pcmk__s(sys_from, "")); + pcmk__xe_set(xml, PCMK_XA_NODE_NAME, pcmk__s(host_from, "")); + pcmk__xe_set(xml, PCMK_XA_STATE, pcmk__s(fsa_state, "")); + pcmk__xe_set(xml, PCMK_XA_RESULT, pcmk__s(result, "")); - pcmk__output_create_xml_node(out, pcmk__s(sys_from, ""), - PCMK_XA_NODE_NAME, pcmk__s(host_from, ""), - PCMK_XA_STATE, pcmk__s(fsa_state, ""), - PCMK_XA_RESULT, pcmk__s(result, ""), - NULL); return pcmk_rc_ok; } @@ -825,6 +803,7 @@ pacemakerd_health_xml(pcmk__output_t *out, va_list args) const char *state_s = va_arg(args, const char *); time_t last_updated = va_arg(args, time_t); + xmlNode *xml = NULL; char *last_updated_s = NULL; if (sys_from == NULL) { @@ -846,11 +825,11 @@ pacemakerd_health_xml(pcmk__output_t *out, va_list args) |crm_time_log_with_timezone); } - pcmk__output_create_xml_node(out, PCMK_XE_PACEMAKERD, - PCMK_XA_SYS_FROM, sys_from, - PCMK_XA_STATE, state_s, - PCMK_XA_LAST_UPDATED, last_updated_s, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_PACEMAKERD); + pcmk__xe_set(xml, PCMK_XA_SYS_FROM, sys_from); + pcmk__xe_set(xml, PCMK_XA_STATE, state_s); + pcmk__xe_set(xml, PCMK_XA_LAST_UPDATED, last_updated_s); + free(last_updated_s); return pcmk_rc_ok; } @@ -875,12 +854,12 @@ profile_xml(pcmk__output_t *out, va_list args) { clock_t start = va_arg(args, clock_t); clock_t end = va_arg(args, clock_t); + xmlNode *xml = NULL; char *duration = pcmk__ftoa((end - start) / (float) CLOCKS_PER_SEC); - pcmk__output_create_xml_node(out, PCMK_XE_TIMING, - PCMK_XA_FILE, xml_file, - PCMK_XA_DURATION, duration, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_TIMING); + pcmk__xe_set(xml, PCMK_XA_FILE, xml_file); + pcmk__xe_set(xml, PCMK_XA_DURATION, duration); free(duration); return pcmk_rc_ok; @@ -919,10 +898,10 @@ static int dc_xml(pcmk__output_t *out, va_list args) { const char *dc = va_arg(args, const char *); + xmlNode *xml = NULL; - pcmk__output_create_xml_node(out, PCMK_XE_DC, - PCMK_XA_NODE_NAME, pcmk__s(dc, ""), - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_DC); + pcmk__xe_set(xml, PCMK_XA_NODE_NAME, pcmk__s(dc, "")); return pcmk_rc_ok; } @@ -972,12 +951,13 @@ crmadmin_node_xml(pcmk__output_t *out, va_list args) const char *name = va_arg(args, const char *); const char *id = va_arg(args, const char *); bool bash_export G_GNUC_UNUSED = va_arg(args, int); + xmlNode *xml = NULL; + + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE); + pcmk__xe_set(xml, PCMK_XA_TYPE, pcmk__s(type, "cluster")); + pcmk__xe_set(xml, PCMK_XA_NAME, pcmk__s(name, "")); + pcmk__xe_set(xml, PCMK_XA_ID, pcmk__s(id, "")); - pcmk__output_create_xml_node(out, PCMK_XE_NODE, - PCMK_XA_TYPE, pcmk__s(type, "cluster"), - PCMK_XA_NAME, pcmk__s(name, ""), - PCMK_XA_ID, pcmk__s(id, ""), - NULL); return pcmk_rc_ok; } @@ -1064,13 +1044,12 @@ digests_xml(pcmk__output_t *out, va_list args) char *interval_s = pcmk__assert_asprintf("%ums", interval_ms); xmlNode *xml = NULL; - xml = pcmk__output_create_xml_node(out, PCMK_XE_DIGESTS, - PCMK_XA_RESOURCE, pcmk__s(rsc->id, ""), - PCMK_XA_NODE, - pcmk__s(node->priv->name, ""), - PCMK_XA_TASK, pcmk__s(task, ""), - PCMK_XA_INTERVAL, interval_s, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_DIGESTS); + pcmk__xe_set(xml, PCMK_XA_RESOURCE, pcmk__s(rsc->id, "")); + pcmk__xe_set(xml, PCMK_XA_NODE, pcmk__s(node->priv->name, "")); + pcmk__xe_set(xml, PCMK_XA_TASK, pcmk__s(task, "")); + pcmk__xe_set(xml, PCMK_XA_INTERVAL, interval_s); + free(interval_s); if (digests != NULL) { add_digest_xml(xml, "all", digests->digest_all_calc, @@ -1356,12 +1335,15 @@ node_action_xml(pcmk__output_t *out, va_list args) if (task == NULL) { return pcmk_rc_no_output; - } else if (reason) { - pcmk__output_create_xml_node(out, PCMK_XE_NODE_ACTION, - PCMK_XA_TASK, task, - PCMK_XA_NODE, node_name, - PCMK_XA_REASON, reason, - NULL); + } + + if (reason != NULL) { + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE_ACTION); + + pcmk__xe_set(xml, PCMK_XA_TASK, task); + pcmk__xe_set(xml, PCMK_XA_NODE, node_name); + pcmk__xe_set(xml, PCMK_XA_REASON, reason); + } else { pcmk__notice(" * %s %s", task, node_name); } @@ -1401,16 +1383,17 @@ node_info_xml(pcmk__output_t *out, va_list args) bool have_quorum = (bool) va_arg(args, int); bool is_remote = (bool) va_arg(args, int); + xmlNode *xml = NULL; char *id_s = pcmk__assert_asprintf("%" PRIu32, node_id); - pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO, - PCMK_XA_NODEID, id_s, - PCMK_XA_UNAME, node_name, - PCMK_XA_ID, uuid, - PCMK_XA_CRMD, state, - PCMK_XA_HAVE_QUORUM, pcmk__btoa(have_quorum), - PCMK_XA_REMOTE_NODE, pcmk__btoa(is_remote), - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO); + pcmk__xe_set(xml, PCMK_XA_NODEID, id_s); + pcmk__xe_set(xml, PCMK_XA_UNAME, node_name); + pcmk__xe_set(xml, PCMK_XA_ID, uuid); + pcmk__xe_set(xml, PCMK_XA_CRMD, state); + pcmk__xe_set_bool(xml, PCMK_XA_HAVE_QUORUM, have_quorum); + pcmk__xe_set_bool(xml, PCMK_XA_REMOTE_NODE, is_remote); + free(id_s); return pcmk_rc_ok; } @@ -1445,21 +1428,20 @@ inject_cluster_action_xml(pcmk__output_t *out, va_list args) { const char *node = va_arg(args, const char *); const char *task = va_arg(args, const char *); - xmlNodePtr rsc = va_arg(args, xmlNodePtr); + const xmlNode *rsc = va_arg(args, const xmlNode *); - xmlNodePtr xml_node = NULL; + xmlNode *cluster_action = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - xml_node = pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_ACTION, - PCMK_XA_TASK, task, - PCMK_XA_NODE, node, - NULL); + cluster_action = pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_ACTION); + pcmk__xe_set(cluster_action, PCMK_XA_TASK, task); + pcmk__xe_set(cluster_action, PCMK_XA_NODE, node); - if (rsc) { - pcmk__xe_set(xml_node, PCMK_XA_ID, pcmk__xe_id(rsc)); + if (rsc != NULL) { + pcmk__xe_set(cluster_action, PCMK_XA_ID, pcmk__xe_id(rsc)); } return pcmk_rc_ok; @@ -1486,15 +1468,16 @@ inject_fencing_action_xml(pcmk__output_t *out, va_list args) { const char *target = va_arg(args, const char *); const char *op = va_arg(args, const char *); + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - pcmk__output_create_xml_node(out, PCMK_XE_FENCING_ACTION, - PCMK_XA_TARGET, target, - PCMK_XA_OP, op, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_FENCING_ACTION); + pcmk__xe_set(xml, PCMK_XA_TARGET, target); + pcmk__xe_set(xml, PCMK_XA_OP, op); + return pcmk_rc_ok; } @@ -1527,23 +1510,26 @@ inject_attr_xml(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); - xmlNodePtr cib_node = va_arg(args, xmlNodePtr); + const xmlNode *cib_node = va_arg(args, const xmlNode *); - xmlChar *node_path = NULL; + xmlNode *inject_attr = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - node_path = xmlGetNodePath(cib_node); + inject_attr = pcmk__output_create_xml_node(out, PCMK_XE_INJECT_ATTR); + pcmk__xe_set(inject_attr, PCMK_XA_NAME, name); + pcmk__xe_set(inject_attr, PCMK_XA_VALUE, value); + + if (cib_node != NULL) { + xmlChar *node_path = xmlGetNodePath(cib_node); + + pcmk__xe_set(inject_attr, PCMK_XA_NODE_PATH, (const char *) node_path); + pcmk__xe_set(inject_attr, PCMK_XA_CIB_NODE, pcmk__xe_id(cib_node)); + xmlFree(node_path); + } - pcmk__output_create_xml_node(out, PCMK_XE_INJECT_ATTR, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, value, - PCMK_XA_NODE_PATH, node_path, - PCMK_XA_CIB_NODE, pcmk__xe_id(cib_node), - NULL); - free(node_path); return pcmk_rc_ok; } @@ -1566,14 +1552,15 @@ static int inject_spec_xml(pcmk__output_t *out, va_list args) { const char *spec = va_arg(args, const char *); + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - pcmk__output_create_xml_node(out, PCMK_XE_INJECT_SPEC, - PCMK_XA_SPEC, spec, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_INJECT_SPEC); + pcmk__xe_set(xml, PCMK_XA_SPEC, spec); + return pcmk_rc_ok; } @@ -1614,7 +1601,7 @@ inject_modify_config_xml(pcmk__output_t *out, va_list args) return pcmk_rc_no_output; } - node = pcmk__output_xml_create_parent(out, PCMK_XE_MODIFICATIONS, NULL); + node = pcmk__output_xml_create_parent(out, PCMK_XE_MODIFICATIONS); if (quorum) { pcmk__xe_set(node, PCMK_XA_QUORUM, quorum); @@ -1659,15 +1646,16 @@ inject_modify_node_xml(pcmk__output_t *out, va_list args) { const char *action = va_arg(args, const char *); const char *node = va_arg(args, const char *); + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - pcmk__output_create_xml_node(out, PCMK_XE_MODIFY_NODE, - PCMK_XA_ACTION, action, - PCMK_XA_NODE, node, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_MODIFY_NODE); + pcmk__xe_set(xml, PCMK_XA_ACTION, action); + pcmk__xe_set(xml, PCMK_XA_NODE, node); + return pcmk_rc_ok; } @@ -1697,15 +1685,16 @@ inject_modify_ticket_xml(pcmk__output_t *out, va_list args) { const char *action = va_arg(args, const char *); const char *ticket = va_arg(args, const char *); + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - pcmk__output_create_xml_node(out, PCMK_XE_MODIFY_TICKET, - PCMK_XA_ACTION, action, - PCMK_XA_TICKET, ticket, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_MODIFY_TICKET); + pcmk__xe_set(xml, PCMK_XA_ACTION, action); + pcmk__xe_set(xml, PCMK_XA_TICKET, ticket); + return pcmk_rc_ok; } @@ -1732,18 +1721,15 @@ inject_pseudo_action_xml(pcmk__output_t *out, va_list args) const char *node = va_arg(args, const char *); const char *task = va_arg(args, const char *); - xmlNodePtr xml_node = NULL; + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - xml_node = pcmk__output_create_xml_node(out, PCMK_XE_PSEUDO_ACTION, - PCMK_XA_TASK, task, - NULL); - if (node) { - pcmk__xe_set(xml_node, PCMK_XA_NODE, node); - } + xml = pcmk__output_create_xml_node(out, PCMK_XE_PSEUDO_ACTION); + pcmk__xe_set(xml, PCMK_XA_TASK, task); + pcmk__xe_set(xml, PCMK_XA_NODE, node); return pcmk_rc_ok; } @@ -1783,23 +1769,19 @@ inject_rsc_action_xml(pcmk__output_t *out, va_list args) const char *node = va_arg(args, const char *); guint interval_ms = va_arg(args, guint); - xmlNodePtr xml_node = NULL; + xmlNode *xml = NULL; if (out->is_quiet(out)) { return pcmk_rc_no_output; } - xml_node = pcmk__output_create_xml_node(out, PCMK_XE_RSC_ACTION, - PCMK_XA_RESOURCE, rsc, - PCMK_XA_OP, operation, - PCMK_XA_NODE, node, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_RSC_ACTION); + pcmk__xe_set(xml, PCMK_XA_RESOURCE, rsc); + pcmk__xe_set(xml, PCMK_XA_OP, operation); + pcmk__xe_set(xml, PCMK_XA_NODE, node); - if (interval_ms) { - char *interval_s = pcmk__itoa(interval_ms); - - pcmk__xe_set(xml_node, PCMK_XA_INTERVAL, interval_s); - free(interval_s); + if (interval_ms != 0) { + pcmk__xe_set_guint(xml, PCMK_XA_INTERVAL, interval_ms); } return pcmk_rc_ok; @@ -2208,10 +2190,7 @@ attribute_default(pcmk__output_t *out, va_list args) s = g_string_sized_new(50); - if (pcmk__xml_needs_escape(value, pcmk__xml_escape_attr_pretty)) { - value_esc = pcmk__xml_escape(value, pcmk__xml_escape_attr_pretty); - value = value_esc; - } + value_esc = pcmk__xml_escape(value, pcmk__xml_escape_attr_pretty); if (!pcmk__str_empty(scope)) { KV_PAIR(PCMK_XA_SCOPE, scope); @@ -2228,9 +2207,11 @@ attribute_default(pcmk__output_t *out, va_list args) } if (legacy) { - pcmk__g_strcat(s, PCMK_XA_VALUE "=", pcmk__s(value, "(null)"), NULL); + pcmk__g_strcat(s, PCMK_XA_VALUE "=", pcmk__s(value_esc, "(null)"), + NULL); } else { - pcmk__g_strcat(s, PCMK_XA_VALUE "=\"", pcmk__s(value, ""), "\"", NULL); + pcmk__g_strcat(s, PCMK_XA_VALUE "=\"", pcmk__s(value_esc, ""), "\"", + NULL); } out->info(out, "%s", s->str); @@ -2253,23 +2234,21 @@ attribute_xml(pcmk__output_t *out, va_list args) bool quiet G_GNUC_UNUSED = va_arg(args, int); bool legacy G_GNUC_UNUSED = va_arg(args, int); - xmlNodePtr node = NULL; + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE); - node = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, pcmk__s(value, ""), - NULL); + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_VALUE, pcmk__s(value, "")); if (!pcmk__str_empty(scope)) { - pcmk__xe_set(node, PCMK_XA_SCOPE, scope); + pcmk__xe_set(xml, PCMK_XA_SCOPE, scope); } if (!pcmk__str_empty(instance)) { - pcmk__xe_set(node, PCMK_XA_ID, instance); + pcmk__xe_set(xml, PCMK_XA_ID, instance); } if (!pcmk__str_empty(host)) { - pcmk__xe_set(node, PCMK_XA_HOST, host); + pcmk__xe_set(xml, PCMK_XA_HOST, host); } return pcmk_rc_ok; @@ -2311,13 +2290,10 @@ rule_check_xml(pcmk__output_t *out, va_list args) int result = va_arg(args, int); const char *error = va_arg(args, const char *); - char *rc_str = pcmk__itoa(pcmk_rc2exitc(result)); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_RULE_CHECK); - pcmk__output_create_xml_node(out, PCMK_XE_RULE_CHECK, - PCMK_XA_RULE_ID, rule_id, - PCMK_XA_RC, rc_str, - NULL); - free(rc_str); + pcmk__xe_set(xml, PCMK_XA_RULE_ID, rule_id); + pcmk__xe_set_int(xml, PCMK_XA_RC, pcmk_rc2exitc(result)); switch (result) { case pcmk_rc_within_range: @@ -2406,14 +2382,12 @@ result_code_xml(pcmk__output_t *out, va_list args) const char *name = va_arg(args, const char *); const char *desc = va_arg(args, const char *); - char *code_str = pcmk__itoa(code); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_RESULT_CODE); + + pcmk__xe_set_int(xml, PCMK_XA_CODE, code); + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); - pcmk__output_create_xml_node(out, PCMK_XE_RESULT_CODE, - PCMK_XA_CODE, code_str, - PCMK_XA_NAME, name, - PCMK_XA_DESCRIPTION, desc, - NULL); - free(code_str); return pcmk_rc_ok; } @@ -2436,6 +2410,7 @@ ticket_attribute_xml(pcmk__output_t *out, va_list args) const char *ticket_id = va_arg(args, const char *); const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); + xmlNode *xml = NULL; /* Create: * @@ -2444,13 +2419,15 @@ ticket_attribute_xml(pcmk__output_t *out, va_list args) * * */ - pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS, NULL); - pcmk__output_xml_create_parent(out, PCMK_XE_TICKET, - PCMK_XA_ID, ticket_id, NULL); - pcmk__output_create_xml_node(out, PCMK_XA_ATTRIBUTE, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, value, - NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS); + + xml = pcmk__output_xml_create_parent(out, PCMK_XE_TICKET); + pcmk__xe_set(xml, PCMK_XA_ID, ticket_id); + + xml = pcmk__output_create_xml_node(out, PCMK_XA_ATTRIBUTE); + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_VALUE, value); + pcmk__output_xml_pop_parent(out); pcmk__output_xml_pop_parent(out); @@ -2511,10 +2488,12 @@ add_ticket_element_with_constraints(xmlNode *node, void *userdata) { pcmk__output_t *out = (pcmk__output_t *) userdata; const char *ticket_id = pcmk__xe_get(node, PCMK_XA_TICKET); + xmlNode *xml = NULL; - pcmk__output_xml_create_parent(out, PCMK_XE_TICKET, - PCMK_XA_ID, ticket_id, NULL); - pcmk__output_xml_create_parent(out, PCMK_XE_CONSTRAINTS, NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_TICKET); + pcmk__xe_set(xml, PCMK_XA_ID, ticket_id); + + pcmk__output_xml_create_parent(out, PCMK_XE_CONSTRAINTS); pcmk__output_xml_add_node_copy(out, node); /* Pop two parents so now we are back under the element */ @@ -2527,11 +2506,13 @@ add_ticket_element_with_constraints(xmlNode *node, void *userdata) static int add_resource_element(xmlNode *node, void *userdata) { - pcmk__output_t *out = (pcmk__output_t *) userdata; + pcmk__output_t *out = userdata; const char *rsc = pcmk__xe_get(node, PCMK_XA_RSC); + xmlNode *xml = NULL; + + xml = pcmk__output_create_xml_node(out, PCMK_XE_RESOURCE); + pcmk__xe_set(xml, PCMK_XA_ID, rsc); - pcmk__output_create_xml_node(out, PCMK_XE_RESOURCE, - PCMK_XA_ID, rsc, NULL); return pcmk_rc_ok; } @@ -2551,7 +2532,7 @@ ticket_constraints_xml(pcmk__output_t *out, va_list args) * ... * */ - pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS); if (pcmk__xe_is(constraint_xml, PCMK__XE_XPATH_QUERY)) { /* Iterate through the list of children once to create all the @@ -2574,7 +2555,7 @@ ticket_constraints_xml(pcmk__output_t *out, va_list args) * ... * */ - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES); pcmk__xe_foreach_child(constraint_xml, NULL, add_resource_element, out); pcmk__output_xml_pop_parent(out); @@ -2585,7 +2566,7 @@ ticket_constraints_xml(pcmk__output_t *out, va_list args) add_ticket_element_with_constraints(constraint_xml, out); pcmk__output_xml_pop_parent(out); - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES); add_resource_element(constraint_xml, out); pcmk__output_xml_pop_parent(out); } @@ -2613,10 +2594,11 @@ static int add_ticket_element(xmlNode *node, void *userdata) { pcmk__output_t *out = (pcmk__output_t *) userdata; - xmlNode *ticket_node = NULL; + xmlNode *ticket = NULL; + + ticket = pcmk__output_create_xml_node(out, PCMK_XE_TICKET); + pcmk__xe_copy_attrs(ticket, node, pcmk__xaf_none); - ticket_node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET, NULL); - pcmk__xe_copy_attrs(ticket_node, node, pcmk__xaf_none); return pcmk_rc_ok; } @@ -2632,7 +2614,7 @@ ticket_state_xml(pcmk__output_t *out, va_list args) * ... * */ - pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_TICKETS); if (state_xml->children != NULL) { /* Iterate through the list of children once to create all the diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index 4ef60985f98..de973a81316 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -212,6 +212,8 @@ pe__foreach_bundle_replica(pcmk_resource_t *bundle, { const pe__bundle_variant_data_t *bundle_data = NULL; + pcmk__assert(fn != NULL); + get_bundle_variant_data(bundle_data, bundle); for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) { if (!fn((pcmk__bundle_replica_t *) iter->data, user_data)) { @@ -237,6 +239,8 @@ pe__foreach_const_bundle_replica(const pcmk_resource_t *bundle, { const pe__bundle_variant_data_t *bundle_data = NULL; + pcmk__assert(fn != NULL); + get_bundle_variant_data(bundle_data, bundle); for (const GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) { @@ -1428,6 +1432,7 @@ pe__bundle_xml(pcmk__output_t *out, va_list args) pcmk_resource_t *remote = replica->remote; char *id = NULL; gboolean print_ip, print_child, print_ctnr, print_remote; + xmlNode *xml = NULL; pcmk__assert(replica != NULL); @@ -1464,25 +1469,25 @@ pe__bundle_xml(pcmk__output_t *out, va_list args) desc = pe__resource_description(rsc, show_opts); - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_BUNDLE, - PCMK_XA_ID, rsc->id, - PCMK_XA_TYPE, type, - PCMK_XA_IMAGE, bundle_data->image, - PCMK_XA_UNIQUE, unique, - PCMK_XA_MAINTENANCE, maintenance, - PCMK_XA_MANAGED, managed, - PCMK_XA_FAILED, failed, - PCMK_XA_DESCRIPTION, desc, - NULL); - pcmk__assert(rc == pcmk_rc_ok); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_BUNDLE); + pcmk__xe_set(xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set(xml, PCMK_XA_TYPE, type); + pcmk__xe_set(xml, PCMK_XA_IMAGE, bundle_data->image); + pcmk__xe_set(xml, PCMK_XA_UNIQUE, unique); + pcmk__xe_set(xml, PCMK_XA_MAINTENANCE, maintenance); + pcmk__xe_set(xml, PCMK_XA_MANAGED, managed); + pcmk__xe_set(xml, PCMK_XA_FAILED, failed); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); } id = pcmk__itoa(replica->offset); - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, - PCMK_XA_ID, id, - NULL); + + xml = pcmk__output_xml_create_parent(out, PCMK_XE_REPLICA); + pcmk__xe_set(xml, PCMK_XA_ID, id); + free(id); - pcmk__assert(rc == pcmk_rc_ok); + + rc = pcmk_rc_ok; if (print_ip) { out->message(out, (const char *) ip->priv->xml->name, show_opts, diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index 47c7ff18917..aae6d6feceb 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -220,8 +220,7 @@ pcmk_resource_t * pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { bool removed = false; - char *inc_num = NULL; - char *inc_max = NULL; + char *max_instances = NULL; pcmk_resource_t *child_rsc = NULL; xmlNode *child_copy = NULL; clone_variant_data_t *clone_data = NULL; @@ -237,13 +236,11 @@ pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) removed = true; } - // Allocate instance numbers in numerical order (starting at 0) - inc_num = pcmk__itoa(clone_data->total_clones); - inc_max = pcmk__itoa(clone_data->clone_max); - // Set PCMK__META_CLONE in a copy, not the original element child_copy = pcmk__xml_copy(NULL, clone_data->xml_obj_child); - pcmk__xe_set(child_copy, PCMK__META_CLONE, inc_num); + + // Allocate instance numbers in numerical order (starting at 0) + pcmk__xe_set_int(child_copy, PCMK__META_CLONE, clone_data->total_clones); if (pe__unpack_resource(child_copy, &child_rsc, rsc, scheduler) != pcmk_rc_ok) { @@ -260,14 +257,13 @@ pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) pe__set_resource_flags_recursive(child_rsc, pcmk__rsc_removed); } - pcmk__insert_meta(child_rsc->priv, PCMK_META_CLONE_MAX, inc_max); + max_instances = pcmk__itoa(clone_data->clone_max); + pcmk__insert_meta(child_rsc->priv, PCMK_META_CLONE_MAX, max_instances); pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id); - bail: - free(inc_num); - free(inc_max); +bail: + free(max_instances); pcmk__xml_free(child_copy); - return child_rsc; } @@ -571,6 +567,7 @@ pe__clone_xml(pcmk__output_t *out, va_list args) } if (!printed_header) { + xmlNode *xml = NULL; const char *multi_state = pcmk__flag_text(rsc->flags, pcmk__rsc_promotable); const char *unique = pcmk__flag_text(rsc->flags, pcmk__rsc_unique); @@ -578,7 +575,6 @@ pe__clone_xml(pcmk__output_t *out, va_list args) pcmk__rsc_maintenance); const char *managed = pcmk__flag_text(rsc->flags, pcmk__rsc_managed); - const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc)); const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed); const char *ignored = pcmk__flag_text(rsc->flags, pcmk__rsc_ignore_failure); @@ -587,21 +583,21 @@ pe__clone_xml(pcmk__output_t *out, va_list args) printed_header = TRUE; - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE, - PCMK_XA_ID, rsc->id, - PCMK_XA_MULTI_STATE, multi_state, - PCMK_XA_UNIQUE, unique, - PCMK_XA_MAINTENANCE, maintenance, - PCMK_XA_MANAGED, managed, - PCMK_XA_DISABLED, disabled, - PCMK_XA_FAILED, failed, - PCMK_XA_FAILURE_IGNORED, ignored, - PCMK_XA_TARGET_ROLE, target_role, - PCMK_XA_DESCRIPTION, desc, - NULL); - pcmk__assert(rc == pcmk_rc_ok); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_CLONE); + pcmk__xe_set(xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set(xml, PCMK_XA_MULTI_STATE, multi_state); + pcmk__xe_set(xml, PCMK_XA_UNIQUE, unique); + pcmk__xe_set(xml, PCMK_XA_MAINTENANCE, maintenance); + pcmk__xe_set(xml, PCMK_XA_MANAGED, managed); + pcmk__xe_set_bool(xml, PCMK_XA_DISABLED, + pe__resource_is_disabled(rsc)); + pcmk__xe_set(xml, PCMK_XA_FAILED, failed); + pcmk__xe_set(xml, PCMK_XA_FAILURE_IGNORED, ignored); + pcmk__xe_set(xml, PCMK_XA_TARGET_ROLE, target_role); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); } + rc = pcmk_rc_ok; out->message(out, (const char *) child_rsc->priv->xml->name, show_opts, child_rsc, only_node, all); } diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index 60c97f9572c..a6aea899665 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -156,6 +156,31 @@ expand_parents_fixed_nvpairs(const pcmk_resource_t *rsc, g_hash_table_destroy(parent_orig_meta); } +/*! + * \internal + * \brief Add an attribute to a meta-attributes table using \c dup_attr() + * + * \param[in] attr XML attribute + * \param[in,out] user_data Table to add to (GHashTable *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +add_attr_to_meta_table(const xmlAttr *attr, void *user_data) +{ + GHashTable *table = user_data; + const char *value = pcmk__xml_attr_value(attr); + + if (value == NULL) { + return true; + } + + dup_attr((void *) attr->name, (void *) value, table); + return true; +} + /* * \brief Get fully evaluated resource meta-attributes * @@ -173,16 +198,8 @@ get_meta_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc, CRM_CHECK((meta_hash != NULL) && (rsc != NULL) && (scheduler != NULL), return); - for (const xmlAttr *attr = pcmk__xe_first_attr(rsc->priv->xml); - attr != NULL; attr = attr->next) { - - if (attr->children == NULL) { - continue; - } - - dup_attr((void *) attr->name, (void *) attr->children->content, - meta_hash); - } + pcmk__xe_foreach_const_attr(rsc->priv->xml, add_attr_to_meta_table, + meta_hash); rule_input.now = scheduler->priv->now; rule_input.rsc_standard = pcmk__xe_get(rsc->priv->xml, PCMK_XA_CLASS); diff --git a/lib/pengine/group.c b/lib/pengine/group.c index bb6cb62c2d2..728867a8f6b 100644 --- a/lib/pengine/group.c +++ b/lib/pengine/group.c @@ -295,23 +295,23 @@ pe__group_xml(pcmk__output_t *out, va_list args) } if (rc == pcmk_rc_no_output) { - char *count = pcmk__itoa(g_list_length(gIter)); + xmlNode *xml = NULL; const char *maintenance = pcmk__flag_text(rsc->flags, pcmk__rsc_maintenance); const char *managed = pcmk__flag_text(rsc->flags, pcmk__rsc_managed); - const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc)); - - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_GROUP, - PCMK_XA_ID, rsc->id, - PCMK_XA_NUMBER_RESOURCES, count, - PCMK_XA_MAINTENANCE, maintenance, - PCMK_XA_MANAGED, managed, - PCMK_XA_DISABLED, disabled, - PCMK_XA_DESCRIPTION, desc, - NULL); - free(count); - pcmk__assert(rc == pcmk_rc_ok); + + xml = pcmk__output_xml_create_parent(out, PCMK_XE_GROUP); + pcmk__xe_set(xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set_int(xml, PCMK_XA_NUMBER_RESOURCES, + g_list_length(gIter)); + pcmk__xe_set(xml, PCMK_XA_MAINTENANCE, maintenance); + pcmk__xe_set(xml, PCMK_XA_MANAGED, managed); + pcmk__xe_set_bool(xml, PCMK_XA_DISABLED, + pe__resource_is_disabled(rsc)); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); + + rc = pcmk_rc_ok; } out->message(out, (const char *) child_rsc->priv->xml->name, @@ -386,7 +386,7 @@ pe__group_default(pcmk__output_t *out, va_list args) } } - PCMK__OUTPUT_LIST_FOOTER(out, rc); + PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } diff --git a/lib/pengine/native.c b/lib/pengine/native.c index acf6b839f09..b68c4e9d744 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -739,6 +739,7 @@ pe__resource_xml(pcmk__output_t *out, va_list args) GList *only_rsc = va_arg(args, GList *); int rc = pcmk_rc_no_output; + xmlNode *xml = NULL; const bool print_pending = pcmk__is_set(show_opts, pcmk_show_pending); const char *class = pcmk__xe_get(rsc->priv->xml, PCMK_XA_CLASS); const char *prov = pcmk__xe_get(rsc->priv->xml, PCMK_XA_PROVIDER); @@ -746,7 +747,6 @@ pe__resource_xml(pcmk__output_t *out, va_list args) char *ra_name = NULL; const char *rsc_state = native_displayable_state(rsc, print_pending); const char *target_role = NULL; - const char *active = pcmk__btoa(rsc->priv->fns->active(rsc, true)); const char *removed = pcmk__flag_text(rsc->flags, pcmk__rsc_removed); const char *blocked = pcmk__flag_text(rsc->flags, pcmk__rsc_blocked); const char *maintenance = pcmk__flag_text(rsc->flags, @@ -754,7 +754,6 @@ pe__resource_xml(pcmk__output_t *out, va_list args) const char *managed = pcmk__flag_text(rsc->flags, pcmk__rsc_managed); const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed); const char *ignored = pcmk__flag_text(rsc->flags, pcmk__rsc_ignore_failure); - char *nodes_running_on = NULL; const char *pending = print_pending? native_pending_action(rsc) : NULL; const char *locked_to = NULL; const char *desc = pe__resource_description(rsc, show_opts); @@ -774,48 +773,42 @@ pe__resource_xml(pcmk__output_t *out, va_list args) target_role = g_hash_table_lookup(rsc->priv->meta, PCMK_META_TARGET_ROLE); - nodes_running_on = pcmk__itoa(g_list_length(rsc->priv->active_nodes)); - if (rsc->priv->lock_node != NULL) { locked_to = rsc->priv->lock_node->priv->name; } // @COMPAT PCMK_XA_ORPHANED is deprecated since 3.0.2 - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_RESOURCE, - PCMK_XA_ID, rsc_printable_id(rsc), - PCMK_XA_RESOURCE_AGENT, ra_name, - PCMK_XA_ROLE, rsc_state, - PCMK_XA_TARGET_ROLE, target_role, - PCMK_XA_ACTIVE, active, - PCMK_XA_ORPHANED, removed, - PCMK_XA_REMOVED, removed, - PCMK_XA_BLOCKED, blocked, - PCMK_XA_MAINTENANCE, maintenance, - PCMK_XA_MANAGED, managed, - PCMK_XA_FAILED, failed, - PCMK_XA_FAILURE_IGNORED, ignored, - PCMK_XA_NODES_RUNNING_ON, nodes_running_on, - PCMK_XA_PENDING, pending, - PCMK_XA_LOCKED_TO, locked_to, - PCMK_XA_DESCRIPTION, desc, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE); + pcmk__xe_set(xml, PCMK_XA_ID, rsc_printable_id(rsc)); + pcmk__xe_set(xml, PCMK_XA_RESOURCE_AGENT, ra_name); + pcmk__xe_set(xml, PCMK_XA_ROLE, rsc_state); + pcmk__xe_set(xml, PCMK_XA_TARGET_ROLE, target_role); + pcmk__xe_set_bool(xml, PCMK_XA_ACTIVE, rsc->priv->fns->active(rsc, true)); + pcmk__xe_set(xml, PCMK_XA_ORPHANED, removed); + pcmk__xe_set(xml, PCMK_XA_REMOVED, removed); + pcmk__xe_set(xml, PCMK_XA_BLOCKED, blocked); + pcmk__xe_set(xml, PCMK_XA_MAINTENANCE, maintenance); + pcmk__xe_set(xml, PCMK_XA_MANAGED, managed); + pcmk__xe_set(xml, PCMK_XA_FAILED, failed); + pcmk__xe_set(xml, PCMK_XA_FAILURE_IGNORED, ignored); + pcmk__xe_set_int(xml, PCMK_XA_NODES_RUNNING_ON, + g_list_length(rsc->priv->active_nodes)); + pcmk__xe_set(xml, PCMK_XA_PENDING, pending); + pcmk__xe_set(xml, PCMK_XA_LOCKED_TO, locked_to); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); + + rc = pcmk_rc_ok; free(ra_name); - free(nodes_running_on); - - pcmk__assert(rc == pcmk_rc_ok); for (GList *gIter = rsc->priv->active_nodes; gIter != NULL; gIter = gIter->next) { - pcmk_node_t *node = (pcmk_node_t *) gIter->data; - const char *cached = pcmk__btoa(node->details->online); + pcmk_node_t *node = gIter->data; - rc = pe__name_and_nvpairs_xml(out, false, PCMK_XE_NODE, - PCMK_XA_NAME, node->priv->name, - PCMK_XA_ID, node->priv->id, - PCMK_XA_CACHED, cached, - NULL); - pcmk__assert(rc == pcmk_rc_ok); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE); + pcmk__xe_set(xml, PCMK_XA_NAME, node->priv->name); + pcmk__xe_set(xml, PCMK_XA_ID, node->priv->id); + pcmk__xe_set_bool(xml, PCMK_XA_CACHED, node->details->online); } pcmk__output_xml_pop_parent(out); diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c index d71c800cf04..a4a55cb32b0 100644 --- a/lib/pengine/pe_actions.c +++ b/lib/pengine/pe_actions.c @@ -709,11 +709,7 @@ pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node, * This ensures we use the name and interval from the tag. * (See below for the only exception, fence device start/probe timeout.) */ - for (xmlAttrPtr attr = action_config->properties; - attr != NULL; attr = attr->next) { - pcmk__insert_dup(meta, (const char *) attr->name, - pcmk__xml_attr_value(attr)); - } + pcmk__xe_foreach_const_attr(action_config, pcmk__xa_insert_dup, meta); } // Derive default timeout for probes from recurring monitor timeouts diff --git a/lib/pengine/pe_digest.c b/lib/pengine/pe_digest.c index 2c809da9b40..766cb2635a3 100644 --- a/lib/pengine/pe_digest.c +++ b/lib/pengine/pe_digest.c @@ -46,7 +46,7 @@ pe__free_digests(gpointer ptr) // Return true if XML attribute name is an element of a given gchar ** array static bool -attr_in_strv(xmlAttrPtr a, void *user_data) +attr_in_strv(const xmlAttr *a, void *user_data) { const char *name = (const char *) a->name; gchar **strv = user_data; @@ -56,7 +56,7 @@ attr_in_strv(xmlAttrPtr a, void *user_data) // Return true if XML attribute name is not an element of a given gchar ** array static bool -attr_not_in_strv(xmlAttrPtr a, void *user_data) +attr_not_in_strv(const xmlAttr *a, void *user_data) { return !attr_in_strv(a, user_data); } @@ -148,7 +148,7 @@ calculate_main_digest(pcmk__op_digest_t *data, pcmk_resource_t *rsc, // Return true if XML attribute name is a Pacemaker-defined fencing parameter static bool -is_fence_param(xmlAttrPtr attr, void *user_data) +is_fence_param(const xmlAttr *attr, void *user_data) { return pcmk_stonith_param((const char *) attr->name); } diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 7b43314937e..6d20aa2f79b 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -619,29 +619,6 @@ pe__node_display_name(pcmk_node_t *node, bool print_detail) return node_name; } -int -pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name, - ...) -{ - xmlNodePtr xml_node = NULL; - va_list pairs; - - pcmk__assert(tag_name != NULL); - - xml_node = pcmk__output_xml_peek_parent(out); - pcmk__assert(xml_node != NULL); - xml_node = pcmk__xe_create(xml_node, tag_name); - - va_start(pairs, tag_name); - pcmk__xe_set_propv(xml_node, pairs); - va_end(pairs); - - if (is_list) { - pcmk__output_xml_push_parent(out, xml_node); - } - return pcmk_rc_ok; -} - static const char * role_desc(enum rsc_role_e role) { @@ -698,25 +675,22 @@ ban_xml(pcmk__output_t *out, va_list args) { pcmk__location_t *location = va_arg(args, pcmk__location_t *); uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); - const char *promoted_only = pcmk__btoa(location->role_filter == pcmk_role_promoted); - char *weight_s = pcmk__itoa(pe_node->assign->score); - - pcmk__output_create_xml_node(out, PCMK_XE_BAN, - PCMK_XA_ID, location->id, - PCMK_XA_RESOURCE, location->rsc->id, - PCMK_XA_NODE, pe_node->priv->name, - PCMK_XA_WEIGHT, weight_s, - PCMK_XA_PROMOTED_ONLY, promoted_only, - /* This is a deprecated alias for - * promoted_only. Removing it will break - * backward compatibility of the API schema, - * which will require an API schema major - * version bump. - */ - PCMK__XA_PROMOTED_ONLY_LEGACY, promoted_only, - NULL); - - free(weight_s); + const bool promoted_only = location->role_filter == pcmk_role_promoted; + xmlNode *xml = NULL; + + xml = pcmk__output_create_xml_node(out, PCMK_XE_BAN); + pcmk__xe_set(xml, PCMK_XA_ID, location->id); + pcmk__xe_set(xml, PCMK_XA_RESOURCE, location->rsc->id); + pcmk__xe_set(xml, PCMK_XA_NODE, pe_node->priv->name); + pcmk__xe_set_int(xml, PCMK_XA_WEIGHT, pe_node->assign->score); + pcmk__xe_set_bool(xml, PCMK_XA_PROMOTED_ONLY, promoted_only); + + /* @COMPAT This is a deprecated alias for promoted_only. Removing it will + * break backward compatibility of the API schema, which will require an API + * schema major version bump. + */ + pcmk__xe_set_bool(xml, PCMK__XA_PROMOTED_ONLY_LEGACY, promoted_only); + return pcmk_rc_ok; } @@ -772,61 +746,61 @@ cluster_counts_html(pcmk__output_t *out, va_list args) { int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); - xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL); - xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *nodes_xml = pcmk__output_create_xml_node(out, "li"); + xmlNode *resources_xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; - child = pcmk__html_create(nodes_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(nodes_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d node%s configured", nnodes, pcmk__plural_s(nnodes)); if (ndisabled && nblocked) { - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ", %d ", nblocked); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "BLOCKED"); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " from further action due to failure)"); } else if (ndisabled && !nblocked) { - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ")"); } else if (!ndisabled && nblocked) { - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), nblocked); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "BLOCKED"); - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " from further action due to failure)"); } else { - child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(resources_xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured", nresources, pcmk__plural_s(nresources)); } @@ -876,31 +850,15 @@ cluster_counts_xml(pcmk__output_t *out, va_list args) { int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); - xmlNodePtr nodes_node = NULL; - xmlNodePtr resources_node = NULL; - char *s = NULL; - - nodes_node = pcmk__output_create_xml_node(out, PCMK_XE_NODES_CONFIGURED, - NULL); - resources_node = pcmk__output_create_xml_node(out, - PCMK_XE_RESOURCES_CONFIGURED, - NULL); - - s = pcmk__itoa(nnodes); - pcmk__xe_set(nodes_node, PCMK_XA_NUMBER, s); - free(s); + xmlNode *xml = NULL; - s = pcmk__itoa(nresources); - pcmk__xe_set(resources_node, PCMK_XA_NUMBER, s); - free(s); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODES_CONFIGURED); + pcmk__xe_set_int(xml, PCMK_XA_NUMBER, nnodes); - s = pcmk__itoa(ndisabled); - pcmk__xe_set(resources_node, PCMK_XA_DISABLED, s); - free(s); - - s = pcmk__itoa(nblocked); - pcmk__xe_set(resources_node, PCMK_XA_BLOCKED, s); - free(s); + xml = pcmk__output_create_xml_node(out, PCMK_XE_RESOURCES_CONFIGURED); + pcmk__xe_set_int(xml, PCMK_XA_NUMBER, nresources); + pcmk__xe_set_int(xml, PCMK_XA_DISABLED, ndisabled); + pcmk__xe_set_int(xml, PCMK_XA_BLOCKED, nblocked); return pcmk_rc_ok; } @@ -915,41 +873,41 @@ cluster_dc_html(pcmk__output_t *out, va_list args) { char *dc_name = va_arg(args, char *); bool mixed_version = va_arg(args, int); - xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Current DC: "); if (dc) { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s (version %s) -", dc_name, pcmk__s(dc_version_s, "unknown")); if (mixed_version) { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, " MIXED-VERSION"); } - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " partition"); if (pcmk__is_true(quorum)) { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " with"); } else { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, " WITHOUT"); } - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " quorum"); } else { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, "NONE"); } @@ -990,24 +948,20 @@ cluster_dc_xml(pcmk__output_t *out, va_list args) { char *dc_name G_GNUC_UNUSED = va_arg(args, char *); bool mixed_version = va_arg(args, int); - if (dc) { - const char *with_quorum = pcmk__btoa(pcmk__is_true(quorum)); - const char *mixed_version_s = pcmk__btoa(mixed_version); - - pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC, - PCMK_XA_PRESENT, PCMK_VALUE_TRUE, - PCMK_XA_VERSION, pcmk__s(dc_version_s, ""), - PCMK_XA_NAME, dc->priv->name, - PCMK_XA_ID, dc->priv->id, - PCMK_XA_WITH_QUORUM, with_quorum, - PCMK_XA_MIXED_VERSION, mixed_version_s, - NULL); - } else { - pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC, - PCMK_XA_PRESENT, PCMK_VALUE_FALSE, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC); + + if (dc == NULL) { + pcmk__xe_set(xml, PCMK_XA_PRESENT, PCMK_VALUE_FALSE); + return pcmk_rc_ok; } + pcmk__xe_set(xml, PCMK_XA_PRESENT, PCMK_VALUE_TRUE); + pcmk__xe_set(xml, PCMK_XA_VERSION, pcmk__s(dc_version_s, "")); + pcmk__xe_set(xml, PCMK_XA_NAME, dc->priv->name); + pcmk__xe_set(xml, PCMK_XA_ID, dc->priv->id); + pcmk__xe_set_bool(xml, PCMK_XA_WITH_QUORUM, pcmk__is_true(quorum)), + pcmk__xe_set_bool(xml, PCMK_XA_MIXED_VERSION, mixed_version); + return pcmk_rc_ok; } @@ -1076,31 +1030,31 @@ cluster_options_html(pcmk__output_t *out, va_list args) { } if (pcmk__is_set(scheduler->flags, pcmk__sched_in_maintenance)) { - xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Resource management: "); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " (the cluster will not attempt to start, stop," " or recover services)"); } else if (pcmk__is_set(scheduler->flags, pcmk__sched_stop_all)) { - xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Resource management: "); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "STOPPED"); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " (the cluster will keep all resources stopped)"); @@ -1216,31 +1170,25 @@ cluster_options_xml(pcmk__output_t *out, va_list args) { pcmk__sched_in_maintenance); const char *stop_all_resources = pcmk__flag_text(scheduler->flags, pcmk__sched_stop_all); - char *fencing_timeout_ms_s = - pcmk__assert_asprintf("%u", scheduler->priv->fence_timeout_ms); - char *priority_fencing_delay_ms_s = - pcmk__assert_asprintf("%u", scheduler->priv->priority_fencing_ms); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_OPTIONS); + + pcmk__xe_set(xml, PCMK_XA_FENCING_ENABLED, fencing_enabled); + pcmk__xe_set_guint(xml, PCMK_XA_FENCING_TIMEOUT_MS, + scheduler->priv->fence_timeout_ms); + pcmk__xe_set(xml, PCMK_XA_SYMMETRIC_CLUSTER, symmetric_cluster); + pcmk__xe_set(xml, PCMK_XA_NO_QUORUM_POLICY, no_quorum_policy); + pcmk__xe_set(xml, PCMK_XA_MAINTENANCE_MODE, maintenance_mode); + pcmk__xe_set(xml, PCMK_XA_STOP_ALL_RESOURCES, stop_all_resources); + pcmk__xe_set_guint(xml, PCMK_XA_PRIORITY_FENCING_DELAY_MS, + scheduler->priv->priority_fencing_ms); /* @COMPAT PCMK_XA_STONITH_ENABLED and PCMK_XA_STONITH_TIMEOUT_MS are * deprecated since 3.0.2 */ - pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_OPTIONS, - PCMK_XA_FENCING_ENABLED, fencing_enabled, - PCMK_XA_FENCING_TIMEOUT_MS, - fencing_timeout_ms_s, - PCMK_XA_SYMMETRIC_CLUSTER, symmetric_cluster, - PCMK_XA_NO_QUORUM_POLICY, no_quorum_policy, - PCMK_XA_MAINTENANCE_MODE, maintenance_mode, - PCMK_XA_STOP_ALL_RESOURCES, stop_all_resources, - PCMK_XA_PRIORITY_FENCING_DELAY_MS, - priority_fencing_delay_ms_s, - PCMK_XA_STONITH_ENABLED, fencing_enabled, - PCMK_XA_STONITH_TIMEOUT_MS, - fencing_timeout_ms_s, - NULL); - free(fencing_timeout_ms_s); - free(priority_fencing_delay_ms_s); + pcmk__xe_set(xml, PCMK_XA_STONITH_ENABLED, fencing_enabled); + pcmk__xe_set_guint(xml, PCMK_XA_STONITH_TIMEOUT_MS, + scheduler->priv->fence_timeout_ms); return pcmk_rc_ok; } @@ -1252,24 +1200,24 @@ cluster_stack_html(pcmk__output_t *out, va_list args) { enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); - xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Stack: "); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", stack_s); if (pcmkd_state != pcmk_pacemakerd_state_invalid) { - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " ("); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", pcmk__pcmkd_state_enum2friendly(pcmkd_state)); - child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ")"); } return pcmk_rc_ok; @@ -1299,16 +1247,16 @@ cluster_stack_xml(pcmk__output_t *out, va_list args) { enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); + xmlNode *xml = NULL; const char *state_s = NULL; if (pcmkd_state != pcmk_pacemakerd_state_invalid) { state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state); } - pcmk__output_create_xml_node(out, PCMK_XE_STACK, - PCMK_XA_TYPE, stack_s, - PCMK_XA_PACEMAKERD_STATE, state_s, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_STACK); + pcmk__xe_set(xml, PCMK_XA_TYPE, stack_s); + pcmk__xe_set(xml, PCMK_XA_PACEMAKERD_STATE, state_s); return pcmk_rc_ok; } @@ -1323,34 +1271,32 @@ cluster_times_html(pcmk__output_t *out, va_list args) { const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); - xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL); - xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *updated = pcmk__output_create_xml_node(out, "li"); + xmlNode *changed = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; char *time_s = NULL; - child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, - PCMK__VALUE_BOLD); + child = pcmk__html_create(updated, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Last updated: "); - child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(updated, PCMK__XE_SPAN, NULL, NULL); time_s = pcmk__epoch2str(NULL, 0); pcmk__xe_set_content(child, "%s", time_s); free(time_s); if (our_nodename != NULL) { - child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(updated, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " on "); - child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(updated, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", our_nodename); } - child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, - PCMK__VALUE_BOLD); + child = pcmk__html_create(changed, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Last change: "); - child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(changed, PCMK__XE_SPAN, NULL, NULL); time_s = last_changed_string(last_written, user, client, origin); pcmk__xe_set_content(child, "%s", time_s); free(time_s); @@ -1368,19 +1314,18 @@ cluster_times_xml(pcmk__output_t *out, va_list args) { const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); + xmlNode *xml = NULL; char *time_s = pcmk__epoch2str(NULL, 0); - pcmk__output_create_xml_node(out, PCMK_XE_LAST_UPDATE, - PCMK_XA_TIME, time_s, - PCMK_XA_ORIGIN, our_nodename, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_LAST_UPDATE); + pcmk__xe_set(xml, PCMK_XA_TIME, time_s); + pcmk__xe_set(xml, PCMK_XA_ORIGIN, our_nodename); - pcmk__output_create_xml_node(out, PCMK_XE_LAST_CHANGE, - PCMK_XA_TIME, pcmk__s(last_written, ""), - PCMK_XA_USER, pcmk__s(user, ""), - PCMK_XA_CLIENT, pcmk__s(client, ""), - PCMK_XA_ORIGIN, pcmk__s(origin, ""), - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_LAST_CHANGE); + pcmk__xe_set(xml, PCMK_XA_TIME, pcmk__s(last_written, "")); + pcmk__xe_set(xml, PCMK_XA_USER, pcmk__s(user, "")); + pcmk__xe_set(xml, PCMK_XA_CLIENT, pcmk__s(client, "")); + pcmk__xe_set(xml, PCMK_XA_ORIGIN, pcmk__s(origin, "")); free(time_s); return pcmk_rc_ok; @@ -1606,39 +1551,29 @@ failed_action_xml(pcmk__output_t *out, va_list args) { int status; const char *uname = pcmk__xe_get(xml_op, PCMK_XA_UNAME); const char *call_id = pcmk__xe_get(xml_op, PCMK__XA_CALL_ID); - const char *exitstatus = NULL; const char *exit_reason = pcmk__s(pcmk__xe_get(xml_op, PCMK_XA_EXIT_REASON), "none"); - const char *status_s = NULL; time_t epoch = 0; - gchar *exit_reason_esc = NULL; - char *rc_s = NULL; - xmlNodePtr node = NULL; + xmlNode *xml = NULL; + gchar *exit_reason_esc = pcmk__xml_escape(exit_reason, + pcmk__xml_escape_attr); - if (pcmk__xml_needs_escape(exit_reason, pcmk__xml_escape_attr)) { - exit_reason_esc = pcmk__xml_escape(exit_reason, pcmk__xml_escape_attr); - exit_reason = exit_reason_esc; - } pcmk__scan_min_int(pcmk__xe_get(xml_op, PCMK__XA_RC_CODE), &rc, 0); pcmk__scan_min_int(pcmk__xe_get(xml_op, PCMK__XA_OP_STATUS), &status, 0); if (pcmk__xe_get(xml_op, PCMK__XA_OPERATION_KEY) == NULL) { op_key_name = PCMK_XA_ID; } - exitstatus = crm_exit_str(rc); - rc_s = pcmk__itoa(rc); - status_s = pcmk_exec_status_str(status); - node = pcmk__output_create_xml_node(out, PCMK_XE_FAILURE, - op_key_name, op_key, - PCMK_XA_NODE, uname, - PCMK_XA_EXITSTATUS, exitstatus, - PCMK_XA_EXITREASON, exit_reason, - PCMK_XA_EXITCODE, rc_s, - PCMK_XA_CALL, call_id, - PCMK_XA_STATUS, status_s, - NULL); - free(rc_s); + + xml = pcmk__output_create_xml_node(out, PCMK_XE_FAILURE); + pcmk__xe_set(xml, op_key_name, op_key); + pcmk__xe_set(xml, PCMK_XA_NODE, uname); + pcmk__xe_set(xml, PCMK_XA_EXITSTATUS, crm_exit_str(rc)); + pcmk__xe_set(xml, PCMK_XA_EXITREASON, exit_reason_esc); + pcmk__xe_set_int(xml, PCMK_XA_EXITCODE, rc); + pcmk__xe_set(xml, PCMK_XA_CALL, call_id); + pcmk__xe_set(xml, PCMK_XA_STATUS, pcmk_exec_status_str(status)); pcmk__xe_get_time(xml_op, PCMK_XA_LAST_RC_CHANGE, &epoch); if (epoch > 0) { @@ -1646,24 +1581,19 @@ failed_action_xml(pcmk__output_t *out, va_list args) { const char *exec = pcmk__xe_get(xml_op, PCMK_XA_EXEC_TIME); const char *task = pcmk__xe_get(xml_op, PCMK_XA_OPERATION); guint interval_ms = 0; - char *interval_ms_s = NULL; char *rc_change = pcmk__epoch2str(&epoch, crm_time_log_date |crm_time_log_timeofday |crm_time_log_with_timezone); pcmk__xe_get_guint(xml_op, PCMK_META_INTERVAL, &interval_ms); - interval_ms_s = pcmk__assert_asprintf("%u", interval_ms); - pcmk__xe_set_props(node, - PCMK_XA_LAST_RC_CHANGE, rc_change, - PCMK_XA_QUEUED, queue_time, - PCMK_XA_EXEC, exec, - PCMK_XA_INTERVAL, interval_ms_s, - PCMK_XA_TASK, task, - NULL); + pcmk__xe_set(xml, PCMK_XA_LAST_RC_CHANGE, rc_change); + pcmk__xe_set(xml, PCMK_XA_QUEUED, queue_time); + pcmk__xe_set(xml, PCMK_XA_EXEC, exec); + pcmk__xe_set_guint(xml, PCMK_XA_INTERVAL, interval_ms); + pcmk__xe_set(xml, PCMK_XA_TASK, task); - free(interval_ms_s); free(rc_change); } @@ -1815,7 +1745,7 @@ node_html(pcmk__output_t *out, va_list args) { GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); out->begin_list(out, NULL, NULL, "%s:", node_name); - item_node = pcmk__output_xml_create_parent(out, "li", NULL); + item_node = pcmk__output_xml_create_parent(out, "li"); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Status:"); status_node(node, item_node, show_opts); @@ -1835,7 +1765,7 @@ node_html(pcmk__output_t *out, va_list args) { int rc = pcmk_rc_no_output; out->begin_list(out, NULL, NULL, "%s:", node_name); - item_node = pcmk__output_xml_create_parent(out, "li", NULL); + item_node = pcmk__output_xml_create_parent(out, "li"); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Status:"); status_node(node, item_node, show_opts); @@ -1855,7 +1785,7 @@ node_html(pcmk__output_t *out, va_list args) { out->end_list(out); } else { - item_node = pcmk__output_create_xml_node(out, "li", NULL); + item_node = pcmk__output_create_xml_node(out, "li"); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "%s:", node_name); @@ -2069,47 +1999,36 @@ node_xml(pcmk__output_t *out, va_list args) { GList *only_rsc = va_arg(args, GList *); if (full) { - const char *online = pcmk__btoa(node->details->online); + xmlNode *xml = NULL; const char *standby = pcmk__flag_text(node->priv->flags, pcmk__node_standby); const char *standby_onfail = pcmk__flag_text(node->priv->flags, pcmk__node_fail_standby); - const char *maintenance = pcmk__btoa(node->details->maintenance); - const char *pending = pcmk__btoa(node->details->pending); - const char *unclean = pcmk__btoa(node->details->unclean); const char *health = health_text(pe__node_health(node)); const char *feature_set = get_node_feature_set(node); - const char *shutdown = pcmk__btoa(node->details->shutdown); const char *expected_up = pcmk__flag_text(node->priv->flags, pcmk__node_expected_up); const bool is_dc = pcmk__same_node(node, node->priv->scheduler->dc_node); - int length = g_list_length(node->details->running_rsc); - char *resources_running = pcmk__itoa(length); const char *node_type = node_variant_text(node->priv->variant); - int rc = pcmk_rc_ok; - - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_NODE, - PCMK_XA_NAME, node->priv->name, - PCMK_XA_ID, node->priv->id, - PCMK_XA_ONLINE, online, - PCMK_XA_STANDBY, standby, - PCMK_XA_STANDBY_ONFAIL, standby_onfail, - PCMK_XA_MAINTENANCE, maintenance, - PCMK_XA_PENDING, pending, - PCMK_XA_UNCLEAN, unclean, - PCMK_XA_HEALTH, health, - PCMK_XA_FEATURE_SET, feature_set, - PCMK_XA_SHUTDOWN, shutdown, - PCMK_XA_EXPECTED_UP, expected_up, - PCMK_XA_IS_DC, pcmk__btoa(is_dc), - PCMK_XA_RESOURCES_RUNNING, resources_running, - PCMK_XA_TYPE, node_type, - NULL); - - free(resources_running); - pcmk__assert(rc == pcmk_rc_ok); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_NODE); + pcmk__xe_set(xml, PCMK_XA_NAME, node->priv->name); + pcmk__xe_set(xml, PCMK_XA_ID, node->priv->id); + pcmk__xe_set_bool(xml, PCMK_XA_ONLINE, node->details->online); + pcmk__xe_set(xml, PCMK_XA_STANDBY, standby); + pcmk__xe_set(xml, PCMK_XA_STANDBY_ONFAIL, standby_onfail); + pcmk__xe_set_bool(xml, PCMK_XA_MAINTENANCE, node->details->maintenance); + pcmk__xe_set_bool(xml, PCMK_XA_PENDING, node->details->pending); + pcmk__xe_set_bool(xml, PCMK_XA_UNCLEAN, node->details->unclean); + pcmk__xe_set(xml, PCMK_XA_HEALTH, health); + pcmk__xe_set(xml, PCMK_XA_FEATURE_SET, feature_set); + pcmk__xe_set_bool(xml, PCMK_XA_SHUTDOWN, node->details->shutdown); + pcmk__xe_set(xml, PCMK_XA_EXPECTED_UP, expected_up); + pcmk__xe_set_bool(xml, PCMK_XA_IS_DC, is_dc); + pcmk__xe_set_int(xml, PCMK_XA_RESOURCES_RUNNING, + g_list_length(node->details->running_rsc)); + pcmk__xe_set(xml, PCMK_XA_TYPE, node_type); if (pcmk__is_guest_or_bundle_node(node)) { xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); @@ -2130,10 +2049,11 @@ node_xml(pcmk__output_t *out, va_list args) { } out->end_list(out); + } else { - pcmk__output_xml_create_parent(out, PCMK_XE_NODE, - PCMK_XA_NAME, node->priv->name, - NULL); + xmlNode *xml = pcmk__output_xml_create_parent(out, PCMK_XE_NODE); + + pcmk__xe_set(xml, PCMK_XA_NAME, node->priv->name); } return pcmk_rc_ok; @@ -2179,23 +2099,23 @@ node_attribute_html(pcmk__output_t *out, va_list args) { if (add_extra) { int v = 0; - xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, "li"); xmlNode *child = NULL; if (value != NULL) { pcmk__scan_min_int(value, &v, INT_MIN); } - child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s: %s", name, value); if (v <= 0) { - child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "(connectivity is lost)"); } else if (v < expected_score) { - child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, + child = pcmk__html_create(xml, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "(connectivity is degraded -- expected %d)", @@ -2276,23 +2196,20 @@ node_and_op_xml(pcmk__output_t *out, va_list args) { const char *uname = pcmk__xe_get(xml_op, PCMK_XA_UNAME); const char *call_id = pcmk__xe_get(xml_op, PCMK__XA_CALL_ID); const char *rc_s = pcmk__xe_get(xml_op, PCMK__XA_RC_CODE); - const char *status_s = NULL; const char *op_rsc = pcmk__xe_get(xml_op, PCMK_XA_RESOURCE); int status; time_t last_change = 0; - xmlNode *node = NULL; + xmlNode *operation = NULL; pcmk__scan_min_int(pcmk__xe_get(xml_op, PCMK__XA_OP_STATUS), &status, PCMK_EXEC_UNKNOWN); - status_s = pcmk_exec_status_str(status); - node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION, - PCMK_XA_OP, pcmk__xe_history_key(xml_op), - PCMK_XA_NODE, uname, - PCMK_XA_CALL, call_id, - PCMK_XA_RC, rc_s, - PCMK_XA_STATUS, status_s, - NULL); + operation = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION); + pcmk__xe_set(operation, PCMK_XA_OP, pcmk__xe_history_key(xml_op)); + pcmk__xe_set(operation, PCMK_XA_NODE, uname); + pcmk__xe_set(operation, PCMK_XA_CALL, call_id); + pcmk__xe_set(operation, PCMK_XA_RC, rc_s); + pcmk__xe_set(operation, PCMK_XA_STATUS, pcmk_exec_status_str(status)); rsc = pe_find_resource(scheduler->priv->resources, op_rsc); @@ -2308,10 +2225,9 @@ node_and_op_xml(pcmk__output_t *out, va_list args) { (has_provider? provider : ""), kind); - pcmk__xe_set_props(node, - PCMK_XA_RSC, rsc_printable_id(rsc), - PCMK_XA_AGENT, agent_tuple, - NULL); + pcmk__xe_set(operation, PCMK_XA_RSC, rsc_printable_id(rsc)); + pcmk__xe_set(operation, PCMK_XA_AGENT, agent_tuple); + free(agent_tuple); } @@ -2320,10 +2236,8 @@ node_and_op_xml(pcmk__output_t *out, va_list args) { const char *last_rc_change = g_strchomp(ctime(&last_change)); const char *exec_time = pcmk__xe_get(xml_op, PCMK_XA_EXEC_TIME); - pcmk__xe_set_props(node, - PCMK_XA_LAST_RC_CHANGE, last_rc_change, - PCMK_XA_EXEC_TIME, exec_time, - NULL); + pcmk__xe_set(operation, PCMK_XA_LAST_RC_CHANGE, last_rc_change); + pcmk__xe_set(operation, PCMK_XA_EXEC_TIME, exec_time); } return pcmk_rc_ok; @@ -2337,15 +2251,12 @@ node_attribute_xml(pcmk__output_t *out, va_list args) { bool add_extra = va_arg(args, int); int expected_score = va_arg(args, int); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, value, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE); + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_VALUE, value); if (add_extra) { - char *buf = pcmk__itoa(expected_score); - pcmk__xe_set(node, PCMK_XA_EXPECTED, buf); - free(buf); + pcmk__xe_set_int(xml, PCMK_XA_EXPECTED, expected_score); } return pcmk_rc_ok; @@ -2445,11 +2356,12 @@ node_capacity_xml(pcmk__output_t *out, va_list args) const char *uname = node->priv->name; const char *comment = va_arg(args, const char *); - xmlNodePtr xml_node = pcmk__output_create_xml_node(out, PCMK_XE_CAPACITY, - PCMK_XA_NODE, uname, - PCMK_XA_COMMENT, comment, - NULL); - g_hash_table_foreach(node->priv->utilization, add_dump_node, xml_node); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_CAPACITY); + + pcmk__xe_set(xml, PCMK_XA_NODE, uname); + pcmk__xe_set(xml, PCMK_XA_COMMENT, comment); + + g_hash_table_foreach(node->priv->utilization, add_dump_node, xml); return pcmk_rc_ok; } @@ -2804,14 +2716,14 @@ node_weight_xml(pcmk__output_t *out, va_list args) const char *uname = va_arg(args, const char *); const char *score = va_arg(args, const char *); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_NODE_WEIGHT, - PCMK_XA_FUNCTION, prefix, - PCMK_XA_NODE, uname, - PCMK_XA_SCORE, score, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE_WEIGHT); - if (rsc) { - pcmk__xe_set(node, PCMK_XA_ID, rsc->id); + pcmk__xe_set(xml, PCMK_XA_FUNCTION, prefix); + pcmk__xe_set(xml, PCMK_XA_NODE, uname); + pcmk__xe_set(xml, PCMK_XA_SCORE, score); + + if (rsc != NULL) { + pcmk__xe_set(xml, PCMK_XA_ID, rsc->id); } return pcmk_rc_ok; @@ -2845,21 +2757,18 @@ op_history_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); const char *call_id = pcmk__xe_get(xml_op, PCMK__XA_CALL_ID); - char *rc_s = pcmk__itoa(rc); - const char *rc_text = crm_exit_str(rc); - xmlNodePtr node = NULL; - - node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION_HISTORY, - PCMK_XA_CALL, call_id, - PCMK_XA_TASK, task, - PCMK_XA_RC, rc_s, - PCMK_XA_RC_TEXT, rc_text, - NULL); - free(rc_s); + + xmlNode *history = pcmk__output_create_xml_node(out, + PCMK_XE_OPERATION_HISTORY); + pcmk__xe_set(history, PCMK_XA_CALL, call_id); + pcmk__xe_set(history, PCMK_XA_TASK, task); + pcmk__xe_set_int(history, PCMK_XA_RC, rc); + pcmk__xe_set(history, PCMK_XA_RC_TEXT, crm_exit_str(rc)); if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { char *s = pcmk__assert_asprintf("%sms", interval_ms_s); - pcmk__xe_set(node, PCMK_XA_INTERVAL, s); + + pcmk__xe_set(history, PCMK_XA_INTERVAL, s); free(s); } @@ -2870,20 +2779,23 @@ op_history_xml(pcmk__output_t *out, va_list args) { pcmk__xe_get_time(xml_op, PCMK_XA_LAST_RC_CHANGE, &epoch); if (epoch > 0) { char *s = pcmk__epoch2str(&epoch, 0); - pcmk__xe_set(node, PCMK_XA_LAST_RC_CHANGE, s); + + pcmk__xe_set(history, PCMK_XA_LAST_RC_CHANGE, s); free(s); } value = pcmk__xe_get(xml_op, PCMK_XA_EXEC_TIME); if (value) { char *s = pcmk__assert_asprintf("%sms", value); - pcmk__xe_set(node, PCMK_XA_EXEC_TIME, s); + + pcmk__xe_set(history, PCMK_XA_EXEC_TIME, s); free(s); } value = pcmk__xe_get(xml_op, PCMK_XA_QUEUE_TIME); if (value) { char *s = pcmk__assert_asprintf("%sms", value); - pcmk__xe_set(node, PCMK_XA_QUEUE_TIME, s); + + pcmk__xe_set(history, PCMK_XA_QUEUE_TIME, s); free(s); } } @@ -2919,13 +2831,13 @@ promotion_score_xml(pcmk__output_t *out, va_list args) pcmk_node_t *chosen = va_arg(args, pcmk_node_t *); const char *score = va_arg(args, const char *); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_PROMOTION_SCORE, - PCMK_XA_ID, child_rsc->id, - PCMK_XA_SCORE, score, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_PROMOTION_SCORE); - if (chosen) { - pcmk__xe_set(node, PCMK_XA_NODE, chosen->priv->name); + pcmk__xe_set(xml, PCMK_XA_ID, child_rsc->id); + pcmk__xe_set(xml, PCMK_XA_SCORE, score); + + if (chosen != NULL) { + pcmk__xe_set(xml, PCMK_XA_NODE, chosen->priv->name); } return pcmk_rc_ok; @@ -2987,38 +2899,30 @@ resource_history_xml(pcmk__output_t *out, va_list args) { int failcount = va_arg(args, int); time_t last_failure = va_arg(args, time_t); bool as_header = va_arg(args, int); + xmlNode *xml = NULL; - xmlNodePtr node = pcmk__output_xml_create_parent(out, - PCMK_XE_RESOURCE_HISTORY, - PCMK_XA_ID, rsc_id, - NULL); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_HISTORY); + pcmk__xe_set(xml, PCMK_XA_ID, rsc_id); // @COMPAT PCMK_XA_ORPHAN is deprecated since 3.0.2 if (rsc == NULL) { - pcmk__xe_set_bool(node, PCMK_XA_ORPHAN, true); - pcmk__xe_set_bool(node, PCMK_XA_REMOVED, true); + pcmk__xe_set_bool(xml, PCMK_XA_ORPHAN, true); + pcmk__xe_set_bool(xml, PCMK_XA_REMOVED, true); } else if (all || failcount || last_failure > 0) { - char *migration_s = pcmk__itoa(rsc->priv->ban_after_failures); - - pcmk__xe_set_props(node, - PCMK_XA_ORPHAN, PCMK_VALUE_FALSE, - PCMK_XA_REMOVED, PCMK_VALUE_FALSE, - PCMK_META_MIGRATION_THRESHOLD, migration_s, - NULL); - free(migration_s); + pcmk__xe_set(xml, PCMK_XA_ORPHAN, PCMK_VALUE_FALSE); + pcmk__xe_set(xml, PCMK_XA_REMOVED, PCMK_VALUE_FALSE); + pcmk__xe_set_int(xml, PCMK_META_MIGRATION_THRESHOLD, + rsc->priv->ban_after_failures); if (failcount > 0) { - char *s = pcmk__itoa(failcount); - - pcmk__xe_set(node, PCMK_XA_FAIL_COUNT, s); - free(s); + pcmk__xe_set_int(xml, PCMK_XA_FAIL_COUNT, failcount); } if (last_failure > 0) { char *s = pcmk__epoch2str(&last_failure, 0); - pcmk__xe_set(node, PCMK_XA_LAST_FAILURE, s); + pcmk__xe_set(xml, PCMK_XA_LAST_FAILURE, s); free(s); } } @@ -3244,14 +3148,13 @@ resource_util_xml(pcmk__output_t *out, va_list args) const char *uname = node->priv->name; const char *fn = va_arg(args, const char *); - xmlNodePtr xml_node = NULL; + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_UTILIZATION); + + pcmk__xe_set(xml, PCMK_XA_RESOURCE, rsc->id); + pcmk__xe_set(xml, PCMK_XA_NODE, uname); + pcmk__xe_set(xml, PCMK_XA_FUNCTION, fn); - xml_node = pcmk__output_create_xml_node(out, PCMK_XE_UTILIZATION, - PCMK_XA_RESOURCE, rsc->id, - PCMK_XA_NODE, uname, - PCMK_XA_FUNCTION, fn, - NULL); - g_hash_table_foreach(rsc->priv->utilization, add_dump_node, xml_node); + g_hash_table_foreach(rsc->priv->utilization, add_dump_node, xml); return pcmk_rc_ok; } @@ -3366,21 +3269,20 @@ ticket_xml(pcmk__output_t *out, va_list args) { const char *standby = pcmk__flag_text(ticket->flags, pcmk__ticket_standby); - xmlNodePtr node = NULL; + xmlNode *xml = NULL; GHashTableIter iter; const char *name = NULL; const char *value = NULL; - node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET, - PCMK_XA_ID, ticket->id, - PCMK_XA_STATUS, ticket_status(ticket), - PCMK_XA_STANDBY, standby, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_TICKET); + pcmk__xe_set(xml, PCMK_XA_ID, ticket->id); + pcmk__xe_set(xml, PCMK_XA_STATUS, ticket_status(ticket)); + pcmk__xe_set(xml, PCMK_XA_STANDBY, standby); if (ticket->last_granted > -1) { char *buf = pcmk__epoch2str(&ticket->last_granted, 0); - pcmk__xe_set(node, PCMK_XA_LAST_GRANTED, buf); + pcmk__xe_set(xml, PCMK_XA_LAST_GRANTED, buf); free(buf); } @@ -3394,7 +3296,7 @@ ticket_xml(pcmk__output_t *out, va_list args) { continue; } - pcmk__xe_set(node, name, value); + pcmk__xe_set(xml, name, value); } return pcmk_rc_ok; diff --git a/lib/pengine/remote.c b/lib/pengine/remote.c index 45a15f7ee07..d00d9d6c766 100644 --- a/lib/pengine/remote.c +++ b/lib/pengine/remote.c @@ -91,7 +91,10 @@ pe_foreach_guest_node(const pcmk_scheduler_t *scheduler, { GList *iter; - CRM_CHECK(scheduler && host && host->details && helper, return); + pcmk__assert(helper != NULL); + CRM_CHECK((scheduler != NULL) && (host != NULL) && (host->details != NULL), + return); + if (!pcmk__is_set(scheduler->flags, pcmk__sched_have_remote_nodes)) { return; } diff --git a/lib/pengine/unpack.c b/lib/pengine/unpack.c index 1430a58a146..5eee680d409 100644 --- a/lib/pengine/unpack.c +++ b/lib/pengine/unpack.c @@ -986,6 +986,29 @@ unpack_tags(xmlNode *xml_tags, pcmk_scheduler_t *scheduler) return TRUE; } +/*! + * \internal + * \brief Add a non-ID attribute to a hash table of name-value pairs + * + * This is like \c pcmk__xa_insert_dup() but does nothing if the attribute's + * name is \c PCMK_XA_ID. + * + * \param[in] attr XML attribute + * \param[in,out] user_data Name-value pair table (GHashTable *) + * + * \return \c true (to continue iterating) + * + * \note This is compatible with \c pcmk__xe_foreach_const_attr(). + */ +static bool +insert_dup_attr_if_not_id(const xmlAttr *attr, void *user_data) +{ + if (!pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none)) { + pcmk__xa_insert_dup(attr, user_data); + } + return true; +} + /*! * \internal * \brief Unpack a ticket state entry @@ -1004,7 +1027,6 @@ unpack_ticket_state(xmlNode *xml_ticket, void *userdata) const char *granted = NULL; const char *last_granted = NULL; const char *standby = NULL; - xmlAttrPtr xIter = NULL; pcmk__ticket_t *ticket = NULL; @@ -1024,15 +1046,8 @@ unpack_ticket_state(xmlNode *xml_ticket, void *userdata) } } - for (xIter = xml_ticket->properties; xIter; xIter = xIter->next) { - const char *prop_name = (const char *)xIter->name; - const char *prop_value = pcmk__xml_attr_value(xIter); - - if (pcmk__str_eq(prop_name, PCMK_XA_ID, pcmk__str_none)) { - continue; - } - pcmk__insert_dup(ticket->state, prop_name, prop_value); - } + pcmk__xe_foreach_const_attr(xml_ticket, insert_dup_attr_if_not_id, + ticket->state); granted = g_hash_table_lookup(ticket->state, PCMK__XA_GRANTED); if (pcmk__is_true(granted)) { diff --git a/lib/services/services_lsb.c b/lib/services/services_lsb.c index f7ceb86e0e7..b82c3e0d515 100644 --- a/lib/services/services_lsb.c +++ b/lib/services/services_lsb.c @@ -92,9 +92,6 @@ static inline gboolean lsb_meta_helper_get_value(const char *line, gchar **value, const char *prefix) { - /* @TODO Perhaps update later to use pcmk__xml_needs_escape(). Involves many - * extra variables in the caller. - */ if ((*value == NULL) && g_str_has_prefix(line, prefix)) { *value = pcmk__xml_escape(line + strlen(prefix), pcmk__xml_escape_text); return TRUE; diff --git a/lib/services/systemd.c b/lib/services/systemd.c index 676276708db..91d8c24bf29 100644 --- a/lib/services/systemd.c +++ b/lib/services/systemd.c @@ -777,6 +777,7 @@ systemd_unit_metadata(const char *name, int timeout) char *meta = NULL; char *desc = NULL; char *path = NULL; + gchar *desc_esc = NULL; if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) { /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */ @@ -786,18 +787,12 @@ systemd_unit_metadata(const char *name, int timeout) desc = pcmk__assert_asprintf("Systemd unit file for %s", name); } - if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) { - gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text); - - meta = pcmk__assert_asprintf(METADATA_FORMAT, name, escaped, name); - g_free(escaped); - - } else { - meta = pcmk__assert_asprintf(METADATA_FORMAT, name, desc, name); - } + desc_esc = pcmk__xml_escape(desc, pcmk__xml_escape_text); + meta = pcmk__assert_asprintf(METADATA_FORMAT, name, desc_esc, name); free(desc); free(path); + g_free(desc_esc); return meta; } diff --git a/tools/cibadmin.c b/tools/cibadmin.c index 0c769aa91ed..b1f48de12d8 100644 --- a/tools/cibadmin.c +++ b/tools/cibadmin.c @@ -844,14 +844,15 @@ static int md5_sum_xml(pcmk__output_t *out, va_list args) { const char *digest = va_arg(args, const char *); + xmlNode *xml = NULL; if (digest == NULL) { return pcmk_rc_no_output; } - pcmk__output_create_xml_node(out, PCMK_XE_MD5_SUM, - PCMK_XA_DIGEST, digest, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_MD5_SUM); + pcmk__xe_set(xml, PCMK_XA_DIGEST, digest); + return pcmk_rc_ok; } @@ -1170,8 +1171,8 @@ main(int argc, char **argv) goto done; } - // @COMPAT Fail if pcmk_acl_required(username) - if (pcmk_acl_required(username)) { + // @COMPAT Fail if pcmk__acl_required(username) + if (pcmk__acl_required(username)) { out->err(out, "Warning: cibadmin is being run as user %s, which is " "subject to ACLs. As a result, ACLs for user %s may " diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 57007aaf2e0..7a90dae0827 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -176,6 +176,7 @@ crm_mon_disconnected_xml(pcmk__output_t *out, va_list args) enum pcmk_pacemakerd_state state = (enum pcmk_pacemakerd_state) va_arg(args, int); const char *state_s = NULL; + xmlNode *xml = NULL; if (out->dest != stdout) { out->reset(out); @@ -185,10 +186,9 @@ crm_mon_disconnected_xml(pcmk__output_t *out, va_list args) state_s = pcmk_pacemakerd_api_daemon_state_enum2text(state); } - pcmk__output_create_xml_node(out, PCMK_XE_CRM_MON_DISCONNECTED, - PCMK_XA_DESCRIPTION, desc, - PCMK_XA_PACEMAKERD_STATE, state_s, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_CRM_MON_DISCONNECTED); + pcmk__xe_set(xml, PCMK_XA_DESCRIPTION, desc); + pcmk__xe_set(xml, PCMK_XA_PACEMAKERD_STATE, state_s); out->finish(out, CRM_EX_DISCONNECT, true, NULL); return pcmk_rc_ok; diff --git a/tools/crm_node.c b/tools/crm_node.c index 3ba888f22ef..b0b121c6875 100644 --- a/tools/crm_node.c +++ b/tools/crm_node.c @@ -155,11 +155,11 @@ static int node_id_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); + xmlNode *xml = NULL; char *id_s = pcmk__assert_asprintf("%" PRIu32, node_id); - pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO, - PCMK_XA_NODEID, id_s, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO); + pcmk__xe_set(xml, PCMK_XA_NODEID, id_s); free(id_s); return pcmk_rc_ok; @@ -190,13 +190,13 @@ simple_node_list_xml(pcmk__output_t *out, va_list args) for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; + xmlNode *xml = NULL; char *id_s = pcmk__assert_asprintf("%" PRIu32, node->id); - pcmk__output_create_xml_node(out, PCMK_XE_NODE, - PCMK_XA_ID, id_s, - PCMK_XA_NAME, node->uname, - PCMK_XA_STATE, node->state, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE); + pcmk__xe_set(xml, PCMK_XA_ID, id_s); + pcmk__xe_set(xml, PCMK_XA_NAME, node->uname); + pcmk__xe_set(xml, PCMK_XA_STATE, node->state); free(id_s); } @@ -222,12 +222,12 @@ node_name_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); const char *node_name = va_arg(args, const char *); + xmlNode *xml = NULL; char *id_s = pcmk__assert_asprintf("%" PRIu32, node_id); - pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO, - PCMK_XA_NODEID, id_s, - PCMK_XA_UNAME, node_name, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO); + pcmk__xe_set(xml, PCMK_XA_NODEID, id_s); + pcmk__xe_set(xml, PCMK_XA_UNAME, node_name); free(id_s); return pcmk_rc_ok; @@ -272,13 +272,14 @@ partition_list_xml(pcmk__output_t *out, va_list args) pcmk_controld_api_node_t *node = node_iter->data; if (pcmk__str_eq(node->state, "member", pcmk__str_none)) { + xmlNode *xml = NULL; char *id_s = pcmk__assert_asprintf("%" PRIu32, node->id); - pcmk__output_create_xml_node(out, PCMK_XE_NODE, - PCMK_XA_ID, id_s, - PCMK_XA_NAME, node->uname, - PCMK_XA_STATE, node->state, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NODE); + pcmk__xe_set(xml, PCMK_XA_ID, id_s); + pcmk__xe_set(xml, PCMK_XA_NAME, node->uname); + pcmk__xe_set(xml, PCMK_XA_STATE, node->state); + free(id_s); } } @@ -300,10 +301,11 @@ PCMK__OUTPUT_ARGS("quorum", "bool") static int quorum_xml(pcmk__output_t *out, va_list args) { bool have_quorum = va_arg(args, int); + xmlNode *xml = NULL; + + xml = pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_INFO); + pcmk__xe_set_bool(xml, PCMK_XA_QUORUM, have_quorum); - pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_INFO, - PCMK_XA_QUORUM, pcmk__btoa(have_quorum), - NULL); return pcmk_rc_ok; } diff --git a/tools/crm_resource_print.c b/tools/crm_resource_print.c index c52e4481616..19485bf19f2 100644 --- a/tools/crm_resource_print.c +++ b/tools/crm_resource_print.c @@ -154,21 +154,19 @@ static int attribute_changed_xml(pcmk__output_t *out, va_list args) { attr_update_data_t *ud = va_arg(args, attr_update_data_t *); + xmlNode *xml = NULL; + const char *rsc_type = (const char *) ud->rsc->priv->xml->name; - pcmk__output_xml_create_parent(out, - (const char *) ud->rsc->priv->xml->name, - PCMK_XA_ID, ud->rsc->id, - NULL); + xml = pcmk__output_xml_create_parent(out, rsc_type); + pcmk__xe_set(xml, PCMK_XA_ID, ud->rsc->id); - pcmk__output_xml_create_parent(out, ud->attr_set_type, - PCMK_XA_ID, ud->attr_set_id, - NULL); + xml = pcmk__output_xml_create_parent(out, ud->attr_set_type); + pcmk__xe_set(xml, PCMK_XA_ID, ud->attr_set_id); - pcmk__output_create_xml_node(out, PCMK_XE_NVPAIR, - PCMK_XA_ID, ud->found_attr_id, - PCMK_XA_VALUE, ud->attr_value, - PCMK_XA_NAME, ud->attr_name, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_NVPAIR); + pcmk__xe_set(xml, PCMK_XA_ID, ud->found_attr_id); + pcmk__xe_set(xml, PCMK_XA_VALUE, ud->attr_value); + pcmk__xe_set(xml, PCMK_XA_NAME, ud->attr_name); pcmk__output_xml_pop_parent(out); pcmk__output_xml_pop_parent(out); @@ -204,7 +202,7 @@ attribute_changed_list_xml(pcmk__output_t *out, va_list args) return pcmk_rc_no_output; } - pcmk__output_xml_create_parent(out, PCMK__XE_RESOURCE_SETTINGS, NULL); + pcmk__output_xml_create_parent(out, PCMK__XE_RESOURCE_SETTINGS); for (GList *iter = results; iter != NULL; iter = iter->next) { attr_update_data_t *ud = iter->data; @@ -292,21 +290,13 @@ agent_status_xml(pcmk__output_t *out, va_list args) { crm_exit_t rc = va_arg(args, crm_exit_t); const char *exit_reason = va_arg(args, const char *); - char *exit_s = pcmk__itoa(rc); - const char *message = crm_exit_str(rc); - char *status_s = pcmk__itoa(status); - const char *execution_message = pcmk_exec_status_str(status); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_AGENT_STATUS); - pcmk__output_create_xml_node(out, PCMK_XE_AGENT_STATUS, - PCMK_XA_CODE, exit_s, - PCMK_XA_MESSAGE, message, - PCMK_XA_EXECUTION_CODE, status_s, - PCMK_XA_EXECUTION_MESSAGE, execution_message, - PCMK_XA_REASON, exit_reason, - NULL); - - free(exit_s); - free(status_s); + pcmk__xe_set_int(xml, PCMK_XA_CODE, rc); + pcmk__xe_set(xml, PCMK_XA_MESSAGE, crm_exit_str(rc)); + pcmk__xe_set_int(xml, PCMK_XA_EXECUTION_CODE, status); + pcmk__xe_set(xml, PCMK_XA_EXECUTION_MESSAGE, pcmk_exec_status_str(status)); + pcmk__xe_set(xml, PCMK_XA_REASON, exit_reason); return pcmk_rc_ok; } @@ -352,14 +342,11 @@ override_xml(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_OVERRIDE, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, value, - NULL); + xmlNode *xml = pcmk__output_create_xml_node(out, PCMK_XE_OVERRIDE); - if (rsc_name != NULL) { - pcmk__xe_set(node, PCMK_XA_RSC, rsc_name); - } + pcmk__xe_set(xml, PCMK_XA_NAME, name); + pcmk__xe_set(xml, PCMK_XA_VALUE, value); + pcmk__xe_set(xml, PCMK_XA_RSC, rsc_name); return pcmk_rc_ok; } @@ -445,19 +432,14 @@ resource_agent_action_xml(pcmk__output_t *out, va_list args) { const char *stdout_data = va_arg(args, const char *); const char *stderr_data = va_arg(args, const char *); - xmlNodePtr node = NULL; - - node = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT_ACTION, - PCMK_XA_ACTION, action, - PCMK_XA_CLASS, class, - PCMK_XA_TYPE, type, - NULL); + xmlNode *xml = NULL; - if (rsc_name) { - pcmk__xe_set(node, PCMK_XA_RSC, rsc_name); - } - - pcmk__xe_set(node, PCMK_XA_PROVIDER, provider); + xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT_ACTION); + pcmk__xe_set(xml, PCMK_XA_ACTION, action); + pcmk__xe_set(xml, PCMK_XA_CLASS, class); + pcmk__xe_set(xml, PCMK_XA_TYPE, type); + pcmk__xe_set(xml, PCMK_XA_RSC, rsc_name); + pcmk__xe_set(xml, PCMK_XA_PROVIDER, provider); if (overrides) { GHashTableIter iter; @@ -547,30 +529,30 @@ static int resource_check_list_xml(pcmk__output_t *out, va_list args) { resource_checks_t *checks = va_arg(args, resource_checks_t *); + xmlNode *xml = NULL; const pcmk_resource_t *parent = pe__const_top_resource(checks->rsc, false); - xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_CHECK, - PCMK_XA_ID, parent->id, - NULL); + xml = pcmk__output_create_xml_node(out, PCMK_XE_CHECK); + pcmk__xe_set(xml, PCMK_XA_ID, parent->id); if (pcmk__is_set(checks->flags, rsc_remain_stopped)) { - pcmk__xe_set_bool(node, PCMK_XA_REMAIN_STOPPED, true); + pcmk__xe_set_bool(xml, PCMK_XA_REMAIN_STOPPED, true); } if (pcmk__is_set(checks->flags, rsc_unpromotable)) { - pcmk__xe_set_bool(node, PCMK_XA_PROMOTABLE, false); + pcmk__xe_set_bool(xml, PCMK_XA_PROMOTABLE, false); } if (pcmk__is_set(checks->flags, rsc_unmanaged)) { - pcmk__xe_set_bool(node, PCMK_XA_UNMANAGED, true); + pcmk__xe_set_bool(xml, PCMK_XA_UNMANAGED, true); } if (pcmk__is_set(checks->flags, rsc_locked)) { - pcmk__xe_set(node, PCMK_XA_LOCKED_TO_HYPHEN, checks->lock_node); + pcmk__xe_set(xml, PCMK_XA_LOCKED_TO_HYPHEN, checks->lock_node); } if (pcmk__is_set(checks->flags, rsc_node_health)) { - pcmk__xe_set_bool(node, PCMK_XA_UNHEALTHY, true); + pcmk__xe_set_bool(xml, PCMK_XA_UNHEALTHY, true); } return pcmk_rc_ok; @@ -627,9 +609,10 @@ resource_search_list_xml(pcmk__output_t *out, va_list args) GList *nodes = va_arg(args, GList *); const gchar *requested_name = va_arg(args, const gchar *); - pcmk__output_xml_create_parent(out, PCMK_XE_NODES, - PCMK_XA_RESOURCE, requested_name, - NULL); + xmlNode *xml = NULL; + + xml = pcmk__output_xml_create_parent(out, PCMK_XE_NODES); + pcmk__xe_set(xml, PCMK_XA_RESOURCE, requested_name); for (GList *lpc = nodes; lpc != NULL; lpc = lpc->next) { node_info_t *ni = (node_info_t *) lpc->data; @@ -739,26 +722,23 @@ resource_reasons_list_xml(pcmk__output_t *out, va_list args) const char *host_uname = (node == NULL)? NULL : node->priv->name; - xmlNodePtr xml_node = pcmk__output_xml_create_parent(out, PCMK_XE_REASON, - NULL); + xmlNode *reason = pcmk__output_xml_create_parent(out, PCMK_XE_REASON); if ((rsc == NULL) && (host_uname == NULL)) { GList *lpc = NULL; GList *hosts = NULL; - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES); for (lpc = resources; lpc != NULL; lpc = lpc->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data; - const char *running = NULL; + xmlNode *rsc_xml = NULL; rsc->priv->fns->location(rsc, &hosts, pcmk__rsc_node_current); - running = pcmk__btoa(hosts != NULL); - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE, - PCMK_XA_ID, rsc->id, - PCMK_XA_RUNNING, running, - NULL); + rsc_xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE); + pcmk__xe_set(rsc_xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set_bool(rsc_xml, PCMK_XA_RUNNING, (hosts != NULL)); cli_resource_check(out, rsc, NULL); pcmk__output_xml_pop_parent(out); @@ -769,7 +749,7 @@ resource_reasons_list_xml(pcmk__output_t *out, va_list args) } else if ((rsc != NULL) && (host_uname != NULL)) { if (resource_is_running_on(rsc, host_uname)) { - pcmk__xe_set(xml_node, PCMK_XA_RUNNING_ON, host_uname); + pcmk__xe_set(reason, PCMK_XA_RUNNING_ON, host_uname); } cli_resource_check(out, rsc, node); @@ -781,16 +761,16 @@ resource_reasons_list_xml(pcmk__output_t *out, va_list args) GList *unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp); GList *lpc = NULL; - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCES); for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data; + xmlNode *rsc_xml = NULL; - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE, - PCMK_XA_ID, rsc->id, - PCMK_XA_RUNNING, PCMK_VALUE_TRUE, - PCMK_XA_HOST, host_uname, - NULL); + rsc_xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE); + pcmk__xe_set(rsc_xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set(rsc_xml, PCMK_XA_RUNNING, PCMK_VALUE_TRUE); + pcmk__xe_set(rsc_xml, PCMK_XA_HOST, host_uname); cli_resource_check(out, rsc, node); pcmk__output_xml_pop_parent(out); @@ -798,12 +778,12 @@ resource_reasons_list_xml(pcmk__output_t *out, va_list args) for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data; + xmlNode *rsc_xml = NULL; - pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE, - PCMK_XA_ID, rsc->id, - PCMK_XA_RUNNING, PCMK_VALUE_FALSE, - PCMK_XA_HOST, host_uname, - NULL); + rsc_xml = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE); + pcmk__xe_set(rsc_xml, PCMK_XA_ID, rsc->id); + pcmk__xe_set(rsc_xml, PCMK_XA_RUNNING, PCMK_VALUE_FALSE); + pcmk__xe_set(rsc_xml, PCMK_XA_HOST, host_uname); cli_resource_check(out, rsc, node); pcmk__output_xml_pop_parent(out); @@ -818,7 +798,7 @@ resource_reasons_list_xml(pcmk__output_t *out, va_list args) GList *hosts = NULL; rsc->priv->fns->location(rsc, &hosts, pcmk__rsc_node_current); - pcmk__xe_set(xml_node, PCMK_XA_RUNNING, pcmk__btoa(hosts != NULL)); + pcmk__xe_set_bool(reason, PCMK_XA_RUNNING, (hosts != NULL)); cli_resource_check(out, rsc, NULL); g_list_free(hosts); } diff --git a/tools/crm_shadow.c b/tools/crm_shadow.c index 890994815de..061f36f994c 100644 --- a/tools/crm_shadow.c +++ b/tools/crm_shadow.c @@ -295,10 +295,11 @@ shadow_xml(pcmk__output_t *out, va_list args) enum shadow_disp_flags flags G_GNUC_UNUSED = (enum shadow_disp_flags) va_arg(args, int); - pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW, - PCMK_XA_INSTANCE, instance, - PCMK_XA_FILE, filename, - NULL); + xmlNode *xml = NULL; + + xml = pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW); + pcmk__xe_set(xml, PCMK_XA_INSTANCE, instance); + pcmk__xe_set(xml, PCMK_XA_FILE, filename); if (content != NULL) { GString *buf = g_string_sized_new(1024); diff --git a/tools/iso8601.c b/tools/iso8601.c index d8d0a1a7f8a..1c411c1da54 100644 --- a/tools/iso8601.c +++ b/tools/iso8601.c @@ -268,7 +268,7 @@ period_xml(pcmk__output_t *out, va_list args) return pcmk_rc_no_output; } - pcmk__output_xml_create_parent(out, PCMK_XE_PERIOD, NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_PERIOD); pcmk__output_create_xml_text_node(out, PCMK_XE_START, start); pcmk__output_create_xml_text_node(out, PCMK_XE_END, end);