From 2cb04a53d9c084194ed966b49eebde36e0e54f80 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 01:47:39 -0800 Subject: [PATCH 001/101] Refactor: libcrmcommon: Use a switch statement in is_mode_allowed() I prefer to list the three meaningful values explicitly. Otherwise we could accept an arbitrary flag that happens to be set in the flag group. Signed-off-by: Reid Wahl --- lib/common/acl.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index ce34b93d6cc..b1bf444bbe6 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -764,30 +764,28 @@ 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; } /*! From bab2a81ee3c068d595c5e2fd8b5778dccc32ab7a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:10:30 -0800 Subject: [PATCH 002/101] Doc: libcrmcommon: Clarify that pcmk__element_xpath() returns non-NULL Signed-off-by: Reid Wahl --- lib/common/xpath.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/common/xpath.c b/lib/common/xpath.c index 96de7b01c42..f0cced1154b 100644 --- a/lib/common/xpath.c +++ b/lib/common/xpath.c @@ -272,10 +272,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) From ada7abfebb7aa8a7f91175eff7d3bd3e76c4025f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:17:44 -0800 Subject: [PATCH 003/101] Refactor: libcrmcommon: Use convenience helpers in implicitly_allowed() Use pcmk__xe_first_attr() and attr_is_not_id(). Signed-off-by: Reid Wahl --- lib/common/acl.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index b1bf444bbe6..77f9acb47a8 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -949,23 +949,24 @@ 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) +implicitly_allowed(xmlNode *xml) { GString *path = NULL; - for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) { - if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) { + for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL; + attr = attr->next) { + + if (attr_is_not_id(attr, NULL)) { return false; } } path = pcmk__element_xpath(xml); - pcmk__assert(path != NULL); - if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) { + if (strstr(path->str, "/" PCMK_XE_ACLS "/") != NULL) { g_string_free(path, TRUE); return false; } From 787684ef5dbcac8f907ae3d6cd7eb865ae1266e6 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:35:02 -0800 Subject: [PATCH 004/101] Refactor: libcrmcommon: Walk up the tree in implicitly_allowed() Instead of creating an XPath string and looking for a substring. It seems clearer this way, though that is debatable. Signed-off-by: Reid Wahl --- lib/common/acl.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 77f9acb47a8..0349dc10d50 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -954,8 +954,6 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, static bool implicitly_allowed(xmlNode *xml) { - GString *path = NULL; - for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL; attr = attr->next) { @@ -964,14 +962,16 @@ implicitly_allowed(xmlNode *xml) } } - path = pcmk__element_xpath(xml); - - if (strstr(path->str, "/" PCMK_XE_ACLS "/") != NULL) { - g_string_free(path, TRUE); - return false; + /* 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; + } } - g_string_free(path, TRUE); return true; } From 116d335c5d319845c244c41edd23eb17ec33b63d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 13:56:34 -0800 Subject: [PATCH 005/101] Refactor: libcrmcommon, libpe_status: Drop strncmp() calls Replace with g_str_has_prefix() in all but one place for clarity. The remaining place is pcmk__ipc_is_authentic_process_active(). In that case, use pcmk__str_eq(). Note that the former length argument was sizeof(last_asked_name), not sizeof(last_asked_name) - 1. This means we were checking whether the two strings were the same length and every character matched up to that length -- in other words, we were checking whether the strings were equal. Signed-off-by: Reid Wahl --- include/crm/pengine/internal.h | 2 +- lib/common/actions.c | 3 ++- lib/common/digest.c | 4 ++-- lib/common/ipc_client.c | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index e27d41cc0cd..c9fba4295fd 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -246,7 +246,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/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/digest.c b/lib/common/digest.c index 211cfe35218..85d88e60536 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -300,10 +300,10 @@ pcmk__xa_filterable(const char *name) static bool should_filter_for_digest(xmlAttrPtr 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 " From 7930f74676745fad05e74bbfbf3b14d1ceb4449d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 14:16:10 -0800 Subject: [PATCH 006/101] Refactor: libcrmcommon: Drop a redundant check in pcmk__xa_remove() We return at the very beginning if attr->parent is NULL. element is then assigned attr->parent, so we know that element is not NULL. Signed-off-by: Reid Wahl --- lib/common/xml_attr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index c7993bbab2f..d3b0785ab36 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -59,7 +59,7 @@ pcmk__xa_remove(xmlAttr *attr, bool force) return EPERM; } - if (!force && (element != NULL) + if (!force && pcmk__xml_doc_all_flags_set(element->doc, pcmk__xf_tracking)) { // Leave in place (marked for removal) until after diff is calculated From 741c6227c0ba27a20e313511e9b93ae9f27f1018 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 14:20:13 -0800 Subject: [PATCH 007/101] Refactor: libcrmcommon: Check force arg sooner in pcmk__xa_remove() The whole function seems clearer to me this way. Signed-off-by: Reid Wahl --- lib/common/xml_attr.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index d3b0785ab36..4ccfbdeb91a 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -50,26 +50,30 @@ 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 - && 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; } From 7e9d3325683375e66bef4e56d3291f47e345761f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 15:28:03 -0800 Subject: [PATCH 008/101] Refactor: libcrmcommon: Functionize cases of new_private_data() Notes: * The tracking flag can never be set when the argument is a document node. If node->_private is not NULL, we return before the switch statement. But pcmk__xml_doc_all_flags_set() returns false when the document's private data is NULL. So tracking is relevant only for element, attribute, and comment nodes. * pcmk__mark_xml_node_dirty() sets the pcmk__xf_dirty flag on the node itself as well as all of its parents. For an element, it doesn't matter whether we call it before or after creating the attributes' private data. Signed-off-by: Reid Wahl --- lib/common/xml.c | 97 +++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 6769c938cbf..8a4678addc1 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -260,6 +260,62 @@ reset_xml_private_data(xml_doc_private_t *docpriv) } } +/*! + * \internal + * \brief Allocate and initialize private data for an XML document + * + * \param[in,out] doc XML document + */ +static void +new_doc_private_data(xmlDoc *doc) +{ + xml_doc_private_t *priv = pcmk__assert_alloc(1, sizeof(xml_doc_private_t)); + + priv->check = PCMK__XML_DOC_PRIVATE_MAGIC; + doc->_private = priv; +} + +/*! + * \internal + * \brief Allocate and initialize private data for a non-document XML node + * + * \param[in,out] xml XML node + */ +static void +new_node_private_data(xmlNode *xml) +{ + 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)); + + priv->check = PCMK__XML_NODE_PRIVATE_MAGIC; + xml->_private = priv; + + if (tracking) { + pcmk__set_xml_flags(priv, pcmk__xf_created); + pcmk__mark_xml_node_dirty(xml); + } +} + +/*! + * \internal + * \brief Allocate and initialize private data for an XML element + * + * \param[in,out] xml XML element + */ +static void +new_element_private_data(xmlNode *xml) +{ + new_node_private_data(xml); + + for (xmlAttr *iter = pcmk__xe_first_attr(xml); iter != NULL; + iter = iter->next) { + + new_node_private_data((xmlNode *) iter); + } +} + /*! * \internal * \brief Allocate and initialize private data for an XML node @@ -274,47 +330,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,11 +359,6 @@ 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); - } - return true; } /*! From f9d0e8b3565ae450fdfb89ba141d8f0a1e012bed Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 16:50:48 -0800 Subject: [PATCH 009/101] Refactor: libcrmcommon: New pcmk__xe_foreach{,_const}_attr() Nothing uses these yet. Signed-off-by: Reid Wahl --- include/crm/common/xml_element_internal.h | 6 +++ lib/common/xml_element.c | 61 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index ec2964c2504..4878cc0ffaa 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -37,6 +37,12 @@ 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); diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 91adf14ff80..ff03ce9bb59 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -26,6 +26,67 @@ #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); + + 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) +{ + 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 From ce29d7c07c6c903eb66cf2f15d12e90910af30a8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 14:35:28 -0800 Subject: [PATCH 010/101] Refactor: libcrmcommon: pcmk__xe_foreach_attr() in new_private_data() Signed-off-by: Reid Wahl --- lib/common/xml.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 8a4678addc1..d6b9237237c 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -298,6 +298,24 @@ new_node_private_data(xmlNode *xml) } } +/*! + * \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) +{ + new_node_private_data((xmlNode *) attr); + return true; +} + /*! * \internal * \brief Allocate and initialize private data for an XML element @@ -308,12 +326,7 @@ static void new_element_private_data(xmlNode *xml) { new_node_private_data(xml); - - for (xmlAttr *iter = pcmk__xe_first_attr(xml); iter != NULL; - iter = iter->next) { - - new_node_private_data((xmlNode *) iter); - } + pcmk__xe_foreach_attr(xml, new_attr_private_data, NULL); } /*! From 7b498d015ecc350d8e0a46d2599b6342b98b300b Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 17:03:00 -0800 Subject: [PATCH 011/101] Refactor: libcrmcommon: Clear flags in reset_xml_private_data() Also rename to reset_doc_private_data(), move the definition to a position below new_private_data() and above its first caller, and add Doxygen. Signed-off-by: Reid Wahl --- lib/common/xml.c | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index d6b9237237c..ce7034659ec 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -245,21 +245,6 @@ free_deleted_object(void *data) } } -// Free and NULL user, ACLs, and deleted objects in an XML node's private data -static void -reset_xml_private_data(xml_doc_private_t *docpriv) -{ - if (docpriv != NULL) { - 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; - } -} - /*! * \internal * \brief Allocate and initialize private data for an XML document @@ -374,6 +359,33 @@ new_private_data(xmlNode *node, void *user_data) } } +/*! + * \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 + */ +static void +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 private data for an XML node @@ -395,7 +407,7 @@ free_private_data(xmlNode *node, void *user_data) } if (node->type == XML_DOCUMENT_NODE) { - reset_xml_private_data((xml_doc_private_t *) node->_private); + reset_doc_private_data((xml_doc_private_t *) node->_private); } else { xml_node_private_t *nodepriv = node->_private; @@ -518,8 +530,7 @@ pcmk__xml_commit_changes(xmlDoc *doc) pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions, NULL); } - reset_xml_private_data(docpriv); - docpriv->flags = pcmk__xf_none; + reset_doc_private_data(docpriv); } /*! From 271ecc5e017fd03016d1d8cb58ca2397904dacf8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 17:19:55 -0800 Subject: [PATCH 012/101] Refactor: libcrmcommon: pcmk__xe_foreach_attr() in free_private_data() To enable this, functionize the pieces of free_private_data(). Signed-off-by: Reid Wahl --- lib/common/xml.c | 84 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index ce7034659ec..5326b91ee53 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -386,6 +386,66 @@ reset_doc_private_data(xml_doc_private_t *docpriv) 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) +{ + 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 @@ -406,23 +466,19 @@ free_private_data(xmlNode *node, void *user_data) return true; } - if (node->type == XML_DOCUMENT_NODE) { - reset_doc_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; } /*! From f08d15e31b0b064e2ca6f8a45b3ca1490c02021b Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 17:26:01 -0800 Subject: [PATCH 013/101] Refactor: libcrmcommon: Use a for-loop in xml_diff_old_attrs() Also rename attr_iter to old_attr and drop the declaration of old_attr within the body. Signed-off-by: Reid Wahl --- lib/common/xml.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 5326b91ee53..c4eb06fe2a0 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1346,15 +1346,13 @@ mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr, static void xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) { - xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml); + for (xmlAttr *old_attr = pcmk__xe_first_attr(old_xml); old_attr != NULL; + old_attr = old_attr->next) { - 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); + const char *name = (const char *) old_attr->name; + const char *old_value = pcmk__xml_attr_value(old_attr); + xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name); - attr_iter = attr_iter->next; if (new_attr == NULL) { mark_attr_deleted(new_xml, (const char *) old_xml->name, name, old_value); From 94645a108dad8ce2ed66a3fb41f9362feb47e6c8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 17:34:00 -0800 Subject: [PATCH 014/101] Refactor: libcrmcommon: Unindent else block in xml_diff_old_attrs() Signed-off-by: Reid Wahl --- lib/common/xml.c | 60 ++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index c4eb06fe2a0..d360522859c 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1350,39 +1350,45 @@ xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) old_attr = old_attr->next) { const char *name = (const char *) old_attr->name; - const char *old_value = pcmk__xml_attr_value(old_attr); + 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, (const char *) old_xml->name, name, old_value); + continue; + } - } 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); - } + new_priv = new_attr->_private; + new_value = pcmk__xe_get(new_xml, name); + + old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); + new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); + + // This attribute isn't new + pcmk__clear_xml_flags(new_priv, 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); } } } From 8114b17db50d28714a72e748636536c0ae8023f1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 17:42:58 -0800 Subject: [PATCH 015/101] Refactor: libcrmcommon: Unindent a bit more of xml_diff_old_attrs() Signed-off-by: Reid Wahl --- lib/common/xml.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index d360522859c..c56fcbcc2a7 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1369,27 +1369,31 @@ xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) new_priv = new_attr->_private; new_value = pcmk__xe_get(new_xml, name); - old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); - new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); - // This attribute isn't new pcmk__clear_xml_flags(new_priv, pcmk__xf_created); - if (strcmp(new_value, old_value) != 0) { + if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) { mark_attr_changed(new_xml, (const char *) old_xml->name, name, old_value); + continue; + } - } 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); + old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); + new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); + + /* 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. + */ + if ((old_pos == new_pos) + || pcmk__xml_doc_all_flags_set(new_xml->doc, + pcmk__xf_ignore_attr_pos + |pcmk__xf_tracking)) { + continue; } + + mark_attr_moved(new_xml, (const char *) old_xml->name, old_attr, + new_attr, old_pos, new_pos); } } From ec3fc57ec05d4d181f4d0b8f5ebde699568718fb Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 18:00:35 -0800 Subject: [PATCH 016/101] Refactor: libcrmcommon: pcmk__xe_foreach_attr() in xml_diff_old_attrs() More precisely, replace xml_diff_old_attrs(). Functionize the for loop body and call the new function through pcmk__xe_foreach_attr(). Signed-off-by: Reid Wahl --- lib/common/xml.c | 96 ++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index c56fcbcc2a7..3756ba3c8ff 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1338,63 +1338,71 @@ mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr, /*! * \internal - * \brief Calculate differences in all previously existing XML attributes + * \brief Mark an XML attribute as deleted, changed, or moved if appropriate * - * \param[in,out] old_xml Original XML to compare - * \param[in,out] new_xml New XML to compare + * 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 void -xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) +static bool +mark_attr_diff(xmlAttr *old_attr, void *user_data) { - for (xmlAttr *old_attr = pcmk__xe_first_attr(old_xml); old_attr != NULL; - old_attr = old_attr->next) { - - const char *name = (const char *) old_attr->name; + xmlNode *old_xml = old_attr->parent; + xmlNode *new_xml = user_data; - xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name); - xml_node_private_t *new_priv = NULL; + const char *name = (const char *) old_attr->name; - const char *old_value = pcmk__xml_attr_value(old_attr); - const char *new_value = NULL; + xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name); + xml_node_private_t *new_priv = NULL; - int old_pos = 0; - int new_pos = 0; + const char *old_value = pcmk__xml_attr_value(old_attr); + const char *new_value = NULL; - if (new_attr == NULL) { - mark_attr_deleted(new_xml, (const char *) old_xml->name, name, - old_value); - continue; - } + int old_pos = 0; + int new_pos = 0; - new_priv = new_attr->_private; - new_value = pcmk__xe_get(new_xml, name); + if (new_attr == NULL) { + mark_attr_deleted(new_xml, (const char *) old_xml->name, name, + old_value); + return true; + } - // This attribute isn't new - pcmk__clear_xml_flags(new_priv, pcmk__xf_created); + new_priv = new_attr->_private; + new_value = pcmk__xe_get(new_xml, name); - if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) { - mark_attr_changed(new_xml, (const char *) old_xml->name, name, - old_value); - continue; - } + // This attribute isn't new + pcmk__clear_xml_flags(new_priv, pcmk__xf_created); - old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); - new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); + if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) { + mark_attr_changed(new_xml, (const char *) old_xml->name, name, + old_value); + return true; + } - /* 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. - */ - if ((old_pos == new_pos) - || pcmk__xml_doc_all_flags_set(new_xml->doc, - pcmk__xf_ignore_attr_pos - |pcmk__xf_tracking)) { - continue; - } + old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); + new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); - mark_attr_moved(new_xml, (const char *) old_xml->name, old_attr, - new_attr, old_pos, new_pos); + /* 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. + */ + if ((old_pos == new_pos) + || pcmk__xml_doc_all_flags_set(new_xml->doc, + pcmk__xf_ignore_attr_pos + |pcmk__xf_tracking)) { + return true; } + + mark_attr_moved(new_xml, (const char *) old_xml->name, old_attr, new_attr, + old_pos, new_pos); + return true; } /*! @@ -1453,7 +1461,7 @@ xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml) pcmk__set_xml_flags(nodepriv, pcmk__xf_created); } - xml_diff_old_attrs(old_xml, new_xml); + pcmk__xe_foreach_attr(old_xml, mark_attr_diff, new_xml); mark_created_attrs(new_xml); } From fe37abc7bffcc491e5895043df6dffbf12814937 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 18:02:36 -0800 Subject: [PATCH 017/101] Refactor: libcrmcommon: Drop redundant check from mark_attr_diff() Signed-off-by: Reid Wahl --- lib/common/xml.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 3756ba3c8ff..cee2489c416 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1389,14 +1389,9 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip); new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip); - /* 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. - */ if ((old_pos == new_pos) || pcmk__xml_doc_all_flags_set(new_xml->doc, - pcmk__xf_ignore_attr_pos - |pcmk__xf_tracking)) { + pcmk__xf_ignore_attr_pos)) { return true; } From d6ed0ea568b36eb0d1c7c1e281f0b6c8b51ba149 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 18:05:50 -0800 Subject: [PATCH 018/101] Refactor: libcrmcommon: Drop redundant args from mark_attr_*() functions We can assume that the old and new XML elements are of the same type. Signed-off-by: Reid Wahl --- lib/common/xml.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index cee2489c416..10d39db36b8 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1251,12 +1251,11 @@ pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type) * 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, +mark_attr_deleted(xmlNode *new_xml, const char *attr_name, const char *old_value) { xml_doc_private_t *docpriv = new_xml->doc->_private; @@ -1279,7 +1278,7 @@ mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name, pcmk__xa_remove(attr, false); pcmk__trace("XML attribute %s=%s was removed from %s", attr_name, old_value, - element); + (const char *) new_xml->name); } /* @@ -1287,14 +1286,14 @@ mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name, * \brief Check ACLs for a changed XML attribute */ static void -mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name, +mark_attr_changed(xmlNode *new_xml, const char *attr_name, const char *old_value) { 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); + attr_name, old_value, vcopy, (const char *) new_xml->name); // Restore the original value (without checking ACLs) pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking); @@ -1311,20 +1310,19 @@ mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name, * \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) +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, element); + 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); @@ -1354,7 +1352,6 @@ mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr, static bool mark_attr_diff(xmlAttr *old_attr, void *user_data) { - xmlNode *old_xml = old_attr->parent; xmlNode *new_xml = user_data; const char *name = (const char *) old_attr->name; @@ -1369,8 +1366,7 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) int new_pos = 0; if (new_attr == NULL) { - mark_attr_deleted(new_xml, (const char *) old_xml->name, name, - old_value); + mark_attr_deleted(new_xml, name, old_value); return true; } @@ -1381,8 +1377,7 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) 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, (const char *) old_xml->name, name, - old_value); + mark_attr_changed(new_xml, name, old_value); return true; } @@ -1395,8 +1390,7 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) return true; } - mark_attr_moved(new_xml, (const char *) old_xml->name, old_attr, new_attr, - old_pos, new_pos); + mark_attr_moved(new_xml, old_attr, new_attr, old_pos, new_pos); return true; } From d954673912d632096307196a657c447130f37d4a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 18:09:43 -0800 Subject: [PATCH 019/101] Refactor: libcrmcommon: Use for loop in mark_created_attrs() Also rename attr_iter to attr, rename attr_name to name, and drop new_attr variable. Signed-off-by: Reid Wahl --- lib/common/xml.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 10d39db36b8..48b9aec584b 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1406,27 +1406,25 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) static void mark_created_attrs(xmlNode *new_xml) { - xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml); + for (xmlAttr *attr = pcmk__xe_first_attr(new_xml); attr != NULL; + attr = attr->next) { - while (attr_iter != NULL) { - xmlAttr *new_attr = attr_iter; - xml_node_private_t *nodepriv = attr_iter->_private; + xml_node_private_t *nodepriv = attr->_private; - attr_iter = attr_iter->next; if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - const char *attr_name = (const char *) new_attr->name; + const char *name = (const char *) attr->name; - pcmk__trace("Created new attribute %s=%s in %s", attr_name, - pcmk__xml_attr_value(new_attr), new_xml->name); + pcmk__trace("Created new attribute %s=%s in %s", name, + pcmk__xml_attr_value(attr), new_xml->name); /* Check ACLs (we can't use the remove-then-create trick because it * would modify the attribute position). */ - if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) { - pcmk__mark_xml_attr_dirty(new_attr); + if (pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) { + pcmk__mark_xml_attr_dirty(attr); } else { // Creation was not allowed, so remove the attribute - pcmk__xa_remove(new_attr, true); + pcmk__xa_remove(attr, true); } } } From b3186d1d86a8813e67488ba126073855ed5b1a6f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 18:13:22 -0800 Subject: [PATCH 020/101] Refactor: libcrmcommon: Unindent most of for loop in mark_created_attrs Signed-off-by: Reid Wahl --- lib/common/xml.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 48b9aec584b..43d2f52a341 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1409,23 +1409,25 @@ mark_created_attrs(xmlNode *new_xml) for (xmlAttr *attr = pcmk__xe_first_attr(new_xml); attr != NULL; attr = attr->next) { + const char *name = (const char *) attr->name; xml_node_private_t *nodepriv = attr->_private; - if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - const char *name = (const char *) attr->name; + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + continue; + } - pcmk__trace("Created new attribute %s=%s in %s", name, - pcmk__xml_attr_value(attr), new_xml->name); + pcmk__trace("Created new attribute %s=%s in %s", name, + pcmk__xml_attr_value(attr), (const char *) new_xml->name); - /* Check ACLs (we can't use the remove-then-create trick because it - * would modify the attribute position). - */ - if (pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) { - pcmk__mark_xml_attr_dirty(attr); - } else { - // Creation was not allowed, so remove the attribute - pcmk__xa_remove(attr, 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__mark_xml_attr_dirty(attr); + + } else { + // Creation was not allowed, so remove the attribute + pcmk__xa_remove(attr, true); } } } From 19ba225ec84974b66d35aebe8417a1de775a5581 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 21:11:39 -0800 Subject: [PATCH 021/101] Refactor: libcrmcommon: pcmk__xe_foreach_attr() for mark_created_attrs() Replace mark_created_attrs(). Functionize the for loop body and call the new function through pcmk__xe_foreach_attr(). Signed-off-by: Reid Wahl --- lib/common/xml.c | 68 ++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 43d2f52a341..09359a626e2 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1396,40 +1396,52 @@ mark_attr_diff(xmlAttr *old_attr, void *user_data) /*! * \internal - * \brief Check all attributes in new XML for creation + * \brief Mark a new attribute dirty if ACLs allow creation, or remove otherwise * - * 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. + * 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. * - * \param[in,out] new_xml XML to check + * 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 void -mark_created_attrs(xmlNode *new_xml) +static bool +check_new_attr_acls(xmlAttr *attr, void *user_data) { - for (xmlAttr *attr = pcmk__xe_first_attr(new_xml); attr != NULL; - attr = attr->next) { - - const char *name = (const char *) attr->name; - xml_node_private_t *nodepriv = attr->_private; + 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)) { - continue; - } - - pcmk__trace("Created new attribute %s=%s in %s", name, - pcmk__xml_attr_value(attr), (const char *) new_xml->name); - - /* Check ACLs (we can't use the remove-then-create trick because it - * would modify the attribute position). - */ - if (pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) { - pcmk__mark_xml_attr_dirty(attr); + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + return true; + } - } else { - // Creation was not allowed, so remove the attribute - pcmk__xa_remove(attr, 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; } /*! @@ -1451,7 +1463,7 @@ xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml) } pcmk__xe_foreach_attr(old_xml, mark_attr_diff, new_xml); - mark_created_attrs(new_xml); + pcmk__xe_foreach_attr(new_xml, check_new_attr_acls, NULL); } /*! From 200b4e49c7b51c2ca34cdf8ea9f4734954cc5b0c Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 21:14:45 -0800 Subject: [PATCH 022/101] Refactor: libcrmcommon: New mark_attr_created() Use with pcmk__xe_foreach_attr(). Signed-off-by: Reid Wahl --- lib/common/xml.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 09359a626e2..75a8be76162 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1241,6 +1241,26 @@ pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type) return g_string_free(copy, FALSE); } +/*! + * \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 @@ -1455,12 +1475,7 @@ 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); - } + 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); From 1a65ed3634916c7ffc43b8bcdecc6d9d57aaee05 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 28 Apr 2026 17:52:14 -0700 Subject: [PATCH 023/101] Refactor: various: Assert fn argument not NULL in foreach functions Signed-off-by: Reid Wahl --- daemons/fenced/fenced_commands.c | 4 ++++ lib/common/ipc_server.c | 4 +++- lib/common/scheduler.c | 3 ++- lib/common/xml.c | 2 ++ lib/common/xml_element.c | 4 ++++ lib/common/xpath.c | 3 ++- lib/fencing/st_client.c | 2 ++ lib/pengine/bundle.c | 4 ++++ lib/pengine/remote.c | 5 ++++- 9 files changed, 27 insertions(+), 4 deletions(-) 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/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/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/xml.c b/lib/common/xml.c index 75a8be76162..1ac10c2bceb 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -87,6 +87,8 @@ bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void *user_data) { + pcmk__assert(fn != NULL); + if (xml == NULL) { return true; } diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index ff03ce9bb59..8101ebe6b71 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -46,6 +46,8 @@ pcmk__xe_foreach_attr(xmlNode *xml, bool (*fn)(xmlAttr *, void *), { xmlAttr *attr = pcmk__xe_first_attr(xml); + pcmk__assert(fn != NULL); + while (attr != NULL) { xmlAttr *next = attr->next; @@ -76,6 +78,8 @@ 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) { diff --git a/lib/common/xpath.c b/lib/common/xpath.c index f0cced1154b..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); 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/pengine/bundle.c b/lib/pengine/bundle.c index 4ef60985f98..5cdc65239df 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) { 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; } From 304220410e9dd9893b586d63215e7ae8b3e1f985 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 28 Apr 2026 23:38:56 -0700 Subject: [PATCH 024/101] Build: devel: Disable Coverity's inconsistent union warnings g_clear_pointer() is triggering these warnings. When we require GLib 2.58 or later, we can probably re-enable the warning. Signed-off-by: Reid Wahl --- devel/Makefile.am | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From d6b687c6fc6ac1702d5ed99d485e5c01d62d1b17 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 21:34:22 -0800 Subject: [PATCH 025/101] Refactor: libcrmcommon: pcmk__xe_copy_attrs pcmk__xe_foreach_const_attr Signed-off-by: Reid Wahl --- lib/common/xml_element.c | 66 +++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 8101ebe6b71..eb939708c20 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -299,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 @@ -315,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; } From b39ffab4f57df8ce4b9696a1d69b2621cbea7f97 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 21:45:19 -0800 Subject: [PATCH 026/101] Refactor: libcrmcommon: Use foreach functions in pcmk__xe_sort_attrs() Signed-off-by: Reid Wahl --- lib/common/xml_element.c | 59 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index eb939708c20..109cfa90f5a 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -391,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 @@ -407,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); } From 99974804599cd0943fc5da356f934b58e0974c14 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 21:59:14 -0800 Subject: [PATCH 027/101] Refactor: libcrmcommon: pcmk__xe_foreach_attr for matching attr removal Signed-off-by: Reid Wahl --- include/crm/common/xml_element_internal.h | 2 +- lib/common/xml_element.c | 61 +++++++++++++++++++---- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index 4878cc0ffaa..e423b0b52ca 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -49,7 +49,7 @@ xmlNode *pcmk__xe_first_child(const xmlNode *parent, const char *node_name, 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)(xmlAttr *, void *), void *user_data); int pcmk__xe_delete_match(xmlNode *xml, xmlNode *search); int pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace); diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 109cfa90f5a..ed9ebf7b5ed 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -501,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)(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 @@ -514,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)(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); } /*! From 59b3db86d33a1dec7a6438257635972465ee9f6d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 22:12:13 -0800 Subject: [PATCH 028/101] Refactor: libcrmcommon: delete matching xe pcmk__xe_foreach_const_attr Signed-off-by: Reid Wahl --- lib/common/xml_element.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index ed9ebf7b5ed..9636ec45eb2 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -824,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 @@ -845,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"); From 2cd11a1081911fb6eb215d9ca9d5dfeb2d356b4d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 22:21:17 -0800 Subject: [PATCH 029/101] Refactor: libpe_status: get_meta_attributes pcmk__xe_foreach_const_attr Signed-off-by: Reid Wahl --- lib/pengine/complex.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) 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); From e95f4c8333548213a57e32d70917846eeaaad438 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 23:27:06 -0800 Subject: [PATCH 030/101] Refactor: libcrmcommon: pcmk__xe_foreach_const_attr() in xml2list() Signed-off-by: Reid Wahl --- lib/common/nvpair.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index b1510b93e12..220114971e5 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -322,6 +322,31 @@ crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name, return nvp; } +/*! + * \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. + * + * \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 +add_attr_to_nvpair_table(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; +} + /*! * \brief Retrieve XML attributes as a hash table * @@ -340,7 +365,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 +378,8 @@ 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, add_attr_to_nvpair_table, + 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)) { From 3db5904e7439e726f67ead1d40e99f31792e8226 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 23:59:37 -0800 Subject: [PATCH 031/101] Refactor: libcrmcommon: Drop check in add_xml_changes_to_patchset() There is only one caller besides the recursive call. patchset is guaranteed to be non-NULL. Signed-off-by: Reid Wahl --- lib/common/patchset.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 311478717b1..2b477a55874 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -52,7 +52,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } // If this XML node is new, just report that - if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { + if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(xml->parent); if (xpath != NULL) { @@ -132,7 +132,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } nodepriv = xml->_private; - if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { + if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { GString *xpath = pcmk__element_xpath(xml); pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), From 0251a0a0b47d412296c19f3081bc7b9007d3418e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 00:07:39 -0800 Subject: [PATCH 032/101] Refactor: libcrmcommon: Drop xpath check in add_xml_changes_to_patchset pcmk__element_xpath() is guaranteed to return non-NULL unless it's argument is NULL. Signed-off-by: Reid Wahl --- lib/common/patchset.c | 51 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 2b477a55874..51bacfc4541 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -54,18 +54,25 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) // If this XML node is new, just report that if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(xml->parent); + int position = 0; - if (xpath != NULL) { - int position = pcmk__xml_position(xml, pcmk__xf_deleted); + if (xpath == NULL) { + /* This can happen only if xml->parent == NULL. + * + * @TODO Is that possible? + */ + return; + } - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + 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, (const char *) xpath->str); - pcmk__xe_set_int(change, PCMK_XE_POSITION, position); - pcmk__xml_copy(change, xml); - g_string_free(xpath, TRUE); - } + change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + + 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, position); + pcmk__xml_copy(change, xml); + g_string_free(xpath, TRUE); return; } @@ -84,15 +91,13 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) if (change == NULL) { GString *xpath = pcmk__element_xpath(xml); - if (xpath != NULL) { - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + 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, (const char *) xpath->str); + pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY); + pcmk__xe_set(change, PCMK_XA_PATH, xpath->str); - change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); - g_string_free(xpath, TRUE); - } + change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); + g_string_free(xpath, TRUE); } attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR); @@ -138,15 +143,13 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), pcmk__xml_position(xml, pcmk__xf_skip)); - if (xpath != NULL) { - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); - 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); - } + 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)); + g_string_free(xpath, TRUE); } } From c219665691ac8f07e58f853a98914558e9e5e37d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 00:15:46 -0800 Subject: [PATCH 033/101] Refactor: libcrmcommon: Clarify pointers in add_xml_changes_to_patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 51bacfc4541..b6fef0da381 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -40,6 +40,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) xmlNode *cIter = NULL; xmlAttr *pIter = NULL; xmlNode *change = NULL; + xmlNode *change_list = NULL; xml_node_private_t *nodepriv = xml->_private; const char *value = NULL; @@ -96,11 +97,11 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY); pcmk__xe_set(change, PCMK_XA_PATH, xpath->str); - change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); + change_list = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); g_string_free(xpath, TRUE); } - attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR); + attr = pcmk__xe_create(change_list, PCMK_XE_CHANGE_ATTR); pcmk__xe_set(attr, PCMK_XA_NAME, (const char *) pIter->name); if (nodepriv->flags & pcmk__xf_deleted) { @@ -117,7 +118,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) if (change) { xmlNode *result = NULL; - change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT); + change = pcmk__xe_create(change, PCMK_XE_CHANGE_RESULT); result = pcmk__xe_create(change, (const char *)xml->name); for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; From eb2f334f6c373158f6274e4c378034d947eea13c Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 14:15:05 -0800 Subject: [PATCH 034/101] Refactor: libcrmcommon: Use foreach for adding attr changes to patchset This change is larger than I'd prefer, but everything here is tied together and I think review will be easier if we do it in one step. In particular, I thought that the previous "create the change element if it's NULL" on every iteration if we found a changed attribute, made the whole thing harder to read. So at the cost of a bit of extra memory allocation (for a GSList) and iteration, we move to the following approach: 1. Make a list of changed or deleted attributes. If the resulting list is empty, stop. Otherwise, continue to next steps. 2. Create a PCMK_XE_CHANGE child of the patchset. 3. Create a PCMK_XE_CHANGE_LIST child of the PCMK_XE_CHANGE. 4. For each changed or deleted attribute, add a PCMK_XE_CHANGE_ATTR child to the PCMK_XE_CHANGE_LIST. 5. Create a PCMK_XE_CHANGE_RESULT child of the PCMK_XE_CHANGE. 6. Create an xml->name child of the PCMK_XE_CHANGE_RESULT. 7. For each attribute that was not deleted, copy it from the source xml to the xml->name child. 8. Free the list of changed attributes. Signed-off-by: Reid Wahl --- lib/common/patchset.c | 155 +++++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 49 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index b6fef0da381..ffe4b99c15b 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -31,6 +31,88 @@ static const char *const vfields[] = { PCMK_XA_NUM_UPDATES, }; +/*! + * \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; + + if (pcmk__any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) { + *changed_attrs = g_slist_append(*changed_attrs, (gpointer) attr); + } + + return true; +} + +/*! + * \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; + + const xml_node_private_t *nodepriv = attr->_private; + xmlNode *change_attr = pcmk__xe_create(change_list, PCMK_XE_CHANGE_ATTR); + + pcmk__xe_set(change_attr, PCMK_XA_NAME, (const char *) attr->name); + + if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + pcmk__xe_set(change_attr, PCMK_XA_OPERATION, "unset"); + + } 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)); + } +} + +/*! + * \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; + + if (!pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { + pcmk__xe_set(target, (const char *) attr->name, + pcmk__xml_attr_value(attr)); + } + + return true; +} + /* Add changes for specified XML to patchset. * For patchset format, refer to diff schema. */ @@ -38,11 +120,9 @@ static void add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { xmlNode *cIter = NULL; - xmlAttr *pIter = NULL; + GSList *changed_attrs = NULL; xmlNode *change = NULL; - xmlNode *change_list = NULL; xml_node_private_t *nodepriv = xml->_private; - const char *value = NULL; if (nodepriv == NULL) { /* Elements that shouldn't occur in a CIB don't have _private set. They @@ -79,56 +159,33 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } // Check each of the XML node's attributes for changes - for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; - pIter = pIter->next) { - xmlNode *attr = NULL; - - nodepriv = pIter->_private; - if (!pcmk__any_flags_set(nodepriv->flags, - pcmk__xf_deleted|pcmk__xf_dirty)) { - continue; - } - - if (change == NULL) { - GString *xpath = pcmk__element_xpath(xml); - - 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); - - change_list = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); - g_string_free(xpath, TRUE); - } - - attr = pcmk__xe_create(change_list, PCMK_XE_CHANGE_ATTR); - - 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"); - - } else { - pcmk__xe_set(attr, PCMK_XA_OPERATION, "set"); + pcmk__xe_foreach_const_attr(xml, append_attr_if_changed, &changed_attrs); + + /* If any attributes changed, create a PCMK_XE_CHANGE child of patchset, + * with the following children: + * - PCMK_XE_CHANGE_LIST, with a PCMK_XE_CHANGE_ATTR child for each deleted + * or modified attribute + * - PCMK_XE_CHANGE_RESULT, with a child of the same type as xml whose + * attributes are set to the changed values + */ + if (changed_attrs != NULL) { + GString *xpath = pcmk__element_xpath(xml); + xmlNode *change_list = NULL; + xmlNode *result = NULL; - value = pcmk__xml_attr_value(pIter); - pcmk__xe_set(attr, PCMK_XA_VALUE, value); - } - } + 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); - if (change) { - xmlNode *result = NULL; + change_list = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); + g_slist_foreach(changed_attrs, add_change_attr, change_list); - change = pcmk__xe_create(change, PCMK_XE_CHANGE_RESULT); - result = pcmk__xe_create(change, (const char *)xml->name); + 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); - 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); - } - } + g_string_free(xpath, TRUE); + g_slist_free(changed_attrs); } // Now recursively do the same for each child node of this node From 2e91f455a80691206d50c11a6cc6ec5a0094a1fb Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 01:19:02 -0800 Subject: [PATCH 035/101] Refactor: libcrmcommon: Drop cIter in add_xml_changes_to_patchset() Signed-off-by: Reid Wahl --- lib/common/patchset.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index ffe4b99c15b..6243d701501 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -119,7 +119,6 @@ copy_attr_if_not_deleted(const xmlAttr *attr, void *user_data) static void add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { - xmlNode *cIter = NULL; GSList *changed_attrs = NULL; xmlNode *change = NULL; xml_node_private_t *nodepriv = xml->_private; @@ -189,9 +188,10 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *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_xml_changes_to_patchset(child, patchset); } nodepriv = xml->_private; From 3057e3e55c6c1e997afa384c504e5ed566b55849 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 14:35:29 -0800 Subject: [PATCH 036/101] Refactor: libcrmcommon: Functionize adding modify change to patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 80 +++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 6243d701501..48019013510 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -113,13 +113,61 @@ copy_attr_if_not_deleted(const xmlAttr *attr, void *user_data) return true; } +/*! + * \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; + } + + xpath = pcmk__element_xpath(xml); + + 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); + + 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); +} + /* Add changes for specified XML to patchset. * For patchset format, refer to diff schema. */ static void add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { - GSList *changed_attrs = NULL; xmlNode *change = NULL; xml_node_private_t *nodepriv = xml->_private; @@ -157,35 +205,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) return; } - // Check each of the XML node's attributes for changes - pcmk__xe_foreach_const_attr(xml, append_attr_if_changed, &changed_attrs); - - /* If any attributes changed, create a PCMK_XE_CHANGE child of patchset, - * with the following children: - * - PCMK_XE_CHANGE_LIST, with a PCMK_XE_CHANGE_ATTR child for each deleted - * or modified attribute - * - PCMK_XE_CHANGE_RESULT, with a child of the same type as xml whose - * attributes are set to the changed values - */ - if (changed_attrs != NULL) { - GString *xpath = pcmk__element_xpath(xml); - xmlNode *change_list = NULL; - xmlNode *result = NULL; - - 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); - - 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); - } + add_modify_change(xml, patchset); // Now recursively do the same for each child node of this node for (xmlNode *child = pcmk__xml_first_child(xml); child != NULL; From d743ad8732534d5588c62c2bc95e9cf1e67d1849 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 14:48:01 -0800 Subject: [PATCH 037/101] Refactor: libcrmcommon: Functionize adding create change to patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 48019013510..17662af7bc4 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -31,6 +31,39 @@ static const char *const vfields[] = { PCMK_XA_NUM_UPDATES, }; +/*! + * \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 *change = NULL; + GString *xpath = pcmk__element_xpath(xml->parent); + + if (xpath == NULL) { + // @TODO This can happen only if xml->parent == NULL. Is that possible? + return; + } + + change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); + + 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); + + g_string_free(xpath, TRUE); +} + /*! * \internal * \brief Append an attribute to a list if it has been deleted or modified @@ -181,27 +214,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) // If this XML node is new, just report that if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) { - GString *xpath = pcmk__element_xpath(xml->parent); - int position = 0; - - if (xpath == NULL) { - /* This can happen only if xml->parent == NULL. - * - * @TODO Is that possible? - */ - return; - } - - position = pcmk__xml_position(xml, pcmk__xf_deleted); - - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); - - 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, position); - pcmk__xml_copy(change, xml); - g_string_free(xpath, TRUE); - + add_create_change(xml, patchset); return; } From 9da03c5eec0ffcfb9193aa5a21d03624bb9a176c Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 14:52:52 -0800 Subject: [PATCH 038/101] Refactor: libcrmcommon: Functionize adding move change to patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 17662af7bc4..a5af7354e1b 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -195,13 +195,39 @@ add_modify_change(const xmlNode *xml, xmlNode *patchset) 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_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { - xmlNode *change = NULL; xml_node_private_t *nodepriv = xml->_private; if (nodepriv == NULL) { @@ -227,20 +253,8 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) add_xml_changes_to_patchset(child, patchset); } - nodepriv = xml->_private; if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { - GString *xpath = pcmk__element_xpath(xml); - - pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), - pcmk__xml_position(xml, pcmk__xf_skip)); - - change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); - - 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)); - g_string_free(xpath, TRUE); + add_move_change(xml, patchset); } } From b5e236f1de4406402644434cbb78d253937778d6 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 14:59:48 -0800 Subject: [PATCH 039/101] Refactor: libcrmcommon: Add modify change only if xml is dirty This doesn't change behavior. It's probably a very slight optimization, but I'm doing this mostly to align with the pcmk__xml_flags guards on the other "add_X_change()" calls. Also rename the function to add_changes_to_patchset() so that it's shorter. Signed-off-by: Reid Wahl --- lib/common/patchset.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index a5af7354e1b..9da544a7a32 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -226,7 +226,7 @@ add_move_change(const xmlNode *xml, xmlNode *patchset) * For patchset format, refer to diff schema. */ static void -add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) +add_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { xml_node_private_t *nodepriv = xml->_private; @@ -244,13 +244,15 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) return; } - add_modify_change(xml, patchset); + 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 (xmlNode *child = pcmk__xml_first_child(xml); child != NULL; child = pcmk__xml_next(child)) { - add_xml_changes_to_patchset(child, patchset); + add_changes_to_patchset(child, patchset); } if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) { @@ -340,7 +342,7 @@ xml_create_patchset_v2(const xmlNode *source, xmlNode *target) } } - add_xml_changes_to_patchset(target, patchset); + add_changes_to_patchset(target, patchset); return patchset; } From 104257f2a8de78d18c17871f0bb9709a91998e98 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 15:13:40 -0800 Subject: [PATCH 040/101] Refactor: libcrmcommon: Functionize adding delete change to patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 9da544a7a32..26422190677 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -31,6 +31,32 @@ static const char *const vfields[] = { PCMK_XA_NUM_UPDATES, }; +/*! + * \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 @@ -295,7 +321,6 @@ 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; @@ -331,16 +356,11 @@ xml_create_patchset_v2(const xmlNode *source, xmlNode *target) 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_changes_to_patchset(target, patchset); return patchset; From b5ca4b2d8ac6774371acb8f54e8ef80a3f7893e1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 15:17:44 -0800 Subject: [PATCH 041/101] Refactor: libcrmcommon: Minor xml_create_patchset_v2() improvements Signed-off-by: Reid Wahl --- lib/common/patchset.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 26422190677..941cee4ca8a 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -320,14 +320,18 @@ is_config_change(xmlNode *xml) static xmlNode * xml_create_patchset_v2(const xmlNode *source, xmlNode *target) { - int lpc = 0; - xml_doc_private_t *docpriv; + xml_doc_private_t *docpriv = NULL; - xmlNode *v = NULL; - xmlNode *version = NULL; xmlNode *patchset = NULL; - pcmk__assert(target != NULL); + xmlNode *version = NULL; + xmlNode *source_version = NULL; + xmlNode *target_version = NULL; + + if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) { + return NULL; + } + pcmk__assert(target->doc != NULL); docpriv = target->doc->_private; @@ -336,24 +340,26 @@ xml_create_patchset_v2(const xmlNode *source, xmlNode *target) version = pcmk__xe_create(patchset, PCMK_XE_VERSION); - 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]); + source_version = pcmk__xe_create(version, PCMK_XE_SOURCE); + + for (int i = 0; i < PCMK__NELEM(vfields); i++) { + const char *value = pcmk__xe_get(source, vfields[i]); if (value == NULL) { value = "1"; } - pcmk__xe_set(v, vfields[lpc], value); + pcmk__xe_set(source_version, vfields[i], 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]); + target_version = pcmk__xe_create(version, PCMK_XE_TARGET); + + for (int i = 0; i < PCMK__NELEM(vfields); i++) { + const char *value = pcmk__xe_get(target, vfields[i]); if (value == NULL) { value = "1"; } - pcmk__xe_set(v, vfields[lpc], value); + pcmk__xe_set(target_version, vfields[i], value); } /* Call this outside of add_changes_to_patchset(). That function is From a64929db4099e8523bdacf51e6e6343d0e43cc94 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 15:26:27 -0800 Subject: [PATCH 042/101] Refactor: libcrmcommon: Functionize setting version fields in patchset Signed-off-by: Reid Wahl --- lib/common/patchset.c | 54 ++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 941cee4ca8a..150740ab366 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -31,6 +31,32 @@ static const char *const vfields[] = { PCMK_XA_NUM_UPDATES, }; +/*! + * \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 +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 @@ -321,12 +347,8 @@ static xmlNode * xml_create_patchset_v2(const xmlNode *source, xmlNode *target) { xml_doc_private_t *docpriv = NULL; - xmlNode *patchset = NULL; - xmlNode *version = NULL; - xmlNode *source_version = NULL; - xmlNode *target_version = NULL; if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) { return NULL; @@ -339,28 +361,8 @@ 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); - - source_version = pcmk__xe_create(version, PCMK_XE_SOURCE); - - for (int i = 0; i < PCMK__NELEM(vfields); i++) { - const char *value = pcmk__xe_get(source, vfields[i]); - - if (value == NULL) { - value = "1"; - } - pcmk__xe_set(source_version, vfields[i], value); - } - - target_version = pcmk__xe_create(version, PCMK_XE_TARGET); - - for (int i = 0; i < PCMK__NELEM(vfields); i++) { - const char *value = pcmk__xe_get(target, vfields[i]); - - if (value == NULL) { - value = "1"; - } - pcmk__xe_set(target_version, vfields[i], value); - } + set_version_fields(version, source, PCMK_XE_SOURCE); + set_version_fields(version, target, PCMK_XE_TARGET); /* 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 From 3e32b4d58ce71d0d150d30e40beaf8a5ebd3ef7d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 02:29:41 -0800 Subject: [PATCH 043/101] Refactor: libcrmcommon: pcmk__xe_copy_attrs() in apply_v2_patchset() Signed-off-by: Reid Wahl --- lib/common/patchset.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 150740ab366..c1652368263 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -868,13 +868,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); From 17bb69048ac9f3476df0a310ea8b08a0ccf54a00 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 02:40:49 -0800 Subject: [PATCH 044/101] Refactor: libcrmcommon: implicitly_allowed() pcmk__xe_foreach_const_attr Signed-off-by: Reid Wahl --- lib/common/acl.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 0349dc10d50..9c0ea552b86 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -788,6 +788,24 @@ is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode) } } +/*! + * \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); +} + /*! * \internal * \brief Check whether an XML attribute's name is not \c PCMK_XA_ID @@ -803,7 +821,7 @@ is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode) static bool attr_is_not_id(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); } /*! @@ -952,14 +970,10 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, * \return \c true if XML element is implicitly allowed, or \c false otherwise */ static bool -implicitly_allowed(xmlNode *xml) +implicitly_allowed(const xmlNode *xml) { - for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL; - attr = attr->next) { - - if (attr_is_not_id(attr, NULL)) { - return false; - } + if (!pcmk__xe_foreach_const_attr(xml, attr_is_id, NULL)) { + return false; } /* Creation is not implicitly allowed for a descendant of PCMK_XE_ACLS, but From 523611ae0e7815723f1e1a49d31abec2aec032cb Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 13:13:30 -0800 Subject: [PATCH 045/101] Refactor: libcrmcommon: Clarify pcmk__xml_attr_value() Signed-off-by: Reid Wahl --- include/crm/common/xml_internal.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 8fd2148354d..24ae827b1ad 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -432,11 +432,23 @@ void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml); 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); From a3609cecc18353607074c7b9b995c5b228f2f060 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 13:14:19 -0800 Subject: [PATCH 046/101] Refactor: libcrmcommon: Simplify/clarify pcmk__dump_xml_attr() Signed-off-by: Reid Wahl --- lib/common/xml_attr.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index 4ccfbdeb91a..b84e4184a5c 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -105,18 +105,20 @@ pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data) * \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] buffer Where to append the content */ void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) { - const char *name = NULL; 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) { + if (attr == NULL) { return; } @@ -125,12 +127,11 @@ pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) return; } - 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; } @@ -140,6 +141,7 @@ pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) value = value_esc; } - pcmk__g_strcat(buffer, " ", name, "=\"", value, "\"", NULL); + pcmk__g_strcat(buffer, " ", (const char *) attr->name, "=\"", value, "\"", + NULL); g_free(value_esc); } From 6074c85a9008127996c309dba5c2e1cd45899d82 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 02:53:18 -0800 Subject: [PATCH 047/101] Refactor: libcrmcommon: pcmk__xe_foreach_const_attr in dump_xml_element Signed-off-by: Reid Wahl --- lib/common/crmcommon_private.h | 2 +- lib/common/xml_attr.c | 20 +++++++++++++------- lib/common/xml_io.c | 32 ++++++++++++++++++++++++++------ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 58add461801..a53ba60746e 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -176,7 +176,7 @@ 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/xml_attr.c b/lib/common/xml_attr.c index b84e4184a5c..ccc2b31d4d7 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -108,23 +108,28 @@ pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data) * 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] buffer Where to append the content + * \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) { + GString *buffer = user_data; const char *value = NULL; gchar *value_esc = NULL; const xml_node_private_t *nodepriv = NULL; if (attr == NULL) { - return; + return true; } nodepriv = attr->_private; if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - return; + return true; } value = pcmk__xml_attr_value(attr); @@ -133,7 +138,7 @@ pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) * including the empty string, could also be a real value that needs to * be treated differently from an unset value. */ - return; + return true; } if (pcmk__xml_needs_escape(value, pcmk__xml_escape_attr)) { @@ -144,4 +149,5 @@ pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) pcmk__g_strcat(buffer, " ", (const char *) attr->name, "=\"", value, "\"", NULL); g_free(value_esc); + return true; } diff --git a/lib/common/xml_io.c b/lib/common/xml_io.c index 62f11f2de6b..23bdb1ee5cb 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) { From d74bda576ab96a365d1458f018e5b7a4a581a6e7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 17:04:24 -0800 Subject: [PATCH 048/101] Low: libcrmcommon: Drop "" fallback in show_xml_element() If show_xml_element() wanted to show an attribute with a NULL value, it would use "" for the value. This didn't make sense. Immediately prior, we XML-escaped the value. "" has XML special characters and would need to be escaped in order to be valid. (Of course, pcmk__xml_show() and its helpers are currently used only with text-like output formats; for those, having perfectly valid XML is less important.) We could use a different fallback string, such as "(null)". However, for any XML element that we create, every attribute in its properties list should have a non-NULL value. So this should have little or no practical effect. "" was a fallback in case of bugs, strange inputs via the public API (for example, do_crm_log_xml()), or other unexpected scenarios. As far as I know, we never promised that NULL attributes would be represented in the output. So I prefer to simply not show them. This will soon allow us to call pcmk__xml_dump_attr() in show_xml_element(). Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 26dfb7f44ca..63eb72a3ddb 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -118,6 +118,10 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, continue; } + if (p_value == NULL) { + continue; + } + if ((hidden != NULL) && !pcmk__str_empty(p_name)) { gchar **hidden_names = g_strsplit(hidden, ",", 0); @@ -129,8 +133,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, p_value_disp = pcmk__xml_escape(p_value, pcmk__xml_escape_attr); - pcmk__g_strcat(buffer, " ", p_name, "=\"", - pcmk__s(p_value_disp, ""), "\"", NULL); + pcmk__g_strcat(buffer, " ", p_name, "=\"", p_value_disp, "\"", NULL); g_free(p_value_disp); } From 377ea8273cad0c69496871d2a95ccac5b707cadf Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 17:37:22 -0800 Subject: [PATCH 049/101] Refactor: libcrmcommon: Call pcmk__dump_xml_attr() in show_xml_element() They do the same thing for attributes, except that show_xml_element() hides hidden attributes. If pcmk__dump_xml_attr() ever hides hidden attributes, then we can reduce duplication even further. For now, I'm not sure what side effects might occur if we hide attributs in pcmk__dump_xml_attr(). Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 63eb72a3ddb..741a9f289bc 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -111,10 +111,9 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, 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)) { + if ((nodepriv != NULL) + && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { continue; } @@ -122,19 +121,20 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, continue; } + // This block is the only real difference from pcmk__dump_xml_attr() 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_string_append_printf(buffer, " %s=\"*****\"", p_name); + g_strfreev(hidden_names); + continue; } + g_strfreev(hidden_names); } - p_value_disp = pcmk__xml_escape(p_value, pcmk__xml_escape_attr); - - pcmk__g_strcat(buffer, " ", p_name, "=\"", p_value_disp, "\"", NULL); - g_free(p_value_disp); + pcmk__dump_xml_attr(attr, buffer); } if ((data->children != NULL) From 57562e3bc890aaa4bd88b8b6e0d7f0ceceff134d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 17:46:35 -0800 Subject: [PATCH 050/101] Refactor: libcrmcommon: Drop a couple checks from show_xml_element() An attribute's value really shouldn't be NULL unless the XML came from public API (and even then, it would be strange). pcmk__dump_xml_attr() already ignores attributes with NULL values. So dropping the NULL check simply means that if an attribute's name is in PCMK__XA_HIDDEN and its value is NULL, we'll now show "*****" for its value instead of skipping it. That should basically never happen in practice, and if it does, this behavior is arguably more correct anyway. Also, an attribute's name can't be empty in valid XML. Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 741a9f289bc..72dd6241f24 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -108,21 +108,16 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; attr = attr->next) { - xml_node_private_t *nodepriv = attr->_private; + const xml_node_private_t *nodepriv = attr->_private; const char *p_name = (const char *) attr->name; - const char *p_value = pcmk__xml_attr_value(attr); if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { continue; } - if (p_value == NULL) { - continue; - } - // This block is the only real difference from pcmk__dump_xml_attr() - if ((hidden != NULL) && !pcmk__str_empty(p_name)) { + if (hidden != NULL) { gchar **hidden_names = g_strsplit(hidden, ",", 0); if (pcmk__g_strv_contains(hidden_names, p_name)) { From c7541ada4487f60a97db37791ddf90daeba7d0ac Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 16:38:13 -0800 Subject: [PATCH 051/101] Refactor: libcrmcommon: pcmk__xe_foreach_const_attr in show_xml_element Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 77 ++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 72dd6241f24..581ef7949fc 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -70,6 +70,55 @@ show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth, return pcmk_rc_no_output; } +/*! + * \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; + const char *name = (const char *) attr->name; + const char *hidden = pcmk__xe_get(attr->parent, PCMK__XA_HIDDEN); + + /* 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 (hidden != NULL) { + gchar **hidden_names = g_strsplit(hidden, ",", 0); + + if (pcmk__g_strv_contains(hidden_names, name)) { + g_string_append_printf(buffer, " %s=\"*****\"", name); + g_strfreev(hidden_names); + return true; + } + g_strfreev(hidden_names); + } + + return pcmk__dump_xml_attr(attr, buffer); +} + /*! * \internal * \brief Output an XML element in a formatted way @@ -97,8 +146,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,31 +153,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) { - const xml_node_private_t *nodepriv = attr->_private; - const char *p_name = (const char *) attr->name; - - if ((nodepriv != NULL) - && pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) { - continue; - } - - // This block is the only real difference from pcmk__dump_xml_attr() - if (hidden != NULL) { - gchar **hidden_names = g_strsplit(hidden, ",", 0); - - if (pcmk__g_strv_contains(hidden_names, p_name)) { - g_string_append_printf(buffer, " %s=\"*****\"", p_name); - g_strfreev(hidden_names); - continue; - } - - g_strfreev(hidden_names); - } - - pcmk__dump_xml_attr(attr, buffer); - } + pcmk__xe_foreach_const_attr(data, show_xml_attribute, buffer); if ((data->children != NULL) && pcmk__is_set(options, pcmk__xml_fmt_children)) { From c5570fd85d428ef98a1b216c16cf1d8f922f3682 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 17:59:58 -0800 Subject: [PATCH 052/101] Refactor: libcrmcommon: Functionize checking whether attribute is hidden Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 48 +++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 581ef7949fc..2121f74aaf3 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -70,6 +70,39 @@ 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 @@ -95,8 +128,6 @@ show_xml_attribute(const xmlAttr *attr, void *user_data) GString *buffer = user_data; const xml_node_private_t *nodepriv = attr->_private; - const char *name = (const char *) attr->name; - const char *hidden = pcmk__xe_get(attr->parent, PCMK__XA_HIDDEN); /* NULL-check nodepriv because, at the time of writing, the argument can be * arbitrary XML from the public API @@ -105,15 +136,10 @@ show_xml_attribute(const xmlAttr *attr, void *user_data) return true; } - if (hidden != NULL) { - gchar **hidden_names = g_strsplit(hidden, ",", 0); - - if (pcmk__g_strv_contains(hidden_names, name)) { - g_string_append_printf(buffer, " %s=\"*****\"", name); - g_strfreev(hidden_names); - return true; - } - g_strfreev(hidden_names); + if (is_attr_hidden(attr)) { + g_string_append_printf(buffer, " %s=\"*****\"", + (const char *) attr->name); + return true; } return pcmk__dump_xml_attr(attr, buffer); From 567ab176be9497775347d6488c8229f71b5e16fb Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 18:30:07 -0800 Subject: [PATCH 053/101] Refactor: libcrmcommon: Rename show_xml_changes_recursive argument From data to xml. I want to use the name data for something else in an upcoming commit, and we typically use "xml" for xmlNode * arguments. Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 2121f74aaf3..24a5e36b693 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -297,22 +297,22 @@ pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, * \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; @@ -322,7 +322,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 @@ -340,11 +340,11 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, } // 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; + for (const xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL; attr = attr->next) { const char *name = (const char *) attr->name; @@ -380,7 +380,7 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, } // 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); @@ -388,13 +388,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); From 00f9d66b5e1a524e8d4359eb0a053b75344813e8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 18:31:41 -0800 Subject: [PATCH 054/101] Refactor: libcrmcommon: foreach_const_attr in show_xml_changes_recursive Signed-off-by: Reid Wahl --- lib/common/xml_display.c | 100 ++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index 24a5e36b693..d049520ebc3 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -292,6 +292,65 @@ 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 @@ -335,6 +394,12 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *xml, 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; } @@ -344,40 +409,7 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *xml, int depth, options|pcmk__xml_fmt_open); // Log changes to attributes - for (const xmlAttr *attr = pcmk__xe_first_attr(xml); 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(xml); child != NULL; From c4994da117e5da41cc163b1e412ed327f3e6281f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 18:40:14 -0800 Subject: [PATCH 055/101] Refactor: libcrmcommon: foreach const attr in pcmk__xe_sort_attrs test Signed-off-by: Reid Wahl --- .../xml_element/pcmk__xe_sort_attrs_test.c | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c b/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c index e7aad245639..5ee342aeaec 100644 --- a/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c +++ b/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c @@ -17,6 +17,30 @@ #include "crmcommon_private.h" // xml_node_private_t +/*! + * \internal + * \brief Add an attribute to a table mapping attribute names to XML flags + * + * \param[in] attr XML attribute + * \param[in,out] user_data Flags table (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); From 3c0984188d0a9bcbcb486f21debe1298096a1401 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 16:41:27 -0800 Subject: [PATCH 056/101] Refactor: libpe_status: foreach_const_attr in pcmk__unpack_action_meta Signed-off-by: Reid Wahl --- include/crm/common/internal.h | 1 + include/crm/common/xml_attr_internal.h | 32 ++++++++++++++++++++++++++ include/crm/common/xml_internal.h | 1 + lib/common/nvpair.c | 28 +--------------------- lib/common/xml_attr.c | 25 ++++++++++++++++++++ lib/pengine/pe_actions.c | 6 +---- 6 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 include/crm/common/xml_attr_internal.h 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/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_internal.h b/include/crm/common/xml_internal.h index 24ae827b1ad..4bfb9e8848f 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -28,6 +28,7 @@ #include // PCMK_XA_ID, PCMK_XE_CLONE // This file is a wrapper for other {xml_*,xpath}_internal.h headers +#include #include #include #include diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index 220114971e5..8d817e675d4 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -322,31 +322,6 @@ crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name, return nvp; } -/*! - * \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. - * - * \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 -add_attr_to_nvpair_table(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; -} - /*! * \brief Retrieve XML attributes as a hash table * @@ -378,8 +353,7 @@ xml2list(const xmlNode *parent) pcmk__log_xml_trace(nvpair_list, "Unpacking"); - pcmk__xe_foreach_const_attr(nvpair_list, add_attr_to_nvpair_table, - nvpair_hash); + 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/xml_attr.c b/lib/common/xml_attr.c index ccc2b31d4d7..52ae6a1cbf7 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -77,6 +77,31 @@ pcmk__xa_remove(xmlAttr *attr, bool force) 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) { 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 From 4aaf54d443284c329cf5982a20a528583191f2d7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 16:48:36 -0800 Subject: [PATCH 057/101] Refactor: libcrmcommon: foreach_const_attr() in unpack_ticket_state() Signed-off-by: Reid Wahl --- lib/pengine/unpack.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) 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)) { From a9622cd719966b07a1bb542482b2f0c21d9298a9 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 18:44:54 -0800 Subject: [PATCH 058/101] Refactor: libcrmcommon: Drop side effect from pcmk__marked_as_deleted() And make it static. Signed-off-by: Reid Wahl --- lib/common/crmcommon_private.h | 3 --- lib/common/xml.c | 22 +++++++++++++++++++++- lib/common/xml_attr.c | 13 ------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index a53ba60746e..1ee78f2215f 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -172,9 +172,6 @@ G_GNUC_PRINTF(2, 3); 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 bool pcmk__dump_xml_attr(const xmlAttr *attr, void *user_data); diff --git a/lib/common/xml.c b/lib/common/xml.c index 1ac10c2bceb..5ccb645d910 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -532,6 +532,26 @@ pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set) return position; } +/*! + * \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(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 @@ -547,7 +567,7 @@ 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); + pcmk__xe_remove_matching_attrs(xml, true, marked_as_deleted, NULL); return true; } diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index 52ae6a1cbf7..24811aa10de 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -113,19 +113,6 @@ 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 From 712c53e78c7ae8f4dea2ded9579b01f7736ac953 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 18:49:22 -0800 Subject: [PATCH 059/101] Refactor: libcrmcommon: pcmk__xe_remove_matching_attrs() match const arg Signed-off-by: Reid Wahl --- daemons/attrd/attrd_messages.c | 5 +++-- include/crm/common/xml_element_internal.h | 2 +- lib/common/acl.c | 2 +- lib/common/digest.c | 2 +- lib/common/xml.c | 2 +- lib/common/xml_element.c | 4 ++-- lib/pengine/pe_digest.c | 6 +++--- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c index 1a680bc6e69..ed90865fb18 100644 --- a/daemons/attrd/attrd_messages.c +++ b/daemons/attrd/attrd_messages.c @@ -24,9 +24,10 @@ int minimum_protocol_version = -1; static GHashTable *attrd_handlers = NULL; static bool -is_sync_point_attr(xmlAttrPtr attr, void *data) +is_sync_point_attr(const xmlAttr *attr, void *data) { - return pcmk__str_eq((const char *) attr->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/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index e423b0b52ca..a19a127e8c3 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -49,7 +49,7 @@ xmlNode *pcmk__xe_first_child(const xmlNode *parent, const char *node_name, 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)(xmlAttr *, 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); diff --git a/lib/common/acl.c b/lib/common/acl.c index 9c0ea552b86..edf3e40d041 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -819,7 +819,7 @@ attr_is_id(const xmlAttr *attr, void *user_data) * \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 !attr_is_id(attr, user_data); } diff --git a/lib/common/digest.c b/lib/common/digest.c index 85d88e60536..53a6f4417a6 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -298,7 +298,7 @@ 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 (g_str_has_prefix((const char *) a->name, CRM_META "_")) { return true; diff --git a/lib/common/xml.c b/lib/common/xml.c index 5ccb645d910..a7ea897198f 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -545,7 +545,7 @@ pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set) * \note This is compatible with \c pcmk__xe_remove_matching_attrs(). */ static bool -marked_as_deleted(xmlAttr *attr, void *user_data) +marked_as_deleted(const xmlAttr *attr, void *user_data) { const xml_node_private_t *nodepriv = attr->_private; diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 9636ec45eb2..2d21f50cf86 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -510,7 +510,7 @@ struct remove_xa_if_matching_data { bool force; //! Match function to call for each attribute - bool (*match)(xmlAttr *, void *); + bool (*match)(const xmlAttr *, void *); //! User data argument for match function void *match_data; @@ -558,7 +558,7 @@ remove_xa_if_matching(xmlAttr *attr, void *user_data) */ void pcmk__xe_remove_matching_attrs(xmlNode *element, bool force, - bool (*match)(xmlAttr *, void *), + bool (*match)(const xmlAttr *, void *), void *user_data) { struct remove_xa_if_matching_data data = { 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); } From 1052ef59ac77e40324ae36c5311ca08953b5af76 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:42:56 -0800 Subject: [PATCH 060/101] Refactor: libcrmcommon: New pcmk__xml_tree_foreach_remove() This is similar to pcmk__xml_tree_foreach(), except for the following: * It removes a node and its subtree if the function returns true. pcmk__xml_tree_foreach() would have a use-after-free if its function freed its xmlNode * argument. (This has not been observed with any existing calls.) * pcmk__xml_tree_foreach_remove() currently offers no way to stop iterating. The function's return value instead tells us whether to remove a node. Obviously we don't recurse down a freed node's subtree, however. * The function does not currently accept user data. A user data argument may be added later if needed. * pcmk__xml_tree_foreach_remove() returns void. We can later return a boolean or integer value if we need to know whether any nodes were removed. Nothing uses this yet. Also correct the Doxygen of pcmk__xml_tree_foreach() (fn is an [in] parameter) and add a note that fn may not remove its argument. Signed-off-by: Reid Wahl --- lib/common/crmcommon_private.h | 3 ++ lib/common/xml.c | 51 ++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 1ee78f2215f..71aeff67f6d 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -113,6 +113,9 @@ 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); diff --git a/lib/common/xml.c b/lib/common/xml.c index a7ea897198f..271d7468857 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -72,16 +72,19 @@ 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 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 *), @@ -107,6 +110,50 @@ 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 *)) +{ + 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) { From c9d3090c24851651d04cf9de0bb53bc7208e706a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:56:35 -0800 Subject: [PATCH 061/101] Refactor: libcrmcommon: Unindent pcmk__apply_creation_acl() Signed-off-by: Reid Wahl --- lib/common/acl.c | 68 ++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index edf3e40d041..f0c82610a44 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1009,51 +1009,51 @@ implicitly_allowed(const xmlNode *xml) void pcmk__apply_creation_acl(xmlNode *xml, bool check_top) { + bool is_root = (xml == xmlDocGetRootElement(xml->doc)); 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)) { + goto recurse; + } - } 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; + if (implicitly_allowed(xml)) { + pcmk__trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\" " + "is implicitly allowed", xml->name, display_id(xml)); + goto recurse; + } - pcmk__trace("ACLs disallow creation of %s<%s> with " - PCMK_XA_ID "=\"%s\"", - (is_root? "root element " : ""), xml->name, - display_id(xml)); + 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)); + goto recurse; + } - // pcmk__xml_free() checks ACLs if enabled, which would fail - pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); - pcmk__xml_free(xml); + if (check_top) { + xml_doc_private_t *docpriv = xml->doc->_private; - if (!is_root) { - // If root, the document was freed. Otherwise re-enable ACLs. - pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled); - } - return; + pcmk__trace("ACLs disallow creation of %s<%s> with " + PCMK_XA_ID "=\"%s\"", (is_root? "root element " : ""), + xml->name, display_id(xml)); - } else { - const bool is_root = (xml == xmlDocGetRootElement(xml->doc)); + // pcmk__xml_free() checks ACLs if enabled, which would fail + pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); + pcmk__xml_free(xml); - pcmk__notice("ACLs would disallow creation of %s<%s> with " - PCMK_XA_ID "=\"%s\"", - (is_root? "root element " : ""), xml->name, - display_id(xml)); + /* is_root=true should be impossible with check_top=true, but check for + * sanity + */ + if (!is_root) { + // If root, the document was freed. Otherwise re-enable ACLs. + pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled); } + return; } + pcmk__notice("ACLs would disallow creation of %s<%s> with " + PCMK_XA_ID "=\"%s\"", (is_root? "root element " : ""), + xml->name, display_id(xml)); + +recurse: for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) { xmlNode *child = cIter; cIter = pcmk__xml_next(cIter); /* In case it is free'd */ From 602f4945483f541fb4aa78624c8e1419d51a809e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 26 Dec 2025 02:56:35 -0800 Subject: [PATCH 062/101] Refactor: libcrmcommon: Make pcmk__apply_creation_acl() non-recursive And change its name to pcmk__check_creation_acls(), since it's checking rather than applying ACLs. Use pcmk__xml_tree_foreach_remove() to "drive" the recursion, instead of building it into pcmk__check_creation_acls(). Functionize the main logic into the helper check_creation_disallowed(). Signed-off-by: Reid Wahl --- cts/cli/regression.acls.exp | 18 +++---- cts/cts-cli.in | 3 +- lib/common/acl.c | 97 ++++++++++++++++------------------ lib/common/crmcommon_private.h | 2 +- lib/common/xml.c | 2 +- 5 files changed, 60 insertions(+), 62 deletions(-) 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'(") - /*! * \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) { - bool is_root = (xml == xmlDocGetRootElement(xml->doc)); + 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)) { - goto recurse; + return false; } if (implicitly_allowed(xml)) { pcmk__trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\" " - "is implicitly allowed", xml->name, display_id(xml)); - goto recurse; + "is implicitly allowed", type, id); + return false; } 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)); - goto recurse; - } - - if (check_top) { - 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); - - /* is_root=true should be impossible with check_top=true, but check for - * sanity - */ - if (!is_root) { - // If root, the document was freed. Otherwise re-enable ACLs. - pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled); - } - return; + type, id); + return false; } - pcmk__notice("ACLs would disallow creation of %s<%s> with " - PCMK_XA_ID "=\"%s\"", (is_root? "root element " : ""), - xml->name, display_id(xml)); + pcmk__trace("ACLs disallow creation of <%s> with " PCMK_XA_ID "=\"%s\"", + type, id); + return true; +} -recurse: - 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); - } +/*! + * \internal + * \brief Remove XML nodes created in violation of ACLs + * + * 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). + * + * \param[in,out] xml XML tree + */ +void +pcmk__check_creation_acls(xmlNode *xml) +{ + pcmk__xml_tree_foreach_remove(xml, check_creation_disallowed); } /*! @@ -1089,7 +1073,20 @@ xml_acl_disable(xmlNode *xml) /* Catch anything that was created but shouldn't have been */ pcmk__apply_acls(xml->doc); - pcmk__apply_creation_acl(xml, false); + + /* 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. + */ + xml = pcmk__xml_first_child(xml); + while (xml != NULL) { + xmlNode *next = pcmk__xml_next(xml); + + pcmk__check_creation_acls(xml); + xml = next; + } pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); } } diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 71aeff67f6d..01070585529 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -157,7 +157,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); diff --git a/lib/common/xml.c b/lib/common/xml.c index 271d7468857..2cbd9d95a84 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1886,7 +1886,7 @@ pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml) mark_xml_tree_dirty_created(new_child); // Check whether creation was allowed (may free new_child) - pcmk__apply_creation_acl(new_child, true); + pcmk__check_creation_acls(new_child); } } From 0c2269ecc319c8dc7b561de1f65fdede511e95fe Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 21:49:36 -0800 Subject: [PATCH 063/101] Refactor: libcrmcommon: Unindent is_config_change() If xml->doc or xml->doc->_private were NULL, then we would not have called this function. See pcmk__xml_doc_all_flags_set(). Signed-off-by: Reid Wahl --- lib/common/patchset.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index c1652368263..41599cbd66a 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -328,17 +328,16 @@ is_config_change(xmlNode *xml) 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; - } + 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; } From 2850ffe167efa72a5ced4e4c35e7c0ceeb64c3ab Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 21:54:05 -0800 Subject: [PATCH 064/101] Refactor: libcrmcommon: Minor best practices in is_config_change() Signed-off-by: Reid Wahl --- lib/common/patchset.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 41599cbd66a..985407fb8c2 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -313,32 +313,33 @@ add_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } 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; } - 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 true; } } - return FALSE; + return false; } // Guaranteed to return non-NULL From 24f500edf9a43b15dd6e6225a961cbb306bf72b0 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 22:06:34 -0800 Subject: [PATCH 065/101] Refactor: libcrmcommon: Functionize search for deleted config element Signed-off-by: Reid Wahl --- lib/common/patchset.c | 49 +++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 985407fb8c2..71a52fd7301 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -312,10 +312,45 @@ add_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } } +/*! + * \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 (strstr(deleted_obj->path, + "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) { + + 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(const xmlNode *xml) { - GList *gIter = NULL; xml_node_private_t *nodepriv = NULL; xml_doc_private_t *docpriv = xml->doc->_private; xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL, @@ -330,16 +365,8 @@ is_config_change(const xmlNode *xml) return true; } - 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 From 09388ec9ebf9507dd41dca6f6545127520b40a39 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 22:25:52 -0800 Subject: [PATCH 066/101] Refactor: libcrmcommon: Functionize filtering by one ACL Note to reviewer: this code is expected to be removed a few commits from now. This commit was part of my process of trying to understand the filtering logic. It may make review easier, or it may make it more tedious. Signed-off-by: Reid Wahl --- lib/common/acl.c | 106 ++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index ef3e9b71791..1115b87a920 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -868,6 +868,70 @@ purge_xml_attributes(xmlNode *xml) return readable_children; } +/*! + * \internal + * \brief User data for \c acl_filter_doc() + */ +struct acl_filter_doc_data { + xmlDoc *doc; //!< XML document being filtered + bool all_filtered; //!< Whether access has been denied to entire document +}; + +/*! + * \internal + * \brief Filter an XML document using one ACL + * + * \param[in] acl ACL object + * \param[in,out] user_data User data (struct acl_filter_doc_data *) + */ +static void +acl_filter_doc(gpointer data, gpointer user_data) +{ + const xml_acl_t *acl = data; + struct acl_filter_doc_data *filter_data = user_data; + + xmlNode *root = xmlDocGetRootElement(filter_data->doc); + xml_doc_private_t *docpriv = filter_data->doc->_private; + xmlXPathObject *xpath_obj = NULL; + int num_results = 0; + + if (filter_data->all_filtered) { + return; + } + + if ((acl->mode != pcmk__xf_acl_deny) || (acl->xpath == NULL)) { + return; + } + + xpath_obj = pcmk__xpath_search(filter_data->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); + + 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 == root)) { + xmlXPathFreeObject(xpath_obj); + filter_data->all_filtered = true; + return; + } + } + + pcmk__trace("ACLs deny user '%s' access to %s (%d match%s)", + docpriv->acl_user, acl->xpath, num_results, + pcmk__plural_alt(num_results, "", "es")); + xmlXPathFreeObject(xpath_obj); +} + /*! * \brief Copy ACL-allowed portions of specified XML * @@ -887,6 +951,7 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, { xmlNode *target = NULL; xml_doc_private_t *docpriv = NULL; + struct acl_filter_doc_data data = { 0, }; *result = NULL; if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) @@ -902,45 +967,10 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, 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); - - 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); - } + data.doc = target->doc; + g_list_foreach(docpriv->acls, acl_filter_doc, &data); - if (!purge_xml_attributes(target)) { + if (data.all_filtered || !purge_xml_attributes(target)) { pcmk__trace("ACLs deny user '%s' access to entire XML document", user); return true; } From 12f5cc14406a8d33de40381272d2a26d04c47b92 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 27 Dec 2025 23:40:07 -0800 Subject: [PATCH 067/101] Refactor: libcrmcommon: Functionize ACL-filtering one match Note to reviewer: this code is expected to be removed a few commits from now. This commit was part of my process of trying to understand the filtering logic. It may make review easier, or it may make it more tedious. Use pcmk__xpath_foreach_result(). Also drop a trace message, for two reasons: * We don't have easy access to num_results anymore. (We'd have to get it via a counter variable or something.) * The message tells us how many nodes were filtered by a given XPath, but not which nodes. So it doesn't seem especially useful. If we want tracing for ACL filtering (which seems like a reasonable thing to want), then we should make it more granular so that it's informative. Signed-off-by: Reid Wahl --- lib/common/acl.c | 75 +++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 1115b87a920..ddd328b4ac0 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -870,13 +870,46 @@ purge_xml_attributes(xmlNode *xml) /*! * \internal - * \brief User data for \c acl_filter_doc() + * \brief User data for \c acl_filter_match() and \c acl_filter_doc() */ -struct acl_filter_doc_data { +struct acl_filter_data { xmlDoc *doc; //!< XML document being filtered bool all_filtered; //!< Whether access has been denied to entire document }; +/*! + * \internal + * \brief Filter a node that matches an ACL's XPath expression + * + * Given a node that matches an ACL's XPath expression, get the corresponding + * XML element. Then filter it. See \c purge_xml_atributes() for details. + * + * \param[in,out] match Node matched by the ACL's XPath expression + * \param[in,out] user_data User data (struct acl_filter_doc_data *) + * + * \note This is compatible with \c pcmk__xpath_foreach_result(). + */ +static void +acl_filter_match(xmlNode *match, void *user_data) +{ + struct acl_filter_data *data = user_data; + xmlNode *root = xmlDocGetRootElement(data->doc); + + if (data->all_filtered) { + return; + } + + // @COMPAT See COMPAT comment in pcmk__apply_acls() + match = pcmk__xpath_match_element(match); + if (match == NULL) { + return; + } + + if (!purge_xml_attributes(match) && (match == root)) { + data->all_filtered = true; + } +} + /*! * \internal * \brief Filter an XML document using one ACL @@ -888,12 +921,7 @@ static void acl_filter_doc(gpointer data, gpointer user_data) { const xml_acl_t *acl = data; - struct acl_filter_doc_data *filter_data = user_data; - - xmlNode *root = xmlDocGetRootElement(filter_data->doc); - xml_doc_private_t *docpriv = filter_data->doc->_private; - xmlXPathObject *xpath_obj = NULL; - int num_results = 0; + struct acl_filter_data *filter_data = user_data; if (filter_data->all_filtered) { return; @@ -903,33 +931,8 @@ acl_filter_doc(gpointer data, gpointer user_data) return; } - xpath_obj = pcmk__xpath_search(filter_data->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); - - 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 == root)) { - xmlXPathFreeObject(xpath_obj); - filter_data->all_filtered = true; - return; - } - } - - pcmk__trace("ACLs deny user '%s' access to %s (%d match%s)", - docpriv->acl_user, acl->xpath, num_results, - pcmk__plural_alt(num_results, "", "es")); - xmlXPathFreeObject(xpath_obj); + pcmk__xpath_foreach_result(filter_data->doc, acl->xpath, acl_filter_match, + user_data); } /*! @@ -951,7 +954,7 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, { xmlNode *target = NULL; xml_doc_private_t *docpriv = NULL; - struct acl_filter_doc_data data = { 0, }; + struct acl_filter_data data = { 0, }; *result = NULL; if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) From 7aa044fb9bd6a33426e4b1b65b7a3401b6f51188 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 00:56:51 -0800 Subject: [PATCH 068/101] Refactor: libcrmcommon: Check "no ACLs" sooner in xml_acl_filtered_copy There's no reason to filter all the children if we can already tell that we're going to deny the user access to the entire document. Also drop the NULL-check of target. It's guaranteed non-NULL by this point. Signed-off-by: Reid Wahl --- lib/common/acl.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index ddd328b4ac0..e1e81139ec9 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -970,6 +970,13 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, pcmk__trace("Filtering XML copy using user '%s' ACLs", user); + if (docpriv->acls == NULL) { + pcmk__trace("User '%s' without ACLs denied access to entire XML " + "document", user); + pcmk__xml_free(target); + return true; + } + data.doc = target->doc; g_list_foreach(docpriv->acls, acl_filter_doc, &data); @@ -978,13 +985,6 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, return true; } - if (docpriv->acls == NULL) { - pcmk__trace("User '%s' without ACLs denied access to entire XML " - "document", user); - pcmk__xml_free(target); - return true; - } - g_clear_pointer(&docpriv->acls, pcmk__free_acls); *result = target; return true; From a54cd926ea3737c5ee793922677bf72a60209c67 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 01:03:16 -0800 Subject: [PATCH 069/101] Refactor: libcrmcommon: Don't delete attrs if we're going to delete node This is less for efficiency and more for understandability, before we make a larger change to this code. Signed-off-by: Reid Wahl --- lib/common/acl.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index e1e81139ec9..e6d303878b2 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -848,8 +848,6 @@ purge_xml_attributes(xmlNode *xml) return true; } - pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL); - child = pcmk__xe_first_child(xml, NULL, NULL, NULL); while (child != NULL) { xmlNode *tmp = child; @@ -861,10 +859,14 @@ purge_xml_attributes(xmlNode *xml) } } - if (!readable_children) { + if (readable_children) { + pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL); + + } else { // Nothing readable under here, so purge completely pcmk__xml_free(xml); } + return readable_children; } From 7623a4786237e16cba5e0518f2fa4f838a9d745c Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 03:09:24 -0800 Subject: [PATCH 070/101] Refactor: libcrmcommon: Clarify xml_acl_filtered_copy() This is a substantial refactor. Most of the details are in the comments. It took me days to understand exactly what xml_acl_filtered_copy() was doing, particularly in the calls to purge_xml_attributes(). For a while I was pretty sure that we could be exposing "denied" children if an ancestor had the pcmk__xf_acl_read or pcmk__xf_acl_write flag set. I'm no longer confident that was the case, although it may have been. The whole process was convoluted. We unpacked all of a user's ACLs and applied them to matching nodes throughout the document. Then we went through the unpacked list of a user's ACLs (docpriv->acls) and found the ones with "deny" permissions and non-NULL xpath. (By the way, I believe all ACLs should have non-NULL xpath by that point.) Then for each of those "deny" ACLs, we filtered the document based on it. We ran an XPath query to find all the nodes in the document that matched the "deny" ACL, and we called purge_xml_attributes() on each such node. A return value of true indicated that the current node either was readable or had at least one readable descendant. * If the current node had the pcmk__xf_acl_read or pcmk__xf_acl_write flag set (is_mode_allowed()), then it was readable and we returned true immediately. Some read or write ACL had matched that node during the apply stage. Of course this could never be true for the top-level call, because the top-level call was always for some node that a "deny" ACL had matched. * Otherwise, the current node was unreadable. We called purge_xml_attributes() on all children. - If any children returned true, then we had at least one readable descendant. We removed all attributes except for ID and then returned true. - Otherwise, we had no readable descendants. Since the current node was also unreadable, we removed the current node's subtree and returned false. As mentioned, this was convoluted. The key observation is that before we did any of that, we had already applied all the ACLs throughout the document. So all we have to do is recurse from the top down, determine which nodes are readable for the current ACL user, and filter out the rest. The comments explain how this works. Signed-off-by: Reid Wahl --- lib/common/acl.c | 199 +++++++++++++++++++++++------------------------ 1 file changed, 98 insertions(+), 101 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index e6d303878b2..f1ee8251e40 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -826,169 +826,166 @@ attr_is_not_id(const xmlAttr *attr, void *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 * - * \param[in,out] xml Root XML node to be purged of attributes + * 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 true if this node or any of its children are readable - * if false is returned, xml will be freed + * 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. * - * \note This function is recursive + * Filter each child. Each child inherits read access or denial from the current + * node, unless an ACL matched the child directly. + * + * 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; - } - - child = pcmk__xe_first_child(xml, NULL, NULL, NULL); - while (child != NULL) { - xmlNode *tmp = child; + in_readable_context = true; + direct = true; - child = pcmk__xe_next(child, NULL); - - if (purge_xml_attributes(tmp)) { - readable_children = true; - } + } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) { + in_readable_context = false; + direct = true; } - if (readable_children) { - pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL); + if (direct) { + how = "directly"; + + } else if (*xml == xmlDocGetRootElement((*xml)->doc)) { + how = "by default"; } else { - // Nothing readable under here, so purge completely - pcmk__xml_free(xml); + how = "by inheritance"; } - return readable_children; -} - -/*! - * \internal - * \brief User data for \c acl_filter_match() and \c acl_filter_doc() - */ -struct acl_filter_data { - xmlDoc *doc; //!< XML document being filtered - bool all_filtered; //!< Whether access has been denied to entire document -}; - -/*! - * \internal - * \brief Filter a node that matches an ACL's XPath expression - * - * Given a node that matches an ACL's XPath expression, get the corresponding - * XML element. Then filter it. See \c purge_xml_atributes() for details. - * - * \param[in,out] match Node matched by the ACL's XPath expression - * \param[in,out] user_data User data (struct acl_filter_doc_data *) - * - * \note This is compatible with \c pcmk__xpath_foreach_result(). - */ -static void -acl_filter_match(xmlNode *match, void *user_data) -{ - struct acl_filter_data *data = user_data; - xmlNode *root = xmlDocGetRootElement(data->doc); - - if (data->all_filtered) { - return; - } + pcmk__trace("ACLs %s read access to %s[@" PCMK_XA_ID "='%s'] %s", + (in_readable_context? "allow" : "deny"), name, id, how); - // @COMPAT See COMPAT comment in pcmk__apply_acls() - match = pcmk__xpath_match_element(match); - if (match == NULL) { - return; - } + // Filter children recursively + child = pcmk__xml_first_child(*xml); + while (child != NULL) { + xmlNode *next = pcmk__xml_next(child); - if (!purge_xml_attributes(match) && (match == root)) { - data->all_filtered = true; + filter_unreadable_nodes(&child, in_readable_context); + child = next; } -} -/*! - * \internal - * \brief Filter an XML document using one ACL - * - * \param[in] acl ACL object - * \param[in,out] user_data User data (struct acl_filter_doc_data *) - */ -static void -acl_filter_doc(gpointer data, gpointer user_data) -{ - const xml_acl_t *acl = data; - struct acl_filter_data *filter_data = user_data; - - if (filter_data->all_filtered) { + if (in_readable_context) { + // This node is readable, either directly or by inheritance return; } - if ((acl->mode != pcmk__xf_acl_deny) || (acl->xpath == NULL)) { + 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; } - pcmk__xpath_foreach_result(filter_data->doc, acl->xpath, acl_filter_match, - user_data); + // 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 + * \brief Copy XML, filtering out portions that are unreadable based on ACLs * - * \param[in] user Username whose ACLs should be used + * 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. + * + * 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. + * + * 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 containing ACLs * \param[in] xml XML to be copied - * \param[out] result Copy of XML portions readable via ACLs + * \param[out] result Where to store ACL-filtered copy of \p xml (will be + * set to \c NULL if entire document is filtered out) * * \return \c true if \p acl_source and \p xml are non-NULL and ACLs * are required for \p user, or \c false otherwise * - * \note If this returns true, caller should use \p result rather than \p xml + * \note If this returns true, caller should use \p *result rather than \p xml. + * \note The caller is responsible for freeing \p *result using + * \c xmlFreeDoc((*result)->doc). The document and its nodes also contain + * Pacemaker-generated private data, which cannot be freed by external + * programs at this time. */ bool xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, xmlNode **result) { - xmlNode *target = NULL; xml_doc_private_t *docpriv = NULL; - struct acl_filter_data data = { 0, }; - *result = NULL; if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) || !pcmk_acl_required(user)) { return false; } - target = pcmk__xml_copy(NULL, xml); - docpriv = target->doc->_private; + *result = pcmk__xml_copy(NULL, xml); - pcmk__enable_acls(acl_source->doc, target->doc, user); - - pcmk__trace("Filtering XML copy using user '%s' ACLs", user); + 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); + g_clear_pointer(result, pcmk__xml_free); return true; } - data.doc = target->doc; - g_list_foreach(docpriv->acls, acl_filter_doc, &data); + pcmk__trace("Filtering XML copy using user '%s' ACLs", user); + filter_unreadable_nodes(result, false); - if (data.all_filtered || !purge_xml_attributes(target)) { + 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 true; } g_clear_pointer(&docpriv->acls, pcmk__free_acls); - *result = target; return true; } From 5ba0e1212fb0434242953338fd2e2be8578d9e85 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 19:40:09 -0800 Subject: [PATCH 071/101] Refactor: libcrmcommon: New pcmk__acl_filtered_copy() To replace xml_acl_filtered_copy(). Signed-off-by: Reid Wahl --- include/crm/common/acl_internal.h | 3 + lib/cib/cib_utils.c | 16 ++--- lib/common/acl.c | 97 ++++++++++++++++++++++--------- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/include/crm/common/acl_internal.h b/include/crm/common/acl_internal.h index cb4930bfaa1..5c7403ab915 100644 --- a/include/crm/common/acl_internal.h +++ b/include/crm/common/acl_internal.h @@ -38,6 +38,9 @@ pcmk__is_privileged(const char *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/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 835f46ff7b7..e133e461b77 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -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"); @@ -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/acl.c b/lib/common/acl.c index f1ee8251e40..564b9b0f371 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -919,6 +919,73 @@ filter_unreadable_nodes(xmlNode **xml, bool in_readable_context) g_clear_pointer(xml, pcmk__xml_free); } +/*! + * \internal + * \brief Copy XML, filtering out portions that are unreadable based on 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. + * + * 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. + * + * 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(). + */ +xmlNode * +pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, xmlNode *xml) +{ + xmlNode *result = NULL; + xml_doc_private_t *docpriv = NULL; + + pcmk__assert((acl_source != NULL) && (xml != NULL)); + + result = pcmk__xml_copy(NULL, xml); + + if (!pcmk_acl_required(user)) { + // Return an unfiltered copy + return result; + } + + 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(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); + return result; +} + /*! * \brief Copy XML, filtering out portions that are unreadable based on ACLs * @@ -943,8 +1010,9 @@ filter_unreadable_nodes(xmlNode **xml, bool in_readable_context) * \param[out] result Where to store ACL-filtered copy of \p xml (will be * set to \c NULL if entire document is filtered out) * - * \return \c true if \p acl_source and \p xml are non-NULL and ACLs - * are required for \p user, or \c false otherwise + * \return \c true if \p acl_source, \p acl_source->doc, and \p xml are + * non-NULL and ACLs are required for \p user, or \c false + * otherwise * * \note If this returns true, caller should use \p *result rather than \p xml. * \note The caller is responsible for freeing \p *result using @@ -956,36 +1024,13 @@ bool xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, xmlNode **result) { - xml_doc_private_t *docpriv = NULL; - if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL) || !pcmk_acl_required(user)) { return false; } - *result = pcmk__xml_copy(NULL, xml); - - 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); - g_clear_pointer(result, pcmk__xml_free); - return true; - } - - 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 true; - } - - g_clear_pointer(&docpriv->acls, pcmk__free_acls); + *result = pcmk__acl_filtered_copy(user, acl_source->doc, xml); return true; } From 8c8becc0e889efc6e8d60d6cb3284901ea85b3a7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sun, 28 Dec 2025 20:08:24 -0800 Subject: [PATCH 072/101] API: libcrmcommon: Deprecate xml_acl_filtered_copy() Perform a CIB query as a given user if you want to filter CIB XML as that user. This function shouldn't have been used for arbitrary (non-CIB) XML anyway. Signed-off-by: Reid Wahl --- include/crm/common/acl.h | 2 -- include/crm/common/acl_compat.h | 4 +++ lib/common/acl.c | 62 ++++++++------------------------- 3 files changed, 18 insertions(+), 50 deletions(-) diff --git a/include/crm/common/acl.h b/include/crm/common/acl.h index bff52f63417..3b273e2dddb 100644 --- a/include/crm/common/acl.h +++ b/include/crm/common/acl.h @@ -25,8 +25,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); diff --git a/include/crm/common/acl_compat.h b/include/crm/common/acl_compat.h index a87db5ca26f..37e73a93d48 100644 --- a/include/crm/common/acl_compat.h +++ b/include/crm/common/acl_compat.h @@ -30,6 +30,10 @@ 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); + #ifdef __cplusplus } #endif diff --git a/lib/common/acl.c b/lib/common/acl.c index 564b9b0f371..8f875a044de 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -986,54 +986,6 @@ pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, xmlNode *xml) return result; } -/*! - * \brief Copy XML, filtering out portions that are unreadable based on 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. - * - * 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. - * - * 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 containing ACLs - * \param[in] xml XML to be copied - * \param[out] result Where to store ACL-filtered copy of \p xml (will be - * set to \c NULL if entire document is filtered out) - * - * \return \c true if \p acl_source, \p acl_source->doc, and \p xml are - * non-NULL and ACLs are required for \p user, or \c false - * otherwise - * - * \note If this returns true, caller should use \p *result rather than \p xml. - * \note The caller is responsible for freeing \p *result using - * \c xmlFreeDoc((*result)->doc). The document and its nodes also contain - * Pacemaker-generated private data, which cannot be freed by external - * programs at this time. - */ -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; -} - /*! * \internal * \brief Check whether creation of an XML element is implicitly allowed @@ -1405,5 +1357,19 @@ 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; +} + // LCOV_EXCL_STOP // End deprecated API From c4438f2b3b6dab146bfd498e70bbd1770a8f16b6 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:38:01 -0800 Subject: [PATCH 073/101] Refactor: libcrmcommon: Make pcmk__unpack_acls() static Signed-off-by: Reid Wahl --- lib/common/acl.c | 6 +++--- lib/common/crmcommon_private.h | 4 ---- lib/common/xml.c | 4 +--- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 8f875a044de..69bcad5a849 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -590,8 +590,8 @@ 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; @@ -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); } diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 01070585529..5ec7b5b409f 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -146,10 +146,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); diff --git a/lib/common/xml.c b/lib/common/xml.c index 2cbd9d95a84..0ae3298d984 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -2065,9 +2065,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); } } From f6a5df542d410294c78f369a30e85dd3b2b37991 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:41:43 -0800 Subject: [PATCH 074/101] Refactor: libcrmcommon: Unindent xml_acl_disable() Signed-off-by: Reid Wahl --- lib/common/acl.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 69bcad5a849..16e87ef63b1 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1095,29 +1095,34 @@ xml_acl_denied(const xmlNode *xml) 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 = 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); + return; + } - /* 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. - */ - xml = pcmk__xml_first_child(xml); - while (xml != NULL) { - xmlNode *next = pcmk__xml_next(xml); + // Catch anything that was created but shouldn't have been + pcmk__apply_acls(xml->doc); - pcmk__check_creation_acls(xml); - xml = next; - } - pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); + /* 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; } + + docpriv = xml->doc->_private; + pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); } /*! From 90f0a58a91ca31593d9fd072d2c6e9a4696414c1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:49:54 -0800 Subject: [PATCH 075/101] Refactor: libcrmcommon: New pcmk__xml_doc_clear_flags() Signed-off-by: Reid Wahl --- lib/common/acl.c | 4 +--- lib/common/crmcommon_private.h | 3 +++ lib/common/xml.c | 37 +++++++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 16e87ef63b1..2a72627fadb 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1096,7 +1096,6 @@ void xml_acl_disable(xmlNode *xml) { xmlNode *child = NULL; - xml_doc_private_t *docpriv = NULL; if ((xml == NULL) || !pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) { @@ -1121,8 +1120,7 @@ xml_acl_disable(xmlNode *xml) child = next; } - docpriv = xml->doc->_private; - pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled); + pcmk__xml_doc_clear_flags(xml->doc, pcmk__xf_acl_enabled); } /*! diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 5ec7b5b409f..4f2bf55201b 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -122,6 +122,9 @@ 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); diff --git a/lib/common/xml.c b/lib/common/xml.c index 0ae3298d984..7d1f65803e6 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -176,11 +176,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; + + 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; - pcmk__set_xml_flags(docpriv, flags); + if (doc == NULL) { + return; } + docpriv = doc->_private; + pcmk__clear_xml_flags(docpriv, flags); } /*! @@ -1347,16 +1368,15 @@ static void mark_attr_deleted(xmlNode *new_xml, 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__xml_doc_clear_flags(new_xml->doc, pcmk__xf_tracking); pcmk__xe_set(new_xml, attr_name, old_value); - pcmk__set_xml_flags(docpriv, pcmk__xf_tracking); + 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); @@ -1378,16 +1398,15 @@ static void mark_attr_changed(xmlNode *new_xml, const char *attr_name, const char *old_value) { - 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, (const char *) new_xml->name); // Restore the original value (without checking ACLs) - pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking); + pcmk__xml_doc_clear_flags(new_xml->doc, pcmk__xf_tracking); pcmk__xe_set(new_xml, attr_name, old_value); - pcmk__set_xml_flags(docpriv, pcmk__xf_tracking); + 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); From 2ffb84eaa8de96914bfca01ec8efbc01ba45766a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:52:54 -0800 Subject: [PATCH 076/101] Refactor: libcrmcommon: Simplify xml_acl_denied() Signed-off-by: Reid Wahl --- lib/common/acl.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/common/acl.c b/lib/common/acl.c index 2a72627fadb..27840060109 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1084,12 +1084,8 @@ pcmk__check_creation_acls(xmlNode *xml) bool xml_acl_denied(const 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; + return (xml != NULL) + && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_denied); } void From 39d51436c5ed1d12042bec59feeefa63c7beabbb Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:57:16 -0800 Subject: [PATCH 077/101] Refactor: libcrmcommon: Drop xml_acl_denied() internally Signed-off-by: Reid Wahl --- lib/cib/cib_utils.c | 2 +- lib/common/tests/acl/Makefile.am | 1 - lib/common/tests/acl/xml_acl_denied_test.c | 61 ---------------------- 3 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 lib/common/tests/acl/xml_acl_denied_test.c diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index e133e461b77..5036da09083 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -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; diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index e8887ff7851..e012046e833 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -15,6 +15,5 @@ include $(top_srcdir)/mk/unittest.mk check_PROGRAMS = pcmk__is_user_in_group_test check_PROGRAMS += pcmk_acl_required_test -check_PROGRAMS += xml_acl_denied_test TESTS = $(check_PROGRAMS) 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)) From 308aa30e07e96941624ca8a2a8372d75e5f89688 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 01:59:48 -0800 Subject: [PATCH 078/101] API: libcrmcommon: Deprecate xml_acl_denied() Signed-off-by: Reid Wahl --- include/crm/common/acl.h | 1 - include/crm/common/acl_compat.h | 3 +++ lib/common/acl.c | 21 +++++++-------------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/include/crm/common/acl.h b/include/crm/common/acl.h index 3b273e2dddb..d1d12b01523 100644 --- a/include/crm/common/acl.h +++ b/include/crm/common/acl.h @@ -24,7 +24,6 @@ extern "C" { */ void xml_acl_disable(xmlNode *xml); -bool xml_acl_denied(const xmlNode *xml); bool pcmk_acl_required(const char *user); diff --git a/include/crm/common/acl_compat.h b/include/crm/common/acl_compat.h index 37e73a93d48..2a24ca25727 100644 --- a/include/crm/common/acl_compat.h +++ b/include/crm/common/acl_compat.h @@ -34,6 +34,9 @@ bool xml_acl_enabled(const xmlNode *xml); 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); + #ifdef __cplusplus } #endif diff --git a/lib/common/acl.c b/lib/common/acl.c index 27840060109..322e40c2668 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1074,20 +1074,6 @@ pcmk__check_creation_acls(xmlNode *xml) pcmk__xml_tree_foreach_remove(xml, check_creation_disallowed); } -/*! - * \brief Check whether or not an XML node is ACL-denied - * - * \param[in] xml node to check - * - * \return true if XML node exists and is ACL-denied, false otherwise - */ -bool -xml_acl_denied(const xmlNode *xml) -{ - return (xml != NULL) - && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_denied); -} - void xml_acl_disable(xmlNode *xml) { @@ -1370,5 +1356,12 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *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); +} + // LCOV_EXCL_STOP // End deprecated API From 67eb04689093e931fec21c2380162bfe6c2ea606 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 02:09:39 -0800 Subject: [PATCH 079/101] Refactor: libcrmcommon: New pcmk__acl_required() To replace pcmk_acl_required(). Signed-off-by: Reid Wahl --- include/crm/common/acl_internal.h | 15 +++++++++++++++ lib/cib/cib_utils.c | 2 +- lib/common/acl.c | 17 ++++------------- lib/common/tests/acl/Makefile.am | 4 ++-- ...equired_test.c => pcmk__acl_required_test.c} | 13 ++++++------- lib/pacemaker/pcmk_acl.c | 2 +- tools/cibadmin.c | 4 ++-- 7 files changed, 31 insertions(+), 26 deletions(-) rename lib/common/tests/acl/{pcmk_acl_required_test.c => pcmk__acl_required_test.c} (56%) diff --git a/include/crm/common/acl_internal.h b/include/crm/common/acl_internal.h index 5c7403ab915..4faab34152a 100644 --- a/include/crm/common/acl_internal.h +++ b/include/crm/common/acl_internal.h @@ -36,6 +36,21 @@ 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, diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 5036da09083..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; } diff --git a/lib/common/acl.c b/lib/common/acl.c index 322e40c2668..05b5d53ab03 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -597,7 +597,7 @@ unpack_acls(xmlDoc *source, xml_doc_private_t *target, const char *user) pcmk__assert(target != NULL); - if ((target->acls != NULL) || !pcmk_acl_required(user)) { + if ((target->acls != NULL) || !pcmk__acl_required(user)) { return; } @@ -958,7 +958,7 @@ pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, xmlNode *xml) result = pcmk__xml_copy(NULL, xml); - if (!pcmk_acl_required(user)) { + if (!pcmk__acl_required(user)) { // Return an unfiltered copy return result; } @@ -1204,16 +1204,7 @@ pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode) 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; + return pcmk__acl_required(user); } char * @@ -1347,7 +1338,7 @@ 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)) { + || !pcmk__acl_required(user)) { return false; } diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index e012046e833..5b10a86d5a9 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -13,7 +13,7 @@ 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 = pcmk__acl_required_test +check_PROGRAMS += pcmk__is_user_in_group_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/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/tools/cibadmin.c b/tools/cibadmin.c index 0c769aa91ed..f9562acf085 100644 --- a/tools/cibadmin.c +++ b/tools/cibadmin.c @@ -1170,8 +1170,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 " From ae20e61e10e18f3867f6ad7424c448d60bf8ecc4 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 02:11:21 -0800 Subject: [PATCH 080/101] API: libcrmcommon: Deprecate pcmk_acl_required() Signed-off-by: Reid Wahl --- include/crm/common/acl.h | 2 -- include/crm/common/acl_compat.h | 3 +++ lib/common/acl.c | 19 ++++++------------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/include/crm/common/acl.h b/include/crm/common/acl.h index d1d12b01523..708b3639a84 100644 --- a/include/crm/common/acl.h +++ b/include/crm/common/acl.h @@ -25,8 +25,6 @@ extern "C" { void xml_acl_disable(xmlNode *xml); -bool pcmk_acl_required(const char *user); - #ifdef __cplusplus } #endif diff --git a/include/crm/common/acl_compat.h b/include/crm/common/acl_compat.h index 2a24ca25727..14fe5237c30 100644 --- a/include/crm/common/acl_compat.h +++ b/include/crm/common/acl_compat.h @@ -37,6 +37,9 @@ bool xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml, //! \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/lib/common/acl.c b/lib/common/acl.c index 05b5d53ab03..bf43df476af 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1194,19 +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) -{ - return pcmk__acl_required(user); -} - char * pcmk__uid2username(uid_t uid) { @@ -1354,5 +1341,11 @@ xml_acl_denied(const xmlNode *xml) && 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 From 839323acd0867b67dfde336b0c506f042613b843 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 02:15:26 -0800 Subject: [PATCH 081/101] Refactor: libcrmcommon: Move pcmk__is_user_in_group_test.c to utils It's in utils.c, not acl.c. Signed-off-by: Reid Wahl --- lib/common/tests/acl/Makefile.am | 1 - lib/common/tests/utils/Makefile.am | 1 + lib/common/tests/{acl => utils}/pcmk__is_user_in_group_test.c | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename lib/common/tests/{acl => utils}/pcmk__is_user_in_group_test.c (100%) diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index 5b10a86d5a9..f54e9748976 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -14,6 +14,5 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = pcmk__acl_required_test -check_PROGRAMS += pcmk__is_user_in_group_test TESTS = $(check_PROGRAMS) 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 From e3057cfb838393aec3aa13dc9eab68b9e70ffa33 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 16:38:58 -0800 Subject: [PATCH 082/101] Refactor: libcrmcommon: Functionize appending XML-escaped character Signed-off-by: Reid Wahl --- lib/common/xml.c | 162 +++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 74 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 7d1f65803e6..4eca5c3ff1b 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1212,6 +1212,93 @@ pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type) return false; } +/*! + * \internal + * \brief Append an XML-escaped character to a buffer + * + * \param[in] current_char Character to escape + * \param[in] type Type of escaping + * \param[in,out] buffer Buffer + */ +static void +append_xml_escaped_char(char current_char, enum pcmk__xml_escape_type type, + GString *buffer) +{ + switch (type) { + case pcmk__xml_escape_text: + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; + case '\n': + case '\t': + g_string_append_c(buffer, current_char); + return; + 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; + } + + case pcmk__xml_escape_attr: + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; + case '"': + g_string_append(buffer, PCMK__XML_ENTITY_QUOT); + return; + 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; + } + + case pcmk__xml_escape_attr_pretty: + switch (current_char) { + case '"': + g_string_append(buffer, "\\\""); + return; + case '\n': + g_string_append(buffer, "\\n"); + return; + case '\r': + g_string_append(buffer, "\\r"); + return; + case '\t': + g_string_append(buffer, "\\t"); + return; + default: + g_string_append_c(buffer, current_char); + return; + } + + default: // Invalid enum value + pcmk__assert(false); + return; + } +} + /*! * \internal * \brief Replace special characters with their XML escape sequences @@ -1251,80 +1338,7 @@ pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type) 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; - } + append_xml_escaped_char(*text, type, copy); text = g_utf8_next_char(text); } From 97a197c20d0f87ab55eaaa49f7604265850521e1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 17:26:13 -0800 Subject: [PATCH 083/101] Refactor: libcrmcommon: Functionize XML escape append modes Signed-off-by: Reid Wahl --- lib/common/xml.c | 182 +++++++++++++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 62 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 4eca5c3ff1b..912c0c0eca1 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1212,6 +1212,120 @@ pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type) return false; } +/*! + * \internal + * \brief Append an XML-escaped character to a buffer (text escaping) + * + * This appends an escaped character in \c pcmk__xml_escape_text mode. + * + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer + */ +static void +append_xml_escaped_char_text(char current_char, GString *buffer) +{ + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; + + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; + + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; + + case '\n': + case '\t': + g_string_append_c(buffer, current_char); + return; + + 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 Append an XML-escaped character to a buffer (attribute escaping) + * + * This appends an escaped character in \c pcmk__xml_escape_attr mode. + * + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer + */ +static void +append_xml_escaped_char_attr(char current_char, GString *buffer) +{ + switch (current_char) { + case '<': + g_string_append(buffer, PCMK__XML_ENTITY_LT); + return; + + case '>': + g_string_append(buffer, PCMK__XML_ENTITY_GT); + return; + + case '&': + g_string_append(buffer, PCMK__XML_ENTITY_AMP); + return; + + case '"': + g_string_append(buffer, PCMK__XML_ENTITY_QUOT); + return; + + 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 Append an XML-escaped character to a buffer (pretty escaping) + * + * This appends an escaped character in \c pcmk__xml_escape_attr_pretty mode. + * + * \param[in] current_char Character to escape + * \param[in,out] buffer Buffer + */ +static void +append_xml_escaped_char_pretty(char current_char, GString *buffer) +{ + switch (current_char) { + case '"': + g_string_append(buffer, "\\\""); + return; + + case '\n': + g_string_append(buffer, "\\n"); + return; + + case '\r': + g_string_append(buffer, "\\r"); + return; + + case '\t': + g_string_append(buffer, "\\t"); + return; + + default: + g_string_append_c(buffer, current_char); + return; + } +} + /*! * \internal * \brief Append an XML-escaped character to a buffer @@ -1226,72 +1340,16 @@ append_xml_escaped_char(char current_char, enum pcmk__xml_escape_type type, { switch (type) { case pcmk__xml_escape_text: - switch (current_char) { - case '<': - g_string_append(buffer, PCMK__XML_ENTITY_LT); - return; - case '>': - g_string_append(buffer, PCMK__XML_ENTITY_GT); - return; - case '&': - g_string_append(buffer, PCMK__XML_ENTITY_AMP); - return; - case '\n': - case '\t': - g_string_append_c(buffer, current_char); - return; - 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; - } + append_xml_escaped_char_text(current_char, buffer); + return; case pcmk__xml_escape_attr: - switch (current_char) { - case '<': - g_string_append(buffer, PCMK__XML_ENTITY_LT); - return; - case '>': - g_string_append(buffer, PCMK__XML_ENTITY_GT); - return; - case '&': - g_string_append(buffer, PCMK__XML_ENTITY_AMP); - return; - case '"': - g_string_append(buffer, PCMK__XML_ENTITY_QUOT); - return; - 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; - } + append_xml_escaped_char_attr(current_char, buffer); + return; case pcmk__xml_escape_attr_pretty: - switch (current_char) { - case '"': - g_string_append(buffer, "\\\""); - return; - case '\n': - g_string_append(buffer, "\\n"); - return; - case '\r': - g_string_append(buffer, "\\r"); - return; - case '\t': - g_string_append(buffer, "\\t"); - return; - default: - g_string_append_c(buffer, current_char); - return; - } + append_xml_escaped_char_pretty(current_char, buffer); + return; default: // Invalid enum value pcmk__assert(false); From ab025b3febd0908e4dc7ae5a99f2e15d028ace34 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 17:44:44 -0800 Subject: [PATCH 084/101] Refactor: libcrmcommon: Drop pcmk__xml_needs_escape() I really doubt that any performance savings we may be getting are enough to justify the readability cost of (a) defining and testing this function and (b) calling it before each call to pcmk__xml_escape(). Signed-off-by: Reid Wahl --- include/crm/common/xml_internal.h | 1 - lib/common/tests/xml/Makefile.am | 1 - .../tests/xml/pcmk__xml_needs_escape_test.c | 336 ------------------ lib/common/xml.c | 75 ---- lib/common/xml_attr.c | 9 +- lib/common/xml_io.c | 9 +- lib/fencing/st_lha.c | 48 +-- lib/pacemaker/pcmk_output.c | 11 +- lib/pengine/pe_output.c | 9 +- lib/services/services_lsb.c | 3 - lib/services/systemd.c | 13 +- 11 files changed, 35 insertions(+), 480 deletions(-) delete mode 100644 lib/common/tests/xml/pcmk__xml_needs_escape_test.c diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 4bfb9e8848f..506531104c1 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -260,7 +260,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); /*! 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 = "': - 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 Append an XML-escaped character to a buffer (text escaping) diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c index 24811aa10de..561b65b6fe9 100644 --- a/lib/common/xml_attr.c +++ b/lib/common/xml_attr.c @@ -153,13 +153,10 @@ pcmk__dump_xml_attr(const xmlAttr *attr, void *user_data) 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, " ", (const char *) attr->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_io.c b/lib/common/xml_io.c index 23bdb1ee5cb..889bab1f7a9 100644 --- a/lib/common/xml_io.c +++ b/lib/common/xml_io.c @@ -297,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/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/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 48e13538eb8..2dc9e7a4e37 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -2208,10 +2208,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 +2225,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); diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 7b43314937e..79f71af5479 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1612,14 +1612,11 @@ failed_action_xml(pcmk__output_t *out, va_list args) { const char *status_s = NULL; time_t epoch = 0; - gchar *exit_reason_esc = NULL; char *rc_s = NULL; xmlNodePtr node = 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); @@ -1633,7 +1630,7 @@ failed_action_xml(pcmk__output_t *out, va_list args) { op_key_name, op_key, PCMK_XA_NODE, uname, PCMK_XA_EXITSTATUS, exitstatus, - PCMK_XA_EXITREASON, exit_reason, + PCMK_XA_EXITREASON, exit_reason_esc, PCMK_XA_EXITCODE, rc_s, PCMK_XA_CALL, call_id, PCMK_XA_STATUS, status_s, 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; } From 85910d819c1bc81b1e89c67192191ab0c44c885e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 23:26:30 -0800 Subject: [PATCH 085/101] Refactor: libcrmcommon: New pcmk__xml_foreach_child() Nothing uses this yet. Also assert on NULL fn in other XML foreach functions. Signed-off-by: Reid Wahl --- include/crm/common/xml_internal.h | 2 ++ lib/common/xml.c | 37 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 506531104c1..611ee92b405 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -429,6 +429,8 @@ 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); diff --git a/lib/common/xml.c b/lib/common/xml.c index f780e03e59d..5aa7ce196eb 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -70,6 +70,41 @@ pcmk__xml_element_type_text(xmlElementType type) return element_type_names[type]; } +/*! + * \internal + * \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) @@ -129,6 +164,8 @@ pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void pcmk__xml_tree_foreach_remove(xmlNode *xml, bool (*fn)(xmlNode *)) { + pcmk__assert(fn != NULL); + if (xml == NULL) { return; } From 1f123af38fb99335547ce983480b761a2fdfcdf1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 23:08:55 -0800 Subject: [PATCH 086/101] Refactor: libcrmcommon: Functionize check/set matching XML children Signed-off-by: Reid Wahl --- lib/common/xml.c | 57 +++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 5aa7ce196eb..fb425b39544 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1790,6 +1790,40 @@ new_child_matches(const xmlNode *old_child, const xmlNode *new_child) } } +/*! + * \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 matching XML node pairs between old and new XML's children @@ -1797,8 +1831,8 @@ new_child_matches(const xmlNode *old_child, const xmlNode *new_child) * A node that is part of a matching pair gets its _private:match * member set to the matching node. * - * \param[in,out] old_xml Old XML - * \param[in,out] new_xml New XML + * \param[in,out] old_xml Old XML + * \param[in,out] new_xml New XML */ static void find_matching_children(xmlNode *old_xml, xmlNode *new_xml) @@ -1813,24 +1847,7 @@ find_matching_children(xmlNode *old_xml, xmlNode *new_xml) 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; - - 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; - } - } + pcmk__xml_foreach_child(new_xml, set_match_if_matching, old_child); } } From 95f15857373bc6e307ece7093c42eaf66ea28ca0 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 23:36:35 -0800 Subject: [PATCH 087/101] Refactor: libcrmcommon: Use foreach_child for find_matching_children Signed-off-by: Reid Wahl --- lib/common/xml.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index fb425b39544..37cedbaf769 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1826,29 +1826,29 @@ set_match_if_matching(xmlNode *new_child, void *user_data) /*! * \internal - * \brief Find matching XML node pairs between old and new XML's children + * \brief Find a child of a new XML node that matches a child of an old node * - * A node that is part of a matching pair gets its _private:match - * member set to the matching 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_xml Old XML - * \param[in,out] new_xml New XML + * \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 void -find_matching_children(xmlNode *old_xml, xmlNode *new_xml) +static bool +find_and_set_match(xmlNode *old_child, void *user_data) { - for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL; - old_child = pcmk__xml_next(old_child)) { - - 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 - continue; - } + xmlNode *new_xml = user_data; + xml_node_private_t *old_nodepriv = old_child->_private; - pcmk__xml_foreach_child(new_xml, set_match_if_matching, old_child); + 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; } /*! @@ -1881,7 +1881,7 @@ pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml) 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); + pcmk__xml_foreach_child(old_xml, find_and_set_match, new_xml); // Process matches (changed children) and deletions for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL; From e006a49afb2e3b12f0bb17e18862d427094da4e7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 29 Dec 2025 23:49:12 -0800 Subject: [PATCH 088/101] Refactor: libcrmcommon: Functionize marking child changed or deleted Signed-off-by: Reid Wahl --- lib/common/xml.c | 86 +++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 37cedbaf769..14aaf6e0ce0 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1851,6 +1851,57 @@ find_and_set_match(xmlNode *old_child, void *user_data) 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 the new_xml loop in + * pcmk__xml_mark_changes(). + */ + 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 changes between two XML trees @@ -1861,6 +1912,7 @@ find_and_set_match(xmlNode *old_child, void *user_data) * \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) @@ -1882,39 +1934,7 @@ pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml) xml_diff_attrs(old_xml, new_xml); pcmk__xml_foreach_child(old_xml, find_and_set_match, 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; - } - - pcmk__xml_mark_changes(old_child, new_child); - } + pcmk__xml_foreach_child(old_xml, mark_child_changed_or_deleted, new_xml); /* Mark unmatched new children as created, and mark matched new children as * moved if their positions changed. Grab the next new child in advance, From e13821869dd4d8f3323ea56bb6570f85e07cb840 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 00:13:58 -0800 Subject: [PATCH 089/101] Refactor: libcrmcommon: Functionize marking child moved or created Signed-off-by: Reid Wahl --- lib/common/xml.c | 109 ++++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index 14aaf6e0ce0..ae7f53f9ac0 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1885,8 +1885,8 @@ mark_child_changed_or_deleted(xmlNode *old_child, void *user_data) } /* Fetch the match and clear old_child->_private's match member. - * new_child->_private's match member is handled in the new_xml loop in - * pcmk__xml_mark_changes(). + * new_child->_private's match member is handled in + * mark_child_moved_or_created(). */ new_child = nodepriv->match; nodepriv->match = NULL; @@ -1902,6 +1902,67 @@ mark_child_changed_or_deleted(xmlNode *old_child, void *user_data) 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) +{ + xml_node_private_t *nodepriv = new_child->_private; + + if (nodepriv == NULL) { + return true; + } + + if (nodepriv->match != NULL) { + /* 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 earlier, 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. + */ + 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; + return true; + } + + // 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); + + // Check whether creation was allowed (may free new_child) + pcmk__check_creation_acls(new_child); + + return true; +} + /*! * \internal * \brief Mark changes between two XML trees @@ -1935,49 +1996,7 @@ pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *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); - - /* 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; - } - - 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; - 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); - - // Check whether creation was allowed (may free new_child) - pcmk__check_creation_acls(new_child); - } + pcmk__xml_foreach_child(new_xml, mark_child_moved_or_created, NULL); } char * From faf45fd09fd20495ee4ecc1a15283207f079652d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 00:34:16 -0800 Subject: [PATCH 090/101] Refactor: libcrmcommon: Functionize marking child as created Signed-off-by: Reid Wahl --- lib/common/xml.c | 81 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index ae7f53f9ac0..dd68d429495 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1687,6 +1687,41 @@ mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos, pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); } +/*! + * \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_xml_tree_dirty_created(new_child); + + // 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 @@ -1925,40 +1960,38 @@ mark_child_changed_or_deleted(xmlNode *old_child, void *user_data) 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) { - /* 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 earlier, 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. - */ - 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; + if (nodepriv->match == NULL) { + // No match in old XML means the new child is newly created + mark_child_created(new_child); return true; } - // 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); + /* 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; - // Check whether creation was allowed (may free new_child) - pcmk__check_creation_acls(new_child); + 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; } From b2c684674cdded73d1112e6e939601078fb724d1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 00:45:56 -0800 Subject: [PATCH 091/101] Refactor: libcrmcommon: Defunctionize mark_xml_tree_dirty_created() It only has one caller, and its checks (non-NULL argument and tracking flag set) will always pass in that caller. So we end up with two lines of code, which are straightforward to pull into mark_child_created(). Signed-off-by: Reid Wahl --- lib/common/xml.c | 74 ++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/lib/common/xml.c b/lib/common/xml.c index dd68d429495..09d13858f26 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -294,52 +294,6 @@ pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data) return true; } -/*! - * \internal - * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node - * - * \param[in,out] xml Node whose flags to set - * \param[in] user_data Ignored - * - * \return \c true (to continue traversing the tree) - * - * \note This is compatible with \c pcmk__xml_tree_foreach(). - */ -static bool -mark_xml_dirty_created(xmlNode *xml, void *user_data) -{ - xml_node_private_t *nodepriv = xml->_private; - - if (nodepriv != NULL) { - pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created); - } - return true; -} - -/*! - * \internal - * \brief Mark an XML tree as dirty and created, and mark its parents dirty - * - * Also mark the document dirty. - * - * \param[in,out] xml Tree to mark as dirty and created - */ -static void -mark_xml_tree_dirty_created(xmlNode *xml) -{ - pcmk__assert(xml != NULL); - - if (!pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking)) { - // Tracking is disabled for entire document - return; - } - - // Mark all parents and document dirty - pcmk__mark_xml_node_dirty(xml); - - pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL); -} - // Free an XML object previously marked as deleted static void free_deleted_object(void *data) @@ -1687,6 +1641,28 @@ mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos, 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 @@ -1716,7 +1692,11 @@ mark_child_created(xmlNode *new_child) // @TODO Why do we set pcmk__xf_skip here? pcmk__set_xml_flags(nodepriv, pcmk__xf_skip); - mark_xml_tree_dirty_created(new_child); + // 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); From 6451c28e64f84136f7a0e85d7738f08a1dfcf9d7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 03:01:02 -0800 Subject: [PATCH 092/101] Refactor: libcrmcommon: Expose reset_doc_private_data() as lib-private We'll use it in an upcoming commit that splits XML change-tracking/committing into a separate file. Signed-off-by: Reid Wahl --- lib/common/crmcommon_private.h | 3 +++ lib/common/xml.c | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 4f2bf55201b..2384a7b69df 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -128,6 +128,9 @@ 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); diff --git a/lib/common/xml.c b/lib/common/xml.c index 09d13858f26..c100d4d3eef 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -429,8 +429,8 @@ new_private_data(xmlNode *node, void *user_data) * * \param[in,out] docpriv XML document private data */ -static void -reset_doc_private_data(xml_doc_private_t *docpriv) +void +pcmk__xml_reset_doc_private_data(xml_doc_private_t *docpriv) { if (docpriv == NULL) { return; @@ -456,7 +456,7 @@ reset_doc_private_data(xml_doc_private_t *docpriv) static void free_doc_private_data(xmlDoc *doc) { - reset_doc_private_data(doc->_private); + pcmk__xml_reset_doc_private_data(doc->_private); g_clear_pointer(&doc->_private, free); } @@ -667,7 +667,7 @@ pcmk__xml_commit_changes(xmlDoc *doc) pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions, NULL); } - reset_doc_private_data(docpriv); + pcmk__xml_reset_doc_private_data(docpriv); } /*! From 77c7d3d2371c385fcd582517aac8096798dc8a0e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 03:06:15 -0800 Subject: [PATCH 093/101] Refactor: libcrmcommon: Expose free_xml_with_position() as lib-private We'll use it in an upcoming commit that splits XML change-tracking/committing into a separate file. Signed-off-by: Reid Wahl --- lib/common/crmcommon_private.h | 3 +++ lib/common/xml.c | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 2384a7b69df..4f02d55bb33 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -137,6 +137,9 @@ 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); diff --git a/lib/common/xml.c b/lib/common/xml.c index c100d4d3eef..a9675ac545c 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -917,8 +917,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; @@ -1005,7 +1005,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); } /*! @@ -1592,14 +1592,14 @@ mark_child_deleted(xmlNode *old_child, xmlNode *new_parent) // 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__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 (free_xml_with_position(candidate, pos) != pcmk_rc_ok) { - // ACLs denied deletion in free_xml_with_position. Free candidate here. + 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); } From 9418882765b6acfb7dda369b4ffa696762a41079 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 01:15:40 -0800 Subject: [PATCH 094/101] Refactor: libcrmcommon: Split XML change code into its own file I wanted to call it xml_change.c. This code is much more about calculating and committing changes than about tracking them. However, we already have patchset{,_display}.c, which deals with XML changes in its own way. I'm trying to introduce as little confusion as possible. I went with tracking based on the flag name pcmk__xf_tracking. Signed-off-by: Reid Wahl --- include/crm/common/xml_internal.h | 4 +- include/crm/common/xml_tracking_internal.h | 30 + lib/common/Makefile.am | 1 + lib/common/xml.c | 772 -------------------- lib/common/xml_tracking.c | 791 +++++++++++++++++++++ 5 files changed, 823 insertions(+), 775 deletions(-) create mode 100644 include/crm/common/xml_tracking_internal.h create mode 100644 lib/common/xml_tracking.c diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 611ee92b405..97e00de10dd 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -426,9 +427,6 @@ 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 *), 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/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/xml.c b/lib/common/xml.c index a9675ac545c..1bb64b2b1d0 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -591,85 +591,6 @@ pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set) return position; } -/*! - * \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); -} - /*! * \internal * \brief Create a new XML document @@ -1319,699 +1240,6 @@ pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type) return g_string_free(copy, FALSE); } -/*! - * \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); -} - char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns) { 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); +} From 0dfe6b6c5e867c29e0a1eeadd24b82954fcb8d06 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 14:46:14 -0800 Subject: [PATCH 095/101] Refactor: libcrmcommon: Drop pcmk__xe_set_props() It takes up more space and is slightly less efficient than the corresponding series of pcmk__xe_set() calls. I don't see an advantage for readability or performance, so we might as well drop it. Signed-off-by: Reid Wahl --- daemons/controld/controld_join_dc.c | 8 +-- include/crm/common/xml_element_internal.h | 25 +------ lib/cib/cib_remote.c | 10 ++- lib/common/messages.c | 21 +++--- lib/common/output_html.c | 12 ++-- lib/common/output_xml.c | 6 +- .../xml_element/pcmk__xe_copy_attrs_test.c | 8 +-- .../xml_element/pcmk__xe_sort_attrs_test.c | 16 ++--- lib/common/xml_element.c | 26 +++---- lib/fencing/st_output.c | 14 ++-- lib/pacemaker/pcmk_injections.c | 22 +++--- lib/pacemaker/pcmk_output.c | 71 +++++++------------ lib/pengine/pe_output.c | 34 ++++----- 13 files changed, 103 insertions(+), 170 deletions(-) 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/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index a19a127e8c3..4d99517170d 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -86,30 +86,7 @@ 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; +void pcmk__xe_set_propv(xmlNode *xml, va_list pairs); /*! * \internal 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/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/output_html.c b/lib/common/output_html.c index a2fbb7d7867..64bb0a01e5e 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -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) { @@ -491,10 +491,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..68c94581d35 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -210,10 +210,8 @@ xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_ 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(node, PCMK_XA_CODE, rc_as_str); + 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); diff --git a/lib/common/tests/xml_element/pcmk__xe_copy_attrs_test.c b/lib/common/tests/xml_element/pcmk__xe_copy_attrs_test.c index c8583fbe7af..19f8b7a1036 100644 --- a/lib/common/tests/xml_element/pcmk__xe_copy_attrs_test.c +++ b/lib/common/tests/xml_element/pcmk__xe_copy_attrs_test.c @@ -71,11 +71,9 @@ copy_multiple(void **state) xmlNode *src = pcmk__xe_create(NULL, "test"); xmlNode *target = pcmk__xe_create(NULL, "test"); - pcmk__xe_set_props(src, - "attr1", "value1", - "attr2", "value2", - "attr3", "value3", - NULL); + pcmk__xe_set(src, "attr1", "value1"); + pcmk__xe_set(src, "attr2", "value2"); + pcmk__xe_set(src, "attr3", "value3"); assert_int_equal(pcmk__xe_copy_attrs(target, src, pcmk__xaf_none), pcmk_rc_ok); diff --git a/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c b/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c index 5ee342aeaec..4d67ebfdb06 100644 --- a/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c +++ b/lib/common/tests/xml_element/pcmk__xe_sort_attrs_test.c @@ -156,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); @@ -199,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_element.c b/lib/common/xml_element.c index 2d21f50cf86..475d60e8acb 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -1109,11 +1109,22 @@ pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags) return ENXIO; } +/*! + * \internal + * \brief Set a list of name/value pairs as attributes for an XML element + * + * \param[in,out] xml XML element + * \param[in] pairs NULL-terminated list of name/value pairs + * + * \note A \c NULL name terminates the arguments; a \c NULL value will be + * skipped. + */ void -pcmk__xe_set_propv(xmlNodePtr node, va_list pairs) +pcmk__xe_set_propv(xmlNode *xml, va_list pairs) { while (true) { - const char *name, *value; + const char *name = NULL; + const char *value = NULL; name = va_arg(pairs, const char *); if (name == NULL) { @@ -1121,19 +1132,10 @@ pcmk__xe_set_propv(xmlNodePtr node, va_list pairs) } value = va_arg(pairs, const char *); - pcmk__xe_set(node, name, value); + pcmk__xe_set(xml, 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/fencing/st_output.c b/lib/fencing/st_output.c index 0f68ed2ad74..fbfd988fe16 100644 --- a/lib/fencing/st_output.c +++ b/lib/fencing/st_output.c @@ -489,10 +489,8 @@ stonith_event_xml(pcmk__output_t *out, va_list args) 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(node, PCMK_XA_STATUS, PCMK_VALUE_FAILED); + pcmk__xe_set(node, PCMK_XA_EXIT_REASON, event->exit_reason); break; case st_done: @@ -501,10 +499,10 @@ stonith_event_xml(pcmk__output_t *out, va_list args) default: { char *state = pcmk__itoa(event->state); - pcmk__xe_set_props(node, - PCMK_XA_STATUS, PCMK_VALUE_PENDING, - PCMK_XA_EXTENDED_STATUS, state, - NULL); + + pcmk__xe_set(node, PCMK_XA_STATUS, PCMK_VALUE_PENDING); + pcmk__xe_set(node, PCMK_XA_EXTENDED_STATUS, state); + free(state); break; } 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 2dc9e7a4e37..e395c121c00 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -287,13 +287,10 @@ rsc_action_item_xml(pcmk__output_t *out, va_list args) 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 +298,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 +307,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 +316,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); diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 79f71af5479..849eabc4e47 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1652,13 +1652,11 @@ failed_action_xml(pcmk__output_t *out, va_list args) { 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(node, PCMK_XA_LAST_RC_CHANGE, rc_change); + pcmk__xe_set(node, PCMK_XA_QUEUED, queue_time); + pcmk__xe_set(node, PCMK_XA_EXEC, exec); + pcmk__xe_set(node, PCMK_XA_INTERVAL, interval_ms_s); + pcmk__xe_set(node, PCMK_XA_TASK, task); free(interval_ms_s); free(rc_change); @@ -2305,10 +2303,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(node, PCMK_XA_RSC, rsc_printable_id(rsc)); + pcmk__xe_set(node, PCMK_XA_AGENT, agent_tuple); + free(agent_tuple); } @@ -2317,10 +2314,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(node, PCMK_XA_LAST_RC_CHANGE, last_rc_change); + pcmk__xe_set(node, PCMK_XA_EXEC_TIME, exec_time); } return pcmk_rc_ok; @@ -2998,11 +2993,10 @@ resource_history_xml(pcmk__output_t *out, va_list args) { } 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); + pcmk__xe_set(node, PCMK_XA_ORPHAN, PCMK_VALUE_FALSE); + pcmk__xe_set(node, PCMK_XA_REMOVED, PCMK_VALUE_FALSE); + pcmk__xe_set(node, PCMK_META_MIGRATION_THRESHOLD, migration_s); + free(migration_s); if (failcount > 0) { From d644a63ca06348ee972aca30d73288f7d4bc3a94 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 14:58:06 -0800 Subject: [PATCH 096/101] Refactor: libpe_status: Return void from pe__name_and_nvpairs_xml() Signed-off-by: Reid Wahl --- include/crm/pengine/internal.h | 4 +-- lib/pengine/bundle.c | 30 ++++++++++---------- lib/pengine/clone.c | 26 +++++++++--------- lib/pengine/group.c | 18 ++++++------ lib/pengine/native.c | 50 ++++++++++++++++------------------ lib/pengine/pe_output.c | 40 ++++++++++++--------------- 6 files changed, 81 insertions(+), 87 deletions(-) diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index c9fba4295fd..14c57afa0d2 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -77,8 +77,8 @@ 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; +void 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); diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index 5cdc65239df..a5aba88b4d9 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1468,25 +1468,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); + 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); } id = pcmk__itoa(replica->offset); - rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, - PCMK_XA_ID, id, - NULL); + pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, + PCMK_XA_ID, id, + NULL); 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..12e89432469 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -587,21 +587,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); + 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); } + 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/group.c b/lib/pengine/group.c index bb6cb62c2d2..973cc374c63 100644 --- a/lib/pengine/group.c +++ b/lib/pengine/group.c @@ -302,16 +302,16 @@ pe__group_xml(pcmk__output_t *out, va_list args) 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); + 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); + rc = pcmk_rc_ok; free(count); - pcmk__assert(rc == pcmk_rc_ok); } out->message(out, (const char *) child_rsc->priv->xml->name, diff --git a/lib/pengine/native.c b/lib/pengine/native.c index acf6b839f09..f0285e02e0a 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -781,41 +781,39 @@ pe__resource_xml(pcmk__output_t *out, va_list args) } // @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); + 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); + 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); - 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); + 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__output_xml_pop_parent(out); diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 849eabc4e47..151f48b141a 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -619,7 +619,7 @@ pe__node_display_name(pcmk_node_t *node, bool print_detail) return node_name; } -int +void pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name, ...) { @@ -639,7 +639,6 @@ pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name if (is_list) { pcmk__output_xml_push_parent(out, xml_node); } - return pcmk_rc_ok; } static const char * @@ -2083,28 +2082,25 @@ node_xml(pcmk__output_t *out, va_list args) { 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); + 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); if (pcmk__is_guest_or_bundle_node(node)) { xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); From c5ff64802d70078eb9105b318b1e920d35595ed3 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 15:10:01 -0800 Subject: [PATCH 097/101] Refactor: libpe_status: Drop pe__name_and_nvpairs_xml() It's the same as pcmk__output_xml_create_parent() when is_list is true, and it's the same as pcmk__output_create_xml_node() when is_list is false. Signed-off-by: Reid Wahl --- include/crm/pengine/internal.h | 2 -- lib/pengine/bundle.c | 26 ++++++++-------- lib/pengine/clone.c | 24 +++++++------- lib/pengine/group.c | 18 +++++------ lib/pengine/native.c | 46 +++++++++++++-------------- lib/pengine/pe_output.c | 57 +++++++++++----------------------- 6 files changed, 75 insertions(+), 98 deletions(-) diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index 14c57afa0d2..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); -void 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); diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index a5aba88b4d9..5446d3222d4 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1468,22 +1468,22 @@ pe__bundle_xml(pcmk__output_t *out, va_list args) desc = pe__resource_description(rsc, show_opts); - 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__output_xml_create_parent(out, 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); } id = pcmk__itoa(replica->offset); - pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, - PCMK_XA_ID, id, - NULL); + pcmk__output_xml_create_parent(out, PCMK_XE_REPLICA, + PCMK_XA_ID, id, + NULL); free(id); rc = pcmk_rc_ok; diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index 12e89432469..a9d85a2ad70 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -587,18 +587,18 @@ pe__clone_xml(pcmk__output_t *out, va_list args) printed_header = TRUE; - 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__output_xml_create_parent(out, 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); } rc = pcmk_rc_ok; diff --git a/lib/pengine/group.c b/lib/pengine/group.c index 973cc374c63..ea7796254b7 100644 --- a/lib/pengine/group.c +++ b/lib/pengine/group.c @@ -302,14 +302,14 @@ pe__group_xml(pcmk__output_t *out, va_list args) pcmk__rsc_managed); const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc)); - 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); + pcmk__output_xml_create_parent(out, 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); rc = pcmk_rc_ok; free(count); } @@ -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 f0285e02e0a..04256269254 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -781,24 +781,24 @@ pe__resource_xml(pcmk__output_t *out, va_list args) } // @COMPAT PCMK_XA_ORPHANED is deprecated since 3.0.2 - 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); + pcmk__output_xml_create_parent(out, 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); rc = pcmk_rc_ok; free(ra_name); free(nodes_running_on); @@ -809,11 +809,11 @@ pe__resource_xml(pcmk__output_t *out, va_list args) pcmk_node_t *node = (pcmk_node_t *) gIter->data; const char *cached = pcmk__btoa(node->details->online); - 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__output_create_xml_node(out, PCMK_XE_NODE, + PCMK_XA_NAME, node->priv->name, + PCMK_XA_ID, node->priv->id, + PCMK_XA_CACHED, cached, + NULL); } pcmk__output_xml_pop_parent(out); diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 151f48b141a..48c81fe0737 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -619,28 +619,6 @@ pe__node_display_name(pcmk_node_t *node, bool print_detail) return node_name; } -void -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); - } -} - static const char * role_desc(enum rsc_role_e role) { @@ -2082,23 +2060,24 @@ node_xml(pcmk__output_t *out, va_list args) { char *resources_running = pcmk__itoa(length); const char *node_type = node_variant_text(node->priv->variant); - 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); + pcmk__output_xml_create_parent(out, 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); From d9cc374b145a6ccdadd5c218a8e8a79343f2b72a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 15:53:51 -0800 Subject: [PATCH 098/101] Refactor: libcrmcommon: pcmk__output_xml_create_parent() drops list arg I don't think the variadic argument helps readability much. It avoids the need to store the result in an xmlNode * variable in the caller, which can save a line. But it requires an extra line for the NULL terminator, and it requires indenting each line much farther, which is one reason we have so many temp variables. Signed-off-by: Reid Wahl --- daemons/pacemakerd/pacemakerd.c | 10 ++-- include/crm/common/output_internal.h | 6 +- lib/common/options_display.c | 29 +++++----- lib/common/output_html.c | 8 ++- lib/common/output_xml.c | 35 ++++-------- lib/lrmd/lrmd_output.c | 26 ++++----- lib/pacemaker/pcmk_output.c | 29 ++++++---- lib/pengine/bundle.c | 27 ++++----- lib/pengine/clone.c | 26 ++++----- lib/pengine/group.c | 24 ++++---- lib/pengine/native.c | 43 +++++++------- lib/pengine/pe_output.c | 85 ++++++++++++---------------- tools/crm_resource_print.c | 82 ++++++++++++--------------- tools/crm_shadow.c | 9 +-- tools/iso8601.c | 2 +- 15 files changed, 203 insertions(+), 238 deletions(-) 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/include/crm/common/output_internal.h b/include/crm/common/output_internal.h index bdc0837bfb9..1d9c6917b66 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 diff --git a/lib/common/options_display.c b/lib/common/options_display.c index 40cc5de75ef..c637e616483 100644 --- a/lib/common/options_display.c +++ b/lib/common/options_display.c @@ -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,14 +428,13 @@ 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, @@ -446,10 +446,9 @@ add_option_metadata_xml(pcmk__output_t *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 +484,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 +495,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 64bb0a01e5e..1a33859694e 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; } @@ -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); } diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index 68c94581d35..d48f6197c1a 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -207,18 +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(node, PCMK_XA_CODE, rc_as_str); + 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) { @@ -245,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); @@ -266,8 +258,6 @@ 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 @@ -359,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); @@ -475,9 +466,9 @@ 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); @@ -485,10 +476,6 @@ pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) { node = pcmk__output_create_xml_node(out, name, NULL); - va_start(args, name); - pcmk__xe_set_propv(node, args); - va_end(args); - pcmk__output_xml_push_parent(out, node); return node; } 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_output.c b/lib/pacemaker/pcmk_output.c index e395c121c00..95e9fea521e 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -616,7 +616,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, @@ -1593,7 +1593,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); @@ -2414,6 +2414,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: * @@ -2422,9 +2423,11 @@ 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_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); + pcmk__output_create_xml_node(out, PCMK_XA_ATTRIBUTE, PCMK_XA_NAME, name, PCMK_XA_VALUE, value, @@ -2489,10 +2492,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; + + 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_TICKET, - PCMK_XA_ID, ticket_id, NULL); - pcmk__output_xml_create_parent(out, PCMK_XE_CONSTRAINTS, NULL); + 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 */ @@ -2529,7 +2534,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 @@ -2552,7 +2557,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); @@ -2563,7 +2568,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); } @@ -2610,7 +2615,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 5446d3222d4..de973a81316 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1432,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); @@ -1468,22 +1469,22 @@ pe__bundle_xml(pcmk__output_t *out, va_list args) desc = pe__resource_description(rsc, show_opts); - pcmk__output_xml_create_parent(out, 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); + 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); - pcmk__output_xml_create_parent(out, 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); rc = pcmk_rc_ok; diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index a9d85a2ad70..debcd889eb5 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -571,6 +571,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 +579,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,18 +587,18 @@ pe__clone_xml(pcmk__output_t *out, va_list args) printed_header = TRUE; - pcmk__output_xml_create_parent(out, 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); + 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; diff --git a/lib/pengine/group.c b/lib/pengine/group.c index ea7796254b7..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)); - - pcmk__output_xml_create_parent(out, 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); + + 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; - free(count); } out->message(out, (const char *) child_rsc->priv->xml->name, diff --git a/lib/pengine/native.c b/lib/pengine/native.c index 04256269254..f64cc20255b 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,34 +773,32 @@ 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 - pcmk__output_xml_create_parent(out, 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); for (GList *gIter = rsc->priv->active_nodes; gIter != NULL; gIter = gIter->next) { diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 48c81fe0737..50f6fc444af 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1787,7 +1787,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); @@ -1807,7 +1807,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); @@ -2041,45 +2041,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); - pcmk__output_xml_create_parent(out, 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); + 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); @@ -2100,10 +2091,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; @@ -2954,37 +2946,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(node, PCMK_XA_ORPHAN, PCMK_VALUE_FALSE); - pcmk__xe_set(node, PCMK_XA_REMOVED, PCMK_VALUE_FALSE); - pcmk__xe_set(node, PCMK_META_MIGRATION_THRESHOLD, migration_s); - - 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); } } diff --git a/tools/crm_resource_print.c b/tools/crm_resource_print.c index c52e4481616..35130be718e 100644 --- a/tools/crm_resource_print.c +++ b/tools/crm_resource_print.c @@ -154,15 +154,14 @@ 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, @@ -204,7 +203,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; @@ -445,19 +444,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; + xmlNode *xml = 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); - - 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; @@ -627,9 +621,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 +734,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 +761,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 +773,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 +790,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 +810,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); From e14e4d8fd2c921b51261d828ce699f77c50f98bc Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 17:28:43 -0800 Subject: [PATCH 099/101] Refactor: libcrmcommon: pcmk__output_create_xml_node() drops list arg I don't think the variadic argument helps readability much. It avoids the need to store the result in an xmlNode * variable in the caller, which can save a line. But it requires an extra line for the NULL terminator, and it requires indenting each line much farther, which is one reason we have so many temp variables. Signed-off-by: Reid Wahl --- include/crm/common/output_internal.h | 6 +- lib/common/options_display.c | 11 +- lib/common/output_html.c | 2 +- lib/common/output_xml.c | 38 +-- lib/fencing/st_output.c | 63 ++-- lib/pacemaker/pcmk_output.c | 303 ++++++++++--------- lib/pengine/native.c | 14 +- lib/pengine/pe_output.c | 429 ++++++++++++--------------- tools/cibadmin.c | 7 +- tools/crm_mon.c | 8 +- tools/crm_node.c | 42 +-- tools/crm_resource_print.c | 56 ++-- 12 files changed, 447 insertions(+), 532 deletions(-) diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h index 1d9c6917b66..2f99732a857 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -779,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/lib/common/options_display.c b/lib/common/options_display.c index c637e616483..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); @@ -437,9 +437,8 @@ add_option_metadata_xml(pcmk__output_t *out, 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); } diff --git a/lib/common/output_html.c b/lib/common/output_html.c index 1a33859694e..861edea9099 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -406,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 diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index d48f6197c1a..6ff4e5f74c7 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -263,17 +263,17 @@ xml_subprocess_output(pcmk__output_t *out, int exit_status, 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) @@ -310,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; } @@ -474,7 +474,7 @@ pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name) 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__output_xml_push_parent(out, node); return node; @@ -499,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); @@ -512,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 @@ -527,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/fencing/st_output.c b/lib/fencing/st_output.c index fbfd988fe16..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,45 +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(node, PCMK_XA_STATUS, PCMK_VALUE_FAILED); - pcmk__xe_set(node, PCMK_XA_EXIT_REASON, event->exit_reason); + 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(node, PCMK_XA_STATUS, PCMK_VALUE_PENDING); - pcmk__xe_set(node, PCMK_XA_EXTENDED_STATUS, state); - - 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); } @@ -583,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/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 95e9fea521e..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,10 +277,9 @@ 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)) { @@ -678,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; } @@ -804,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) { @@ -825,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; } @@ -854,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; @@ -898,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; } @@ -951,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; } @@ -1043,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, @@ -1335,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); } @@ -1380,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; } @@ -1424,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; @@ -1465,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; } @@ -1506,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; } @@ -1545,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; } @@ -1638,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; } @@ -1676,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; } @@ -1711,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; } @@ -1762,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); - - if (interval_ms) { - char *interval_s = pcmk__itoa(interval_ms); + 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); - 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; @@ -2231,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; @@ -2289,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: @@ -2384,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; } @@ -2428,10 +2424,10 @@ ticket_attribute_xml(pcmk__output_t *out, va_list args) xml = pcmk__output_xml_create_parent(out, PCMK_XE_TICKET); pcmk__xe_set(xml, PCMK_XA_ID, ticket_id); - pcmk__output_create_xml_node(out, PCMK_XA_ATTRIBUTE, - PCMK_XA_NAME, name, - PCMK_XA_VALUE, value, - NULL); + 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); @@ -2510,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; } @@ -2596,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; } diff --git a/lib/pengine/native.c b/lib/pengine/native.c index f64cc20255b..b68c4e9d744 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -803,14 +803,12 @@ pe__resource_xml(pcmk__output_t *out, va_list args) 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__output_create_xml_node(out, PCMK_XE_NODE, - PCMK_XA_NAME, node->priv->name, - PCMK_XA_ID, node->priv->id, - PCMK_XA_CACHED, cached, - NULL); + pcmk_node_t *node = gIter->data; + + 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_output.c b/lib/pengine/pe_output.c index 50f6fc444af..6d20aa2f79b 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -675,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; } @@ -749,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)); } @@ -853,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); - - s = pcmk__itoa(nresources); - pcmk__xe_set(resources_node, PCMK_XA_NUMBER, s); - free(s); + xmlNode *xml = NULL; - s = pcmk__itoa(ndisabled); - pcmk__xe_set(resources_node, PCMK_XA_DISABLED, 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(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; } @@ -892,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"); } @@ -967,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; } @@ -1053,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)"); @@ -1193,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; } @@ -1229,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; @@ -1276,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; } @@ -1300,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); @@ -1345,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; @@ -1583,14 +1551,11 @@ 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; - char *rc_s = NULL; - xmlNodePtr node = NULL; + xmlNode *xml = NULL; gchar *exit_reason_esc = pcmk__xml_escape(exit_reason, pcmk__xml_escape_attr); @@ -1600,19 +1565,15 @@ failed_action_xml(pcmk__output_t *out, va_list args) { 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_esc, - 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) { @@ -1620,22 +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(node, PCMK_XA_LAST_RC_CHANGE, rc_change); - pcmk__xe_set(node, PCMK_XA_QUEUED, queue_time); - pcmk__xe_set(node, PCMK_XA_EXEC, exec); - pcmk__xe_set(node, PCMK_XA_INTERVAL, interval_ms_s); - pcmk__xe_set(node, PCMK_XA_TASK, task); + 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); } @@ -1827,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); @@ -2141,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)", @@ -2238,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); @@ -2270,8 +2225,8 @@ node_and_op_xml(pcmk__output_t *out, va_list args) { (has_provider? provider : ""), kind); - pcmk__xe_set(node, PCMK_XA_RSC, rsc_printable_id(rsc)); - pcmk__xe_set(node, PCMK_XA_AGENT, agent_tuple); + pcmk__xe_set(operation, PCMK_XA_RSC, rsc_printable_id(rsc)); + pcmk__xe_set(operation, PCMK_XA_AGENT, agent_tuple); free(agent_tuple); } @@ -2281,8 +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(node, PCMK_XA_LAST_RC_CHANGE, last_rc_change); - pcmk__xe_set(node, PCMK_XA_EXEC_TIME, exec_time); + 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; @@ -2296,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; @@ -2404,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; } @@ -2763,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; @@ -2804,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); } @@ -2829,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); } } @@ -2878,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); + + pcmk__xe_set(xml, PCMK_XA_ID, child_rsc->id); + pcmk__xe_set(xml, PCMK_XA_SCORE, score); - if (chosen) { - pcmk__xe_set(node, PCMK_XA_NODE, chosen->priv->name); + if (chosen != NULL) { + pcmk__xe_set(xml, PCMK_XA_NODE, chosen->priv->name); } return pcmk_rc_ok; @@ -3195,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); - 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); + 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); + + g_hash_table_foreach(rsc->priv->utilization, add_dump_node, xml); return pcmk_rc_ok; } @@ -3317,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); } @@ -3345,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/tools/cibadmin.c b/tools/cibadmin.c index f9562acf085..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; } 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 35130be718e..19485bf19f2 100644 --- a/tools/crm_resource_print.c +++ b/tools/crm_resource_print.c @@ -163,11 +163,10 @@ attribute_changed_xml(pcmk__output_t *out, va_list args) 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); @@ -291,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; } @@ -351,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; } @@ -541,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; From b27d13df044863af5a91444fb91f5cc6c4467171 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 17:45:07 -0800 Subject: [PATCH 100/101] Refactor: libpe_status: Drop a pcmk__itoa() call Use pcmk__xe_set_int() instead. Signed-off-by: Reid Wahl --- lib/pengine/clone.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index debcd889eb5..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; } From f57c1122bca9dcaabf814d665f621a664a3bd154 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 30 Dec 2025 17:45:49 -0800 Subject: [PATCH 101/101] Refactor: libcrmcommon: Drop pcmk__xe_set_propv() Nothing calls it anymore. Signed-off-by: Reid Wahl --- include/crm/common/xml_element_internal.h | 2 -- lib/common/xml_element.c | 27 ----------------------- 2 files changed, 29 deletions(-) diff --git a/include/crm/common/xml_element_internal.h b/include/crm/common/xml_element_internal.h index 4d99517170d..2114e942556 100644 --- a/include/crm/common/xml_element_internal.h +++ b/include/crm/common/xml_element_internal.h @@ -86,8 +86,6 @@ void pcmk__xe_sort_attrs(xmlNode *xml); void pcmk__xe_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3); -void pcmk__xe_set_propv(xmlNode *xml, va_list pairs); - /*! * \internal * \brief Get first attribute of an XML element diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 475d60e8acb..7d969e4faa9 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -1109,33 +1109,6 @@ pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags) return ENXIO; } -/*! - * \internal - * \brief Set a list of name/value pairs as attributes for an XML element - * - * \param[in,out] xml XML element - * \param[in] pairs NULL-terminated list of name/value pairs - * - * \note A \c NULL name terminates the arguments; a \c NULL value will be - * skipped. - */ -void -pcmk__xe_set_propv(xmlNode *xml, va_list pairs) -{ - while (true) { - const char *name = NULL; - const char *value = NULL; - - name = va_arg(pairs, const char *); - if (name == NULL) { - return; - } - - value = va_arg(pairs, const char *); - pcmk__xe_set(xml, name, value); - } -} - int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int (*handler)(xmlNode *xml, void *userdata),