root/lib/common/patchset_display.c

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

DEFINITIONS

This source file includes following definitions.
  1. xml_show_patchset_header
  2. xml_show_patchset_v1_recursive
  3. xml_show_patchset_v1
  4. xml_show_patchset_v2
  5. PCMK__OUTPUT_ARGS
  6. PCMK__OUTPUT_ARGS
  7. PCMK__OUTPUT_ARGS
  8. pcmk__register_patchset_messages
  9. xml_log_patchset

   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 <crm/msg_xml.h>
  13 
  14 #include "crmcommon_private.h"
  15 
  16 /*!
  17  * \internal
  18  * \brief Output an XML patchset header
  19  *
  20  * This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
  21  * element and its children).
  22  *
  23  * All header lines contain three integers separated by dots, of the form
  24  * <tt>{0}.{1}.{2}</tt>:
  25  * * \p {0}: \p XML_ATTR_GENERATION_ADMIN
  26  * * \p {1}: \p XML_ATTR_GENERATION
  27  * * \p {2}: \p XML_ATTR_NUMUPDATES
  28  *
  29  * Lines containing \p "---" describe removals and end with the patch format
  30  * number. Lines containing \p "+++" describe additions and end with the patch
  31  * digest.
  32  *
  33  * \param[in,out] out       Output object
  34  * \param[in]     patchset  XML patchset to output
  35  *
  36  * \return Standard Pacemaker return code
  37  *
  38  * \note This function produces output only for text-like formats.
  39  */
  40 static int
  41 xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
  42 {
  43     int rc = pcmk_rc_no_output;
  44     int add[] = { 0, 0, 0 };
  45     int del[] = { 0, 0, 0 };
  46 
  47     xml_patch_versions(patchset, add, del);
  48 
  49     if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
  50         const char *fmt = crm_element_value(patchset, "format");
  51         const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
  52 
  53         out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
  54         rc = out->info(out, "Diff: +++ %d.%d.%d %s",
  55                        add[0], add[1], add[2], digest);
  56 
  57     } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
  58         rc = out->info(out, "Local-only Change: %d.%d.%d",
  59                        add[0], add[1], add[2]);
  60     }
  61 
  62     return rc;
  63 }
  64 
  65 /*!
  66  * \internal
  67  * \brief Output a user-friendly form of XML additions or removals
  68  *
  69  * \param[in,out] out      Output object
  70  * \param[in]     prefix   String to prepend to every line of output
  71  * \param[in]     data     XML node to output
  72  * \param[in]     depth    Current indentation level
  73  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  74  *
  75  * \return Standard Pacemaker return code
  76  *
  77  * \note This function produces output only for text-like formats.
  78  */
  79 static int
  80 xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
  81                                const xmlNode *data, int depth, uint32_t options)
  82 {
  83     if (!xml_has_children(data)
  84         || (crm_element_value(data, XML_DIFF_MARKER) != NULL)) {
  85 
  86         // Found a change; clear the pcmk__xml_fmt_diff_short option if set
  87         options &= ~pcmk__xml_fmt_diff_short;
  88 
  89         if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
  90             prefix = PCMK__XML_PREFIX_CREATED;
  91         } else {    // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
  92             prefix = PCMK__XML_PREFIX_DELETED;
  93         }
  94     }
  95 
  96     if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
  97         int rc = pcmk_rc_no_output;
  98 
  99         // Keep looking for the actual change
 100         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 101              child = pcmk__xml_next(child)) {
 102             int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child,
 103                                                          depth + 1, options);
 104             rc = pcmk__output_select_rc(rc, temp_rc);
 105         }
 106         return rc;
 107     }
 108 
 109     return pcmk__xml_show(out, prefix, data, depth,
 110                           options
 111                           |pcmk__xml_fmt_open
 112                           |pcmk__xml_fmt_children
 113                           |pcmk__xml_fmt_close);
 114 }
 115 
 116 /*!
 117  * \internal
 118  * \brief Output a user-friendly form of an XML patchset (format 1)
 119  *
 120  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 121  * children) into a user-friendly combined diff output.
 122  *
 123  * \param[in,out] out       Output object
 124  * \param[in]     patchset  XML patchset to output
 125  * \param[in]     options   Group of \p pcmk__xml_fmt_options flags
 126  *
 127  * \return Standard Pacemaker return code
 128  *
 129  * \note This function produces output only for text-like formats.
 130  */
 131 static int
 132 xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
     /* [previous][next][first][last][top][bottom][index][help] */
 133                      uint32_t options)
 134 {
 135     const xmlNode *removed = NULL;
 136     const xmlNode *added = NULL;
 137     const xmlNode *child = NULL;
 138     bool is_first = true;
 139     int rc = xml_show_patchset_header(out, patchset);
 140 
 141     /* It's not clear whether "- " or "+ " ever does *not* get overridden by
 142      * PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
 143      * However, v1 patchsets can only exist during rolling upgrades from
 144      * Pacemaker 1.1.11, so not worth worrying about.
 145      */
 146     removed = find_xml_node(patchset, "diff-removed", FALSE);
 147     for (child = pcmk__xml_first_child(removed); child != NULL;
 148          child = pcmk__xml_next(child)) {
 149         int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0,
 150                                                      options
 151                                                      |pcmk__xml_fmt_diff_minus);
 152         rc = pcmk__output_select_rc(rc, temp_rc);
 153 
 154         if (is_first) {
 155             is_first = false;
 156         } else {
 157             rc = pcmk__output_select_rc(rc, out->info(out, " --- "));
 158         }
 159     }
 160 
 161     is_first = true;
 162     added = find_xml_node(patchset, "diff-added", FALSE);
 163     for (child = pcmk__xml_first_child(added); child != NULL;
 164          child = pcmk__xml_next(child)) {
 165         int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0,
 166                                                      options
 167                                                      |pcmk__xml_fmt_diff_plus);
 168         rc = pcmk__output_select_rc(rc, temp_rc);
 169 
 170         if (is_first) {
 171             is_first = false;
 172         } else {
 173             rc = pcmk__output_select_rc(rc, out->info(out, " +++ "));
 174         }
 175     }
 176 
 177     return rc;
 178 }
 179 
 180 /*!
 181  * \internal
 182  * \brief Output a user-friendly form of an XML patchset (format 2)
 183  *
 184  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 185  * children) into a user-friendly combined diff output.
 186  *
 187  * \param[in,out] out       Output object
 188  * \param[in]     patchset  XML patchset to output
 189  *
 190  * \return Standard Pacemaker return code
 191  *
 192  * \note This function produces output only for text-like formats.
 193  */
 194 static int
 195 xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 196 {
 197     int rc = xml_show_patchset_header(out, patchset);
 198     int temp_rc = pcmk_rc_no_output;
 199 
 200     for (const xmlNode *change = pcmk__xml_first_child(patchset);
 201          change != NULL; change = pcmk__xml_next(change)) {
 202         const char *op = crm_element_value(change, XML_DIFF_OP);
 203         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
 204 
 205         if (op == NULL) {
 206             continue;
 207         }
 208 
 209         if (strcmp(op, "create") == 0) {
 210             char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
 211                                              xpath);
 212 
 213             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
 214                                      pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
 215             rc = pcmk__output_select_rc(rc, temp_rc);
 216 
 217             // Overwrite all except the first two characters with spaces
 218             for (char *ch = prefix + 2; *ch != '\0'; ch++) {
 219                 *ch = ' ';
 220             }
 221 
 222             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
 223                                      pcmk__xml_fmt_pretty
 224                                      |pcmk__xml_fmt_children
 225                                      |pcmk__xml_fmt_close);
 226             rc = pcmk__output_select_rc(rc, temp_rc);
 227             free(prefix);
 228 
 229         } else if (strcmp(op, "move") == 0) {
 230             const char *position = crm_element_value(change, XML_DIFF_POSITION);
 231 
 232             temp_rc = out->info(out,
 233                                 PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
 234                                 xpath, position);
 235             rc = pcmk__output_select_rc(rc, temp_rc);
 236 
 237         } else if (strcmp(op, "modify") == 0) {
 238             xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
 239             GString *buffer_set = NULL;
 240             GString *buffer_unset = NULL;
 241 
 242             for (const xmlNode *child = pcmk__xml_first_child(clist);
 243                  child != NULL; child = pcmk__xml_next(child)) {
 244                 const char *name = crm_element_value(child, "name");
 245 
 246                 op = crm_element_value(child, XML_DIFF_OP);
 247                 if (op == NULL) {
 248                     continue;
 249                 }
 250 
 251                 if (strcmp(op, "set") == 0) {
 252                     const char *value = crm_element_value(child, "value");
 253 
 254                     pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
 255                     pcmk__g_strcat(buffer_set, name, "=", value, NULL);
 256 
 257                 } else if (strcmp(op, "unset") == 0) {
 258                     pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
 259                     g_string_append(buffer_unset, name);
 260                 }
 261             }
 262 
 263             if (buffer_set != NULL) {
 264                 temp_rc = out->info(out, "+  %s:  %s", xpath, buffer_set->str);
 265                 rc = pcmk__output_select_rc(rc, temp_rc);
 266                 g_string_free(buffer_set, TRUE);
 267             }
 268 
 269             if (buffer_unset != NULL) {
 270                 temp_rc = out->info(out, "-- %s:  %s",
 271                                     xpath, buffer_unset->str);
 272                 rc = pcmk__output_select_rc(rc, temp_rc);
 273                 g_string_free(buffer_unset, TRUE);
 274             }
 275 
 276         } else if (strcmp(op, "delete") == 0) {
 277             int position = -1;
 278 
 279             crm_element_value_int(change, XML_DIFF_POSITION, &position);
 280             if (position >= 0) {
 281                 temp_rc = out->info(out, "-- %s (%d)", xpath, position);
 282             } else {
 283                 temp_rc = out->info(out, "-- %s", xpath);
 284             }
 285             rc = pcmk__output_select_rc(rc, temp_rc);
 286         }
 287     }
 288 
 289     return rc;
 290 }
 291 
 292 /*!
 293  * \internal
 294  * \brief Output a user-friendly form of an XML patchset
 295  *
 296  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 297  * children) into a user-friendly combined diff output.
 298  *
 299  * \param[in,out] out   Output object
 300  * \param[in]     args  Message-specific arguments
 301  *
 302  * \return Standard Pacemaker return code
 303  *
 304  * \note \p args should contain only the XML patchset
 305  */
 306 PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
     /* [previous][next][first][last][top][bottom][index][help] */
 307 static int
 308 xml_patchset_default(pcmk__output_t *out, va_list args)
 309 {
 310     xmlNodePtr patchset = va_arg(args, xmlNodePtr);
 311 
 312     int format = 1;
 313 
 314     if (patchset == NULL) {
 315         crm_trace("Empty patch");
 316         return pcmk_rc_no_output;
 317     }
 318 
 319     crm_element_value_int(patchset, "format", &format);
 320     switch (format) {
 321         case 1:
 322             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 323         case 2:
 324             return xml_show_patchset_v2(out, patchset);
 325         default:
 326             crm_err("Unknown patch format: %d", format);
 327             return pcmk_rc_bad_xml_patch;
 328     }
 329 }
 330 
 331 /*!
 332  * \internal
 333  * \brief Output a user-friendly form of an XML patchset
 334  *
 335  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 336  * children) into a user-friendly combined diff output.
 337  *
 338  * \param[in,out] out   Output object
 339  * \param[in]     args  Message-specific arguments
 340  *
 341  * \return Standard Pacemaker return code
 342  *
 343  * \note \p args should contain only the XML patchset
 344  */
 345 PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
     /* [previous][next][first][last][top][bottom][index][help] */
 346 static int
 347 xml_patchset_log(pcmk__output_t *out, va_list args)
 348 {
 349     static struct qb_log_callsite *patchset_cs = NULL;
 350 
 351     xmlNodePtr patchset = va_arg(args, xmlNodePtr);
 352 
 353     uint8_t log_level = pcmk__output_get_log_level(out);
 354     int format = 1;
 355 
 356     if (log_level == LOG_NEVER) {
 357         return pcmk_rc_no_output;
 358     }
 359 
 360     if (patchset == NULL) {
 361         crm_trace("Empty patch");
 362         return pcmk_rc_no_output;
 363     }
 364 
 365     if (patchset_cs == NULL) {
 366         patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
 367                                           log_level, __LINE__,
 368                                           crm_trace_nonlog);
 369     }
 370 
 371     if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
 372         // Nothing would be logged, so skip all the work
 373         return pcmk_rc_no_output;
 374     }
 375 
 376     crm_element_value_int(patchset, "format", &format);
 377     switch (format) {
 378         case 1:
 379             if (log_level < LOG_DEBUG) {
 380                 return xml_show_patchset_v1(out, patchset,
 381                                             pcmk__xml_fmt_pretty
 382                                             |pcmk__xml_fmt_diff_short);
 383             }
 384             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 385         case 2:
 386             return xml_show_patchset_v2(out, patchset);
 387         default:
 388             crm_err("Unknown patch format: %d", format);
 389             return pcmk_rc_bad_xml_patch;
 390     }
 391 }
 392 
 393 /*!
 394  * \internal
 395  * \brief Output an XML patchset
 396  *
 397  * This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
 398  * children) without modification, as a CDATA block.
 399  *
 400  * \param[in,out] out   Output object
 401  * \param[in]     args  Message-specific arguments
 402  *
 403  * \return Standard Pacemaker return code
 404  *
 405  * \note \p args should contain only the XML patchset
 406  */
 407 PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
     /* [previous][next][first][last][top][bottom][index][help] */
 408 static int
 409 xml_patchset_xml(pcmk__output_t *out, va_list args)
 410 {
 411     xmlNodePtr patchset = va_arg(args, xmlNodePtr);
 412 
 413     if (patchset != NULL) {
 414         char *buf = dump_xml_formatted_with_text(patchset);
 415 
 416         out->output_xml(out, "xml-patchset", buf);
 417         free(buf);
 418         return pcmk_rc_ok;
 419     }
 420     crm_trace("Empty patch");
 421     return pcmk_rc_no_output;
 422 }
 423 
 424 static pcmk__message_entry_t fmt_functions[] = {
 425     { "xml-patchset", "default", xml_patchset_default },
 426     { "xml-patchset", "log", xml_patchset_log },
 427     { "xml-patchset", "xml", xml_patchset_xml },
 428 
 429     { NULL, NULL, NULL }
 430 };
 431 
 432 /*!
 433  * \internal
 434  * \brief Register the formatting functions for XML patchsets
 435  *
 436  * \param[in,out] out  Output object
 437  */
 438 void
 439 pcmk__register_patchset_messages(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 440     pcmk__register_messages(out, fmt_functions);
 441 }
 442 
 443 // Deprecated functions kept only for backward API compatibility
 444 // LCOV_EXCL_START
 445 
 446 #include <crm/common/xml_compat.h>
 447 
 448 void
 449 xml_log_patchset(uint8_t log_level, const char *function,
     /* [previous][next][first][last][top][bottom][index][help] */
 450                  const xmlNode *patchset)
 451 {
 452     /* This function has some duplication relative to the message functions.
 453      * This way, we can maintain the const xmlNode * in the signature. The
 454      * message functions must be non-const. They have to support XML output
 455      * objects, which must make a copy of a the patchset, requiring a non-const
 456      * function call.
 457      *
 458      * In contrast, this legacy function doesn't need to support XML output.
 459      */
 460     static struct qb_log_callsite *patchset_cs = NULL;
 461 
 462     pcmk__output_t *out = NULL;
 463     int format = 1;
 464     int rc = pcmk_rc_no_output;
 465 
 466     switch (log_level) {
 467         case LOG_NEVER:
 468             return;
 469         case LOG_STDOUT:
 470             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 471             break;
 472         default:
 473             if (patchset_cs == NULL) {
 474                 patchset_cs = qb_log_callsite_get(__func__, __FILE__,
 475                                                   "xml-patchset", log_level,
 476                                                   __LINE__, crm_trace_nonlog);
 477             }
 478             if (!crm_is_callsite_active(patchset_cs, log_level,
 479                                         crm_trace_nonlog)) {
 480                 return;
 481             }
 482             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 483             pcmk__output_set_log_level(out, log_level);
 484             break;
 485     }
 486 
 487     if (patchset == NULL) {
 488         // Should come after the LOG_NEVER check
 489         crm_trace("Empty patch");
 490         goto done;
 491     }
 492 
 493     crm_element_value_int(patchset, "format", &format);
 494     switch (format) {
 495         case 1:
 496             if (log_level < LOG_DEBUG) {
 497                 rc = xml_show_patchset_v1(out, patchset,
 498                                           pcmk__xml_fmt_pretty
 499                                           |pcmk__xml_fmt_diff_short);
 500             } else {    // Note: LOG_STDOUT > LOG_DEBUG
 501                 rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 502             }
 503             break;
 504         case 2:
 505             rc = xml_show_patchset_v2(out, patchset);
 506             break;
 507         default:
 508             crm_err("Unknown patch format: %d", format);
 509             rc = pcmk_rc_bad_xml_patch;
 510             break;
 511     }
 512 
 513 done:
 514     out->finish(out, pcmk_rc2exitc(rc), true, NULL);
 515     pcmk__output_free(out);
 516 }
 517 
 518 // LCOV_EXCL_STOP
 519 // End deprecated API

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