pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
patchset_display.c
Go to the documentation of this file.
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 
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, 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 
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)
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
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 = find_xml_node(patchset, XML_TAG_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
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, XML_TAG_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
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__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,
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,
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 
306 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
307 static int
308 xml_patchset_default(pcmk__output_t *out, va_list args)
309 {
310  const xmlNode *patchset = va_arg(args, const xmlNode *);
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, PCMK_XA_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 
345 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
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  const xmlNode *patchset = va_arg(args, const xmlNode *);
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__,
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, PCMK_XA_FORMAT, &format);
377  switch (format) {
378  case 1:
379  if (log_level < LOG_DEBUG) {
380  return xml_show_patchset_v1(out, patchset,
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 
407 PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
408 static int
409 xml_patchset_xml(pcmk__output_t *out, va_list args)
410 {
411  const xmlNode *patchset = va_arg(args, const xmlNode *);
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 
438 void
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,
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, PCMK_XA_FORMAT, &format);
494  switch (format) {
495  case 1:
496  if (log_level < LOG_DEBUG) {
497  rc = xml_show_patchset_v1(out, patchset,
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);
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
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:238
char data[0]
Definition: cpg.c:55
void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
Definition: output.c:196
const char * name
Definition: cib.c:26
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:2484
void xml_log_patchset(uint8_t log_level, const char *function, const xmlNode *patchset)
xmlNode * find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
Definition: xml.c:384
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:140
void pcmk__register_patchset_messages(pcmk__output_t *out)
#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:700
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:369
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:483
#define LOG_NEVER
Definition: logging.h:48
Include indentation and newlines.
Definition: xml_internal.h:137
Deprecated Pacemaker XML API.
Log a created XML subtree.
Definition: xml_internal.h:153
#define XML_TAG_DIFF_ADDED
Definition: msg_xml.h:423
#define XML_DIFF_OP
Definition: msg_xml.h:470
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:447
#define PCMK__OUTPUT_ARGS(ARGS...)
Include the children of an XML element.
Definition: xml_internal.h:143
#define crm_trace(fmt, args...)
Definition: logging.h:387
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1217
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:99
#define PCMK_XA_FORMAT
Definition: msg_xml.h:51
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
#define XML_DIFF_POSITION
Definition: msg_xml.h:472
#define PCMK__XML_PREFIX_CREATED
XML is newly created.
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:28
char * dump_xml_formatted_with_text(const xmlNode *xml)
Definition: xml.c:1632
gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
Definition: logging.c:680
#define XML_DIFF_PATH
Definition: msg_xml.h:471
Log a removed XML subtree.
Definition: xml_internal.h:157
#define XML_DIFF_LIST
Definition: msg_xml.h:467
int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options)
Definition: xml_display.c:228
#define XML_DIFF_MARKER
Definition: msg_xml.h:136
Include the closing tag of an XML element.
Definition: xml_internal.h:146
#define crm_err(fmt, args...)
Definition: logging.h:381
#define PCMK__XML_PREFIX_DELETED
XML has been deleted.
This structure contains everything that makes up a single output formatter.
Log a minimal version of an XML diff (only showing the changes)
Definition: xml_internal.h:161
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:572
#define XML_TAG_DIFF_REMOVED
Definition: msg_xml.h:426
uint8_t pcmk__output_get_log_level(const pcmk__output_t *out)
Definition: output_log.c:357
int pcmk__text_output_new(pcmk__output_t **out, const char *filename)
Definition: output.c:301
#define LOG_STDOUT
Definition: logging.h:43
int pcmk__log_output_new(pcmk__output_t **out)
Definition: output.c:272
#define XML_ATTR_DIGEST
Definition: msg_xml.h:141