summaryrefslogtreecommitdiffstats
path: root/lib/common/patchset_display.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/patchset_display.c')
-rw-r--r--lib/common/patchset_display.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c
new file mode 100644
index 0000000..731d437
--- /dev/null
+++ b/lib/common/patchset_display.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <crm/msg_xml.h>
+
+#include "crmcommon_private.h"
+
+/*!
+ * \internal
+ * \brief Output an XML patchset header
+ *
+ * This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
+ * element and its children).
+ *
+ * All header lines contain three integers separated by dots, of the form
+ * <tt>{0}.{1}.{2}</tt>:
+ * * \p {0}: \p XML_ATTR_GENERATION_ADMIN
+ * * \p {1}: \p XML_ATTR_GENERATION
+ * * \p {2}: \p XML_ATTR_NUMUPDATES
+ *
+ * Lines containing \p "---" describe removals and end with the patch format
+ * number. Lines containing \p "+++" describe additions and end with the patch
+ * digest.
+ *
+ * \param[in,out] out Output object
+ * \param[in] patchset XML patchset to output
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This function produces output only for text-like formats.
+ */
+static int
+xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
+{
+ int rc = pcmk_rc_no_output;
+ int add[] = { 0, 0, 0 };
+ int del[] = { 0, 0, 0 };
+
+ xml_patch_versions(patchset, add, del);
+
+ if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
+ const char *fmt = crm_element_value(patchset, "format");
+ const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
+
+ out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
+ rc = out->info(out, "Diff: +++ %d.%d.%d %s",
+ add[0], add[1], add[2], digest);
+
+ } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
+ rc = out->info(out, "Local-only Change: %d.%d.%d",
+ add[0], add[1], add[2]);
+ }
+
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Output a user-friendly form of XML additions or removals
+ *
+ * \param[in,out] out Output object
+ * \param[in] prefix String to prepend to every line of output
+ * \param[in] data XML node to output
+ * \param[in] depth Current indentation level
+ * \param[in] options Group of \p pcmk__xml_fmt_options flags
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This function produces output only for text-like formats.
+ */
+static int
+xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
+ const xmlNode *data, int depth, uint32_t options)
+{
+ if (!xml_has_children(data)
+ || (crm_element_value(data, XML_DIFF_MARKER) != NULL)) {
+
+ // Found a change; clear the pcmk__xml_fmt_diff_short option if set
+ options &= ~pcmk__xml_fmt_diff_short;
+
+ if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
+ prefix = PCMK__XML_PREFIX_CREATED;
+ } else { // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
+ prefix = PCMK__XML_PREFIX_DELETED;
+ }
+ }
+
+ if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
+ int rc = pcmk_rc_no_output;
+
+ // Keep looking for the actual change
+ for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
+ child = pcmk__xml_next(child)) {
+ int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child,
+ depth + 1, options);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+ }
+ return rc;
+ }
+
+ return pcmk__xml_show(out, prefix, data, depth,
+ options
+ |pcmk__xml_fmt_open
+ |pcmk__xml_fmt_children
+ |pcmk__xml_fmt_close);
+}
+
+/*!
+ * \internal
+ * \brief Output a user-friendly form of an XML patchset (format 1)
+ *
+ * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
+ * children) into a user-friendly combined diff output.
+ *
+ * \param[in,out] out Output object
+ * \param[in] patchset XML patchset to output
+ * \param[in] options Group of \p pcmk__xml_fmt_options flags
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This function produces output only for text-like formats.
+ */
+static int
+xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
+ uint32_t options)
+{
+ const xmlNode *removed = NULL;
+ const xmlNode *added = NULL;
+ const xmlNode *child = NULL;
+ bool is_first = true;
+ int rc = xml_show_patchset_header(out, patchset);
+
+ /* It's not clear whether "- " or "+ " ever does *not* get overridden by
+ * PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
+ * However, v1 patchsets can only exist during rolling upgrades from
+ * Pacemaker 1.1.11, so not worth worrying about.
+ */
+ removed = find_xml_node(patchset, "diff-removed", FALSE);
+ for (child = pcmk__xml_first_child(removed); child != NULL;
+ child = pcmk__xml_next(child)) {
+ int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0,
+ options
+ |pcmk__xml_fmt_diff_minus);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+
+ if (is_first) {
+ is_first = false;
+ } else {
+ rc = pcmk__output_select_rc(rc, out->info(out, " --- "));
+ }
+ }
+
+ is_first = true;
+ added = find_xml_node(patchset, "diff-added", FALSE);
+ for (child = pcmk__xml_first_child(added); child != NULL;
+ child = pcmk__xml_next(child)) {
+ int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0,
+ options
+ |pcmk__xml_fmt_diff_plus);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+
+ if (is_first) {
+ is_first = false;
+ } else {
+ rc = pcmk__output_select_rc(rc, out->info(out, " +++ "));
+ }
+ }
+
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Output a user-friendly form of an XML patchset (format 2)
+ *
+ * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
+ * children) into a user-friendly combined diff output.
+ *
+ * \param[in,out] out Output object
+ * \param[in] patchset XML patchset to output
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note This function produces output only for text-like formats.
+ */
+static int
+xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
+{
+ int rc = xml_show_patchset_header(out, patchset);
+ int temp_rc = pcmk_rc_no_output;
+
+ for (const xmlNode *change = pcmk__xml_first_child(patchset);
+ change != NULL; change = pcmk__xml_next(change)) {
+ const char *op = crm_element_value(change, XML_DIFF_OP);
+ const char *xpath = crm_element_value(change, XML_DIFF_PATH);
+
+ if (op == NULL) {
+ continue;
+ }
+
+ if (strcmp(op, "create") == 0) {
+ char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
+ xpath);
+
+ temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
+ pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+
+ // Overwrite all except the first two characters with spaces
+ for (char *ch = prefix + 2; *ch != '\0'; ch++) {
+ *ch = ' ';
+ }
+
+ temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
+ pcmk__xml_fmt_pretty
+ |pcmk__xml_fmt_children
+ |pcmk__xml_fmt_close);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+ free(prefix);
+
+ } else if (strcmp(op, "move") == 0) {
+ const char *position = crm_element_value(change, XML_DIFF_POSITION);
+
+ temp_rc = out->info(out,
+ PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
+ xpath, position);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+
+ } else if (strcmp(op, "modify") == 0) {
+ xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
+ GString *buffer_set = NULL;
+ GString *buffer_unset = NULL;
+
+ for (const xmlNode *child = pcmk__xml_first_child(clist);
+ child != NULL; child = pcmk__xml_next(child)) {
+ const char *name = crm_element_value(child, "name");
+
+ op = crm_element_value(child, XML_DIFF_OP);
+ if (op == NULL) {
+ continue;
+ }
+
+ if (strcmp(op, "set") == 0) {
+ const char *value = crm_element_value(child, "value");
+
+ pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
+ pcmk__g_strcat(buffer_set, name, "=", value, NULL);
+
+ } else if (strcmp(op, "unset") == 0) {
+ pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
+ g_string_append(buffer_unset, name);
+ }
+ }
+
+ if (buffer_set != NULL) {
+ temp_rc = out->info(out, "+ %s: %s", xpath, buffer_set->str);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+ g_string_free(buffer_set, TRUE);
+ }
+
+ if (buffer_unset != NULL) {
+ temp_rc = out->info(out, "-- %s: %s",
+ xpath, buffer_unset->str);
+ rc = pcmk__output_select_rc(rc, temp_rc);
+ g_string_free(buffer_unset, TRUE);
+ }
+
+ } else if (strcmp(op, "delete") == 0) {
+ int position = -1;
+
+ crm_element_value_int(change, XML_DIFF_POSITION, &position);
+ if (position >= 0) {
+ temp_rc = out->info(out, "-- %s (%d)", xpath, position);
+ } else {
+ temp_rc = out->info(out, "-- %s", xpath);
+ }
+ rc = pcmk__output_select_rc(rc, temp_rc);
+ }
+ }
+
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Output a user-friendly form of an XML patchset
+ *
+ * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
+ * children) into a user-friendly combined diff output.
+ *
+ * \param[in,out] out Output object
+ * \param[in] args Message-specific arguments
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note \p args should contain only the XML patchset
+ */
+PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
+static int
+xml_patchset_default(pcmk__output_t *out, va_list args)
+{
+ xmlNodePtr patchset = va_arg(args, xmlNodePtr);
+
+ int format = 1;
+
+ if (patchset == NULL) {
+ crm_trace("Empty patch");
+ return pcmk_rc_no_output;
+ }
+
+ crm_element_value_int(patchset, "format", &format);
+ switch (format) {
+ case 1:
+ return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
+ case 2:
+ return xml_show_patchset_v2(out, patchset);
+ default:
+ crm_err("Unknown patch format: %d", format);
+ return pcmk_rc_bad_xml_patch;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Output a user-friendly form of an XML patchset
+ *
+ * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
+ * children) into a user-friendly combined diff output.
+ *
+ * \param[in,out] out Output object
+ * \param[in] args Message-specific arguments
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note \p args should contain only the XML patchset
+ */
+PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
+static int
+xml_patchset_log(pcmk__output_t *out, va_list args)
+{
+ static struct qb_log_callsite *patchset_cs = NULL;
+
+ xmlNodePtr patchset = va_arg(args, xmlNodePtr);
+
+ uint8_t log_level = pcmk__output_get_log_level(out);
+ int format = 1;
+
+ if (log_level == LOG_NEVER) {
+ return pcmk_rc_no_output;
+ }
+
+ if (patchset == NULL) {
+ crm_trace("Empty patch");
+ return pcmk_rc_no_output;
+ }
+
+ if (patchset_cs == NULL) {
+ patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
+ log_level, __LINE__,
+ crm_trace_nonlog);
+ }
+
+ if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
+ // Nothing would be logged, so skip all the work
+ return pcmk_rc_no_output;
+ }
+
+ crm_element_value_int(patchset, "format", &format);
+ switch (format) {
+ case 1:
+ if (log_level < LOG_DEBUG) {
+ return xml_show_patchset_v1(out, patchset,
+ pcmk__xml_fmt_pretty
+ |pcmk__xml_fmt_diff_short);
+ }
+ return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
+ case 2:
+ return xml_show_patchset_v2(out, patchset);
+ default:
+ crm_err("Unknown patch format: %d", format);
+ return pcmk_rc_bad_xml_patch;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Output an XML patchset
+ *
+ * This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
+ * children) without modification, as a CDATA block.
+ *
+ * \param[in,out] out Output object
+ * \param[in] args Message-specific arguments
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note \p args should contain only the XML patchset
+ */
+PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
+static int
+xml_patchset_xml(pcmk__output_t *out, va_list args)
+{
+ xmlNodePtr patchset = va_arg(args, xmlNodePtr);
+
+ if (patchset != NULL) {
+ char *buf = dump_xml_formatted_with_text(patchset);
+
+ out->output_xml(out, "xml-patchset", buf);
+ free(buf);
+ return pcmk_rc_ok;
+ }
+ crm_trace("Empty patch");
+ return pcmk_rc_no_output;
+}
+
+static pcmk__message_entry_t fmt_functions[] = {
+ { "xml-patchset", "default", xml_patchset_default },
+ { "xml-patchset", "log", xml_patchset_log },
+ { "xml-patchset", "xml", xml_patchset_xml },
+
+ { NULL, NULL, NULL }
+};
+
+/*!
+ * \internal
+ * \brief Register the formatting functions for XML patchsets
+ *
+ * \param[in,out] out Output object
+ */
+void
+pcmk__register_patchset_messages(pcmk__output_t *out) {
+ pcmk__register_messages(out, fmt_functions);
+}
+
+// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
+
+#include <crm/common/xml_compat.h>
+
+void
+xml_log_patchset(uint8_t log_level, const char *function,
+ const xmlNode *patchset)
+{
+ /* This function has some duplication relative to the message functions.
+ * This way, we can maintain the const xmlNode * in the signature. The
+ * message functions must be non-const. They have to support XML output
+ * objects, which must make a copy of a the patchset, requiring a non-const
+ * function call.
+ *
+ * In contrast, this legacy function doesn't need to support XML output.
+ */
+ static struct qb_log_callsite *patchset_cs = NULL;
+
+ pcmk__output_t *out = NULL;
+ int format = 1;
+ int rc = pcmk_rc_no_output;
+
+ switch (log_level) {
+ case LOG_NEVER:
+ return;
+ case LOG_STDOUT:
+ CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
+ break;
+ default:
+ if (patchset_cs == NULL) {
+ patchset_cs = qb_log_callsite_get(__func__, __FILE__,
+ "xml-patchset", log_level,
+ __LINE__, crm_trace_nonlog);
+ }
+ if (!crm_is_callsite_active(patchset_cs, log_level,
+ crm_trace_nonlog)) {
+ return;
+ }
+ CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
+ pcmk__output_set_log_level(out, log_level);
+ break;
+ }
+
+ if (patchset == NULL) {
+ // Should come after the LOG_NEVER check
+ crm_trace("Empty patch");
+ goto done;
+ }
+
+ crm_element_value_int(patchset, "format", &format);
+ switch (format) {
+ case 1:
+ if (log_level < LOG_DEBUG) {
+ rc = xml_show_patchset_v1(out, patchset,
+ pcmk__xml_fmt_pretty
+ |pcmk__xml_fmt_diff_short);
+ } else { // Note: LOG_STDOUT > LOG_DEBUG
+ rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
+ }
+ break;
+ case 2:
+ rc = xml_show_patchset_v2(out, patchset);
+ break;
+ default:
+ crm_err("Unknown patch format: %d", format);
+ rc = pcmk_rc_bad_xml_patch;
+ break;
+ }
+
+done:
+ out->finish(out, pcmk_rc2exitc(rc), true, NULL);
+ pcmk__output_free(out);
+}
+
+// LCOV_EXCL_STOP
+// End deprecated API