pacemaker  2.1.6-802a72226b
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  const char *name = crm_element_name(data);
96  int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
97  int rc = pcmk_rc_no_output;
98 
99  if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
100  const char *hidden = crm_element_value(data, "hidden");
101 
102  g_string_truncate(buffer, 0);
103 
104  for (int lpc = 0; lpc < spaces; lpc++) {
105  g_string_append_c(buffer, ' ');
106  }
107  pcmk__g_strcat(buffer, "<", name, NULL);
108 
109  for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
110  attr = attr->next) {
111  xml_node_private_t *nodepriv = attr->_private;
112  const char *p_name = (const char *) attr->name;
113  const char *p_value = pcmk__xml_attr_value(attr);
114  char *p_copy = NULL;
115 
116  if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
117  continue;
118  }
119 
120  // @COMPAT Remove when v1 patchsets are removed
121  if (pcmk_any_flags_set(options,
124  && (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
125  continue;
126  }
127 
128  if ((hidden != NULL) && (p_name[0] != '\0')
129  && (strstr(hidden, p_name) != NULL)) {
130  pcmk__str_update(&p_copy, "*****");
131 
132  } else {
133  p_copy = crm_xml_escape(p_value);
134  }
135 
136  pcmk__g_strcat(buffer, " ", p_name, "=\"",
137  pcmk__s(p_copy, "<null>"), "\"", NULL);
138  free(p_copy);
139  }
140 
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 (!xml_has_children(data)) {
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, "", 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 = crm_element_value(data, name);
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 = crm_element_value(data, name);
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_full_fledged)) {
451  options |= pcmk__xml_fmt_full;
452  }
453  if (pcmk_is_set(legacy_options, xml_log_option_open)) {
454  options |= pcmk__xml_fmt_open;
455  }
456  if (pcmk_is_set(legacy_options, xml_log_option_children)) {
457  options |= pcmk__xml_fmt_children;
458  }
459  if (pcmk_is_set(legacy_options, xml_log_option_close)) {
460  options |= pcmk__xml_fmt_close;
461  }
462  if (pcmk_is_set(legacy_options, xml_log_option_text)) {
463  options |= pcmk__xml_fmt_text;
464  }
465  if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
466  options |= pcmk__xml_fmt_diff_plus;
467  }
468  if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
469  options |= pcmk__xml_fmt_diff_minus;
470  }
471  if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
472  options |= pcmk__xml_fmt_diff_short;
473  }
474 
475  // Log element based on options
476  if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
477  CRM_CHECK(depth >= 0, depth = 0);
478  show_xml_changes_recursive(out, data, depth, options);
479  goto done;
480  }
481 
482  if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
483  && (!xml_has_children(data)
484  || (crm_element_value(data, XML_DIFF_MARKER) != NULL))) {
485 
486  if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
487  legacy_options |= xml_log_option_diff_all;
488  prefix = PCMK__XML_PREFIX_CREATED;
489 
490  } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
491  legacy_options |= xml_log_option_diff_all;
492  prefix = PCMK__XML_PREFIX_DELETED;
493  }
494  }
495 
497  && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
498 
499  if (!pcmk_any_flags_set(options,
502  // Nothing will ever be logged
503  goto done;
504  }
505 
506  // Keep looking for the actual change
507  for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
508  child = pcmk__xml_next(child)) {
509  log_data_element(log_level, file, function, line, prefix, child,
510  depth + 1, options);
511  }
512 
513  } else {
514  pcmk__xml_show(out, prefix, data, depth,
515  options
519  }
520 
521 done:
522  out->finish(out, CRM_EX_OK, true, NULL);
523  pcmk__output_free(out);
524 }
525 
526 void
527 xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
528 {
529  pcmk__output_t *out = NULL;
530  int rc = pcmk_rc_ok;
531 
532  switch (log_level) {
533  case LOG_NEVER:
534  return;
535  case LOG_STDOUT:
536  CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
537  break;
538  default:
539  CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
540  pcmk__output_set_log_level(out, log_level);
541  break;
542  }
543  rc = pcmk__xml_show_changes(out, xml);
544  out->finish(out, pcmk_rc2exitc(rc), true, NULL);
545  pcmk__output_free(out);
546 }
547 
548 // LCOV_EXCL_STOP
549 // End deprecated API
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:235
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:410
const char * name
Definition: cib.c:24
Exclude certain XML attributes (for calculating digests)
Definition: xml_internal.h:133
crm_exit_t pcmk_rc2exitc(int rc)
Map a function return code to the most similar exit code.
Definition: results.c:689
Include the opening tag of an XML element, and include XML comments.
Definition: xml_internal.h:142
void xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
Definition: xml_display.c:527
#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:345
#define LOG_NEVER
Definition: logging.h:47
Deprecated Pacemaker logging API.
Include indentation and newlines.
Definition: xml_internal.h:136
Deprecated Pacemaker XML API.
Log a created XML subtree.
Definition: xml_internal.h:156
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:496
#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:68
Include the children of an XML element.
Definition: xml_internal.h:145
#define do_crm_log(level, fmt, args...)
Log a message.
Definition: logging.h:172
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:229
Include full XML subtree (with any text), using libxml serialization.
Definition: xml_internal.h:139
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:121
int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
Definition: xml_display.c:370
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:237
void pcmk__log_xmllib_err(void *ctx, const char *fmt,...)
Definition: xml_display.c:26
#define PCMK__XML_PREFIX_CREATED
XML is newly created.
gboolean xml_has_children(const xmlNode *root)
Definition: xml.c:1726
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:28
Log a removed XML subtree.
Definition: xml_internal.h:160
#define XML_DIFF_MARKER
Definition: msg_xml.h:127
Include the closing tag of an XML element.
Definition: xml_internal.h:148
#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:152
Log a minimal version of an XML diff (only showing the changes)
Definition: xml_internal.h:164
char * crm_xml_escape(const char *text)
Replace special characters with their XML escape sequences.
Definition: xml.c:1310
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:42
int pcmk__log_output_new(pcmk__output_t **out)
Definition: output.c:272