pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
xml_io.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2025 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#include <libxml/xmlstring.h> // xmlChar
22
23#include <crm/crm.h>
24#include <crm/common/xml.h>
25#include <crm/common/xml_io.h>
26#include "crmcommon_private.h"
27
39static char *
40decompress_file(const char *filename)
41{
42 char *buffer = NULL;
43 int rc = pcmk_rc_ok;
44 size_t length = 0;
45 BZFILE *bz_file = NULL;
46 FILE *input = fopen(filename, "r");
47
48 if (input == NULL) {
49 crm_perror(LOG_ERR, "Could not open %s for reading", filename);
50 return NULL;
51 }
52
53 bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
54 rc = pcmk__bzlib2rc(rc);
55 if (rc != pcmk_rc_ok) {
56 crm_err("Could not prepare to read compressed %s: %s "
57 QB_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
58 goto done;
59 }
60
61 do {
62 int read_len = 0;
63
64 buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
65 read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
66
67 if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
68 crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
69 length += read_len;
70 }
71 } while (rc == BZ_OK);
72
73 rc = pcmk__bzlib2rc(rc);
74 if (rc != pcmk_rc_ok) {
75 rc = pcmk__bzlib2rc(rc);
76 crm_err("Could not read compressed %s: %s " QB_XS " rc=%d",
77 filename, pcmk_rc_str(rc), rc);
78 free(buffer);
79 buffer = NULL;
80 } else {
81 buffer[length] = '\0';
82 }
83
84done:
85 BZ2_bzReadClose(&rc, bz_file);
86 fclose(input);
87 return buffer;
88}
89
100xmlNode *
101pcmk__xml_read(const char *filename)
102{
103 bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
104 xmlNode *xml = NULL;
105 xmlDoc *output = NULL;
106 xmlParserCtxt *ctxt = NULL;
107 const xmlError *last_error = NULL;
108
109 // Create a parser context
110 ctxt = xmlNewParserCtxt();
111 CRM_CHECK(ctxt != NULL, return NULL);
112
113 xmlCtxtResetLastError(ctxt);
114 xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
115
116 if (use_stdin) {
117 output = xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL,
118 XML_PARSE_NOBLANKS);
119
120 } else if (pcmk__ends_with_ext(filename, ".bz2")) {
121 char *input = decompress_file(filename);
122
123 if (input != NULL) {
124 output = xmlCtxtReadDoc(ctxt, (const xmlChar *) input, NULL, NULL,
125 XML_PARSE_NOBLANKS);
126 free(input);
127 }
128
129 } else {
130 output = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_NOBLANKS);
131 }
132
133 if (output != NULL) {
134 pcmk__xml_new_private_data((xmlNode *) output);
135 xml = xmlDocGetRootElement(output);
136 if (xml != NULL) {
137 /* @TODO Should we really be stripping out text? This seems like an
138 * overly broad way to get rid of whitespace, if that's the goal.
139 * Text nodes may be invalid in most or all Pacemaker inputs, but
140 * stripping them in a generic "parse XML from file" function may
141 * not be the best way to ignore them.
142 */
144 }
145 }
146
147 last_error = xmlCtxtGetLastError(ctxt);
148 if ((last_error != NULL) && (xml != NULL)) {
149 crm_log_xml_debug(xml, "partial");
150 pcmk__xml_free(xml);
151 xml = NULL;
152 }
153
154 xmlFreeParserCtxt(ctxt);
155 return xml;
156}
157
166xmlNode *
168{
169 xmlNode *xml = NULL;
170 xmlDoc *output = NULL;
171 xmlParserCtxt *ctxt = NULL;
172 const xmlError *last_error = NULL;
173
174 if (input == NULL) {
175 return NULL;
176 }
177
178 ctxt = xmlNewParserCtxt();
179 if (ctxt == NULL) {
180 return NULL;
181 }
182
183 xmlCtxtResetLastError(ctxt);
184 xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
185
186 output = xmlCtxtReadDoc(ctxt, (const xmlChar *) input, NULL, NULL,
187 XML_PARSE_NOBLANKS);
188 if (output != NULL) {
189 pcmk__xml_new_private_data((xmlNode *) output);
190 xml = xmlDocGetRootElement(output);
191 }
192
193 last_error = xmlCtxtGetLastError(ctxt);
194 if ((last_error != NULL) && (xml != NULL)) {
195 crm_log_xml_debug(xml, "partial");
196 pcmk__xml_free(xml);
197 xml = NULL;
198 }
199
200 xmlFreeParserCtxt(ctxt);
201 return xml;
202}
203
213static void
214dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
215 int depth)
216{
217 bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
218 bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
219 int spaces = pretty? (2 * depth) : 0;
220
221 for (int lpc = 0; lpc < spaces; lpc++) {
222 g_string_append_c(buffer, ' ');
223 }
224
225 pcmk__g_strcat(buffer, "<", data->name, NULL);
226
227 for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
228 attr = attr->next) {
229
230 if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
231 pcmk__dump_xml_attr(attr, buffer);
232 }
233 }
234
235 if (data->children == NULL) {
236 g_string_append(buffer, "/>");
237
238 } else {
239 g_string_append_c(buffer, '>');
240 }
241
242 if (pretty) {
243 g_string_append_c(buffer, '\n');
244 }
245
246 if (data->children) {
247 for (const xmlNode *child = data->children; child != NULL;
248 child = child->next) {
249 pcmk__xml_string(child, options, buffer, depth + 1);
250 }
251
252 for (int lpc = 0; lpc < spaces; lpc++) {
253 g_string_append_c(buffer, ' ');
254 }
255
256 pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
257
258 if (pretty) {
259 g_string_append_c(buffer, '\n');
260 }
261 }
262}
263
273static void
274dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
275 int depth)
276{
277 bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
278 int spaces = pretty? (2 * depth) : 0;
279 const char *content = (const char *) data->content;
280 gchar *content_esc = NULL;
281
283 content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
284 content = content_esc;
285 }
286
287 for (int lpc = 0; lpc < spaces; lpc++) {
288 g_string_append_c(buffer, ' ');
289 }
290
291 g_string_append(buffer, content);
292
293 if (pretty) {
294 g_string_append_c(buffer, '\n');
295 }
296 g_free(content_esc);
297}
298
308static void
309dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
310 int depth)
311{
312 bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
313 int spaces = pretty? (2 * depth) : 0;
314
315 for (int lpc = 0; lpc < spaces; lpc++) {
316 g_string_append_c(buffer, ' ');
317 }
318
319 pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
320 NULL);
321
322 if (pretty) {
323 g_string_append_c(buffer, '\n');
324 }
325}
326
336static void
337dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
338 int depth)
339{
340 bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
341 int spaces = pretty? (2 * depth) : 0;
342
343 for (int lpc = 0; lpc < spaces; lpc++) {
344 g_string_append_c(buffer, ' ');
345 }
346
347 pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
348
349 if (pretty) {
350 g_string_append_c(buffer, '\n');
351 }
352}
353
369void
370pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
371 int depth)
372{
373 if (data == NULL) {
374 crm_trace("Nothing to dump");
375 return;
376 }
377
378 pcmk__assert(buffer != NULL);
379 CRM_CHECK(depth >= 0, depth = 0);
380
381 switch(data->type) {
382 case XML_ELEMENT_NODE:
383 /* Handle below */
384 dump_xml_element(data, options, buffer, depth);
385 break;
386 case XML_TEXT_NODE:
387 if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
388 dump_xml_text(data, options, buffer, depth);
389 }
390 break;
391 case XML_COMMENT_NODE:
392 dump_xml_comment(data, options, buffer, depth);
393 break;
394 case XML_CDATA_SECTION_NODE:
395 dump_xml_cdata(data, options, buffer, depth);
396 break;
397 default:
398 crm_warn("Cannot convert XML %s node to text " QB_XS " type=%d",
400 break;
401 }
402}
403
415static int
416write_compressed_stream(char *text, const char *filename, FILE *stream,
417 unsigned int *bytes_out)
418{
419 unsigned int bytes_in = 0;
420 int rc = pcmk_rc_ok;
421
422 // (5, 0, 0): (intermediate block size, silent, default workFactor)
423 BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
424
425 rc = pcmk__bzlib2rc(rc);
426 if (rc != pcmk_rc_ok) {
427 crm_warn("Not compressing %s: could not prepare file stream: %s "
428 QB_XS " rc=%d",
429 filename, pcmk_rc_str(rc), rc);
430 goto done;
431 }
432
433 BZ2_bzWrite(&rc, bz_file, text, strlen(text));
434 rc = pcmk__bzlib2rc(rc);
435 if (rc != pcmk_rc_ok) {
436 crm_warn("Not compressing %s: could not compress data: %s "
437 QB_XS " rc=%d errno=%d",
438 filename, pcmk_rc_str(rc), rc, errno);
439 goto done;
440 }
441
442 BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
443 bz_file = NULL;
444 rc = pcmk__bzlib2rc(rc);
445 if (rc != pcmk_rc_ok) {
446 crm_warn("Not compressing %s: could not write compressed data: %s "
447 QB_XS " rc=%d errno=%d",
448 filename, pcmk_rc_str(rc), rc, errno);
449 goto done;
450 }
451
452 crm_trace("Compressed XML for %s from %u bytes to %u",
453 filename, bytes_in, *bytes_out);
454
455done:
456 if (bz_file != NULL) {
457 BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
458 }
459 return rc;
460}
461
474static int
475write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
476 bool compress)
477{
478 GString *buffer = g_string_sized_new(1024);
479 unsigned int bytes_out = 0;
480 int rc = pcmk_rc_ok;
481
482 pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
483 CRM_CHECK(!pcmk__str_empty(buffer->str),
484 crm_log_xml_info(xml, "dump-failed");
485 rc = pcmk_rc_error;
486 goto done);
487
488 crm_log_xml_trace(xml, "writing");
489
490 if (compress
491 && (write_compressed_stream(buffer->str, filename, stream,
492 &bytes_out) == pcmk_rc_ok)) {
493 goto done;
494 }
495
496 rc = fprintf(stream, "%s", buffer->str);
497 if (rc < 0) {
498 rc = EIO;
499 crm_perror(LOG_ERR, "writing %s", filename);
500 goto done;
501 }
502 bytes_out = (unsigned int) rc;
503 rc = pcmk_rc_ok;
504
505done:
506 if (fflush(stream) != 0) {
507 rc = errno;
508 crm_perror(LOG_ERR, "flushing %s", filename);
509 }
510
511 // Don't report error if the file does not support synchronization
512 if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
513 rc = errno;
514 crm_perror(LOG_ERR, "synchronizing %s", filename);
515 }
516
517 fclose(stream);
518 crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
519
520 g_string_free(buffer, TRUE);
521 return rc;
522}
523
534int
535pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
536{
537 FILE *stream = NULL;
538
539 CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
540 stream = fdopen(fd, "w");
541 if (stream == NULL) {
542 return errno;
543 }
544
545 return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
546 false);
547}
548
559int
560pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
561{
562 FILE *stream = NULL;
563
564 CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
565 stream = fopen(filename, "w");
566 if (stream == NULL) {
567 return errno;
568 }
569
570 return write_xml_stream(xml, filename, stream, compress);
571}
572
582int
583pcmk__xml2fd(int fd, xmlNode *cur)
584{
585 bool success;
586
587 xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
588 pcmk__mem_assert(fd_out);
589 xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
590
591 success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
592
593 success = xmlOutputBufferClose(fd_out) != -1 && success;
594
595 if (!success) {
596 return EIO;
597 }
598
599 fsync(fd);
600 return pcmk_rc_ok;
601}
602
603void
604save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
605{
606 char *f = NULL;
607
608 if (filename == NULL) {
609 char *uuid = crm_generate_uuid();
610
611 f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
612 filename = f;
613 free(uuid);
614 }
615
616 crm_info("Saving %s to %s", desc, filename);
617 pcmk__xml_write_file(xml, filename, false);
618 free(f);
619}
char * crm_generate_uuid(void)
Definition utils.c:339
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition util.h:80
char data[0]
Definition cpg.c:10
A dumping ground.
G_GNUC_INTERNAL const char * pcmk__xml_element_type_text(xmlElementType type)
Definition xml.c:41
#define PCMK__BUFFER_SIZE
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
Definition xml.c:387
G_GNUC_INTERNAL void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer)
Definition xml_attr.c:108
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition digest.c:232
G_GNUC_INTERNAL void pcmk__log_xmllib_err(void *ctx, const char *fmt,...) G_GNUC_PRINTF(2
const char * pcmk__get_tmpdir(void)
Definition io.c:548
#define crm_log_xml_info(xml, text)
Definition logging.h:376
#define crm_info(fmt, args...)
Definition logging.h:365
#define crm_warn(fmt, args...)
Definition logging.h:360
#define crm_log_xml_debug(xml, text)
Definition logging.h:377
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:299
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define crm_log_xml_trace(xml, text)
Definition logging.h:378
#define crm_trace(fmt, args...)
Definition logging.h:370
xmlNode * input
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition results.c:617
@ pcmk_rc_ok
Definition results.h:159
@ pcmk_rc_error
Definition results.h:154
#define pcmk__assert(expr)
#define pcmk__mem_assert(ptr)
int pcmk__bzlib2rc(int bz2)
Map a bz2 return code to the most similar Pacemaker return code.
Definition results.c:1028
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition strings.c:637
@ pcmk__str_null_matches
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1299
Wrappers for and extensions to libxml2.
@ pcmk__xml_fmt_pretty
Include indentation and newlines.
@ pcmk__xml_fmt_filtered
Exclude certain XML attributes (for calculating digests)
@ pcmk__xml_fmt_text
Include XML text nodes.
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
Definition xml.c:909
void pcmk__strip_xml_text(xmlNode *xml)
Definition xml.c:870
@ pcmk__xml_escape_text
char * pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
Definition xml.c:991
void pcmk__xml_free(xmlNode *xml)
Definition xml.c:816
int pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
Definition xml_io.c:535
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition xml_io.c:604
void pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, int depth)
Definition xml_io.c:370
xmlNode * pcmk__xml_read(const char *filename)
Definition xml_io.c:101
int pcmk__xml2fd(int fd, xmlNode *cur)
Definition xml_io.c:583
int pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
Definition xml_io.c:560
xmlNode * pcmk__xml_parse(const char *input)
Definition xml_io.c:167
Wrappers for and extensions to XML input/output functions.