Coverage Report

Created: 2025-11-26 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/xml_display.c
Line
Count
Source
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 <libxml/tree.h>
13
14
#include <crm/crm.h>
15
#include <crm/common/xml.h>
16
#include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
17
#include "crmcommon_private.h"
18
19
static int show_xml_node(pcmk__output_t *out, GString *buffer,
20
                         const char *prefix, const xmlNode *data, int depth,
21
                         uint32_t options);
22
23
// Log an XML library error
24
void
25
pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
26
0
{
27
0
    va_list ap;
28
29
0
    va_start(ap, fmt);
30
0
    pcmk__if_tracing(
31
0
        {
32
0
            PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
33
0
                               crm_abort(__FILE__, __PRETTY_FUNCTION__,
34
0
                                         __LINE__, "xml library error", TRUE,
35
0
                                         TRUE),
36
0
                               "XML Error: ", fmt, ap);
37
0
        },
38
0
        {
39
0
            PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
40
0
        }
41
0
    );
42
0
    va_end(ap);
43
0
}
44
45
/*!
46
 * \internal
47
 * \brief Output an XML comment with depth-based indentation
48
 *
49
 * \param[in,out] out      Output object
50
 * \param[in]     data     XML node to output
51
 * \param[in]     depth    Current indentation level
52
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
53
 *
54
 * \return Standard Pacemaker return code
55
 *
56
 * \note This currently produces output only for text-like output objects.
57
 */
58
static int
59
show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
60
                 uint32_t options)
61
0
{
62
0
    if (pcmk__is_set(options, pcmk__xml_fmt_open)) {
63
0
        int width =
64
0
            pcmk__is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
65
66
0
        return out->info(out, "%*s<!--%s-->",
67
0
                         width, "", (const char *) data->content);
68
0
    }
69
0
    return pcmk_rc_no_output;
70
0
}
71
72
/*!
73
 * \internal
74
 * \brief Output an XML element in a formatted way
75
 *
76
 * \param[in,out] out      Output object
77
 * \param[in,out] buffer   Where to build output strings
78
 * \param[in]     prefix   String to prepend to every line of output
79
 * \param[in]     data     XML node to output
80
 * \param[in]     depth    Current indentation level
81
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
82
 *
83
 * \return Standard Pacemaker return code
84
 *
85
 * \note This is a recursive helper function for \p show_xml_node().
86
 * \note This currently produces output only for text-like output objects.
87
 * \note \p buffer may be overwritten many times. The caller is responsible for
88
 *       freeing it using \p g_string_free() but should not rely on its
89
 *       contents.
90
 */
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
0
{
95
0
    int spaces = pcmk__is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
96
0
    int rc = pcmk_rc_no_output;
97
98
0
    if (pcmk__is_set(options, pcmk__xml_fmt_open)) {
99
0
        const char *hidden = pcmk__xe_get(data, PCMK__XA_HIDDEN);
100
101
0
        g_string_truncate(buffer, 0);
102
103
0
        for (int lpc = 0; lpc < spaces; lpc++) {
104
0
            g_string_append_c(buffer, ' ');
105
0
        }
106
0
        pcmk__g_strcat(buffer, "<", data->name, NULL);
107
108
0
        for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
109
0
             attr = attr->next) {
110
0
            xml_node_private_t *nodepriv = attr->_private;
111
0
            const char *p_name = (const char *) attr->name;
112
0
            const char *p_value = pcmk__xml_attr_value(attr);
113
0
            gchar *p_copy = NULL;
114
115
0
            if ((nodepriv == NULL)
116
0
                || pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) {
117
0
                continue;
118
0
            }
119
120
0
            if ((hidden != NULL) && (p_name[0] != '\0')
121
0
                && (strstr(hidden, p_name) != NULL)) {
122
123
0
                p_value = "*****";
124
125
0
            } else {
126
0
                p_copy = pcmk__xml_escape(p_value, true);
127
0
                p_value = p_copy;
128
0
            }
129
130
0
            pcmk__g_strcat(buffer, " ", p_name, "=\"",
131
0
                           pcmk__s(p_value, "<null>"), "\"", NULL);
132
0
            g_free(p_copy);
133
0
        }
134
135
0
        if ((data->children != NULL)
136
0
            && pcmk__is_set(options, pcmk__xml_fmt_children)) {
137
0
            g_string_append_c(buffer, '>');
138
139
0
        } else {
140
0
            g_string_append(buffer, "/>");
141
0
        }
142
143
0
        rc = out->info(out, "%s%s%s",
144
0
                       pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
145
0
                       buffer->str);
146
0
    }
147
148
0
    if (data->children == NULL) {
149
0
        return rc;
150
0
    }
151
152
0
    if (pcmk__is_set(options, pcmk__xml_fmt_children)) {
153
0
        for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
154
0
             child = pcmk__xml_next(child)) {
155
156
0
            int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
157
0
                                        options
158
0
                                        |pcmk__xml_fmt_open
159
0
                                        |pcmk__xml_fmt_close);
160
0
            rc = pcmk__output_select_rc(rc, temp_rc);
161
0
        }
162
0
    }
163
164
0
    if (pcmk__is_set(options, pcmk__xml_fmt_close)) {
165
0
        int temp_rc = out->info(out, "%s%s%*s</%s>",
166
0
                                pcmk__s(prefix, ""),
167
0
                                pcmk__str_empty(prefix)? "" : " ",
168
0
                                spaces, "", data->name);
169
0
        rc = pcmk__output_select_rc(rc, temp_rc);
170
0
    }
171
172
0
    return rc;
173
0
}
174
175
/*!
176
 * \internal
177
 * \brief Output an XML element or comment in a formatted way
178
 *
179
 * \param[in,out] out      Output object
180
 * \param[in,out] buffer   Where to build output strings
181
 * \param[in]     prefix   String to prepend to every line of output
182
 * \param[in]     data     XML node to log
183
 * \param[in]     depth    Current indentation level
184
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
185
 *
186
 * \return Standard Pacemaker return code
187
 *
188
 * \note This is a recursive helper function for \p pcmk__xml_show().
189
 * \note This currently produces output only for text-like output objects.
190
 * \note \p buffer may be overwritten many times. The caller is responsible for
191
 *       freeing it using \p g_string_free() but should not rely on its
192
 *       contents.
193
 */
194
static int
195
show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
196
              const xmlNode *data, int depth, uint32_t options)
197
0
{
198
0
    switch (data->type) {
199
0
        case XML_COMMENT_NODE:
200
0
            return show_xml_comment(out, data, depth, options);
201
0
        case XML_ELEMENT_NODE:
202
0
            return show_xml_element(out, buffer, prefix, data, depth, options);
203
0
        default:
204
0
            return pcmk_rc_no_output;
205
0
    }
206
0
}
207
208
/*!
209
 * \internal
210
 * \brief Output an XML element or comment in a formatted way
211
 *
212
 * \param[in,out] out        Output object
213
 * \param[in]     prefix     String to prepend to every line of output
214
 * \param[in]     data       XML node to output
215
 * \param[in]     depth      Current nesting level
216
 * \param[in]     options    Group of \p pcmk__xml_fmt_options flags
217
 *
218
 * \return Standard Pacemaker return code
219
 *
220
 * \note This currently produces output only for text-like output objects.
221
 */
222
int
223
pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
224
               int depth, uint32_t options)
225
0
{
226
0
    int rc = pcmk_rc_no_output;
227
0
    GString *buffer = NULL;
228
229
0
    pcmk__assert(out != NULL);
230
0
    CRM_CHECK(depth >= 0, depth = 0);
231
232
0
    if (data == NULL) {
233
0
        return rc;
234
0
    }
235
236
    /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
237
     * recursive calls
238
     */
239
0
    buffer = g_string_sized_new(1024);
240
0
    rc = show_xml_node(out, buffer, prefix, data, depth, options);
241
0
    g_string_free(buffer, TRUE);
242
243
0
    return rc;
244
0
}
245
246
/*!
247
 * \internal
248
 * \brief Output XML portions that have been marked as changed
249
 *
250
 * \param[in,out] out      Output object
251
 * \param[in]     data     XML node to output
252
 * \param[in]     depth    Current indentation level
253
 * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
254
 *
255
 * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing
256
 *       changes to \p data and its children.
257
 * \note This currently produces output only for text-like output objects.
258
 */
259
static int
260
show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
261
                           uint32_t options)
262
0
{
263
    /* @COMPAT: When log_data_element() is removed, we can remove the options
264
     * argument here and instead hard-code pcmk__xml_log_pretty.
265
     */
266
0
    xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
267
0
    int rc = pcmk_rc_no_output;
268
0
    int temp_rc = pcmk_rc_no_output;
269
270
0
    if (nodepriv == NULL) {
271
0
        return pcmk_rc_no_output;
272
0
    }
273
274
0
    if (pcmk__all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
275
        // Newly created
276
0
        return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
277
0
                              options
278
0
                              |pcmk__xml_fmt_open
279
0
                              |pcmk__xml_fmt_children
280
0
                              |pcmk__xml_fmt_close);
281
0
    }
282
283
0
    if (pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) {
284
        // Modified or moved
285
0
        bool pretty = pcmk__is_set(options, pcmk__xml_fmt_pretty);
286
0
        int spaces = pretty? (2 * depth) : 0;
287
0
        const char *prefix = PCMK__XML_PREFIX_MODIFIED;
288
289
0
        if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) {
290
0
            prefix = PCMK__XML_PREFIX_MOVED;
291
0
        }
292
293
        // Log opening tag
294
0
        rc = pcmk__xml_show(out, prefix, data, depth,
295
0
                            options|pcmk__xml_fmt_open);
296
297
        // Log changes to attributes
298
0
        for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
299
0
             attr = attr->next) {
300
0
            const char *name = (const char *) attr->name;
301
302
0
            nodepriv = attr->_private;
303
304
0
            if (pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) {
305
0
                const char *value = pcmk__xml_attr_value(attr);
306
307
0
                temp_rc = out->info(out, "%s %*s @%s=%s",
308
0
                                    PCMK__XML_PREFIX_DELETED, spaces, "", name,
309
0
                                    value);
310
311
0
            } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) {
312
0
                const char *value = pcmk__xml_attr_value(attr);
313
314
0
                if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
315
0
                    prefix = PCMK__XML_PREFIX_CREATED;
316
317
0
                } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_modified)) {
318
0
                    prefix = PCMK__XML_PREFIX_MODIFIED;
319
320
0
                } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) {
321
0
                    prefix = PCMK__XML_PREFIX_MOVED;
322
323
0
                } else {
324
0
                    prefix = PCMK__XML_PREFIX_MODIFIED;
325
0
                }
326
327
0
                temp_rc = out->info(out, "%s %*s @%s=%s",
328
0
                                    prefix, spaces, "", name, value);
329
0
            }
330
0
            rc = pcmk__output_select_rc(rc, temp_rc);
331
0
        }
332
333
        // Log changes to children
334
0
        for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
335
0
             child = pcmk__xml_next(child)) {
336
0
            temp_rc = show_xml_changes_recursive(out, child, depth + 1,
337
0
                                                 options);
338
0
            rc = pcmk__output_select_rc(rc, temp_rc);
339
0
        }
340
341
        // Log closing tag
342
0
        temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
343
0
                                 options|pcmk__xml_fmt_close);
344
0
        return pcmk__output_select_rc(rc, temp_rc);
345
0
    }
346
347
    // This node hasn't changed, but check its children
348
0
    for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
349
0
         child = pcmk__xml_next(child)) {
350
0
        temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
351
0
        rc = pcmk__output_select_rc(rc, temp_rc);
352
0
    }
353
0
    return rc;
354
0
}
355
356
/*!
357
 * \internal
358
 * \brief Output changes to an XML node and any children
359
 *
360
 * \param[in,out] out  Output object
361
 * \param[in]     xml  XML node to output
362
 *
363
 * \return Standard Pacemaker return code
364
 *
365
 * \note This currently produces output only for text-like output objects.
366
 */
367
int
368
pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
369
0
{
370
0
    xml_doc_private_t *docpriv = NULL;
371
0
    int rc = pcmk_rc_no_output;
372
0
    int temp_rc = pcmk_rc_no_output;
373
374
0
    pcmk__assert((out != NULL) && (xml != NULL) && (xml->doc != NULL));
375
376
0
    docpriv = xml->doc->_private;
377
0
    if (!pcmk__is_set(docpriv->flags, pcmk__xf_dirty)) {
378
0
        return rc;
379
0
    }
380
381
0
    for (const GList *iter = docpriv->deleted_objs; iter != NULL;
382
0
         iter = iter->next) {
383
0
        const pcmk__deleted_xml_t *deleted_obj = iter->data;
384
385
0
        if (deleted_obj->position >= 0) {
386
0
            temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
387
0
                                deleted_obj->path, deleted_obj->position);
388
0
        } else {
389
0
            temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
390
0
                                deleted_obj->path);
391
0
        }
392
0
        rc = pcmk__output_select_rc(rc, temp_rc);
393
0
    }
394
395
0
    temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
396
0
    return pcmk__output_select_rc(rc, temp_rc);
397
0
}