Coverage Report

Created: 2026-08-14 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/patchset_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 <crm/common/xml.h>
13
14
#include "crmcommon_private.h"
15
16
/*!
17
 * \internal
18
 * \brief Output an XML patchset header
19
 *
20
 * This function parses a header from an XML patchset (a \c PCMK_XE_DIFF element
21
 * and its children).
22
 *
23
 * All header lines contain three integers separated by dots, of the form
24
 * <tt>{0}.{1}.{2}</tt>:
25
 * * \p {0}: \c PCMK_XA_ADMIN_EPOCH
26
 * * \p {1}: \c PCMK_XA_EPOCH
27
 * * \p {2}: \c PCMK_XA_NUM_UPDATES
28
 *
29
 * Lines containing \p "---" describe removals and end with the patch format
30
 * number. Lines containing \p "+++" describe additions and end with the patch
31
 * digest.
32
 *
33
 * \param[in,out] out       Output object
34
 * \param[in]     patchset  XML patchset to output
35
 *
36
 * \return Standard Pacemaker return code
37
 *
38
 * \note This function produces output only for text-like formats.
39
 */
40
static int
41
xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
42
0
{
43
0
    int rc = pcmk_rc_no_output;
44
0
    int add[] = { 0, 0, 0 };
45
0
    int del[] = { 0, 0, 0 };
46
47
0
    pcmk__xml_patchset_versions(patchset, del, add);
48
49
0
    if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
50
0
        const char *fmt = pcmk__xe_get(patchset, PCMK_XA_FORMAT);
51
0
        const char *digest = pcmk__xe_get(patchset, PCMK_XA_DIGEST);
52
53
0
        out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2],
54
0
                  pcmk__s(fmt, "(no format)"));
55
0
        rc = out->info(out, "Diff: +++ %d.%d.%d %s",
56
0
                       add[0], add[1], add[2], pcmk__s(digest, "(no digest)"));
57
58
0
    } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
59
0
        rc = out->info(out, "Local-only Change: %d.%d.%d",
60
0
                       add[0], add[1], add[2]);
61
0
    }
62
63
0
    return rc;
64
0
}
65
66
/*!
67
 * \internal
68
 * \brief Output a user-friendly form of an XML patchset
69
 *
70
 * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
71
 * children) into a user-friendly combined diff output.
72
 *
73
 * \param[in,out] out       Output object
74
 * \param[in]     patchset  XML patchset to output
75
 *
76
 * \return Standard Pacemaker return code
77
 *
78
 * \note This function produces output only for text-like formats.
79
 */
80
static int
81
xml_show_patchset(pcmk__output_t *out, const xmlNode *patchset)
82
0
{
83
0
    int rc = xml_show_patchset_header(out, patchset);
84
0
    int temp_rc = pcmk_rc_no_output;
85
86
0
    for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL,
87
0
                                                      NULL);
88
0
         change != NULL; change = pcmk__xe_next(change, NULL)) {
89
90
0
        const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION);
91
0
        const char *xpath = pcmk__xe_get(change, PCMK_XA_PATH);
92
93
0
        if (op == NULL) {
94
0
            continue;
95
0
        }
96
97
0
        if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
98
0
            char *prefix = pcmk__assert_asprintf(PCMK__XML_PREFIX_CREATED
99
0
                                                 " %s: ",
100
0
                                                 xpath);
101
102
0
            temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
103
0
                                     pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
104
0
            rc = pcmk__output_select_rc(rc, temp_rc);
105
106
            // Overwrite all except the first two characters with spaces
107
0
            for (char *ch = prefix + 2; *ch != '\0'; ch++) {
108
0
                *ch = ' ';
109
0
            }
110
111
0
            temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
112
0
                                     pcmk__xml_fmt_pretty
113
0
                                     |pcmk__xml_fmt_children
114
0
                                     |pcmk__xml_fmt_close);
115
0
            rc = pcmk__output_select_rc(rc, temp_rc);
116
0
            free(prefix);
117
118
0
        } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
119
0
            const char *position = pcmk__xe_get(change, PCMK_XE_POSITION);
120
121
0
            temp_rc = out->info(out,
122
0
                                PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
123
0
                                xpath, position);
124
0
            rc = pcmk__output_select_rc(rc, temp_rc);
125
126
0
        } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
127
0
            xmlNode *clist = pcmk__xe_first_child(change, PCMK_XE_CHANGE_LIST,
128
0
                                                  NULL, NULL);
129
0
            GString *buffer_set = NULL;
130
0
            GString *buffer_unset = NULL;
131
132
0
            for (const xmlNode *child = pcmk__xe_first_child(clist, NULL, NULL,
133
0
                                                             NULL);
134
0
                 child != NULL; child = pcmk__xe_next(child, NULL)) {
135
136
0
                const char *name = pcmk__xe_get(child, PCMK_XA_NAME);
137
138
0
                op = pcmk__xe_get(child, PCMK_XA_OPERATION);
139
0
                if (op == NULL) {
140
0
                    continue;
141
0
                }
142
143
0
                if (strcmp(op, "set") == 0) {
144
0
                    const char *value = pcmk__xe_get(child, PCMK_XA_VALUE);
145
146
0
                    pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
147
0
                    pcmk__g_strcat(buffer_set, name, "=", value, NULL);
148
149
0
                } else if (strcmp(op, "unset") == 0) {
150
0
                    pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
151
0
                    g_string_append(buffer_unset, name);
152
0
                }
153
0
            }
154
155
0
            if (buffer_set != NULL) {
156
0
                temp_rc = out->info(out, "+  %s:  %s", xpath, buffer_set->str);
157
0
                rc = pcmk__output_select_rc(rc, temp_rc);
158
0
                g_string_free(buffer_set, TRUE);
159
0
            }
160
161
0
            if (buffer_unset != NULL) {
162
0
                temp_rc = out->info(out, "-- %s:  %s",
163
0
                                    xpath, buffer_unset->str);
164
0
                rc = pcmk__output_select_rc(rc, temp_rc);
165
0
                g_string_free(buffer_unset, TRUE);
166
0
            }
167
168
0
        } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
169
0
            int position = -1;
170
171
0
            pcmk__xe_get_int(change, PCMK_XE_POSITION, &position);
172
0
            if (position >= 0) {
173
0
                temp_rc = out->info(out, "-- %s (%d)", xpath, position);
174
0
            } else {
175
0
                temp_rc = out->info(out, "-- %s", xpath);
176
0
            }
177
0
            rc = pcmk__output_select_rc(rc, temp_rc);
178
0
        }
179
0
    }
180
181
0
    return rc;
182
0
}
183
184
/*!
185
 * \internal
186
 * \brief Output a user-friendly form of an XML patchset
187
 *
188
 * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
189
 * children) into a user-friendly combined diff output.
190
 *
191
 * \param[in,out] out   Output object
192
 * \param[in]     args  Message-specific arguments
193
 *
194
 * \return Standard Pacemaker return code
195
 *
196
 * \note \p args should contain the following:
197
 *       -# XML patchset
198
 */
199
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
200
static int
201
xml_patchset_default(pcmk__output_t *out, va_list args)
202
0
{
203
0
    const xmlNode *patchset = va_arg(args, const xmlNode *);
204
205
0
    int format = 1;
206
207
0
    if (patchset == NULL) {
208
0
        pcmk__trace("Empty patch");
209
0
        return pcmk_rc_no_output;
210
0
    }
211
212
0
    pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
213
0
    if (format != 2) {
214
0
        pcmk__err("Unknown patch format: %d", format);
215
0
        return pcmk_rc_bad_xml_patch;
216
0
    }
217
218
0
    return xml_show_patchset(out, patchset);
219
0
}
220
221
/*!
222
 * \internal
223
 * \brief Output a user-friendly form of an XML patchset
224
 *
225
 * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
226
 * children) into a user-friendly combined diff output.
227
 *
228
 * \param[in,out] out   Output object
229
 * \param[in]     args  Message-specific arguments
230
 *
231
 * \return Standard Pacemaker return code
232
 *
233
 * \note \p args should contain the following:
234
 *       -# XML patchset
235
 */
236
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
237
static int
238
xml_patchset_log(pcmk__output_t *out, va_list args)
239
0
{
240
0
    static struct qb_log_callsite *patchset_cs = NULL;
241
242
0
    const xmlNode *patchset = va_arg(args, const xmlNode *);
243
244
0
    uint8_t log_level = pcmk__output_get_log_level(out);
245
0
    int format = 1;
246
247
0
    if (log_level == PCMK__LOG_NEVER) {
248
0
        return pcmk_rc_no_output;
249
0
    }
250
251
0
    if (patchset == NULL) {
252
0
        pcmk__trace("Empty patch");
253
0
        return pcmk_rc_no_output;
254
0
    }
255
256
0
    if (patchset_cs == NULL) {
257
0
        patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
258
0
                                          log_level, __LINE__,
259
0
                                          crm_trace_nonlog);
260
0
    }
261
262
0
    if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
263
        // Nothing would be logged, so skip all the work
264
0
        return pcmk_rc_no_output;
265
0
    }
266
267
0
    pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
268
0
    if (format != 2) {
269
0
        pcmk__err("Unknown patch format: %d", format);
270
0
        return pcmk_rc_bad_xml_patch;
271
0
    }
272
273
0
    return xml_show_patchset(out, patchset);
274
0
}
275
276
/*!
277
 * \internal
278
 * \brief Output an XML patchset
279
 *
280
 * This function outputs an XML patchset (a \c PCMK_XE_DIFF element and its
281
 * children) without modification, as a CDATA block.
282
 *
283
 * \param[in,out] out   Output object
284
 * \param[in]     args  Message-specific arguments
285
 *
286
 * \return Standard Pacemaker return code
287
 *
288
 * \note \p args should contain the following:
289
 *       -# XML patchset
290
 */
291
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
292
static int
293
xml_patchset_xml(pcmk__output_t *out, va_list args)
294
0
{
295
0
    const xmlNode *patchset = va_arg(args, const xmlNode *);
296
297
0
    if (patchset != NULL) {
298
0
        GString *buf = g_string_sized_new(1024);
299
300
0
        pcmk__xml_string(patchset, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
301
0
                         0);
302
303
0
        out->output_xml(out, PCMK_XE_XML_PATCHSET, buf->str);
304
0
        g_string_free(buf, TRUE);
305
0
        return pcmk_rc_ok;
306
0
    }
307
0
    pcmk__trace("Empty patch");
308
0
    return pcmk_rc_no_output;
309
0
}
310
311
static pcmk__message_entry_t fmt_functions[] = {
312
    { "xml-patchset", "default", xml_patchset_default },
313
    { "xml-patchset", "log", xml_patchset_log },
314
    { "xml-patchset", "xml", xml_patchset_xml },
315
316
    { NULL, NULL, NULL }
317
};
318
319
/*!
320
 * \internal
321
 * \brief Register the formatting functions for XML patchsets
322
 *
323
 * \param[in,out] out  Output object
324
 */
325
void
326
0
pcmk__register_patchset_messages(pcmk__output_t *out) {
327
0
    pcmk__register_messages(out, fmt_functions);
328
0
}