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-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 <crm/common/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}: \c PCMK_XA_ADMIN_EPOCH
  26  * * \p {1}: \c PCMK_XA_EPOCH
  27  * * \p {2}: \c PCMK_XA_NUM_UPDATES
  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, PCMK_XA_FORMAT);
  51         const char *digest = crm_element_value(patchset, PCMK__XA_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 ((data->children == NULL)
  84         || (crm_element_value(data, PCMK__XA_CRM_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 = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED, NULL, NULL);
 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 = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL, NULL);
 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__xe_first_child(patchset, NULL, NULL,
 201                                                       NULL);
 202          change != NULL; change = pcmk__xe_next(change)) {
 203 
 204         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 205         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
 206 
 207         if (op == NULL) {
 208             continue;
 209         }
 210 
 211         if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
 212             char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
 213                                              xpath);
 214 
 215             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
 216                                      pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
 217             rc = pcmk__output_select_rc(rc, temp_rc);
 218 
 219             // Overwrite all except the first two characters with spaces
 220             for (char *ch = prefix + 2; *ch != '\0'; ch++) {
 221                 *ch = ' ';
 222             }
 223 
 224             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
 225                                      pcmk__xml_fmt_pretty
 226                                      |pcmk__xml_fmt_children
 227                                      |pcmk__xml_fmt_close);
 228             rc = pcmk__output_select_rc(rc, temp_rc);
 229             free(prefix);
 230 
 231         } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
 232             const char *position = crm_element_value(change, PCMK_XE_POSITION);
 233 
 234             temp_rc = out->info(out,
 235                                 PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
 236                                 xpath, position);
 237             rc = pcmk__output_select_rc(rc, temp_rc);
 238 
 239         } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
 240             xmlNode *clist = pcmk__xe_first_child(change, PCMK_XE_CHANGE_LIST,
 241                                                   NULL, NULL);
 242             GString *buffer_set = NULL;
 243             GString *buffer_unset = NULL;
 244 
 245             for (const xmlNode *child = pcmk__xe_first_child(clist, NULL, NULL,
 246                                                              NULL);
 247                  child != NULL; child = pcmk__xe_next(child)) {
 248 
 249                 const char *name = crm_element_value(child, PCMK_XA_NAME);
 250 
 251                 op = crm_element_value(child, PCMK_XA_OPERATION);
 252                 if (op == NULL) {
 253                     continue;
 254                 }
 255 
 256                 if (strcmp(op, "set") == 0) {
 257                     const char *value = crm_element_value(child, PCMK_XA_VALUE);
 258 
 259                     pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
 260                     pcmk__g_strcat(buffer_set, name, "=", value, NULL);
 261 
 262                 } else if (strcmp(op, "unset") == 0) {
 263                     pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
 264                     g_string_append(buffer_unset, name);
 265                 }
 266             }
 267 
 268             if (buffer_set != NULL) {
 269                 temp_rc = out->info(out, "+  %s:  %s", xpath, buffer_set->str);
 270                 rc = pcmk__output_select_rc(rc, temp_rc);
 271                 g_string_free(buffer_set, TRUE);
 272             }
 273 
 274             if (buffer_unset != NULL) {
 275                 temp_rc = out->info(out, "-- %s:  %s",
 276                                     xpath, buffer_unset->str);
 277                 rc = pcmk__output_select_rc(rc, temp_rc);
 278                 g_string_free(buffer_unset, TRUE);
 279             }
 280 
 281         } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
 282             int position = -1;
 283 
 284             crm_element_value_int(change, PCMK_XE_POSITION, &position);
 285             if (position >= 0) {
 286                 temp_rc = out->info(out, "-- %s (%d)", xpath, position);
 287             } else {
 288                 temp_rc = out->info(out, "-- %s", xpath);
 289             }
 290             rc = pcmk__output_select_rc(rc, temp_rc);
 291         }
 292     }
 293 
 294     return rc;
 295 }
 296 
 297 /*!
 298  * \internal
 299  * \brief Output a user-friendly form of an XML patchset
 300  *
 301  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 302  * children) into a user-friendly combined diff output.
 303  *
 304  * \param[in,out] out   Output object
 305  * \param[in]     args  Message-specific arguments
 306  *
 307  * \return Standard Pacemaker return code
 308  *
 309  * \note \p args should contain the following:
 310  *       -# XML patchset
 311  */
 312 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     /* [previous][next][first][last][top][bottom][index][help] */
 313 static int
 314 xml_patchset_default(pcmk__output_t *out, va_list args)
 315 {
 316     const xmlNode *patchset = va_arg(args, const xmlNode *);
 317 
 318     int format = 1;
 319 
 320     if (patchset == NULL) {
 321         crm_trace("Empty patch");
 322         return pcmk_rc_no_output;
 323     }
 324 
 325     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 326     switch (format) {
 327         case 1:
 328             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 329         case 2:
 330             return xml_show_patchset_v2(out, patchset);
 331         default:
 332             crm_err("Unknown patch format: %d", format);
 333             return pcmk_rc_bad_xml_patch;
 334     }
 335 }
 336 
 337 /*!
 338  * \internal
 339  * \brief Output a user-friendly form of an XML patchset
 340  *
 341  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
 342  * children) into a user-friendly combined diff output.
 343  *
 344  * \param[in,out] out   Output object
 345  * \param[in]     args  Message-specific arguments
 346  *
 347  * \return Standard Pacemaker return code
 348  *
 349  * \note \p args should contain the following:
 350  *       -# XML patchset
 351  */
 352 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     /* [previous][next][first][last][top][bottom][index][help] */
 353 static int
 354 xml_patchset_log(pcmk__output_t *out, va_list args)
 355 {
 356     static struct qb_log_callsite *patchset_cs = NULL;
 357 
 358     const xmlNode *patchset = va_arg(args, const xmlNode *);
 359 
 360     uint8_t log_level = pcmk__output_get_log_level(out);
 361     int format = 1;
 362 
 363     if (log_level == LOG_NEVER) {
 364         return pcmk_rc_no_output;
 365     }
 366 
 367     if (patchset == NULL) {
 368         crm_trace("Empty patch");
 369         return pcmk_rc_no_output;
 370     }
 371 
 372     if (patchset_cs == NULL) {
 373         patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
 374                                           log_level, __LINE__,
 375                                           crm_trace_nonlog);
 376     }
 377 
 378     if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
 379         // Nothing would be logged, so skip all the work
 380         return pcmk_rc_no_output;
 381     }
 382 
 383     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 384     switch (format) {
 385         case 1:
 386             if (log_level < LOG_DEBUG) {
 387                 return xml_show_patchset_v1(out, patchset,
 388                                             pcmk__xml_fmt_pretty
 389                                             |pcmk__xml_fmt_diff_short);
 390             }
 391             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 392         case 2:
 393             return xml_show_patchset_v2(out, patchset);
 394         default:
 395             crm_err("Unknown patch format: %d", format);
 396             return pcmk_rc_bad_xml_patch;
 397     }
 398 }
 399 
 400 /*!
 401  * \internal
 402  * \brief Output an XML patchset
 403  *
 404  * This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
 405  * children) without modification, as a CDATA block.
 406  *
 407  * \param[in,out] out   Output object
 408  * \param[in]     args  Message-specific arguments
 409  *
 410  * \return Standard Pacemaker return code
 411  *
 412  * \note \p args should contain the following:
 413  *       -# XML patchset
 414  */
 415 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     /* [previous][next][first][last][top][bottom][index][help] */
 416 static int
 417 xml_patchset_xml(pcmk__output_t *out, va_list args)
 418 {
 419     const xmlNode *patchset = va_arg(args, const xmlNode *);
 420 
 421     if (patchset != NULL) {
 422         GString *buf = g_string_sized_new(1024);
 423 
 424         pcmk__xml_string(patchset, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
 425                          0);
 426 
 427         out->output_xml(out, PCMK_XE_XML_PATCHSET, buf->str);
 428         g_string_free(buf, TRUE);
 429         return pcmk_rc_ok;
 430     }
 431     crm_trace("Empty patch");
 432     return pcmk_rc_no_output;
 433 }
 434 
 435 static pcmk__message_entry_t fmt_functions[] = {
 436     { "xml-patchset", "default", xml_patchset_default },
 437     { "xml-patchset", "log", xml_patchset_log },
 438     { "xml-patchset", "xml", xml_patchset_xml },
 439 
 440     { NULL, NULL, NULL }
 441 };
 442 
 443 /*!
 444  * \internal
 445  * \brief Register the formatting functions for XML patchsets
 446  *
 447  * \param[in,out] out  Output object
 448  */
 449 void
 450 pcmk__register_patchset_messages(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 451     pcmk__register_messages(out, fmt_functions);
 452 }
 453 
 454 // Deprecated functions kept only for backward API compatibility
 455 // LCOV_EXCL_START
 456 
 457 #include <crm/common/xml_compat.h>
 458 
 459 void
 460 xml_log_patchset(uint8_t log_level, const char *function,
     /* [previous][next][first][last][top][bottom][index][help] */
 461                  const xmlNode *patchset)
 462 {
 463     /* This function has some duplication relative to the message functions.
 464      * This way, we can maintain the const xmlNode * in the signature. The
 465      * message functions must be non-const. They have to support XML output
 466      * objects, which must make a copy of a the patchset, requiring a non-const
 467      * function call.
 468      *
 469      * In contrast, this legacy function doesn't need to support XML output.
 470      */
 471     static struct qb_log_callsite *patchset_cs = NULL;
 472 
 473     pcmk__output_t *out = NULL;
 474     int format = 1;
 475     int rc = pcmk_rc_no_output;
 476 
 477     switch (log_level) {
 478         case LOG_NEVER:
 479             return;
 480         case LOG_STDOUT:
 481             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 482             break;
 483         default:
 484             if (patchset_cs == NULL) {
 485                 patchset_cs = qb_log_callsite_get(__func__, __FILE__,
 486                                                   "xml-patchset", log_level,
 487                                                   __LINE__, crm_trace_nonlog);
 488             }
 489             if (!crm_is_callsite_active(patchset_cs, log_level,
 490                                         crm_trace_nonlog)) {
 491                 return;
 492             }
 493             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 494             pcmk__output_set_log_level(out, log_level);
 495             break;
 496     }
 497 
 498     if (patchset == NULL) {
 499         // Should come after the LOG_NEVER check
 500         crm_trace("Empty patch");
 501         goto done;
 502     }
 503 
 504     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 505     switch (format) {
 506         case 1:
 507             if (log_level < LOG_DEBUG) {
 508                 rc = xml_show_patchset_v1(out, patchset,
 509                                           pcmk__xml_fmt_pretty
 510                                           |pcmk__xml_fmt_diff_short);
 511             } else {    // Note: LOG_STDOUT > LOG_DEBUG
 512                 rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
 513             }
 514             break;
 515         case 2:
 516             rc = xml_show_patchset_v2(out, patchset);
 517             break;
 518         default:
 519             crm_err("Unknown patch format: %d", format);
 520             rc = pcmk_rc_bad_xml_patch;
 521             break;
 522     }
 523 
 524 done:
 525     out->finish(out, pcmk_rc2exitc(rc), true, NULL);
 526     pcmk__output_free(out);
 527 }
 528 
 529 // LCOV_EXCL_STOP
 530 // End deprecated API

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