pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
xml_io.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 <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/types.h>
16 
17 #include <bzlib.h>
18 #include <libxml/parser.h>
19 #include <libxml/tree.h>
20 #include <libxml/xmlIO.h> // xmlOutputBuffer*
21 
22 #include <crm/crm.h>
23 #include <crm/common/xml.h>
24 #include <crm/common/xml_io.h>
25 #include "crmcommon_private.h"
26 
38 static char *
39 decompress_file(const char *filename)
40 {
41  char *buffer = NULL;
42  int rc = pcmk_rc_ok;
43  size_t length = 0;
44  BZFILE *bz_file = NULL;
45  FILE *input = fopen(filename, "r");
46 
47  if (input == NULL) {
48  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
49  return NULL;
50  }
51 
52  bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
53  rc = pcmk__bzlib2rc(rc);
54  if (rc != pcmk_rc_ok) {
55  crm_err("Could not prepare to read compressed %s: %s "
56  QB_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
57  goto done;
58  }
59 
60  // cppcheck seems not to understand the abort-logic in pcmk__realloc
61  // cppcheck-suppress memleak
62  do {
63  int read_len = 0;
64 
65  buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
66  read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
67 
68  if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
69  crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
70  length += read_len;
71  }
72  } while (rc == BZ_OK);
73 
74  rc = pcmk__bzlib2rc(rc);
75  if (rc != pcmk_rc_ok) {
76  rc = pcmk__bzlib2rc(rc);
77  crm_err("Could not read compressed %s: %s " QB_XS " rc=%d",
78  filename, pcmk_rc_str(rc), rc);
79  free(buffer);
80  buffer = NULL;
81  } else {
82  buffer[length] = '\0';
83  }
84 
85 done:
86  BZ2_bzReadClose(&rc, bz_file);
87  fclose(input);
88  return buffer;
89 }
90 
101 xmlNode *
102 pcmk__xml_read(const char *filename)
103 {
104  bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
105  xmlNode *xml = NULL;
106  xmlDoc *output = NULL;
107  xmlParserCtxt *ctxt = NULL;
108  const xmlError *last_error = NULL;
109 
110  // Create a parser context
111  ctxt = xmlNewParserCtxt();
112  CRM_CHECK(ctxt != NULL, return NULL);
113 
114  xmlCtxtResetLastError(ctxt);
115  xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
116 
117  if (use_stdin) {
118  output = xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL,
119  XML_PARSE_NOBLANKS);
120 
121  } else if (pcmk__ends_with_ext(filename, ".bz2")) {
122  char *input = decompress_file(filename);
123 
124  if (input != NULL) {
125  output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
126  XML_PARSE_NOBLANKS);
127  free(input);
128  }
129 
130  } else {
131  output = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_NOBLANKS);
132  }
133 
134  if (output != NULL) {
135  pcmk__xml_new_private_data((xmlNode *) output);
136  xml = xmlDocGetRootElement(output);
137  if (xml != NULL) {
138  /* @TODO Should we really be stripping out text? This seems like an
139  * overly broad way to get rid of whitespace, if that's the goal.
140  * Text nodes may be invalid in most or all Pacemaker inputs, but
141  * stripping them in a generic "parse XML from file" function may
142  * not be the best way to ignore them.
143  */
145  }
146  }
147 
148  last_error = xmlCtxtGetLastError(ctxt);
149  if ((last_error != NULL) && (xml != NULL)) {
150  crm_log_xml_debug(xml, "partial");
151  pcmk__xml_free(xml);
152  xml = NULL;
153  }
154 
155  xmlFreeParserCtxt(ctxt);
156  return xml;
157 }
158 
167 xmlNode *
168 pcmk__xml_parse(const char *input)
169 {
170  xmlNode *xml = NULL;
171  xmlDoc *output = NULL;
172  xmlParserCtxt *ctxt = NULL;
173  const xmlError *last_error = NULL;
174 
175  if (input == NULL) {
176  return NULL;
177  }
178 
179  ctxt = xmlNewParserCtxt();
180  if (ctxt == NULL) {
181  return NULL;
182  }
183 
184  xmlCtxtResetLastError(ctxt);
185  xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
186 
187  output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
188  XML_PARSE_NOBLANKS);
189  if (output != NULL) {
190  pcmk__xml_new_private_data((xmlNode *) output);
191  xml = xmlDocGetRootElement(output);
192  }
193 
194  last_error = xmlCtxtGetLastError(ctxt);
195  if ((last_error != NULL) && (xml != NULL)) {
196  crm_log_xml_debug(xml, "partial");
197  pcmk__xml_free(xml);
198  xml = NULL;
199  }
200 
201  xmlFreeParserCtxt(ctxt);
202  return xml;
203 }
204 
214 static void
215 dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
216  int depth)
217 {
218  bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
219  bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
220  int spaces = pretty? (2 * depth) : 0;
221 
222  for (int lpc = 0; lpc < spaces; lpc++) {
223  g_string_append_c(buffer, ' ');
224  }
225 
226  pcmk__g_strcat(buffer, "<", data->name, NULL);
227 
228  for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
229  attr = attr->next) {
230 
231  if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
232  pcmk__dump_xml_attr(attr, buffer);
233  }
234  }
235 
236  if (data->children == NULL) {
237  g_string_append(buffer, "/>");
238 
239  } else {
240  g_string_append_c(buffer, '>');
241  }
242 
243  if (pretty) {
244  g_string_append_c(buffer, '\n');
245  }
246 
247  if (data->children) {
248  for (const xmlNode *child = data->children; child != NULL;
249  child = child->next) {
250  pcmk__xml_string(child, options, buffer, depth + 1);
251  }
252 
253  for (int lpc = 0; lpc < spaces; lpc++) {
254  g_string_append_c(buffer, ' ');
255  }
256 
257  pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
258 
259  if (pretty) {
260  g_string_append_c(buffer, '\n');
261  }
262  }
263 }
264 
274 static void
275 dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
276  int depth)
277 {
278  bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
279  int spaces = pretty? (2 * depth) : 0;
280  const char *content = (const char *) data->content;
281  gchar *content_esc = NULL;
282 
284  content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
285  content = content_esc;
286  }
287 
288  for (int lpc = 0; lpc < spaces; lpc++) {
289  g_string_append_c(buffer, ' ');
290  }
291 
292  g_string_append(buffer, content);
293 
294  if (pretty) {
295  g_string_append_c(buffer, '\n');
296  }
297  g_free(content_esc);
298 }
299 
309 static void
310 dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
311  int depth)
312 {
313  bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
314  int spaces = pretty? (2 * depth) : 0;
315 
316  for (int lpc = 0; lpc < spaces; lpc++) {
317  g_string_append_c(buffer, ' ');
318  }
319 
320  pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
321  NULL);
322 
323  if (pretty) {
324  g_string_append_c(buffer, '\n');
325  }
326 }
327 
337 static void
338 dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
339  int depth)
340 {
341  bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
342  int spaces = pretty? (2 * depth) : 0;
343 
344  for (int lpc = 0; lpc < spaces; lpc++) {
345  g_string_append_c(buffer, ' ');
346  }
347 
348  pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
349 
350  if (pretty) {
351  g_string_append_c(buffer, '\n');
352  }
353 }
354 
363 static const char *
364 xml_element_type_text(xmlElementType type)
365 {
366  static const char *const element_type_names[] = {
367  [XML_ELEMENT_NODE] = "element",
368  [XML_ATTRIBUTE_NODE] = "attribute",
369  [XML_TEXT_NODE] = "text",
370  [XML_CDATA_SECTION_NODE] = "CDATA section",
371  [XML_ENTITY_REF_NODE] = "entity reference",
372  [XML_ENTITY_NODE] = "entity",
373  [XML_PI_NODE] = "PI",
374  [XML_COMMENT_NODE] = "comment",
375  [XML_DOCUMENT_NODE] = "document",
376  [XML_DOCUMENT_TYPE_NODE] = "document type",
377  [XML_DOCUMENT_FRAG_NODE] = "document fragment",
378  [XML_NOTATION_NODE] = "notation",
379  [XML_HTML_DOCUMENT_NODE] = "HTML document",
380  [XML_DTD_NODE] = "DTD",
381  [XML_ELEMENT_DECL] = "element declaration",
382  [XML_ATTRIBUTE_DECL] = "attribute declaration",
383  [XML_ENTITY_DECL] = "entity declaration",
384  [XML_NAMESPACE_DECL] = "namespace declaration",
385  [XML_XINCLUDE_START] = "XInclude start",
386  [XML_XINCLUDE_END] = "XInclude end",
387  };
388 
389  if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
390  return "unrecognized type";
391  }
392  return element_type_names[type];
393 }
394 
410 void
411 pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
412  int depth)
413 {
414  if (data == NULL) {
415  crm_trace("Nothing to dump");
416  return;
417  }
418 
419  pcmk__assert(buffer != NULL);
420  CRM_CHECK(depth >= 0, depth = 0);
421 
422  switch(data->type) {
423  case XML_ELEMENT_NODE:
424  /* Handle below */
425  dump_xml_element(data, options, buffer, depth);
426  break;
427  case XML_TEXT_NODE:
428  if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
429  dump_xml_text(data, options, buffer, depth);
430  }
431  break;
432  case XML_COMMENT_NODE:
433  dump_xml_comment(data, options, buffer, depth);
434  break;
435  case XML_CDATA_SECTION_NODE:
436  dump_xml_cdata(data, options, buffer, depth);
437  break;
438  default:
439  crm_warn("Cannot convert XML %s node to text " QB_XS " type=%d",
440  xml_element_type_text(data->type), data->type);
441  break;
442  }
443 }
444 
456 static int
457 write_compressed_stream(char *text, const char *filename, FILE *stream,
458  unsigned int *bytes_out)
459 {
460  unsigned int bytes_in = 0;
461  int rc = pcmk_rc_ok;
462 
463  // (5, 0, 0): (intermediate block size, silent, default workFactor)
464  BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
465 
466  rc = pcmk__bzlib2rc(rc);
467  if (rc != pcmk_rc_ok) {
468  crm_warn("Not compressing %s: could not prepare file stream: %s "
469  QB_XS " rc=%d",
470  filename, pcmk_rc_str(rc), rc);
471  goto done;
472  }
473 
474  BZ2_bzWrite(&rc, bz_file, text, strlen(text));
475  rc = pcmk__bzlib2rc(rc);
476  if (rc != pcmk_rc_ok) {
477  crm_warn("Not compressing %s: could not compress data: %s "
478  QB_XS " rc=%d errno=%d",
479  filename, pcmk_rc_str(rc), rc, errno);
480  goto done;
481  }
482 
483  BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
484  bz_file = NULL;
485  rc = pcmk__bzlib2rc(rc);
486  if (rc != pcmk_rc_ok) {
487  crm_warn("Not compressing %s: could not write compressed data: %s "
488  QB_XS " rc=%d errno=%d",
489  filename, pcmk_rc_str(rc), rc, errno);
490  goto done;
491  }
492 
493  crm_trace("Compressed XML for %s from %u bytes to %u",
494  filename, bytes_in, *bytes_out);
495 
496 done:
497  if (bz_file != NULL) {
498  BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
499  }
500  return rc;
501 }
502 
515 static int
516 write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
517  bool compress)
518 {
519  GString *buffer = g_string_sized_new(1024);
520  unsigned int bytes_out = 0;
521  int rc = pcmk_rc_ok;
522 
523  pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
524  CRM_CHECK(!pcmk__str_empty(buffer->str),
525  crm_log_xml_info(xml, "dump-failed");
526  rc = pcmk_rc_error;
527  goto done);
528 
529  crm_log_xml_trace(xml, "writing");
530 
531  if (compress
532  && (write_compressed_stream(buffer->str, filename, stream,
533  &bytes_out) == pcmk_rc_ok)) {
534  goto done;
535  }
536 
537  rc = fprintf(stream, "%s", buffer->str);
538  if (rc < 0) {
539  rc = EIO;
540  crm_perror(LOG_ERR, "writing %s", filename);
541  goto done;
542  }
543  bytes_out = (unsigned int) rc;
544  rc = pcmk_rc_ok;
545 
546 done:
547  if (fflush(stream) != 0) {
548  rc = errno;
549  crm_perror(LOG_ERR, "flushing %s", filename);
550  }
551 
552  // Don't report error if the file does not support synchronization
553  if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
554  rc = errno;
555  crm_perror(LOG_ERR, "synchronizing %s", filename);
556  }
557 
558  fclose(stream);
559  crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
560 
561  g_string_free(buffer, TRUE);
562  return rc;
563 }
564 
575 int
576 pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
577 {
578  FILE *stream = NULL;
579 
580  CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
581  stream = fdopen(fd, "w");
582  if (stream == NULL) {
583  return errno;
584  }
585 
586  return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
587  false);
588 }
589 
600 int
601 pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
602 {
603  FILE *stream = NULL;
604 
605  CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
606  stream = fopen(filename, "w");
607  if (stream == NULL) {
608  return errno;
609  }
610 
611  return write_xml_stream(xml, filename, stream, compress);
612 }
613 
623 int
624 pcmk__xml2fd(int fd, xmlNode *cur)
625 {
626  bool success;
627 
628  xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
629  pcmk__mem_assert(fd_out);
630  xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
631 
632  success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
633 
634  success = xmlOutputBufferClose(fd_out) != -1 && success;
635 
636  if (!success) {
637  return EIO;
638  }
639 
640  fsync(fd);
641  return pcmk_rc_ok;
642 }
643 
644 void
645 save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
646 {
647  char *f = NULL;
648 
649  if (filename == NULL) {
650  char *uuid = crm_generate_uuid();
651 
652  f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
653  filename = f;
654  free(uuid);
655  }
656 
657  crm_info("Saving %s to %s", desc, filename);
658  pcmk__xml_write_file(xml, filename, false);
659  free(f);
660 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
A dumping ground.
char * pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
Definition: xml.c:964
char data[0]
Definition: cpg.c:58
char * crm_generate_uuid(void)
Definition: utils.c:335
void pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, int depth)
Definition: xml_io.c:411
enum pcmk_ipc_server type
Definition: cpg.c:51
int pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
Definition: xml_io.c:576
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
Definition: xml.c:882
G_GNUC_INTERNAL void pcmk__log_xmllib_err(void *ctx, const char *fmt,...) G_GNUC_PRINTF(2
Exclude certain XML attributes (for calculating digests)
Definition: xml_internal.h:146
xmlNode * pcmk__xml_parse(const char *input)
Definition: xml_io.c:168
Wrappers for and extensions to XML input/output functions.
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:609
Include indentation and newlines.
Definition: xml_internal.h:149
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
#define crm_warn(fmt, args...)
Definition: logging.h:362
void pcmk__strip_xml_text(xmlNode *xml)
Definition: xml.c:843
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
Definition: xml.c:328
#define crm_trace(fmt, args...)
Definition: logging.h:372
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1297
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
#define crm_log_xml_debug(xml, text)
Definition: logging.h:379
Wrappers for and extensions to libxml2.
#define PCMK__NELEM(a)
Definition: internal.h:49
int pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
Definition: xml_io.c:601
xmlNode * pcmk__xml_read(const char *filename)
Definition: xml_io.c:102
int pcmk__xml2fd(int fd, xmlNode *cur)
Definition: xml_io.c:624
const xmlChar * pcmkXmlStr
Definition: xml.h:41
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:232
#define pcmk__assert(expr)
const char * pcmk__get_tmpdir(void)
Definition: io.c:543
int pcmk__bzlib2rc(int bz2)
Map a bz2 return code to the most similar Pacemaker return code.
Definition: results.c:1014
#define PCMK__BUFFER_SIZE
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:299
#define crm_err(fmt, args...)
Definition: logging.h:359
xmlNode * input
G_GNUC_INTERNAL void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer)
Definition: xml_attr.c:106
#define pcmk__mem_assert(ptr)
#define crm_log_xml_info(xml, text)
Definition: logging.h:378
Include XML text nodes.
Definition: xml_internal.h:162
#define crm_log_xml_trace(xml, text)
Definition: logging.h:380
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition: xml_io.c:645
#define crm_info(fmt, args...)
Definition: logging.h:367
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:637
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1