pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
xml_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 <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, ...)
27 {
28  va_list ap;
29 
30  va_start(ap, fmt);
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 
59 static int
60 show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
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 
91 static int
92 show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
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,
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
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 
199 static int
200 show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
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 
227 int
228 pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
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 
264 static int
265 show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
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
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 
368 int
369 pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
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 
406 #include <crm/common/xml_compat.h>
407 
408 void
409 log_data_element(int log_level, const char *file, const char *function,
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 
493  && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
494 
495  if (!pcmk_any_flags_set(options,
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
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)
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
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:238
A dumping ground.
char data[0]
Definition: cpg.c:55
#define pcmk__if_tracing(if_action, else_action)
void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int legacy_options)
Definition: xml_display.c:409
const char * name
Definition: cib.c:26
Exclude certain XML attributes (for calculating digests)
Definition: xml_internal.h:134
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 xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
Definition: xml_display.c:523
#define PCMK__XML_PREFIX_MOVED
XML has been moved.
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
Definition: output_log.c:369
#define LOG_NEVER
Definition: logging.h:48
Deprecated Pacemaker logging API.
Include indentation and newlines.
Definition: xml_internal.h:137
Deprecated Pacemaker XML API.
Log a created XML subtree.
Definition: xml_internal.h:153
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:447
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
Definition: xml_internal.h:69
Include the children of an XML element.
Definition: xml_internal.h:143
#define do_crm_log(level, fmt, args...)
Log a message.
Definition: logging.h:175
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1217
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 pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:99
int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
Definition: xml_display.c:369
void pcmk__str_update(char **str, const char *value)
Definition: strings.c:1193
Wrappers for and extensions to libxml2.
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
Success.
Definition: results.h:240
void pcmk__log_xmllib_err(void *ctx, const char *fmt,...)
Definition: xml_display.c:26
#define PCMK__XML_PREFIX_CREATED
XML is newly created.
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:28
Log a removed XML subtree.
Definition: xml_internal.h:157
#define XML_DIFF_MARKER
Definition: msg_xml.h:136
Include the closing tag of an XML element.
Definition: xml_internal.h:146
#define CRM_ASSERT(expr)
Definition: results.h:42
#define PCMK__XML_PREFIX_DELETED
XML has been deleted.
This structure contains everything that makes up a single output formatter.
Include XML text nodes.
Definition: xml_internal.h:149
Log a minimal version of an XML diff (only showing the changes)
Definition: xml_internal.h:161
char * crm_xml_escape(const char *text)
Replace special characters with their XML escape sequences.
Definition: xml.c:1329
void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork)
Definition: utils.c:397
int pcmk__text_output_new(pcmk__output_t **out, const char *filename)
Definition: output.c:301
#define PCMK__XML_PREFIX_MODIFIED
XML has been modified.
#define LOG_STDOUT
Definition: logging.h:43
int pcmk__log_output_new(pcmk__output_t **out)
Definition: output.c:272