Line data Source code
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 :
27 : /* @COMPAT XML_PARSE_RECOVER allows some XML errors to be silently worked around
28 : * by libxml2, which is potentially ambiguous and dangerous. We should drop it
29 : * when we can break backward compatibility with configurations that might be
30 : * relying on it (i.e. pacemaker 3.0.0).
31 : */
32 : #define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER (XML_PARSE_NOBLANKS)
33 : #define PCMK__XML_PARSE_OPTS_WITH_RECOVER (XML_PARSE_NOBLANKS \
34 : |XML_PARSE_RECOVER)
35 :
36 : /*!
37 : * \internal
38 : * \brief Read from \c stdin until EOF or error
39 : *
40 : * \return Newly allocated string containing the bytes read from \c stdin, or
41 : * \c NULL on error
42 : *
43 : * \note The caller is responsible for freeing the return value using \c free().
44 : */
45 : static char *
46 0 : read_stdin(void)
47 : {
48 0 : char *buf = NULL;
49 0 : size_t length = 0;
50 :
51 : do {
52 0 : buf = pcmk__realloc(buf, length + PCMK__BUFFER_SIZE + 1);
53 0 : length += fread(buf + length, 1, PCMK__BUFFER_SIZE, stdin);
54 0 : } while ((feof(stdin) == 0) && (ferror(stdin) == 0));
55 :
56 0 : if (ferror(stdin) != 0) {
57 0 : crm_err("Error reading input from stdin");
58 0 : free(buf);
59 0 : buf = NULL;
60 : } else {
61 0 : buf[length] = '\0';
62 : }
63 0 : clearerr(stdin);
64 0 : return buf;
65 : }
66 :
67 : /*!
68 : * \internal
69 : * \brief Decompress a <tt>bzip2</tt>-compressed file into a string buffer
70 : *
71 : * \param[in] filename Name of file to decompress
72 : *
73 : * \return Newly allocated string with the decompressed contents of \p filename,
74 : * or \c NULL on error.
75 : *
76 : * \note The caller is responsible for freeing the return value using \c free().
77 : */
78 : static char *
79 0 : decompress_file(const char *filename)
80 : {
81 0 : char *buffer = NULL;
82 0 : int rc = pcmk_rc_ok;
83 0 : size_t length = 0;
84 0 : BZFILE *bz_file = NULL;
85 0 : FILE *input = fopen(filename, "r");
86 :
87 0 : if (input == NULL) {
88 0 : crm_perror(LOG_ERR, "Could not open %s for reading", filename);
89 0 : return NULL;
90 : }
91 :
92 0 : bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
93 0 : rc = pcmk__bzlib2rc(rc);
94 0 : if (rc != pcmk_rc_ok) {
95 0 : crm_err("Could not prepare to read compressed %s: %s "
96 : CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
97 0 : goto done;
98 : }
99 :
100 : // cppcheck seems not to understand the abort-logic in pcmk__realloc
101 : // cppcheck-suppress memleak
102 : do {
103 0 : int read_len = 0;
104 :
105 0 : buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
106 0 : read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
107 :
108 0 : if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
109 0 : crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
110 0 : length += read_len;
111 : }
112 0 : } while (rc == BZ_OK);
113 :
114 0 : rc = pcmk__bzlib2rc(rc);
115 0 : if (rc != pcmk_rc_ok) {
116 0 : rc = pcmk__bzlib2rc(rc);
117 0 : crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d",
118 : filename, pcmk_rc_str(rc), rc);
119 0 : free(buffer);
120 0 : buffer = NULL;
121 : } else {
122 0 : buffer[length] = '\0';
123 : }
124 :
125 0 : done:
126 0 : BZ2_bzReadClose(&rc, bz_file);
127 0 : fclose(input);
128 0 : return buffer;
129 : }
130 :
131 : // @COMPAT Remove macro at 3.0.0 when we drop XML_PARSE_RECOVER
132 : /*!
133 : * \internal
134 : * \brief Try to parse XML first without and then with recovery enabled
135 : *
136 : * \param[out] result Where to store the resulting XML doc (<tt>xmlDoc **</tt>)
137 : * \param[in] fn XML parser function
138 : * \param[in] ... All arguments for \p fn except the final one (an
139 : * \c xmlParserOption group)
140 : */
141 : #define parse_xml_recover(result, fn, ...) do { \
142 : *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); \
143 : if (*result == NULL) { \
144 : *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITH_RECOVER); \
145 : \
146 : if (*result != NULL) { \
147 : crm_warn("Successfully recovered from XML errors " \
148 : "(note: a future release will treat this as a " \
149 : "fatal failure)"); \
150 : } \
151 : } \
152 : } while (0);
153 :
154 : /*!
155 : * \internal
156 : * \brief Parse XML from a file
157 : *
158 : * \param[in] filename Name of file containing XML (\c NULL or \c "-" for
159 : * \c stdin); if \p filename ends in \c ".bz2", the file
160 : * will be decompressed using \c bzip2
161 : *
162 : * \return XML tree parsed from the given file; may be \c NULL or only partial
163 : * on error
164 : */
165 : xmlNode *
166 0 : pcmk__xml_read(const char *filename)
167 : {
168 0 : bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
169 0 : xmlNode *xml = NULL;
170 0 : xmlDoc *output = NULL;
171 0 : xmlParserCtxt *ctxt = NULL;
172 0 : const xmlError *last_error = NULL;
173 :
174 : // Create a parser context
175 0 : ctxt = xmlNewParserCtxt();
176 0 : CRM_CHECK(ctxt != NULL, return NULL);
177 :
178 0 : xmlCtxtResetLastError(ctxt);
179 0 : xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
180 :
181 0 : if (use_stdin) {
182 : /* @COMPAT After dropping XML_PARSE_RECOVER, we can avoid capturing
183 : * stdin into a buffer and instead call
184 : * xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL, XML_PARSE_NOBLANKS);
185 : *
186 : * For now we have to save the input so that we can use it twice.
187 : */
188 0 : char *input = read_stdin();
189 :
190 0 : if (input != NULL) {
191 0 : parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input,
192 : NULL, NULL);
193 0 : free(input);
194 : }
195 :
196 0 : } else if (pcmk__ends_with_ext(filename, ".bz2")) {
197 0 : char *input = decompress_file(filename);
198 :
199 0 : if (input != NULL) {
200 0 : parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input,
201 : NULL, NULL);
202 0 : free(input);
203 : }
204 :
205 : } else {
206 0 : parse_xml_recover(&output, xmlCtxtReadFile, ctxt, filename, NULL);
207 : }
208 :
209 0 : if (output != NULL) {
210 0 : xml = xmlDocGetRootElement(output);
211 0 : if (xml != NULL) {
212 : /* @TODO Should we really be stripping out text? This seems like an
213 : * overly broad way to get rid of whitespace, if that's the goal.
214 : * Text nodes may be invalid in most or all Pacemaker inputs, but
215 : * stripping them in a generic "parse XML from file" function may
216 : * not be the best way to ignore them.
217 : */
218 0 : pcmk__strip_xml_text(xml);
219 : }
220 : }
221 :
222 : // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL on error
223 0 : last_error = xmlCtxtGetLastError(ctxt);
224 0 : if (last_error != NULL) {
225 0 : if (xml != NULL) {
226 0 : crm_log_xml_info(xml, "Partial");
227 : }
228 : }
229 :
230 0 : xmlFreeParserCtxt(ctxt);
231 0 : return xml;
232 : }
233 :
234 : /*!
235 : * \internal
236 : * \brief Parse XML from a string
237 : *
238 : * \param[in] input String to parse
239 : *
240 : * \return XML tree parsed from the given string; may be \c NULL or only partial
241 : * on error
242 : */
243 : xmlNode *
244 0 : pcmk__xml_parse(const char *input)
245 : {
246 0 : xmlNode *xml = NULL;
247 0 : xmlDoc *output = NULL;
248 0 : xmlParserCtxt *ctxt = NULL;
249 0 : const xmlError *last_error = NULL;
250 :
251 0 : if (input == NULL) {
252 0 : return NULL;
253 : }
254 :
255 0 : ctxt = xmlNewParserCtxt();
256 0 : if (ctxt == NULL) {
257 0 : return NULL;
258 : }
259 :
260 0 : xmlCtxtResetLastError(ctxt);
261 0 : xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
262 :
263 0 : parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input, NULL,
264 : NULL);
265 :
266 0 : if (output != NULL) {
267 0 : xml = xmlDocGetRootElement(output);
268 : }
269 :
270 : // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL; update doxygen
271 0 : last_error = xmlCtxtGetLastError(ctxt);
272 0 : if (last_error != NULL) {
273 0 : if (xml != NULL) {
274 0 : crm_log_xml_info(xml, "Partial");
275 : }
276 : }
277 :
278 0 : xmlFreeParserCtxt(ctxt);
279 0 : return xml;
280 : }
281 :
282 : /*!
283 : * \internal
284 : * \brief Append a string representation of an XML element to a buffer
285 : *
286 : * \param[in] data XML whose representation to append
287 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
288 : * \param[in,out] buffer Where to append the content (must not be \p NULL)
289 : * \param[in] depth Current indentation level
290 : */
291 : static void
292 0 : dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
293 : int depth)
294 : {
295 0 : bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
296 0 : bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
297 0 : int spaces = pretty? (2 * depth) : 0;
298 :
299 0 : for (int lpc = 0; lpc < spaces; lpc++) {
300 : g_string_append_c(buffer, ' ');
301 : }
302 :
303 0 : pcmk__g_strcat(buffer, "<", data->name, NULL);
304 :
305 0 : for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
306 0 : attr = attr->next) {
307 :
308 0 : if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
309 0 : pcmk__dump_xml_attr(attr, buffer);
310 : }
311 : }
312 :
313 0 : if (data->children == NULL) {
314 0 : g_string_append(buffer, "/>");
315 :
316 : } else {
317 : g_string_append_c(buffer, '>');
318 : }
319 :
320 0 : if (pretty) {
321 : g_string_append_c(buffer, '\n');
322 : }
323 :
324 0 : if (data->children) {
325 0 : for (const xmlNode *child = data->children; child != NULL;
326 0 : child = child->next) {
327 0 : pcmk__xml_string(child, options, buffer, depth + 1);
328 : }
329 :
330 0 : for (int lpc = 0; lpc < spaces; lpc++) {
331 : g_string_append_c(buffer, ' ');
332 : }
333 :
334 0 : pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
335 :
336 0 : if (pretty) {
337 : g_string_append_c(buffer, '\n');
338 : }
339 : }
340 0 : }
341 :
342 : /*!
343 : * \internal
344 : * \brief Append XML text content to a buffer
345 : *
346 : * \param[in] data XML whose content to append
347 : * \param[in] options Group of \p xml_log_options flags
348 : * \param[in,out] buffer Where to append the content (must not be \p NULL)
349 : * \param[in] depth Current indentation level
350 : */
351 : static void
352 0 : dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
353 : int depth)
354 : {
355 0 : bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
356 0 : int spaces = pretty? (2 * depth) : 0;
357 0 : const char *content = (const char *) data->content;
358 0 : gchar *content_esc = NULL;
359 :
360 0 : if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) {
361 0 : content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
362 0 : content = content_esc;
363 : }
364 :
365 0 : for (int lpc = 0; lpc < spaces; lpc++) {
366 : g_string_append_c(buffer, ' ');
367 : }
368 :
369 : g_string_append(buffer, content);
370 :
371 0 : if (pretty) {
372 : g_string_append_c(buffer, '\n');
373 : }
374 0 : g_free(content_esc);
375 0 : }
376 :
377 : /*!
378 : * \internal
379 : * \brief Append XML CDATA content to a buffer
380 : *
381 : * \param[in] data XML whose content to append
382 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
383 : * \param[in,out] buffer Where to append the content (must not be \p NULL)
384 : * \param[in] depth Current indentation level
385 : */
386 : static void
387 0 : dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
388 : int depth)
389 : {
390 0 : bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
391 0 : int spaces = pretty? (2 * depth) : 0;
392 :
393 0 : for (int lpc = 0; lpc < spaces; lpc++) {
394 : g_string_append_c(buffer, ' ');
395 : }
396 :
397 0 : pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
398 : NULL);
399 :
400 0 : if (pretty) {
401 : g_string_append_c(buffer, '\n');
402 : }
403 0 : }
404 :
405 : /*!
406 : * \internal
407 : * \brief Append an XML comment to a buffer
408 : *
409 : * \param[in] data XML whose content to append
410 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
411 : * \param[in,out] buffer Where to append the content (must not be \p NULL)
412 : * \param[in] depth Current indentation level
413 : */
414 : static void
415 0 : dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
416 : int depth)
417 : {
418 0 : bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
419 0 : int spaces = pretty? (2 * depth) : 0;
420 :
421 0 : for (int lpc = 0; lpc < spaces; lpc++) {
422 : g_string_append_c(buffer, ' ');
423 : }
424 :
425 0 : pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
426 :
427 0 : if (pretty) {
428 : g_string_append_c(buffer, '\n');
429 : }
430 0 : }
431 :
432 : /*!
433 : * \internal
434 : * \brief Get a string representation of an XML element type
435 : *
436 : * \param[in] type XML element type
437 : *
438 : * \return String representation of \p type
439 : */
440 : static const char *
441 0 : xml_element_type_text(xmlElementType type)
442 : {
443 : static const char *const element_type_names[] = {
444 : [XML_ELEMENT_NODE] = "element",
445 : [XML_ATTRIBUTE_NODE] = "attribute",
446 : [XML_TEXT_NODE] = "text",
447 : [XML_CDATA_SECTION_NODE] = "CDATA section",
448 : [XML_ENTITY_REF_NODE] = "entity reference",
449 : [XML_ENTITY_NODE] = "entity",
450 : [XML_PI_NODE] = "PI",
451 : [XML_COMMENT_NODE] = "comment",
452 : [XML_DOCUMENT_NODE] = "document",
453 : [XML_DOCUMENT_TYPE_NODE] = "document type",
454 : [XML_DOCUMENT_FRAG_NODE] = "document fragment",
455 : [XML_NOTATION_NODE] = "notation",
456 : [XML_HTML_DOCUMENT_NODE] = "HTML document",
457 : [XML_DTD_NODE] = "DTD",
458 : [XML_ELEMENT_DECL] = "element declaration",
459 : [XML_ATTRIBUTE_DECL] = "attribute declaration",
460 : [XML_ENTITY_DECL] = "entity declaration",
461 : [XML_NAMESPACE_DECL] = "namespace declaration",
462 : [XML_XINCLUDE_START] = "XInclude start",
463 : [XML_XINCLUDE_END] = "XInclude end",
464 : };
465 :
466 0 : if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
467 0 : return "unrecognized type";
468 : }
469 0 : return element_type_names[type];
470 : }
471 :
472 : /*!
473 : * \internal
474 : * \brief Create a string representation of an XML object
475 : *
476 : * libxml2's \c xmlNodeDumpOutput() doesn't allow filtering, doesn't escape
477 : * special characters thoroughly, and doesn't allow a const argument.
478 : *
479 : * \param[in] data XML to convert
480 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
481 : * \param[in,out] buffer Where to store the text (must not be \p NULL)
482 : * \param[in] depth Current indentation level
483 : *
484 : * \todo Create a wrapper that doesn't require \p depth. Only used with
485 : * recursive calls currently.
486 : */
487 : void
488 0 : pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
489 : int depth)
490 : {
491 0 : if (data == NULL) {
492 0 : crm_trace("Nothing to dump");
493 0 : return;
494 : }
495 :
496 0 : CRM_ASSERT(buffer != NULL);
497 0 : CRM_CHECK(depth >= 0, depth = 0);
498 :
499 0 : switch(data->type) {
500 0 : case XML_ELEMENT_NODE:
501 : /* Handle below */
502 0 : dump_xml_element(data, options, buffer, depth);
503 0 : break;
504 0 : case XML_TEXT_NODE:
505 0 : if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
506 0 : dump_xml_text(data, options, buffer, depth);
507 : }
508 0 : break;
509 0 : case XML_COMMENT_NODE:
510 0 : dump_xml_comment(data, options, buffer, depth);
511 0 : break;
512 0 : case XML_CDATA_SECTION_NODE:
513 0 : dump_xml_cdata(data, options, buffer, depth);
514 0 : break;
515 0 : default:
516 0 : crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d",
517 : xml_element_type_text(data->type), data->type);
518 0 : break;
519 : }
520 : }
521 :
522 : /*!
523 : * \internal
524 : * \brief Write a string to a file stream, compressed using \c bzip2
525 : *
526 : * \param[in] text String to write
527 : * \param[in] filename Name of file being written (for logging only)
528 : * \param[in,out] stream Open file stream to write to
529 : * \param[out] bytes_out Number of bytes written (valid only on success)
530 : *
531 : * \return Standard Pacemaker return code
532 : */
533 : static int
534 0 : write_compressed_stream(char *text, const char *filename, FILE *stream,
535 : unsigned int *bytes_out)
536 : {
537 0 : unsigned int bytes_in = 0;
538 0 : int rc = pcmk_rc_ok;
539 :
540 : // (5, 0, 0): (intermediate block size, silent, default workFactor)
541 0 : BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
542 :
543 0 : rc = pcmk__bzlib2rc(rc);
544 0 : if (rc != pcmk_rc_ok) {
545 0 : crm_warn("Not compressing %s: could not prepare file stream: %s "
546 : CRM_XS " rc=%d",
547 : filename, pcmk_rc_str(rc), rc);
548 0 : goto done;
549 : }
550 :
551 0 : BZ2_bzWrite(&rc, bz_file, text, strlen(text));
552 0 : rc = pcmk__bzlib2rc(rc);
553 0 : if (rc != pcmk_rc_ok) {
554 0 : crm_warn("Not compressing %s: could not compress data: %s "
555 : CRM_XS " rc=%d errno=%d",
556 : filename, pcmk_rc_str(rc), rc, errno);
557 0 : goto done;
558 : }
559 :
560 0 : BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
561 0 : bz_file = NULL;
562 0 : rc = pcmk__bzlib2rc(rc);
563 0 : if (rc != pcmk_rc_ok) {
564 0 : crm_warn("Not compressing %s: could not write compressed data: %s "
565 : CRM_XS " rc=%d errno=%d",
566 : filename, pcmk_rc_str(rc), rc, errno);
567 0 : goto done;
568 : }
569 :
570 0 : crm_trace("Compressed XML for %s from %u bytes to %u",
571 : filename, bytes_in, *bytes_out);
572 :
573 0 : done:
574 0 : if (bz_file != NULL) {
575 0 : BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
576 : }
577 0 : return rc;
578 : }
579 :
580 : /*!
581 : * \internal
582 : * \brief Write XML to a file stream
583 : *
584 : * \param[in] xml XML to write
585 : * \param[in] filename Name of file being written (for logging only)
586 : * \param[in,out] stream Open file stream corresponding to filename (closed
587 : * when this function returns)
588 : * \param[in] compress Whether to compress XML before writing
589 : * \param[out] nbytes Number of bytes written
590 : *
591 : * \return Standard Pacemaker return code
592 : */
593 : static int
594 0 : write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
595 : bool compress, unsigned int *nbytes)
596 : {
597 : // @COMPAT Drop nbytes as arg when we drop write_xml_fd()/write_xml_file()
598 0 : GString *buffer = g_string_sized_new(1024);
599 0 : unsigned int bytes_out = 0;
600 0 : int rc = pcmk_rc_ok;
601 :
602 0 : pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
603 0 : CRM_CHECK(!pcmk__str_empty(buffer->str),
604 : crm_log_xml_info(xml, "dump-failed");
605 : rc = pcmk_rc_error;
606 : goto done);
607 :
608 0 : crm_log_xml_trace(xml, "writing");
609 :
610 0 : if (compress
611 0 : && (write_compressed_stream(buffer->str, filename, stream,
612 : &bytes_out) == pcmk_rc_ok)) {
613 0 : goto done;
614 : }
615 :
616 0 : rc = fprintf(stream, "%s", buffer->str);
617 0 : if (rc < 0) {
618 0 : rc = EIO;
619 0 : crm_perror(LOG_ERR, "writing %s", filename);
620 0 : goto done;
621 : }
622 0 : bytes_out = (unsigned int) rc;
623 0 : rc = pcmk_rc_ok;
624 :
625 0 : done:
626 0 : if (fflush(stream) != 0) {
627 0 : rc = errno;
628 0 : crm_perror(LOG_ERR, "flushing %s", filename);
629 : }
630 :
631 : // Don't report error if the file does not support synchronization
632 0 : if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
633 0 : rc = errno;
634 0 : crm_perror(LOG_ERR, "synchronizing %s", filename);
635 : }
636 :
637 0 : fclose(stream);
638 0 : crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
639 :
640 0 : if (nbytes != NULL) {
641 0 : *nbytes = bytes_out;
642 : }
643 0 : g_string_free(buffer, TRUE);
644 0 : return rc;
645 : }
646 :
647 : /*!
648 : * \internal
649 : * \brief Write XML to a file descriptor
650 : *
651 : * \param[in] xml XML to write
652 : * \param[in] filename Name of file being written (for logging only)
653 : * \param[in] fd Open file descriptor corresponding to \p filename
654 : * \param[in] compress If \c true, compress XML before writing
655 : * \param[out] nbytes Number of bytes written (can be \c NULL)
656 : *
657 : * \return Standard Pacemaker return code
658 : */
659 : int
660 0 : pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd,
661 : bool compress, unsigned int *nbytes)
662 : {
663 : // @COMPAT Drop compress and nbytes arguments when we drop write_xml_fd()
664 0 : FILE *stream = NULL;
665 :
666 0 : CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
667 0 : stream = fdopen(fd, "w");
668 0 : if (stream == NULL) {
669 0 : return errno;
670 : }
671 :
672 0 : return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
673 : compress, nbytes);
674 : }
675 :
676 : /*!
677 : * \internal
678 : * \brief Write XML to a file
679 : *
680 : * \param[in] xml XML to write
681 : * \param[in] filename Name of file to write
682 : * \param[in] compress If \c true, compress XML before writing
683 : * \param[out] nbytes Number of bytes written (can be \c NULL)
684 : *
685 : * \return Standard Pacemaker return code
686 : */
687 : int
688 0 : pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress,
689 : unsigned int *nbytes)
690 : {
691 : // @COMPAT Drop nbytes argument when we drop write_xml_fd()
692 0 : FILE *stream = NULL;
693 :
694 0 : CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
695 0 : stream = fopen(filename, "w");
696 0 : if (stream == NULL) {
697 0 : return errno;
698 : }
699 :
700 0 : return write_xml_stream(xml, filename, stream, compress, nbytes);
701 : }
702 :
703 : /*!
704 : * \internal
705 : * \brief Serialize XML (using libxml) into provided descriptor
706 : *
707 : * \param[in] fd File descriptor to (piece-wise) write to
708 : * \param[in] cur XML subtree to proceed
709 : *
710 : * \return a standard Pacemaker return code
711 : */
712 : int
713 0 : pcmk__xml2fd(int fd, xmlNode *cur)
714 : {
715 : bool success;
716 :
717 0 : xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
718 0 : pcmk__mem_assert(fd_out);
719 0 : xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
720 :
721 0 : success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
722 :
723 0 : success = xmlOutputBufferClose(fd_out) != -1 && success;
724 :
725 0 : if (!success) {
726 0 : return EIO;
727 : }
728 :
729 0 : fsync(fd);
730 0 : return pcmk_rc_ok;
731 : }
732 :
733 : void
734 0 : save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
735 : {
736 0 : char *f = NULL;
737 :
738 0 : if (filename == NULL) {
739 0 : char *uuid = crm_generate_uuid();
740 :
741 0 : f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
742 0 : filename = f;
743 0 : free(uuid);
744 : }
745 :
746 0 : crm_info("Saving %s to %s", desc, filename);
747 0 : pcmk__xml_write_file(xml, filename, false, NULL);
748 0 : free(f);
749 0 : }
750 :
751 :
752 : // Deprecated functions kept only for backward API compatibility
753 : // LCOV_EXCL_START
754 :
755 : #include <crm/common/xml_io_compat.h>
756 :
757 : xmlNode *
758 : filename2xml(const char *filename)
759 : {
760 : return pcmk__xml_read(filename);
761 : }
762 :
763 : xmlNode *
764 : stdin2xml(void)
765 : {
766 : return pcmk__xml_read(NULL);
767 : }
768 :
769 : xmlNode *
770 : string2xml(const char *input)
771 : {
772 : return pcmk__xml_parse(input);
773 : }
774 :
775 : char *
776 : dump_xml_formatted(const xmlNode *xml)
777 : {
778 : char *str = NULL;
779 : GString *buffer = g_string_sized_new(1024);
780 :
781 : pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
782 :
783 : str = pcmk__str_copy(buffer->str);
784 : g_string_free(buffer, TRUE);
785 : return str;
786 : }
787 :
788 : char *
789 : dump_xml_formatted_with_text(const xmlNode *xml)
790 : {
791 : char *str = NULL;
792 : GString *buffer = g_string_sized_new(1024);
793 :
794 : pcmk__xml_string(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buffer, 0);
795 :
796 : str = pcmk__str_copy(buffer->str);
797 : g_string_free(buffer, TRUE);
798 : return str;
799 : }
800 :
801 : char *
802 : dump_xml_unformatted(const xmlNode *xml)
803 : {
804 : char *str = NULL;
805 : GString *buffer = g_string_sized_new(1024);
806 :
807 : pcmk__xml_string(xml, 0, buffer, 0);
808 :
809 : str = pcmk__str_copy(buffer->str);
810 : g_string_free(buffer, TRUE);
811 : return str;
812 : }
813 :
814 : int
815 : write_xml_fd(const xmlNode *xml, const char *filename, int fd,
816 : gboolean compress)
817 : {
818 : unsigned int nbytes = 0;
819 : int rc = pcmk__xml_write_fd(xml, filename, fd, compress, &nbytes);
820 :
821 : if (rc != pcmk_rc_ok) {
822 : return pcmk_rc2legacy(rc);
823 : }
824 : return (int) nbytes;
825 : }
826 :
827 : int
828 : write_xml_file(const xmlNode *xml, const char *filename, gboolean compress)
829 : {
830 : unsigned int nbytes = 0;
831 : int rc = pcmk__xml_write_file(xml, filename, compress, &nbytes);
832 :
833 : if (rc != pcmk_rc_ok) {
834 : return pcmk_rc2legacy(rc);
835 : }
836 : return (int) nbytes;
837 : }
838 :
839 : // LCOV_EXCL_STOP
840 : // End deprecated API
|