Page MenuHomeClusterLabs Projects

xml_display.c
No OneTemporary

xml_display.c

/*
* 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 <libxml/tree.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
#include "crmcommon_private.h"
static int show_xml_node(pcmk__output_t *out, GString *buffer,
const char *prefix, const xmlNode *data, int depth,
uint32_t options);
// Log an XML library error
void
pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pcmk__if_tracing(
{
PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
crm_abort(__FILE__, __PRETTY_FUNCTION__,
__LINE__, "xml library error", TRUE,
TRUE),
"XML Error: ", fmt, ap);
},
{
PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
}
);
va_end(ap);
}
/*!
* \internal
* \brief Output an XML comment with depth-based indentation
*
* \param[in,out] out Output object
* \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 currently produces output only for text-like output objects.
*/
static int
show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
uint32_t options)
{
if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
return out->info(out, "%*s<!--%s-->",
width, "", (const char *) data->content);
}
return pcmk_rc_no_output;
}
/*!
* \internal
* \brief Output an XML element in a formatted way
*
* \param[in,out] out Output object
* \param[in,out] buffer Where to build output strings
* \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 is a recursive helper function for \p show_xml_node().
* \note This currently produces output only for text-like output objects.
* \note \p buffer may be overwritten many times. The caller is responsible for
* freeing it using \p g_string_free() but should not rely on its
* contents.
*/
static int
show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
const xmlNode *data, int depth, uint32_t options)
{
int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
int rc = pcmk_rc_no_output;
if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
const char *hidden = crm_element_value(data, "hidden");
g_string_truncate(buffer, 0);
for (int lpc = 0; lpc < spaces; lpc++) {
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) {
xml_node_private_t *nodepriv = attr->_private;
const char *p_name = (const char *) attr->name;
const char *p_value = pcmk__xml_attr_value(attr);
char *p_copy = NULL;
if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
continue;
}
// @COMPAT Remove when v1 patchsets are removed
if (pcmk_any_flags_set(options,
pcmk__xml_fmt_diff_plus
|pcmk__xml_fmt_diff_minus)
&& (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
continue;
}
if ((hidden != NULL) && (p_name[0] != '\0')
&& (strstr(hidden, p_name) != NULL)) {
pcmk__str_update(&p_copy, "*****");
} else {
p_copy = crm_xml_escape(p_value);
}
pcmk__g_strcat(buffer, " ", p_name, "=\"",
pcmk__s(p_copy, "<null>"), "\"", NULL);
free(p_copy);
}
if ((data->children != NULL)
&& pcmk_is_set(options, pcmk__xml_fmt_children)) {
g_string_append_c(buffer, '>');
} else {
g_string_append(buffer, "/>");
}
rc = out->info(out, "%s%s%s",
pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
buffer->str);
}
if (data->children == NULL) {
return rc;
}
if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_close);
rc = pcmk__output_select_rc(rc, temp_rc);
}
}
if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
int temp_rc = out->info(out, "%s%s%*s</%s>",
pcmk__s(prefix, ""),
pcmk__str_empty(prefix)? "" : " ",
spaces, "", data->name);
rc = pcmk__output_select_rc(rc, temp_rc);
}
return rc;
}
/*!
* \internal
* \brief Output an XML element or comment in a formatted way
*
* \param[in,out] out Output object
* \param[in,out] buffer Where to build output strings
* \param[in] prefix String to prepend to every line of output
* \param[in] data XML node to log
* \param[in] depth Current indentation level
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This is a recursive helper function for \p pcmk__xml_show().
* \note This currently produces output only for text-like output objects.
* \note \p buffer may be overwritten many times. The caller is responsible for
* freeing it using \p g_string_free() but should not rely on its
* contents.
*/
static int
show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
const xmlNode *data, int depth, uint32_t options)
{
switch (data->type) {
case XML_COMMENT_NODE:
return show_xml_comment(out, data, depth, options);
case XML_ELEMENT_NODE:
return show_xml_element(out, buffer, prefix, data, depth, options);
default:
return pcmk_rc_no_output;
}
}
/*!
* \internal
* \brief Output an XML element or comment in a formatted way
*
* \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 nesting level
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This currently produces output only for text-like output objects.
*/
int
pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
int depth, uint32_t options)
{
int rc = pcmk_rc_no_output;
GString *buffer = NULL;
CRM_ASSERT(out != NULL);
CRM_CHECK(depth >= 0, depth = 0);
if (data == NULL) {
return rc;
}
/* Allocate a buffer once, for show_xml_node() to truncate and reuse in
* recursive calls
*/
buffer = g_string_sized_new(1024);
rc = show_xml_node(out, buffer, prefix, data, depth, options);
g_string_free(buffer, TRUE);
return rc;
}
/*!
* \internal
* \brief Output XML portions that have been marked as changed
*
* \param[in,out] out Output object
* \param[in] data XML node to output
* \param[in] 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 currently produces output only for text-like output objects.
*/
static int
show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, 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;
int rc = pcmk_rc_no_output;
int temp_rc = pcmk_rc_no_output;
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,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
// Modified or moved
bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
int spaces = pretty? (2 * depth) : 0;
const char *prefix = PCMK__XML_PREFIX_MODIFIED;
if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
prefix = PCMK__XML_PREFIX_MOVED;
}
// Log opening tag
rc = pcmk__xml_show(out, prefix, data, depth,
options|pcmk__xml_fmt_open);
// Log changes to attributes
for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
attr = attr->next) {
const char *name = (const char *) attr->name;
nodepriv = attr->_private;
if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
const char *value = pcmk__xml_attr_value(attr);
temp_rc = out->info(out, "%s %*s @%s=%s",
PCMK__XML_PREFIX_DELETED, spaces, "", name,
value);
} else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
const char *value = pcmk__xml_attr_value(attr);
if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
prefix = PCMK__XML_PREFIX_CREATED;
} else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
prefix = PCMK__XML_PREFIX_MODIFIED;
} else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
prefix = PCMK__XML_PREFIX_MOVED;
} else {
prefix = PCMK__XML_PREFIX_MODIFIED;
}
temp_rc = out->info(out, "%s %*s @%s=%s",
prefix, spaces, "", name, value);
}
rc = pcmk__output_select_rc(rc, temp_rc);
}
// Log changes to children
for (const xmlNode *child = pcmk__xml_first_child(data); 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);
}
// Log closing tag
temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, 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;
child = pcmk__xml_next(child)) {
temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
rc = pcmk__output_select_rc(rc, temp_rc);
}
return rc;
}
/*!
* \internal
* \brief Output changes to an XML node and any children
*
* \param[in,out] out Output object
* \param[in] xml XML node to output
*
* \return Standard Pacemaker return code
*
* \note This currently produces output only for text-like output objects.
*/
int
pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
{
xml_doc_private_t *docpriv = NULL;
int rc = pcmk_rc_no_output;
int temp_rc = pcmk_rc_no_output;
CRM_ASSERT(out != NULL);
CRM_ASSERT(xml != NULL);
CRM_ASSERT(xml->doc != NULL);
docpriv = xml->doc->_private;
if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
return rc;
}
for (const GList *iter = docpriv->deleted_objs; iter != NULL;
iter = iter->next) {
const pcmk__deleted_xml_t *deleted_obj = iter->data;
if (deleted_obj->position >= 0) {
temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
deleted_obj->path, deleted_obj->position);
} else {
temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
deleted_obj->path);
}
rc = pcmk__output_select_rc(rc, temp_rc);
}
temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
return pcmk__output_select_rc(rc, temp_rc);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/logging_compat.h>
#include <crm/common/xml_compat.h>
void
log_data_element(int log_level, const char *file, const char *function,
int line, const char *prefix, const xmlNode *data, int depth,
int legacy_options)
{
uint32_t options = 0;
pcmk__output_t *out = NULL;
// Confine log_level to uint8_t range
log_level = pcmk__clip_log_level(log_level);
if (data == NULL) {
do_crm_log(log_level, "%s%sNo data to dump as XML",
pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ");
return;
}
switch (log_level) {
case LOG_NEVER:
return;
case LOG_STDOUT:
CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
break;
default:
CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
pcmk__output_set_log_level(out, log_level);
break;
}
/* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and
* start using the pcmk__xml_fmt_options in all the internal functions.
*
* xml_log_option_dirty_add and xml_log_option_diff_all are ignored by
* internal code and only used here, so they don't need to be addressed.
*/
if (pcmk_is_set(legacy_options, xml_log_option_filtered)) {
options |= pcmk__xml_fmt_filtered;
}
if (pcmk_is_set(legacy_options, xml_log_option_formatted)) {
options |= pcmk__xml_fmt_pretty;
}
if (pcmk_is_set(legacy_options, xml_log_option_open)) {
options |= pcmk__xml_fmt_open;
}
if (pcmk_is_set(legacy_options, xml_log_option_children)) {
options |= pcmk__xml_fmt_children;
}
if (pcmk_is_set(legacy_options, xml_log_option_close)) {
options |= pcmk__xml_fmt_close;
}
if (pcmk_is_set(legacy_options, xml_log_option_text)) {
options |= pcmk__xml_fmt_text;
}
if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
options |= pcmk__xml_fmt_diff_plus;
}
if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
options |= pcmk__xml_fmt_diff_minus;
}
if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
options |= pcmk__xml_fmt_diff_short;
}
// Log element based on options
if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
CRM_CHECK(depth >= 0, depth = 0);
show_xml_changes_recursive(out, data, depth, options);
goto done;
}
if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
&& ((data->children == NULL)
|| (crm_element_value(data, XML_DIFF_MARKER) != NULL))) {
if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
legacy_options |= xml_log_option_diff_all;
prefix = PCMK__XML_PREFIX_CREATED;
} else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
legacy_options |= xml_log_option_diff_all;
prefix = PCMK__XML_PREFIX_DELETED;
}
}
if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)
&& !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
if (!pcmk_any_flags_set(options,
pcmk__xml_fmt_diff_plus
|pcmk__xml_fmt_diff_minus)) {
// Nothing will ever be logged
goto done;
}
// Keep looking for the actual change
for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
child = pcmk__xml_next(child)) {
log_data_element(log_level, file, function, line, prefix, child,
depth + 1, options);
}
} else {
pcmk__xml_show(out, prefix, data, depth,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
done:
out->finish(out, CRM_EX_OK, true, NULL);
pcmk__output_free(out);
}
void
xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
switch (log_level) {
case LOG_NEVER:
return;
case LOG_STDOUT:
CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
break;
default:
CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
pcmk__output_set_log_level(out, log_level);
break;
}
rc = pcmk__xml_show_changes(out, xml);
out->finish(out, pcmk_rc2exitc(rc), true, NULL);
pcmk__output_free(out);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-c
Expires
Wed, Jun 25, 3:25 AM (19 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1952020
Default Alt Text
xml_display.c (18 KB)

Event Timeline