diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index c564fa9134..4283ef69b9 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -1,415 +1,415 @@ /* * Copyright 2017-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. */ #ifndef PCMK__XML_INTERNAL__H # define PCMK__XML_INTERNAL__H /* * Internal-only wrappers for and extensions to libxml2 (libxslt) */ # include # include # include # include /* transitively imports qblog.h */ # include /*! * \brief Base for directing lib{xml2,xslt} log into standard libqb backend * * This macro implements the core of what can be needed for directing * libxml2 or libxslt error messaging into standard, preconfigured * libqb-backed log stream. * * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt) * emits a single message by chunks (location is emitted separatedly from * the message itself), so we have to take the effort to combine these * chunks back to single message. Whether to do this or not is driven * with \p dechunk toggle. * * The form of a macro was chosen for implicit deriving of __FILE__, etc. * and also because static dechunking buffer should be differentiated per * library (here we assume different functions referring to this macro * will not ever be using both at once), preferably also per-library * context of use to avoid clashes altogether. * * Note that we cannot use qb_logt, because callsite data have to be known * at the moment of compilation, which it is not always the case -- xml_log * (and unfortunately there's no clear explanation of the fail to compile). * * Also note that there's no explicit guard against said libraries producing * never-newline-terminated chunks (which would just keep consuming memory), * as it's quite improbable. Termination of the program in between the * same-message chunks will raise a flag with valgrind and the likes, though. * * And lastly, regarding how dechunking combines with other non-message * parameters -- for \p priority, most important running specification * wins (possibly elevated to LOG_ERR in case of nonconformance with the * newline-termination "protocol"), \p dechunk is expected to always be * on once it was at the start, and the rest (\p postemit and \p prefix) * are picked directly from the last chunk entry finalizing the message * (also reasonable to always have it the same with all related entries). * * \param[in] priority Syslog priority for the message to be logged * \param[in] dechunk Whether to dechunk new-line terminated message * \param[in] postemit Code to be executed once message is sent out * \param[in] prefix How to prefix the message or NULL for raw passing * \param[in] fmt Format string as with printf-like functions * \param[in] ap Variable argument list to supplement \p fmt format string */ #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \ do { \ if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \ qb_log_from_external_source_va(__func__, __FILE__, (fmt), \ (priority), __LINE__, 0, (ap)); \ (void) (postemit); \ } else { \ int CXLB_len = 0; \ char *CXLB_buf = NULL; \ static int CXLB_buffer_len = 0; \ static char *CXLB_buffer = NULL; \ static uint8_t CXLB_priority = 0; \ \ CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \ \ if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \ if (CXLB_len < 0) { \ CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\ CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \ } else if (CXLB_len > 0 /* && (dechunk) */ \ && CXLB_buf[CXLB_len - 1] == '\n') { \ CXLB_buf[CXLB_len - 1] = '\0'; \ } \ if (CXLB_buffer) { \ qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \ CXLB_priority, __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buffer, CXLB_buf); \ free(CXLB_buffer); \ } else { \ qb_log_from_external_source(__func__, __FILE__, "%s%s", \ (priority), __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buf); \ } \ if (CXLB_len < 0) { \ CXLB_buf = NULL; /* restore temporary override */ \ } \ CXLB_buffer = NULL; \ CXLB_buffer_len = 0; \ (void) (postemit); \ \ } else if (CXLB_buffer == NULL) { \ CXLB_buffer_len = CXLB_len; \ CXLB_buffer = CXLB_buf; \ CXLB_buf = NULL; \ CXLB_priority = (priority); /* remember as a running severest */ \ \ } else { \ CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \ memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \ CXLB_buffer_len += CXLB_len; \ CXLB_buffer[CXLB_buffer_len] = '\0'; \ CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \ } \ free(CXLB_buf); \ } \ } while (0) /* * \enum pcmk__xml_fmt_options * \brief Bit flags to control format in XML logs and dumps */ enum pcmk__xml_fmt_options { //! Exclude certain XML attributes (for calculating digests) pcmk__xml_fmt_filtered = (1 << 0), //! Include indentation and newlines pcmk__xml_fmt_pretty = (1 << 1), //! Include full XML subtree (with any text), using libxml serialization pcmk__xml_fmt_full = (1 << 2), //! Include the opening tag of an XML element, and include XML comments pcmk__xml_fmt_open = (1 << 3), //! Include the children of an XML element pcmk__xml_fmt_children = (1 << 4), //! Include the closing tag of an XML element pcmk__xml_fmt_close = (1 << 5), // @COMPAT Remove when log_data_element() is removed //! Include XML text nodes pcmk__xml_fmt_text = (1 << 6), // @COMPAT Remove when v1 patchsets are removed //! Log a created XML subtree pcmk__xml_fmt_diff_plus = (1 << 7), // @COMPAT Remove when v1 patchsets are removed //! Log a removed XML subtree pcmk__xml_fmt_diff_minus = (1 << 8), // @COMPAT Remove when v1 patchsets are removed //! Log a minimal version of an XML diff (only showing the changes) pcmk__xml_fmt_diff_short = (1 << 9), }; int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options); int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml); -void pcmk__xml_log_patchset(uint8_t log_level, const xmlNode *patchset); +int pcmk__xml_log_patchset(uint8_t log_level, const xmlNode *patchset); /* XML search strings for guest, remote and pacemaker_remote nodes */ /* search string to find CIB resources entries for cluster nodes */ #define PCMK__XP_MEMBER_NODE_CONFIG \ "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \ "/" XML_CIB_TAG_NODE "[not(@type) or @type='member']" /* search string to find CIB resources entries for guest nodes */ #define PCMK__XP_GUEST_NODE_CONFIG \ "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \ "//" XML_TAG_META_SETS "//" XML_CIB_TAG_NVPAIR \ "[@name='" XML_RSC_ATTR_REMOTE_NODE "']" /* search string to find CIB resources entries for remote nodes */ #define PCMK__XP_REMOTE_NODE_CONFIG \ "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \ "[@type='remote'][@provider='pacemaker']" /* search string to find CIB node status entries for pacemaker_remote nodes */ #define PCMK__XP_REMOTE_NODE_STATUS \ "//" XML_TAG_CIB "//" XML_CIB_TAG_STATUS "//" XML_CIB_TAG_STATE \ "[@" XML_NODE_IS_REMOTE "='true']" enum pcmk__xml_artefact_ns { pcmk__xml_artefact_ns_legacy_rng = 1, pcmk__xml_artefact_ns_legacy_xslt, pcmk__xml_artefact_ns_base_rng, pcmk__xml_artefact_ns_base_xslt, }; void pcmk__strip_xml_text(xmlNode *xml); const char *pcmk__xe_add_last_written(xmlNode *xe); xmlNode *pcmk__xe_match(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v); void pcmk__xe_remove_matching_attrs(xmlNode *element, bool (*match)(xmlAttrPtr, void *), void *user_data); GString *pcmk__element_xpath(const xmlNode *xml); /*! * \internal * \brief Get the root directory to scan XML artefacts of given kind for * * \param[in] ns governs the hierarchy nesting against the inherent root dir * * \return root directory to scan XML artefacts of given kind for */ char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); /*! * \internal * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) * * \param[in] ns denotes path forming details (parent dir, suffix) * \param[in] filespec symbolic file specification to be combined with * #artefact_ns to form the final path * \return unwrapped path to particular XML artifact (RNG/XSLT) */ char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec); /*! * \internal * \brief Return first non-text child node of an XML node * * \param[in] parent XML node to check * * \return First non-text child node of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xml_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type == XML_TEXT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling node of an XML node * * \param[in] child XML node to check * * \return Next non-text sibling of \p child (or NULL if none) */ static inline xmlNode * pcmk__xml_next(const xmlNode *child) { xmlNode *next = (child? child->next : NULL); while (next && (next->type == XML_TEXT_NODE)) { next = next->next; } return next; } /*! * \internal * \brief Return first non-text child element of an XML node * * \param[in] parent XML node to check * * \return First child element of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xe_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type != XML_ELEMENT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling element of an XML element * * \param[in] child XML element to check * * \return Next sibling element of \p child (or NULL if none) */ static inline xmlNode * pcmk__xe_next(const xmlNode *child) { xmlNode *next = child? child->next : NULL; while (next && (next->type != XML_ELEMENT_NODE)) { next = next->next; } return next; } /*! * \internal * \brief Like pcmk__xe_set_props, but takes a va_list instead of * arguments directly. * * \param[in,out] node XML to add attributes to * \param[in] pairs NULL-terminated list of name/value pairs to add */ void pcmk__xe_set_propv(xmlNodePtr node, va_list pairs); /*! * \internal * \brief Add a NULL-terminated list of name/value pairs to the given * XML node as properties. * * \param[in,out] node XML node to add properties to * \param[in] ... NULL-terminated list of name/value pairs * * \note A NULL name terminates the arguments; a NULL value will be skipped. */ void pcmk__xe_set_props(xmlNodePtr node, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Get first attribute of an XML element * * \param[in] xe XML element to check * * \return First attribute of \p xe (or NULL if \p xe is NULL or has none) */ static inline xmlAttr * pcmk__xe_first_attr(const xmlNode *xe) { return (xe == NULL)? NULL : xe->properties; } /*! * \internal * \brief Extract the ID attribute from an XML element * * \param[in] xpath String to search * \param[in] node Node to get the ID for * * \return ID attribute of \p node in xpath string \p xpath */ char * pcmk__xpath_node_id(const char *xpath, const char *node); /* internal XML-related utilities */ enum xml_private_flags { pcmk__xf_none = 0x0000, pcmk__xf_dirty = 0x0001, pcmk__xf_deleted = 0x0002, pcmk__xf_created = 0x0004, pcmk__xf_modified = 0x0008, pcmk__xf_tracking = 0x0010, pcmk__xf_processed = 0x0020, pcmk__xf_skip = 0x0040, pcmk__xf_moved = 0x0080, pcmk__xf_acl_enabled = 0x0100, pcmk__xf_acl_read = 0x0200, pcmk__xf_acl_write = 0x0400, pcmk__xf_acl_deny = 0x0800, pcmk__xf_acl_create = 0x1000, pcmk__xf_acl_denied = 0x2000, pcmk__xf_lazy = 0x4000, }; void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); /*! * \internal * \brief Iterate over child elements of \p xml * * This function iterates over the children of \p xml, performing the * callback function \p handler on each node. If the callback returns * a value other than pcmk_rc_ok, the iteration stops and the value is * returned. It is therefore possible that not all children will be * visited. * * \param[in,out] xml The starting XML node. Can be NULL. * \param[in] child_element_name The name that the node must match in order * for \p handler to be run. If NULL, all * child elements will match. * \param[in] handler The callback function. * \param[in,out] userdata User data to pass to the callback function. * Can be NULL. * * \return Standard Pacemaker return code */ int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int (*handler)(xmlNode *xml, void *userdata), void *userdata); #endif // PCMK__XML_INTERNAL__H diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c index d76b1c887d..a87ae593d3 100644 --- a/lib/common/patchset_display.c +++ b/lib/common/patchset_display.c @@ -1,347 +1,400 @@ /* * 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 #include #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 * {0}.{1}.{2}: * * \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 void +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); - out->info(out, "Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); + 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)) { - out->info(out, "Local-only Change: %d.%d.%d", add[0], add[1], add[2]); + 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 void +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)) { - xml_show_patchset_v1_recursive(out, prefix, child, depth + 1, - options); + int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child, + depth + 1, options); + rc = pcmk__output_select_rc(rc, temp_rc); } + return rc; - } else { - pcmk__xml_show(out, prefix, data, depth, - options - |pcmk__xml_fmt_open - |pcmk__xml_fmt_children - |pcmk__xml_fmt_close); } + + 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 void -xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset) +static int +xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset, + uint32_t options) { - uint32_t options = pcmk__xml_fmt_pretty; const xmlNode *removed = NULL; const xmlNode *added = NULL; const xmlNode *child = NULL; bool is_first = true; - - // @FIXME: Use message functions to get rid of explicit fmt_name check - if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none) - && (pcmk__output_get_log_level(out) < LOG_DEBUG)) { - options |= pcmk__xml_fmt_diff_short; - } - - xml_show_patchset_header(out, patchset); + 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)) { - xml_show_patchset_v1_recursive(out, "- ", child, 0, - options|pcmk__xml_fmt_diff_minus); + 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 { - out->info(out, " --- "); + 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)) { - xml_show_patchset_v1_recursive(out, "+ ", child, 0, - options|pcmk__xml_fmt_diff_plus); + 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 { - out->info(out, " +++ "); + 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 void +static int xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset) { - xml_show_patchset_header(out, 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); - pcmk__xml_show(out, prefix, change->children, 0, - pcmk__xml_fmt_pretty|pcmk__xml_fmt_open); + 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 = ' '; } - pcmk__xml_show(out, prefix, change->children, 0, - pcmk__xml_fmt_pretty - |pcmk__xml_fmt_children - |pcmk__xml_fmt_close); + 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); - out->info(out, PCMK__XML_PREFIX_MOVED " %s moved to offset %s", - xpath, 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) { - out->info(out, "+ %s: %s", xpath, buffer_set->str); + 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) { - out->info(out, "-- %s: %s", xpath, buffer_unset->str); + 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) { - out->info(out, "-- %s (%d)", xpath, position); + temp_rc = out->info(out, "-- %s (%d)", xpath, position); } else { - out->info(out, "-- %s", xpath); + temp_rc = out->info(out, "-- %s", xpath); } + rc = pcmk__output_select_rc(rc, temp_rc); } } + + return rc; } /*! * \internal - * \brief Log a user-friendly form of an XML patchset + * \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. Depending on the value * of \p log_level, the output may be written to \p stdout or to a log file. * * \param[in] log_level Priority at which to log the messages * \param[in] patchset XML patchset to log + * + * \return Standard Pacemaker return code */ -void +int pcmk__xml_log_patchset(uint8_t log_level, const xmlNode *patchset) { - int format = 1; pcmk__output_t *out = NULL; + int format = 1; + int rc = pcmk_rc_ok; static struct qb_log_callsite *patchset_cs = NULL; switch (log_level) { case LOG_NEVER: - return; + return pcmk_rc_no_output; case LOG_STDOUT: - CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return); + rc = pcmk__text_output_new(&out, NULL); + CRM_CHECK(rc == pcmk_rc_ok, return rc); 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; + return pcmk_rc_no_output; } - CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return); + + rc = pcmk__log_output_new(&out); + CRM_CHECK(rc == pcmk_rc_ok, return rc); + 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: - xml_show_patchset_v1(out, patchset); + 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: - xml_show_patchset_v2(out, patchset); + rc = xml_show_patchset_v2(out, patchset); break; default: crm_err("Unknown patch format: %d", format); + rc = pcmk_rc_unknown_format; break; } done: - out->finish(out, CRM_EX_OK, true, NULL); + out->finish(out, pcmk_rc2exitc(rc), true, NULL); pcmk__output_free(out); + return rc; } static pcmk__message_entry_t fmt_functions[] = { { 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 void xml_log_patchset(uint8_t log_level, const char *function, const xmlNode *patchset) { pcmk__xml_log_patchset(log_level, patchset); } // LCOV_EXCL_STOP // End deprecated API