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

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