Coverage Report

Created: 2024-10-12 00:30

/src/pacemaker/lib/common/xml_io.c
Line
Count
Source (jump to first uncovered line)
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
/*!
28
 * \internal
29
 * \brief Decompress a <tt>bzip2</tt>-compressed file into a string buffer
30
 *
31
 * \param[in] filename  Name of file to decompress
32
 *
33
 * \return Newly allocated string with the decompressed contents of \p filename,
34
 *         or \c NULL on error.
35
 *
36
 * \note The caller is responsible for freeing the return value using \c free().
37
 */
38
static char *
39
decompress_file(const char *filename)
40
0
{
41
0
    char *buffer = NULL;
42
0
    int rc = pcmk_rc_ok;
43
0
    size_t length = 0;
44
0
    BZFILE *bz_file = NULL;
45
0
    FILE *input = fopen(filename, "r");
46
47
0
    if (input == NULL) {
48
0
        crm_perror(LOG_ERR, "Could not open %s for reading", filename);
49
0
        return NULL;
50
0
    }
51
52
0
    bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
53
0
    rc = pcmk__bzlib2rc(rc);
54
0
    if (rc != pcmk_rc_ok) {
55
0
        crm_err("Could not prepare to read compressed %s: %s "
56
0
                QB_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
57
0
        goto done;
58
0
    }
59
60
    // cppcheck seems not to understand the abort-logic in pcmk__realloc
61
    // cppcheck-suppress memleak
62
0
    do {
63
0
        int read_len = 0;
64
65
0
        buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
66
0
        read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
67
68
0
        if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
69
0
            crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
70
0
            length += read_len;
71
0
        }
72
0
    } while (rc == BZ_OK);
73
74
0
    rc = pcmk__bzlib2rc(rc);
75
0
    if (rc != pcmk_rc_ok) {
76
0
        rc = pcmk__bzlib2rc(rc);
77
0
        crm_err("Could not read compressed %s: %s " QB_XS " rc=%d",
78
0
                filename, pcmk_rc_str(rc), rc);
79
0
        free(buffer);
80
0
        buffer = NULL;
81
0
    } else {
82
0
        buffer[length] = '\0';
83
0
    }
84
85
0
done:
86
0
    BZ2_bzReadClose(&rc, bz_file);
87
0
    fclose(input);
88
0
    return buffer;
89
0
}
90
91
/*!
92
 * \internal
93
 * \brief Parse XML from a file
94
 *
95
 * \param[in] filename  Name of file containing XML (\c NULL or \c "-" for
96
 *                      \c stdin); if \p filename ends in \c ".bz2", the file
97
 *                      will be decompressed using \c bzip2
98
 *
99
 * \return XML tree parsed from the given file on success, otherwise \c NULL
100
 */
101
xmlNode *
102
pcmk__xml_read(const char *filename)
103
0
{
104
0
    bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
105
0
    xmlNode *xml = NULL;
106
0
    xmlDoc *output = NULL;
107
0
    xmlParserCtxt *ctxt = NULL;
108
0
    const xmlError *last_error = NULL;
109
110
    // Create a parser context
111
0
    ctxt = xmlNewParserCtxt();
112
0
    CRM_CHECK(ctxt != NULL, return NULL);
113
114
0
    xmlCtxtResetLastError(ctxt);
115
0
    xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
116
117
0
    if (use_stdin) {
118
0
        output = xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL,
119
0
                               XML_PARSE_NOBLANKS);
120
121
0
    } else if (pcmk__ends_with_ext(filename, ".bz2")) {
122
0
        char *input = decompress_file(filename);
123
124
0
        if (input != NULL) {
125
0
            output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
126
0
                                    XML_PARSE_NOBLANKS);
127
0
            free(input);
128
0
        }
129
130
0
    } else {
131
0
        output = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_NOBLANKS);
132
0
    }
133
134
0
    if (output != NULL) {
135
0
        pcmk__xml_new_private_data((xmlNode *) output);
136
0
        xml = xmlDocGetRootElement(output);
137
0
        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
             */
144
0
            pcmk__strip_xml_text(xml);
145
0
        }
146
0
    }
147
148
0
    last_error = xmlCtxtGetLastError(ctxt);
149
0
    if ((last_error != NULL) && (xml != NULL)) {
150
0
        crm_log_xml_debug(xml, "partial");
151
0
        pcmk__xml_free(xml);
152
0
        xml = NULL;
153
0
    }
154
155
0
    xmlFreeParserCtxt(ctxt);
156
0
    return xml;
157
0
}
158
159
/*!
160
 * \internal
161
 * \brief Parse XML from a string
162
 *
163
 * \param[in] input  String to parse
164
 *
165
 * \return XML tree parsed from the given string on success, otherwise \c NULL
166
 */
167
xmlNode *
168
pcmk__xml_parse(const char *input)
169
0
{
170
0
    xmlNode *xml = NULL;
171
0
    xmlDoc *output = NULL;
172
0
    xmlParserCtxt *ctxt = NULL;
173
0
    const xmlError *last_error = NULL;
174
175
0
    if (input == NULL) {
176
0
        return NULL;
177
0
    }
178
179
0
    ctxt = xmlNewParserCtxt();
180
0
    if (ctxt == NULL) {
181
0
        return NULL;
182
0
    }
183
184
0
    xmlCtxtResetLastError(ctxt);
185
0
    xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
186
187
0
    output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
188
0
                            XML_PARSE_NOBLANKS);
189
0
    if (output != NULL) {
190
0
        pcmk__xml_new_private_data((xmlNode *) output);
191
0
        xml = xmlDocGetRootElement(output);
192
0
    }
193
194
0
    last_error = xmlCtxtGetLastError(ctxt);
195
0
    if ((last_error != NULL) && (xml != NULL)) {
196
0
        crm_log_xml_debug(xml, "partial");
197
0
        pcmk__xml_free(xml);
198
0
        xml = NULL;
199
0
    }
200
201
0
    xmlFreeParserCtxt(ctxt);
202
0
    return xml;
203
0
}
204
205
/*!
206
 * \internal
207
 * \brief Append a string representation of an XML element to a buffer
208
 *
209
 * \param[in]     data     XML whose representation to append
210
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
211
 * \param[in,out] buffer   Where to append the content (must not be \p NULL)
212
 * \param[in]     depth    Current indentation level
213
 */
214
static void
215
dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
216
                 int depth)
217
0
{
218
0
    bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
219
0
    bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
220
0
    int spaces = pretty? (2 * depth) : 0;
221
222
0
    for (int lpc = 0; lpc < spaces; lpc++) {
223
0
        g_string_append_c(buffer, ' ');
224
0
    }
225
226
0
    pcmk__g_strcat(buffer, "<", data->name, NULL);
227
228
0
    for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
229
0
         attr = attr->next) {
230
231
0
        if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
232
0
            pcmk__dump_xml_attr(attr, buffer);
233
0
        }
234
0
    }
235
236
0
    if (data->children == NULL) {
237
0
        g_string_append(buffer, "/>");
238
239
0
    } else {
240
0
        g_string_append_c(buffer, '>');
241
0
    }
242
243
0
    if (pretty) {
244
0
        g_string_append_c(buffer, '\n');
245
0
    }
246
247
0
    if (data->children) {
248
0
        for (const xmlNode *child = data->children; child != NULL;
249
0
             child = child->next) {
250
0
            pcmk__xml_string(child, options, buffer, depth + 1);
251
0
        }
252
253
0
        for (int lpc = 0; lpc < spaces; lpc++) {
254
0
            g_string_append_c(buffer, ' ');
255
0
        }
256
257
0
        pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
258
259
0
        if (pretty) {
260
0
            g_string_append_c(buffer, '\n');
261
0
        }
262
0
    }
263
0
}
264
265
/*!
266
 * \internal
267
 * \brief Append XML text content to a buffer
268
 *
269
 * \param[in]     data     XML whose content to append
270
 * \param[in]     options  Group of <tt>enum pcmk__xml_fmt_options</tt>
271
 * \param[in,out] buffer   Where to append the content (must not be \p NULL)
272
 * \param[in]     depth    Current indentation level
273
 */
274
static void
275
dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
276
              int depth)
277
0
{
278
0
    bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
279
0
    int spaces = pretty? (2 * depth) : 0;
280
0
    const char *content = (const char *) data->content;
281
0
    gchar *content_esc = NULL;
282
283
0
    if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) {
284
0
        content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
285
0
        content = content_esc;
286
0
    }
287
288
0
    for (int lpc = 0; lpc < spaces; lpc++) {
289
0
        g_string_append_c(buffer, ' ');
290
0
    }
291
292
0
    g_string_append(buffer, content);
293
294
0
    if (pretty) {
295
0
        g_string_append_c(buffer, '\n');
296
0
    }
297
0
    g_free(content_esc);
298
0
}
299
300
/*!
301
 * \internal
302
 * \brief Append XML CDATA content to a buffer
303
 *
304
 * \param[in]     data     XML whose content to append
305
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
306
 * \param[in,out] buffer   Where to append the content (must not be \p NULL)
307
 * \param[in]     depth    Current indentation level
308
 */
309
static void
310
dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
311
               int depth)
312
0
{
313
0
    bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
314
0
    int spaces = pretty? (2 * depth) : 0;
315
316
0
    for (int lpc = 0; lpc < spaces; lpc++) {
317
0
        g_string_append_c(buffer, ' ');
318
0
    }
319
320
0
    pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
321
0
                   NULL);
322
323
0
    if (pretty) {
324
0
        g_string_append_c(buffer, '\n');
325
0
    }
326
0
}
327
328
/*!
329
 * \internal
330
 * \brief Append an XML comment to a buffer
331
 *
332
 * \param[in]     data     XML whose content to append
333
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
334
 * \param[in,out] buffer   Where to append the content (must not be \p NULL)
335
 * \param[in]     depth    Current indentation level
336
 */
337
static void
338
dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
339
                 int depth)
340
0
{
341
0
    bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
342
0
    int spaces = pretty? (2 * depth) : 0;
343
344
0
    for (int lpc = 0; lpc < spaces; lpc++) {
345
0
        g_string_append_c(buffer, ' ');
346
0
    }
347
348
0
    pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
349
350
0
    if (pretty) {
351
0
        g_string_append_c(buffer, '\n');
352
0
    }
353
0
}
354
355
/*!
356
 * \internal
357
 * \brief Get a string representation of an XML element type
358
 *
359
 * \param[in] type  XML element type
360
 *
361
 * \return String representation of \p type
362
 */
363
static const char *
364
xml_element_type_text(xmlElementType type)
365
0
{
366
0
    static const char *const element_type_names[] = {
367
0
        [XML_ELEMENT_NODE]       = "element",
368
0
        [XML_ATTRIBUTE_NODE]     = "attribute",
369
0
        [XML_TEXT_NODE]          = "text",
370
0
        [XML_CDATA_SECTION_NODE] = "CDATA section",
371
0
        [XML_ENTITY_REF_NODE]    = "entity reference",
372
0
        [XML_ENTITY_NODE]        = "entity",
373
0
        [XML_PI_NODE]            = "PI",
374
0
        [XML_COMMENT_NODE]       = "comment",
375
0
        [XML_DOCUMENT_NODE]      = "document",
376
0
        [XML_DOCUMENT_TYPE_NODE] = "document type",
377
0
        [XML_DOCUMENT_FRAG_NODE] = "document fragment",
378
0
        [XML_NOTATION_NODE]      = "notation",
379
0
        [XML_HTML_DOCUMENT_NODE] = "HTML document",
380
0
        [XML_DTD_NODE]           = "DTD",
381
0
        [XML_ELEMENT_DECL]       = "element declaration",
382
0
        [XML_ATTRIBUTE_DECL]     = "attribute declaration",
383
0
        [XML_ENTITY_DECL]        = "entity declaration",
384
0
        [XML_NAMESPACE_DECL]     = "namespace declaration",
385
0
        [XML_XINCLUDE_START]     = "XInclude start",
386
0
        [XML_XINCLUDE_END]       = "XInclude end",
387
0
    };
388
389
0
    if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
390
0
        return "unrecognized type";
391
0
    }
392
0
    return element_type_names[type];
393
0
}
394
395
/*!
396
 * \internal
397
 * \brief Create a string representation of an XML object
398
 *
399
 * libxml2's \c xmlNodeDumpOutput() doesn't allow filtering, doesn't escape
400
 * special characters thoroughly, and doesn't allow a const argument.
401
 *
402
 * \param[in]     data     XML to convert
403
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
404
 * \param[in,out] buffer   Where to store the text (must not be \p NULL)
405
 * \param[in]     depth    Current indentation level
406
 *
407
 * \todo Create a wrapper that doesn't require \p depth. Only used with
408
 *       recursive calls currently.
409
 */
410
void
411
pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
412
                 int depth)
413
0
{
414
0
    if (data == NULL) {
415
0
        crm_trace("Nothing to dump");
416
0
        return;
417
0
    }
418
419
0
    pcmk__assert(buffer != NULL);
420
0
    CRM_CHECK(depth >= 0, depth = 0);
421
422
0
    switch(data->type) {
423
0
        case XML_ELEMENT_NODE:
424
            /* Handle below */
425
0
            dump_xml_element(data, options, buffer, depth);
426
0
            break;
427
0
        case XML_TEXT_NODE:
428
0
            if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
429
0
                dump_xml_text(data, options, buffer, depth);
430
0
            }
431
0
            break;
432
0
        case XML_COMMENT_NODE:
433
0
            dump_xml_comment(data, options, buffer, depth);
434
0
            break;
435
0
        case XML_CDATA_SECTION_NODE:
436
0
            dump_xml_cdata(data, options, buffer, depth);
437
0
            break;
438
0
        default:
439
0
            crm_warn("Cannot convert XML %s node to text " QB_XS " type=%d",
440
0
                     xml_element_type_text(data->type), data->type);
441
0
            break;
442
0
    }
443
0
}
444
445
/*!
446
 * \internal
447
 * \brief Write a string to a file stream, compressed using \c bzip2
448
 *
449
 * \param[in]     text       String to write
450
 * \param[in]     filename   Name of file being written (for logging only)
451
 * \param[in,out] stream     Open file stream to write to
452
 * \param[out]    bytes_out  Number of bytes written (valid only on success)
453
 *
454
 * \return Standard Pacemaker return code
455
 */
456
static int
457
write_compressed_stream(char *text, const char *filename, FILE *stream,
458
                        unsigned int *bytes_out)
459
0
{
460
0
    unsigned int bytes_in = 0;
461
0
    int rc = pcmk_rc_ok;
462
463
    // (5, 0, 0): (intermediate block size, silent, default workFactor)
464
0
    BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
465
466
0
    rc = pcmk__bzlib2rc(rc);
467
0
    if (rc != pcmk_rc_ok) {
468
0
        crm_warn("Not compressing %s: could not prepare file stream: %s "
469
0
                 QB_XS " rc=%d",
470
0
                 filename, pcmk_rc_str(rc), rc);
471
0
        goto done;
472
0
    }
473
474
0
    BZ2_bzWrite(&rc, bz_file, text, strlen(text));
475
0
    rc = pcmk__bzlib2rc(rc);
476
0
    if (rc != pcmk_rc_ok) {
477
0
        crm_warn("Not compressing %s: could not compress data: %s "
478
0
                 QB_XS " rc=%d errno=%d",
479
0
                 filename, pcmk_rc_str(rc), rc, errno);
480
0
        goto done;
481
0
    }
482
483
0
    BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
484
0
    bz_file = NULL;
485
0
    rc = pcmk__bzlib2rc(rc);
486
0
    if (rc != pcmk_rc_ok) {
487
0
        crm_warn("Not compressing %s: could not write compressed data: %s "
488
0
                 QB_XS " rc=%d errno=%d",
489
0
                 filename, pcmk_rc_str(rc), rc, errno);
490
0
        goto done;
491
0
    }
492
493
0
    crm_trace("Compressed XML for %s from %u bytes to %u",
494
0
              filename, bytes_in, *bytes_out);
495
496
0
done:
497
0
    if (bz_file != NULL) {
498
0
        BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
499
0
    }
500
0
    return rc;
501
0
}
502
503
/*!
504
 * \internal
505
 * \brief Write XML to a file stream
506
 *
507
 * \param[in]     xml       XML to write
508
 * \param[in]     filename  Name of file being written (for logging only)
509
 * \param[in,out] stream    Open file stream corresponding to filename (closed
510
 *                          when this function returns)
511
 * \param[in]     compress  Whether to compress XML before writing
512
 *
513
 * \return Standard Pacemaker return code
514
 */
515
static int
516
write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
517
                 bool compress)
518
0
{
519
0
    GString *buffer = g_string_sized_new(1024);
520
0
    unsigned int bytes_out = 0;
521
0
    int rc = pcmk_rc_ok;
522
523
0
    pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
524
0
    CRM_CHECK(!pcmk__str_empty(buffer->str),
525
0
              crm_log_xml_info(xml, "dump-failed");
526
0
              rc = pcmk_rc_error;
527
0
              goto done);
528
529
0
    crm_log_xml_trace(xml, "writing");
530
531
0
    if (compress
532
0
        && (write_compressed_stream(buffer->str, filename, stream,
533
0
                                    &bytes_out) == pcmk_rc_ok)) {
534
0
        goto done;
535
0
    }
536
537
0
    rc = fprintf(stream, "%s", buffer->str);
538
0
    if (rc < 0) {
539
0
        rc = EIO;
540
0
        crm_perror(LOG_ERR, "writing %s", filename);
541
0
        goto done;
542
0
    }
543
0
    bytes_out = (unsigned int) rc;
544
0
    rc = pcmk_rc_ok;
545
546
0
done:
547
0
    if (fflush(stream) != 0) {
548
0
        rc = errno;
549
0
        crm_perror(LOG_ERR, "flushing %s", filename);
550
0
    }
551
552
    // Don't report error if the file does not support synchronization
553
0
    if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
554
0
        rc = errno;
555
0
        crm_perror(LOG_ERR, "synchronizing %s", filename);
556
0
    }
557
558
0
    fclose(stream);
559
0
    crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
560
561
0
    g_string_free(buffer, TRUE);
562
0
    return rc;
563
0
}
564
565
/*!
566
 * \internal
567
 * \brief Write XML to a file descriptor
568
 *
569
 * \param[in]  xml       XML to write
570
 * \param[in]  filename  Name of file being written (for logging only)
571
 * \param[in]  fd        Open file descriptor corresponding to \p filename
572
 *
573
 * \return Standard Pacemaker return code
574
 */
575
int
576
pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
577
0
{
578
0
    FILE *stream = NULL;
579
580
0
    CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
581
0
    stream = fdopen(fd, "w");
582
0
    if (stream == NULL) {
583
0
        return errno;
584
0
    }
585
586
0
    return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
587
0
                            false);
588
0
}
589
590
/*!
591
 * \internal
592
 * \brief Write XML to a file
593
 *
594
 * \param[in]  xml       XML to write
595
 * \param[in]  filename  Name of file to write
596
 * \param[in]  compress  If \c true, compress XML before writing
597
 *
598
 * \return Standard Pacemaker return code
599
 */
600
int
601
pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
602
0
{
603
0
    FILE *stream = NULL;
604
605
0
    CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
606
0
    stream = fopen(filename, "w");
607
0
    if (stream == NULL) {
608
0
        return errno;
609
0
    }
610
611
0
    return write_xml_stream(xml, filename, stream, compress);
612
0
}
613
614
/*!
615
 * \internal
616
 * \brief Serialize XML (using libxml) into provided descriptor
617
 *
618
 * \param[in] fd  File descriptor to (piece-wise) write to
619
 * \param[in] cur XML subtree to proceed
620
 *
621
 * \return a standard Pacemaker return code
622
 */
623
int
624
pcmk__xml2fd(int fd, xmlNode *cur)
625
0
{
626
0
    bool success;
627
628
0
    xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
629
0
    pcmk__mem_assert(fd_out);
630
0
    xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
631
632
0
    success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
633
634
0
    success = xmlOutputBufferClose(fd_out) != -1 && success;
635
636
0
    if (!success) {
637
0
        return EIO;
638
0
    }
639
640
0
    fsync(fd);
641
0
    return pcmk_rc_ok;
642
0
}
643
644
void
645
save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
646
0
{
647
0
    char *f = NULL;
648
649
0
    if (filename == NULL) {
650
0
        char *uuid = crm_generate_uuid();
651
652
0
        f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
653
0
        filename = f;
654
0
        free(uuid);
655
0
    }
656
657
0
    crm_info("Saving %s to %s", desc, filename);
658
0
    pcmk__xml_write_file(xml, filename, false);
659
0
    free(f);
660
0
}