pacemaker  2.1.8-3980678f03
Scalable High-Availability cluster resource manager
patchset_display.c
Go to the documentation of this file.
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 
40 static int
41 xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
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 
79 static int
80 xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
81  const xmlNode *data, int depth, uint32_t options)
82 {
83  if ((data->children == 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
114 }
115 
131 static int
132 xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
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
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
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 
194 static int
195 xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
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,
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,
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 
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 
312 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
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 
352 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
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__,
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,
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 
415 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
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 
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 
449 void
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,
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,
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);
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
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
#define PCMK__XA_DIGEST
#define PCMK__XE_DIFF_REMOVED
#define PCMK_XA_NAME
Definition: xml_names.h:325
char data[0]
Definition: cpg.c:58
void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
Definition: output.c:204
#define PCMK_XA_PATH
Definition: xml_names.h:350
const char * name
Definition: cib.c:26
void pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, int depth)
Definition: xml_io.c:488
#define PCMK_VALUE_CREATE
Definition: options.h:139
void xml_log_patchset(uint8_t log_level, const char *function, const xmlNode *patchset)
crm_exit_t pcmk_rc2exitc(int rc)
Map a function return code to the most similar exit code.
Definition: results.c:702
Include the opening tag of an XML element, and include XML comments.
Definition: xml_internal.h:143
#define PCMK_XE_CHANGE_LIST
Definition: xml_names.h:76
void pcmk__register_patchset_messages(pcmk__output_t *out)
#define PCMK_XA_FORMAT
Definition: xml_names.h:286
#define PCMK__XML_PREFIX_MOVED
XML has been moved.
void pcmk__add_separated_word(GString **list, size_t init_size, const char *word, const char *separator)
Definition: strings.c:794
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
unsigned int crm_trace_nonlog
Definition: logging.c:46
void pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
Definition: output_log.c:390
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:482
#define LOG_NEVER
Definition: logging.h:48
#define PCMK_XA_OPERATION
Definition: xml_names.h:344
Include indentation and newlines.
Definition: xml_internal.h:140
Deprecated Pacemaker XML API.
Log a created XML subtree.
Definition: xml_internal.h:157
#define PCMK_VALUE_MOVE
Definition: options.h:175
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:446
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml.c:440
#define PCMK__XE_DIFF_ADDED
#define PCMK__OUTPUT_ARGS(ARGS...)
Include the children of an XML element.
Definition: xml_internal.h:146
#define PCMK_VALUE_MODIFY
Definition: options.h:174
#define crm_trace(fmt, args...)
Definition: logging.h:404
#define PCMK_VALUE_DELETE
Definition: options.h:144
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1296
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:98
Wrappers for and extensions to libxml2.
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
#define PCMK_XA_VALUE
Definition: xml_names.h:437
#define PCMK__XML_PREFIX_CREATED
XML is newly created.
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:30
gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
Definition: logging.c:680
Log a removed XML subtree.
Definition: xml_internal.h:161
#define PCMK_XE_XML_PATCHSET
Definition: xml_names.h:217
int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options)
Definition: xml_display.c:229
Include the closing tag of an XML element.
Definition: xml_internal.h:149
#define crm_err(fmt, args...)
Definition: logging.h:391
#define PCMK__XML_PREFIX_DELETED
XML has been deleted.
This structure contains everything that makes up a single output formatter.
#define PCMK__XA_CRM_DIFF_MARKER
Include XML text nodes.
Definition: xml_internal.h:153
Log a minimal version of an XML diff (only showing the changes)
Definition: xml_internal.h:165
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:788
uint8_t pcmk__output_get_log_level(const pcmk__output_t *out)
Definition: output_log.c:363
int pcmk__text_output_new(pcmk__output_t **out, const char *filename)
Definition: output.c:320
#define PCMK_XE_POSITION
Definition: xml_names.h:159
#define LOG_STDOUT
Definition: logging.h:43
int pcmk__log_output_new(pcmk__output_t **out)
Definition: output.c:291