root/lib/common/xml_display.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__log_xmllib_err
  2. show_xml_comment
  3. show_xml_element
  4. show_xml_node
  5. pcmk__xml_show
  6. show_xml_changes_recursive
  7. pcmk__xml_show_changes
  8. log_data_element
  9. xml_log_changes

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <libxml/tree.h>
  13 
  14 #include <crm/crm.h>
  15 #include <crm/common/xml.h>
  16 #include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
  17 #include "crmcommon_private.h"
  18 
  19 static int show_xml_node(pcmk__output_t *out, GString *buffer,
  20                          const char *prefix, const xmlNode *data, int depth,
  21                          uint32_t options);
  22 
  23 // Log an XML library error
  24 void
  25 pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
  26 {
  27     va_list ap;
  28 
  29     va_start(ap, fmt);
  30     pcmk__if_tracing(
  31         {
  32             PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
  33                                crm_abort(__FILE__, __PRETTY_FUNCTION__,
  34                                          __LINE__, "xml library error", TRUE,
  35                                          TRUE),
  36                                "XML Error: ", fmt, ap);
  37         },
  38         {
  39             PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
  40         }
  41     );
  42     va_end(ap);
  43 }
  44 
  45 /*!
  46  * \internal
  47  * \brief Output an XML comment with depth-based indentation
  48  *
  49  * \param[in,out] out      Output object
  50  * \param[in]     data     XML node to output
  51  * \param[in]     depth    Current indentation level
  52  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  53  *
  54  * \return Standard Pacemaker return code
  55  *
  56  * \note This currently produces output only for text-like output objects.
  57  */
  58 static int
  59 show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
  60                  uint32_t options)
  61 {
  62     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
  63         int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  64 
  65         return out->info(out, "%*s<!--%s-->",
  66                          width, "", (const char *) data->content);
  67     }
  68     return pcmk_rc_no_output;
  69 }
  70 
  71 /*!
  72  * \internal
  73  * \brief Output an XML element in a formatted way
  74  *
  75  * \param[in,out] out      Output object
  76  * \param[in,out] buffer   Where to build output strings
  77  * \param[in]     prefix   String to prepend to every line of output
  78  * \param[in]     data     XML node to output
  79  * \param[in]     depth    Current indentation level
  80  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  81  *
  82  * \return Standard Pacemaker return code
  83  *
  84  * \note This is a recursive helper function for \p show_xml_node().
  85  * \note This currently produces output only for text-like output objects.
  86  * \note \p buffer may be overwritten many times. The caller is responsible for
  87  *       freeing it using \p g_string_free() but should not rely on its
  88  *       contents.
  89  */
  90 static int
  91 show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
  92                  const xmlNode *data, int depth, uint32_t options)
  93 {
  94     int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  95     int rc = pcmk_rc_no_output;
  96 
  97     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
  98         const char *hidden = crm_element_value(data, PCMK__XA_HIDDEN);
  99 
 100         g_string_truncate(buffer, 0);
 101 
 102         for (int lpc = 0; lpc < spaces; lpc++) {
 103             g_string_append_c(buffer, ' ');
 104         }
 105         pcmk__g_strcat(buffer, "<", data->name, NULL);
 106 
 107         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 108              attr = attr->next) {
 109             xml_node_private_t *nodepriv = attr->_private;
 110             const char *p_name = (const char *) attr->name;
 111             const char *p_value = pcmk__xml_attr_value(attr);
 112             gchar *p_copy = NULL;
 113 
 114             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 115                 continue;
 116             }
 117 
 118             // @COMPAT Remove when v1 patchsets are removed
 119             if (pcmk_any_flags_set(options,
 120                                    pcmk__xml_fmt_diff_plus
 121                                    |pcmk__xml_fmt_diff_minus)
 122                 && (strcmp(PCMK__XA_CRM_DIFF_MARKER, p_name) == 0)) {
 123                 continue;
 124             }
 125 
 126             if ((hidden != NULL) && (p_name[0] != '\0')
 127                 && (strstr(hidden, p_name) != NULL)) {
 128 
 129                 p_value = "*****";
 130 
 131             } else {
 132                 p_copy = pcmk__xml_escape(p_value, true);
 133                 p_value = p_copy;
 134             }
 135 
 136             pcmk__g_strcat(buffer, " ", p_name, "=\"",
 137                            pcmk__s(p_value, "<null>"), "\"", NULL);
 138             g_free(p_copy);
 139         }
 140 
 141         if ((data->children != NULL)
 142             && pcmk_is_set(options, pcmk__xml_fmt_children)) {
 143             g_string_append_c(buffer, '>');
 144 
 145         } else {
 146             g_string_append(buffer, "/>");
 147         }
 148 
 149         rc = out->info(out, "%s%s%s",
 150                        pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
 151                        buffer->str);
 152     }
 153 
 154     if (data->children == NULL) {
 155         return rc;
 156     }
 157 
 158     if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
 159         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 160              child = pcmk__xml_next(child)) {
 161 
 162             int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
 163                                         options
 164                                         |pcmk__xml_fmt_open
 165                                         |pcmk__xml_fmt_close);
 166             rc = pcmk__output_select_rc(rc, temp_rc);
 167         }
 168     }
 169 
 170     if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
 171         int temp_rc = out->info(out, "%s%s%*s</%s>",
 172                                 pcmk__s(prefix, ""),
 173                                 pcmk__str_empty(prefix)? "" : " ",
 174                                 spaces, "", data->name);
 175         rc = pcmk__output_select_rc(rc, temp_rc);
 176     }
 177 
 178     return rc;
 179 }
 180 
 181 /*!
 182  * \internal
 183  * \brief Output an XML element or comment in a formatted way
 184  *
 185  * \param[in,out] out      Output object
 186  * \param[in,out] buffer   Where to build output strings
 187  * \param[in]     prefix   String to prepend to every line of output
 188  * \param[in]     data     XML node to log
 189  * \param[in]     depth    Current indentation level
 190  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 191  *
 192  * \return Standard Pacemaker return code
 193  *
 194  * \note This is a recursive helper function for \p pcmk__xml_show().
 195  * \note This currently produces output only for text-like output objects.
 196  * \note \p buffer may be overwritten many times. The caller is responsible for
 197  *       freeing it using \p g_string_free() but should not rely on its
 198  *       contents.
 199  */
 200 static int
 201 show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
 202               const xmlNode *data, int depth, uint32_t options)
 203 {
 204     switch (data->type) {
 205         case XML_COMMENT_NODE:
 206             return show_xml_comment(out, data, depth, options);
 207         case XML_ELEMENT_NODE:
 208             return show_xml_element(out, buffer, prefix, data, depth, options);
 209         default:
 210             return pcmk_rc_no_output;
 211     }
 212 }
 213 
 214 /*!
 215  * \internal
 216  * \brief Output an XML element or comment in a formatted way
 217  *
 218  * \param[in,out] out        Output object
 219  * \param[in]     prefix     String to prepend to every line of output
 220  * \param[in]     data       XML node to output
 221  * \param[in]     depth      Current nesting level
 222  * \param[in]     options    Group of \p pcmk__xml_fmt_options flags
 223  *
 224  * \return Standard Pacemaker return code
 225  *
 226  * \note This currently produces output only for text-like output objects.
 227  */
 228 int
 229 pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
     /* [previous][next][first][last][top][bottom][index][help] */
 230                int depth, uint32_t options)
 231 {
 232     int rc = pcmk_rc_no_output;
 233     GString *buffer = NULL;
 234 
 235     CRM_ASSERT(out != NULL);
 236     CRM_CHECK(depth >= 0, depth = 0);
 237 
 238     if (data == NULL) {
 239         return rc;
 240     }
 241 
 242     /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
 243      * recursive calls
 244      */
 245     buffer = g_string_sized_new(1024);
 246     rc = show_xml_node(out, buffer, prefix, data, depth, options);
 247     g_string_free(buffer, TRUE);
 248 
 249     return rc;
 250 }
 251 
 252 /*!
 253  * \internal
 254  * \brief Output XML portions that have been marked as changed
 255  *
 256  * \param[in,out] out      Output object
 257  * \param[in]     data     XML node to output
 258  * \param[in]     depth    Current indentation level
 259  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 260  *
 261  * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing
 262  *       changes to \p data and its children.
 263  * \note This currently produces output only for text-like output objects.
 264  */
 265 static int
 266 show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
 267                            uint32_t options)
 268 {
 269     /* @COMPAT: When log_data_element() is removed, we can remove the options
 270      * argument here and instead hard-code pcmk__xml_log_pretty.
 271      */
 272     xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
 273     int rc = pcmk_rc_no_output;
 274     int temp_rc = pcmk_rc_no_output;
 275 
 276     if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
 277         // Newly created
 278         return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
 279                               options
 280                               |pcmk__xml_fmt_open
 281                               |pcmk__xml_fmt_children
 282                               |pcmk__xml_fmt_close);
 283     }
 284 
 285     if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 286         // Modified or moved
 287         bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 288         int spaces = pretty? (2 * depth) : 0;
 289         const char *prefix = PCMK__XML_PREFIX_MODIFIED;
 290 
 291         if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 292             prefix = PCMK__XML_PREFIX_MOVED;
 293         }
 294 
 295         // Log opening tag
 296         rc = pcmk__xml_show(out, prefix, data, depth,
 297                             options|pcmk__xml_fmt_open);
 298 
 299         // Log changes to attributes
 300         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 301              attr = attr->next) {
 302             const char *name = (const char *) attr->name;
 303 
 304             nodepriv = attr->_private;
 305 
 306             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 307                 const char *value = pcmk__xml_attr_value(attr);
 308 
 309                 temp_rc = out->info(out, "%s %*s @%s=%s",
 310                                     PCMK__XML_PREFIX_DELETED, spaces, "", name,
 311                                     value);
 312 
 313             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 314                 const char *value = pcmk__xml_attr_value(attr);
 315 
 316                 if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 317                     prefix = PCMK__XML_PREFIX_CREATED;
 318 
 319                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
 320                     prefix = PCMK__XML_PREFIX_MODIFIED;
 321 
 322                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 323                     prefix = PCMK__XML_PREFIX_MOVED;
 324 
 325                 } else {
 326                     prefix = PCMK__XML_PREFIX_MODIFIED;
 327                 }
 328 
 329                 temp_rc = out->info(out, "%s %*s @%s=%s",
 330                                     prefix, spaces, "", name, value);
 331             }
 332             rc = pcmk__output_select_rc(rc, temp_rc);
 333         }
 334 
 335         // Log changes to children
 336         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 337              child = pcmk__xml_next(child)) {
 338             temp_rc = show_xml_changes_recursive(out, child, depth + 1,
 339                                                  options);
 340             rc = pcmk__output_select_rc(rc, temp_rc);
 341         }
 342 
 343         // Log closing tag
 344         temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
 345                                  options|pcmk__xml_fmt_close);
 346         return pcmk__output_select_rc(rc, temp_rc);
 347     }
 348 
 349     // This node hasn't changed, but check its children
 350     for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 351          child = pcmk__xml_next(child)) {
 352         temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
 353         rc = pcmk__output_select_rc(rc, temp_rc);
 354     }
 355     return rc;
 356 }
 357 
 358 /*!
 359  * \internal
 360  * \brief Output changes to an XML node and any children
 361  *
 362  * \param[in,out] out  Output object
 363  * \param[in]     xml  XML node to output
 364  *
 365  * \return Standard Pacemaker return code
 366  *
 367  * \note This currently produces output only for text-like output objects.
 368  */
 369 int
 370 pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 371 {
 372     xml_doc_private_t *docpriv = NULL;
 373     int rc = pcmk_rc_no_output;
 374     int temp_rc = pcmk_rc_no_output;
 375 
 376     CRM_ASSERT(out != NULL);
 377     CRM_ASSERT(xml != NULL);
 378     CRM_ASSERT(xml->doc != NULL);
 379 
 380     docpriv = xml->doc->_private;
 381     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 382         return rc;
 383     }
 384 
 385     for (const GList *iter = docpriv->deleted_objs; iter != NULL;
 386          iter = iter->next) {
 387         const pcmk__deleted_xml_t *deleted_obj = iter->data;
 388 
 389         if (deleted_obj->position >= 0) {
 390             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
 391                                 deleted_obj->path, deleted_obj->position);
 392         } else {
 393             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
 394                                 deleted_obj->path);
 395         }
 396         rc = pcmk__output_select_rc(rc, temp_rc);
 397     }
 398 
 399     temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
 400     return pcmk__output_select_rc(rc, temp_rc);
 401 }
 402 
 403 // Deprecated functions kept only for backward API compatibility
 404 // LCOV_EXCL_START
 405 
 406 #include <crm/common/logging_compat.h>
 407 #include <crm/common/xml_compat.h>
 408 
 409 void
 410 log_data_element(int log_level, const char *file, const char *function,
     /* [previous][next][first][last][top][bottom][index][help] */
 411                  int line, const char *prefix, const xmlNode *data, int depth,
 412                  int legacy_options)
 413 {
 414     uint32_t options = 0;
 415     pcmk__output_t *out = NULL;
 416 
 417     // Confine log_level to uint8_t range
 418     log_level = pcmk__clip_log_level(log_level);
 419 
 420     if (data == NULL) {
 421         do_crm_log(log_level, "%s%sNo data to dump as XML",
 422                    pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ");
 423         return;
 424     }
 425 
 426     switch (log_level) {
 427         case LOG_NEVER:
 428             return;
 429         case LOG_STDOUT:
 430             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 431             break;
 432         default:
 433             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 434             pcmk__output_set_log_level(out, log_level);
 435             break;
 436     }
 437 
 438     /* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and
 439      * start using the pcmk__xml_fmt_options in all the internal functions.
 440      *
 441      * xml_log_option_dirty_add and xml_log_option_diff_all are ignored by
 442      * internal code and only used here, so they don't need to be addressed.
 443      */
 444     if (pcmk_is_set(legacy_options, xml_log_option_filtered)) {
 445         options |= pcmk__xml_fmt_filtered;
 446     }
 447     if (pcmk_is_set(legacy_options, xml_log_option_formatted)) {
 448         options |= pcmk__xml_fmt_pretty;
 449     }
 450     if (pcmk_is_set(legacy_options, xml_log_option_open)) {
 451         options |= pcmk__xml_fmt_open;
 452     }
 453     if (pcmk_is_set(legacy_options, xml_log_option_children)) {
 454         options |= pcmk__xml_fmt_children;
 455     }
 456     if (pcmk_is_set(legacy_options, xml_log_option_close)) {
 457         options |= pcmk__xml_fmt_close;
 458     }
 459     if (pcmk_is_set(legacy_options, xml_log_option_text)) {
 460         options |= pcmk__xml_fmt_text;
 461     }
 462     if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
 463         options |= pcmk__xml_fmt_diff_plus;
 464     }
 465     if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
 466         options |= pcmk__xml_fmt_diff_minus;
 467     }
 468     if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
 469         options |= pcmk__xml_fmt_diff_short;
 470     }
 471 
 472     // Log element based on options
 473     if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
 474         CRM_CHECK(depth >= 0, depth = 0);
 475         show_xml_changes_recursive(out, data, depth, options);
 476         goto done;
 477     }
 478 
 479     if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
 480         && ((data->children == NULL)
 481             || (crm_element_value(data, PCMK__XA_CRM_DIFF_MARKER) != NULL))) {
 482 
 483         if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
 484             legacy_options |= xml_log_option_diff_all;
 485             prefix = PCMK__XML_PREFIX_CREATED;
 486 
 487         } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
 488             legacy_options |= xml_log_option_diff_all;
 489             prefix = PCMK__XML_PREFIX_DELETED;
 490         }
 491     }
 492 
 493     if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)
 494         && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
 495 
 496         if (!pcmk_any_flags_set(options,
 497                                 pcmk__xml_fmt_diff_plus
 498                                 |pcmk__xml_fmt_diff_minus)) {
 499             // Nothing will ever be logged
 500             goto done;
 501         }
 502 
 503         // Keep looking for the actual change
 504         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 505              child = pcmk__xml_next(child)) {
 506             log_data_element(log_level, file, function, line, prefix, child,
 507                              depth + 1, options);
 508         }
 509 
 510     } else {
 511         pcmk__xml_show(out, prefix, data, depth,
 512                        options
 513                        |pcmk__xml_fmt_open
 514                        |pcmk__xml_fmt_children
 515                        |pcmk__xml_fmt_close);
 516     }
 517 
 518 done:
 519     out->finish(out, CRM_EX_OK, true, NULL);
 520     pcmk__output_free(out);
 521 }
 522 
 523 void
 524 xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 525 {
 526     pcmk__output_t *out = NULL;
 527     int rc = pcmk_rc_ok;
 528 
 529     switch (log_level) {
 530         case LOG_NEVER:
 531             return;
 532         case LOG_STDOUT:
 533             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 534             break;
 535         default:
 536             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 537             pcmk__output_set_log_level(out, log_level);
 538             break;
 539     }
 540     rc = pcmk__xml_show_changes(out, xml);
 541     out->finish(out, pcmk_rc2exitc(rc), true, NULL);
 542     pcmk__output_free(out);
 543 }
 544 
 545 // LCOV_EXCL_STOP
 546 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */