pacemaker  2.1.8-3980678f03
Scalable High-Availability cluster resource manager
xml_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 <libxml/tree.h>
13 
14 #include <crm/crm.h>
15 #include <crm/common/xml.h>
16 #include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
17 #include "crmcommon_private.h"
18 
19 static int show_xml_node(pcmk__output_t *out, GString *buffer,
20  const char *prefix, const xmlNode *data, int depth,
21  uint32_t options);
22 
23 // Log an XML library error
24 void
25 pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
26 {
27  va_list ap;
28 
29  va_start(ap, fmt);
31  {
32  PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
33  crm_abort(__FILE__, __PRETTY_FUNCTION__,
34  __LINE__, "xml library error", TRUE,
35  TRUE),
36  "XML Error: ", fmt, ap);
37  },
38  {
39  PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
40  }
41  );
42  va_end(ap);
43 }
44 
58 static int
59 show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
60  uint32_t options)
61 {
62  if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
63  int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
64 
65  return out->info(out, "%*s<!--%s-->",
66  width, "", (const char *) data->content);
67  }
68  return pcmk_rc_no_output;
69 }
70 
90 static int
91 show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
92  const xmlNode *data, int depth, uint32_t options)
93 {
94  int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
95  int rc = pcmk_rc_no_output;
96 
97  if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
98  const char *hidden = crm_element_value(data, PCMK__XA_HIDDEN);
99 
100  g_string_truncate(buffer, 0);
101 
102  for (int lpc = 0; lpc < spaces; lpc++) {
103  g_string_append_c(buffer, ' ');
104  }
105  pcmk__g_strcat(buffer, "<", data->name, NULL);
106 
107  for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
108  attr = attr->next) {
109  xml_node_private_t *nodepriv = attr->_private;
110  const char *p_name = (const char *) attr->name;
111  const char *p_value = pcmk__xml_attr_value(attr);
112  gchar *p_copy = NULL;
113 
114  if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
115  continue;
116  }
117 
118  // @COMPAT Remove when v1 patchsets are removed
119  if (pcmk_any_flags_set(options,
122  && (strcmp(PCMK__XA_CRM_DIFF_MARKER, p_name) == 0)) {
123  continue;
124  }
125 
126  if ((hidden != NULL) && (p_name[0] != '\0')
127  && (strstr(hidden, p_name) != NULL)) {
128 
129  p_value = "*****";
130 
131  } else {
132  p_copy = pcmk__xml_escape(p_value, true);
133  p_value = p_copy;
134  }
135 
136  pcmk__g_strcat(buffer, " ", p_name, "=\"",
137  pcmk__s(p_value, "<null>"), "\"", NULL);
138  g_free(p_copy);
139  }
140 
141  if ((data->children != NULL)
142  && pcmk_is_set(options, pcmk__xml_fmt_children)) {
143  g_string_append_c(buffer, '>');
144 
145  } else {
146  g_string_append(buffer, "/>");
147  }
148 
149  rc = out->info(out, "%s%s%s",
150  pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
151  buffer->str);
152  }
153 
154  if (data->children == NULL) {
155  return rc;
156  }
157 
158  if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
159  for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
160  child = pcmk__xml_next(child)) {
161 
162  int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
163  options
166  rc = pcmk__output_select_rc(rc, temp_rc);
167  }
168  }
169 
170  if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
171  int temp_rc = out->info(out, "%s%s%*s</%s>",
172  pcmk__s(prefix, ""),
173  pcmk__str_empty(prefix)? "" : " ",
174  spaces, "", data->name);
175  rc = pcmk__output_select_rc(rc, temp_rc);
176  }
177 
178  return rc;
179 }
180 
200 static int
201 show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
202  const xmlNode *data, int depth, uint32_t options)
203 {
204  switch (data->type) {
205  case XML_COMMENT_NODE:
206  return show_xml_comment(out, data, depth, options);
207  case XML_ELEMENT_NODE:
208  return show_xml_element(out, buffer, prefix, data, depth, options);
209  default:
210  return pcmk_rc_no_output;
211  }
212 }
213 
228 int
229 pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
230  int depth, uint32_t options)
231 {
232  int rc = pcmk_rc_no_output;
233  GString *buffer = NULL;
234 
235  CRM_ASSERT(out != NULL);
236  CRM_CHECK(depth >= 0, depth = 0);
237 
238  if (data == NULL) {
239  return rc;
240  }
241 
242  /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
243  * recursive calls
244  */
245  buffer = g_string_sized_new(1024);
246  rc = show_xml_node(out, buffer, prefix, data, depth, options);
247  g_string_free(buffer, TRUE);
248 
249  return rc;
250 }
251 
265 static int
266 show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
267  uint32_t options)
268 {
269  /* @COMPAT: When log_data_element() is removed, we can remove the options
270  * argument here and instead hard-code pcmk__xml_log_pretty.
271  */
272  xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
273  int rc = pcmk_rc_no_output;
274  int temp_rc = pcmk_rc_no_output;
275 
276  if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
277  // Newly created
278  return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
279  options
283  }
284 
285  if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
286  // Modified or moved
287  bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
288  int spaces = pretty? (2 * depth) : 0;
289  const char *prefix = PCMK__XML_PREFIX_MODIFIED;
290 
291  if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
292  prefix = PCMK__XML_PREFIX_MOVED;
293  }
294 
295  // Log opening tag
296  rc = pcmk__xml_show(out, prefix, data, depth,
297  options|pcmk__xml_fmt_open);
298 
299  // Log changes to attributes
300  for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
301  attr = attr->next) {
302  const char *name = (const char *) attr->name;
303 
304  nodepriv = attr->_private;
305 
306  if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
307  const char *value = pcmk__xml_attr_value(attr);
308 
309  temp_rc = out->info(out, "%s %*s @%s=%s",
310  PCMK__XML_PREFIX_DELETED, spaces, "", name,
311  value);
312 
313  } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
314  const char *value = pcmk__xml_attr_value(attr);
315 
316  if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
317  prefix = PCMK__XML_PREFIX_CREATED;
318 
319  } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
320  prefix = PCMK__XML_PREFIX_MODIFIED;
321 
322  } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
323  prefix = PCMK__XML_PREFIX_MOVED;
324 
325  } else {
326  prefix = PCMK__XML_PREFIX_MODIFIED;
327  }
328 
329  temp_rc = out->info(out, "%s %*s @%s=%s",
330  prefix, spaces, "", name, value);
331  }
332  rc = pcmk__output_select_rc(rc, temp_rc);
333  }
334 
335  // Log changes to children
336  for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
337  child = pcmk__xml_next(child)) {
338  temp_rc = show_xml_changes_recursive(out, child, depth + 1,
339  options);
340  rc = pcmk__output_select_rc(rc, temp_rc);
341  }
342 
343  // Log closing tag
344  temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
345  options|pcmk__xml_fmt_close);
346  return pcmk__output_select_rc(rc, temp_rc);
347  }
348 
349  // This node hasn't changed, but check its children
350  for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
351  child = pcmk__xml_next(child)) {
352  temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
353  rc = pcmk__output_select_rc(rc, temp_rc);
354  }
355  return rc;
356 }
357 
369 int
370 pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
371 {
372  xml_doc_private_t *docpriv = NULL;
373  int rc = pcmk_rc_no_output;
374  int temp_rc = pcmk_rc_no_output;
375 
376  CRM_ASSERT(out != NULL);
377  CRM_ASSERT(xml != NULL);
378  CRM_ASSERT(xml->doc != NULL);
379 
380  docpriv = xml->doc->_private;
381  if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
382  return rc;
383  }
384 
385  for (const GList *iter = docpriv->deleted_objs; iter != NULL;
386  iter = iter->next) {
387  const pcmk__deleted_xml_t *deleted_obj = iter->data;
388 
389  if (deleted_obj->position >= 0) {
390  temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
391  deleted_obj->path, deleted_obj->position);
392  } else {
393  temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
394  deleted_obj->path);
395  }
396  rc = pcmk__output_select_rc(rc, temp_rc);
397  }
398 
399  temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
400  return pcmk__output_select_rc(rc, temp_rc);
401 }
402 
403 // Deprecated functions kept only for backward API compatibility
404 // LCOV_EXCL_START
405 
407 #include <crm/common/xml_compat.h>
408 
409 void
410 log_data_element(int log_level, const char *file, const char *function,
411  int line, const char *prefix, const xmlNode *data, int depth,
412  int legacy_options)
413 {
414  uint32_t options = 0;
415  pcmk__output_t *out = NULL;
416 
417  // Confine log_level to uint8_t range
418  log_level = pcmk__clip_log_level(log_level);
419 
420  if (data == NULL) {
421  do_crm_log(log_level, "%s%sNo data to dump as XML",
422  pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ");
423  return;
424  }
425 
426  switch (log_level) {
427  case LOG_NEVER:
428  return;
429  case LOG_STDOUT:
430  CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
431  break;
432  default:
433  CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
434  pcmk__output_set_log_level(out, log_level);
435  break;
436  }
437 
438  /* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and
439  * start using the pcmk__xml_fmt_options in all the internal functions.
440  *
441  * xml_log_option_dirty_add and xml_log_option_diff_all are ignored by
442  * internal code and only used here, so they don't need to be addressed.
443  */
444  if (pcmk_is_set(legacy_options, xml_log_option_filtered)) {
445  options |= pcmk__xml_fmt_filtered;
446  }
447  if (pcmk_is_set(legacy_options, xml_log_option_formatted)) {
448  options |= pcmk__xml_fmt_pretty;
449  }
450  if (pcmk_is_set(legacy_options, xml_log_option_open)) {
451  options |= pcmk__xml_fmt_open;
452  }
453  if (pcmk_is_set(legacy_options, xml_log_option_children)) {
454  options |= pcmk__xml_fmt_children;
455  }
456  if (pcmk_is_set(legacy_options, xml_log_option_close)) {
457  options |= pcmk__xml_fmt_close;
458  }
459  if (pcmk_is_set(legacy_options, xml_log_option_text)) {
460  options |= pcmk__xml_fmt_text;
461  }
462  if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
463  options |= pcmk__xml_fmt_diff_plus;
464  }
465  if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
466  options |= pcmk__xml_fmt_diff_minus;
467  }
468  if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
469  options |= pcmk__xml_fmt_diff_short;
470  }
471 
472  // Log element based on options
473  if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
474  CRM_CHECK(depth >= 0, depth = 0);
475  show_xml_changes_recursive(out, data, depth, options);
476  goto done;
477  }
478 
479  if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
480  && ((data->children == NULL)
482 
483  if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
484  legacy_options |= xml_log_option_diff_all;
485  prefix = PCMK__XML_PREFIX_CREATED;
486 
487  } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
488  legacy_options |= xml_log_option_diff_all;
489  prefix = PCMK__XML_PREFIX_DELETED;
490  }
491  }
492 
494  && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
495 
496  if (!pcmk_any_flags_set(options,
499  // Nothing will ever be logged
500  goto done;
501  }
502 
503  // Keep looking for the actual change
504  for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
505  child = pcmk__xml_next(child)) {
506  log_data_element(log_level, file, function, line, prefix, child,
507  depth + 1, options);
508  }
509 
510  } else {
511  pcmk__xml_show(out, prefix, data, depth,
512  options
516  }
517 
518 done:
519  out->finish(out, CRM_EX_OK, true, NULL);
520  pcmk__output_free(out);
521 }
522 
523 void
524 xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
525 {
526  pcmk__output_t *out = NULL;
527  int rc = pcmk_rc_ok;
528 
529  switch (log_level) {
530  case LOG_NEVER:
531  return;
532  case LOG_STDOUT:
533  CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
534  break;
535  default:
536  CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
537  pcmk__output_set_log_level(out, log_level);
538  break;
539  }
540  rc = pcmk__xml_show_changes(out, xml);
541  out->finish(out, pcmk_rc2exitc(rc), true, NULL);
542  pcmk__output_free(out);
543 }
544 
545 // LCOV_EXCL_STOP
546 // End deprecated API
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
A dumping ground.
char * pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
Definition: xml.c:1110
char data[0]
Definition: cpg.c:58
#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:410
const char * name
Definition: cib.c:26
Exclude certain XML attributes (for calculating digests)
Definition: xml_internal.h:137
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
void xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
Definition: xml_display.c:524
#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:390
#define LOG_NEVER
Definition: logging.h:48
Deprecated Pacemaker logging API.
Include indentation and newlines.
Definition: xml_internal.h:140
Deprecated Pacemaker XML API.
Log a created XML subtree.
Definition: xml_internal.h:157
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:446
#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:72
Include the children of an XML element.
Definition: xml_internal.h:146
#define do_crm_log(level, fmt, args...)
Log a message.
Definition: logging.h:181
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1296
int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options)
Definition: xml_display.c:229
#define PCMK__XA_HIDDEN
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:98
int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
Definition: xml_display.c:370
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:255
void pcmk__log_xmllib_err(void *ctx, const char *fmt,...)
Definition: xml_display.c:25
#define PCMK__XML_PREFIX_CREATED
XML is newly created.
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:30
Log a removed XML subtree.
Definition: xml_internal.h:161
Include the closing tag of an XML element.
Definition: xml_internal.h:149
#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.
#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
void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork)
Definition: utils.c:356
int pcmk__text_output_new(pcmk__output_t **out, const char *filename)
Definition: output.c:320
#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:291