Coverage Report

Created: 2026-03-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/output_xml.c
Line
Count
Source
1
/*
2
 * Copyright 2019-2026 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 <ctype.h>
13
#include <stdarg.h>
14
#include <stdbool.h>
15
#include <stdlib.h>
16
#include <stdio.h>
17
#include <crm/crm.h>
18
19
#include <glib.h>
20
#include <libxml/tree.h>                    // xmlNode
21
#include <libxml/xmlstring.h>               // xmlChar
22
23
#include <crm/common/output.h>
24
#include <crm/common/xml.h>
25
26
typedef struct {
27
    const char *from;
28
    const char *to;
29
} subst_t;
30
31
static const subst_t substitutions[] = {
32
    { "Active Resources",
33
      PCMK_XE_RESOURCES, },
34
    { "Assignment Scores",
35
      PCMK_XE_ALLOCATIONS, },
36
    { "Assignment Scores and Utilization Information",
37
      PCMK_XE_ALLOCATIONS_UTILIZATIONS, },
38
    { "Cluster Summary",
39
      PCMK_XE_SUMMARY, },
40
    { "Current cluster status",
41
      PCMK_XE_CLUSTER_STATUS, },
42
    { "Executing Cluster Transition",
43
      PCMK_XE_TRANSITION, },
44
    { "Failed Resource Actions",
45
      PCMK_XE_FAILURES, },
46
    { "Fencing History",
47
      PCMK_XE_FENCE_HISTORY, },
48
    { "Full List of Resources",
49
      PCMK_XE_RESOURCES, },
50
    { "Inactive Resources",
51
      PCMK_XE_RESOURCES, },
52
    { "Migration Summary",
53
      PCMK_XE_NODE_HISTORY, },
54
    { "Negative Location Constraints",
55
      PCMK_XE_BANS, },
56
    { "Node Attributes",
57
      PCMK_XE_NODE_ATTRIBUTES, },
58
    { "Operations",
59
      PCMK_XE_NODE_HISTORY, },
60
    { "Resource Config",
61
      PCMK_XE_RESOURCE_CONFIG, },
62
    { "Resource Operations",
63
      PCMK_XE_OPERATIONS, },
64
    { "Revised Cluster Status",
65
      PCMK_XE_REVISED_CLUSTER_STATUS, },
66
    { "Timings",
67
      PCMK_XE_TIMINGS, },
68
    { "Transition Summary",
69
      PCMK_XE_ACTIONS, },
70
    { "Utilization Information",
71
      PCMK_XE_UTILIZATIONS, },
72
73
    { NULL, NULL }
74
};
75
76
/* The first several elements of this struct must be the same as the first
77
 * several elements of private_data_s in lib/common/output_html.c.  That
78
 * struct gets passed to a bunch of the pcmk__output_xml_* functions which
79
 * assume an XML private_data_s.  Keeping them laid out the same means this
80
 * still works.
81
 */
82
typedef struct {
83
    /* Begin members that must match the HTML version */
84
    xmlNode *root;
85
    GQueue *parent_q;
86
    GSList *errors;
87
    /* End members that must match the HTML version */
88
    bool legacy_xml;
89
    bool list_element;
90
} private_data_t;
91
92
static bool
93
has_root_node(pcmk__output_t *out)
94
0
{
95
0
    private_data_t *priv = NULL;
96
97
0
    pcmk__assert(out != NULL);
98
99
0
    priv = out->priv;
100
0
    return priv != NULL && priv->root != NULL;
101
0
}
102
103
static void
104
add_root_node(pcmk__output_t *out)
105
0
{
106
0
    private_data_t *priv = NULL;
107
108
    /* has_root_node will assert if out is NULL, so no need to do it here */
109
0
    if (has_root_node(out)) {
110
0
        return;
111
0
    }
112
113
0
    priv = out->priv;
114
115
0
    if (priv->legacy_xml) {
116
0
        priv->root = pcmk__xe_create(NULL, PCMK_XE_CRM_MON);
117
0
        pcmk__xe_set(priv->root, PCMK_XA_VERSION, PACEMAKER_VERSION);
118
0
    } else {
119
0
        priv->root = pcmk__xe_create(NULL, PCMK_XE_PACEMAKER_RESULT);
120
0
        pcmk__xe_set(priv->root, PCMK_XA_API_VERSION, PCMK__API_VERSION);
121
0
        pcmk__xe_set(priv->root, PCMK_XA_REQUEST,
122
0
                    pcmk__s(out->request, "libpacemaker"));
123
0
    }
124
125
0
    priv->parent_q = g_queue_new();
126
0
    g_queue_push_tail(priv->parent_q, priv->root);
127
0
}
128
129
static void
130
0
xml_free_priv(pcmk__output_t *out) {
131
0
    private_data_t *priv = NULL;
132
133
0
    if (out == NULL || out->priv == NULL) {
134
0
        return;
135
0
    }
136
137
0
    priv = out->priv;
138
139
0
    if (has_root_node(out)) {
140
0
        pcmk__xml_free(priv->root);
141
        /* The elements of parent_q are xmlNodes that are a part of the
142
         * priv->root document, so the above line already frees them.  Don't
143
         * call g_queue_free_full here.
144
         */
145
0
        g_queue_free(priv->parent_q);
146
0
    }
147
148
0
    g_slist_free_full(priv->errors, free);
149
0
    free(priv);
150
0
    out->priv = NULL;
151
0
}
152
153
static bool
154
0
xml_init(pcmk__output_t *out) {
155
0
    private_data_t *priv = NULL;
156
157
0
    pcmk__assert(out != NULL);
158
159
    /* If xml_init was previously called on this output struct, just return. */
160
0
    if (out->priv != NULL) {
161
0
        return true;
162
0
    } else {
163
0
        out->priv = calloc(1, sizeof(private_data_t));
164
0
        if (out->priv == NULL) {
165
0
            return false;
166
0
        }
167
168
0
        priv = out->priv;
169
0
    }
170
171
0
    priv->errors = NULL;
172
173
0
    return true;
174
0
}
175
176
static void
177
0
add_error_node(gpointer data, gpointer user_data) {
178
0
    const char *str = (const char *) data;
179
0
    xmlNodePtr node = (xmlNodePtr) user_data;
180
181
0
    node = pcmk__xe_create(node, PCMK_XE_ERROR);
182
0
    pcmk__xe_set_content(node, "%s", str);
183
0
}
184
185
static void
186
0
xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
187
0
    private_data_t *priv = NULL;
188
0
    xmlNodePtr node;
189
190
0
    pcmk__assert(out != NULL);
191
0
    priv = out->priv;
192
193
0
    if (priv == NULL) {
194
0
        return;
195
0
    }
196
197
0
    add_root_node(out);
198
199
0
    if (priv->legacy_xml) {
200
0
        GSList *node = priv->errors;
201
202
0
        if (exit_status != CRM_EX_OK) {
203
0
            fprintf(stderr, "%s\n", crm_exit_str(exit_status));
204
0
        }
205
206
0
        while (node != NULL) {
207
0
            fprintf(stderr, "%s\n", (char *) node->data);
208
0
            node = node->next;
209
0
        }
210
0
    } else {
211
0
        char *rc_as_str = pcmk__itoa(exit_status);
212
213
0
        node = pcmk__xe_create(priv->root, PCMK_XE_STATUS);
214
0
        pcmk__xe_set_props(node,
215
0
                           PCMK_XA_CODE, rc_as_str,
216
0
                           PCMK_XA_MESSAGE, crm_exit_str(exit_status),
217
0
                           NULL);
218
219
0
        if (g_slist_length(priv->errors) > 0) {
220
0
            xmlNodePtr errors_node = pcmk__xe_create(node, PCMK_XE_ERRORS);
221
0
            g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
222
0
        }
223
224
0
        free(rc_as_str);
225
0
    }
226
227
0
    if (print) {
228
0
        pcmk__xml2fd(fileno(out->dest), priv->root);
229
0
    }
230
231
0
    if (copy_dest != NULL) {
232
0
        *copy_dest = pcmk__xml_copy(NULL, priv->root);
233
0
    }
234
0
}
235
236
static void
237
0
xml_reset(pcmk__output_t *out) {
238
0
    pcmk__assert(out != NULL);
239
240
0
    out->dest = freopen(NULL, "w", out->dest);
241
0
    pcmk__assert(out->dest != NULL);
242
243
0
    xml_free_priv(out);
244
0
    xml_init(out);
245
0
}
246
247
static void
248
xml_subprocess_output(pcmk__output_t *out, int exit_status,
249
0
                      const char *proc_stdout, const char *proc_stderr) {
250
0
    xmlNodePtr node, child_node;
251
0
    char *rc_as_str = NULL;
252
253
0
    pcmk__assert(out != NULL);
254
255
0
    rc_as_str = pcmk__itoa(exit_status);
256
257
0
    node = pcmk__output_xml_create_parent(out, PCMK_XE_COMMAND,
258
0
                                          PCMK_XA_CODE, rc_as_str,
259
0
                                          NULL);
260
261
0
    if (proc_stdout != NULL) {
262
0
        child_node = pcmk__xe_create(node, PCMK_XE_OUTPUT);
263
0
        pcmk__xe_set_content(child_node, "%s", proc_stdout);
264
0
        pcmk__xe_set(child_node, PCMK_XA_SOURCE, "stdout");
265
0
    }
266
267
0
    if (proc_stderr != NULL) {
268
0
        child_node = pcmk__xe_create(node, PCMK_XE_OUTPUT);
269
0
        pcmk__xe_set_content(child_node, "%s", proc_stderr);
270
0
        pcmk__xe_set(child_node, PCMK_XA_SOURCE, "stderr");
271
0
    }
272
273
0
    free(rc_as_str);
274
0
}
275
276
static void
277
xml_version(pcmk__output_t *out)
278
0
{
279
0
    const char *author = "Andrew Beekhof and the Pacemaker project "
280
0
                         "contributors";
281
0
    pcmk__assert(out != NULL);
282
283
0
    pcmk__output_create_xml_node(out, PCMK_XE_VERSION,
284
0
                                 PCMK_XA_PROGRAM, "Pacemaker",
285
0
                                 PCMK_XA_VERSION, PACEMAKER_VERSION,
286
0
                                 PCMK_XA_AUTHOR, author,
287
0
                                 PCMK_XA_BUILD, BUILD_VERSION,
288
0
                                 PCMK_XA_FEATURES, CRM_FEATURES,
289
0
                                 NULL);
290
0
}
291
292
G_GNUC_PRINTF(2, 3)
293
static void
294
0
xml_err(pcmk__output_t *out, const char *format, ...) {
295
0
    private_data_t *priv = NULL;
296
0
    int len = 0;
297
0
    char *buf = NULL;
298
0
    va_list ap;
299
300
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
301
0
    priv = out->priv;
302
303
0
    add_root_node(out);
304
305
0
    va_start(ap, format);
306
0
    len = vasprintf(&buf, format, ap);
307
0
    pcmk__assert(len > 0);
308
0
    va_end(ap);
309
310
0
    priv->errors = g_slist_append(priv->errors, buf);
311
0
}
312
313
G_GNUC_PRINTF(2, 3)
314
static int
315
0
xml_info(pcmk__output_t *out, const char *format, ...) {
316
0
    return pcmk_rc_no_output;
317
0
}
318
319
static void
320
0
xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
321
0
    xmlNodePtr parent = NULL;
322
0
    xmlNodePtr cdata_node = NULL;
323
324
0
    pcmk__assert(out != NULL);
325
326
0
    parent = pcmk__output_create_xml_node(out, name, NULL);
327
0
    if (parent == NULL) {
328
0
        return;
329
0
    }
330
0
    cdata_node = xmlNewCDataBlock(parent->doc, (const xmlChar *) buf,
331
0
                                  strlen(buf));
332
0
    xmlAddChild(parent, cdata_node);
333
0
}
334
335
G_GNUC_PRINTF(4, 5)
336
static void
337
xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
338
0
               const char *format, ...) {
339
0
    va_list ap;
340
0
    char *name = NULL;
341
0
    char *buf = NULL;
342
0
    int len;
343
0
    private_data_t *priv = NULL;
344
345
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
346
0
    priv = out->priv;
347
348
0
    va_start(ap, format);
349
0
    len = vasprintf(&buf, format, ap);
350
0
    pcmk__assert(len >= 0);
351
0
    va_end(ap);
352
353
0
    for (const subst_t *s = substitutions; s->from != NULL; s++) {
354
0
        if (strcmp(s->from, buf) == 0) {
355
0
            name = g_strdup(s->to);
356
0
            break;
357
0
        }
358
0
    }
359
360
0
    if (name == NULL) {
361
0
        name = g_ascii_strdown(buf, -1);
362
0
    }
363
364
0
    if (priv->list_element) {
365
0
        pcmk__output_xml_create_parent(out, PCMK_XE_LIST,
366
0
                                       PCMK_XA_NAME, name,
367
0
                                       NULL);
368
0
    } else {
369
0
        pcmk__output_xml_create_parent(out, name, NULL);
370
0
    }
371
372
0
    g_free(name);
373
0
    free(buf);
374
0
}
375
376
G_GNUC_PRINTF(3, 4)
377
static void
378
0
xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
379
0
    xmlNodePtr item_node = NULL;
380
0
    va_list ap;
381
0
    char *buf = NULL;
382
0
    int len;
383
384
0
    pcmk__assert(out != NULL);
385
386
0
    va_start(ap, format);
387
0
    len = vasprintf(&buf, format, ap);
388
0
    pcmk__assert(len >= 0);
389
0
    va_end(ap);
390
391
0
    item_node = pcmk__output_create_xml_text_node(out, PCMK_XE_ITEM, buf);
392
393
0
    if (name != NULL) {
394
0
        pcmk__xe_set(item_node, PCMK_XA_NAME, name);
395
0
    }
396
397
0
    free(buf);
398
0
}
399
400
static void
401
0
xml_increment_list(pcmk__output_t *out) {
402
    /* This function intentially left blank */
403
0
}
404
405
static void
406
0
xml_end_list(pcmk__output_t *out) {
407
0
    private_data_t *priv = NULL;
408
409
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
410
0
    priv = out->priv;
411
412
0
    if (priv->list_element) {
413
0
        char *buf = NULL;
414
0
        xmlNodePtr node;
415
416
        /* Do not free node here - it's still part of the document */
417
0
        node = g_queue_pop_tail(priv->parent_q);
418
0
        buf = pcmk__assert_asprintf("%lu", xmlChildElementCount(node));
419
0
        pcmk__xe_set(node, PCMK_XA_COUNT, buf);
420
0
        free(buf);
421
0
    } else {
422
        /* Do not free this result - it's still part of the document */
423
0
        g_queue_pop_tail(priv->parent_q);
424
0
    }
425
0
}
426
427
static bool
428
0
xml_is_quiet(pcmk__output_t *out) {
429
0
    return false;
430
0
}
431
432
static void
433
0
xml_spacer(pcmk__output_t *out) {
434
    /* This function intentionally left blank */
435
0
}
436
437
static void
438
0
xml_progress(pcmk__output_t *out, bool end) {
439
    /* This function intentionally left blank */
440
0
}
441
442
pcmk__output_t *
443
0
pcmk__mk_xml_output(char **argv) {
444
0
    pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
445
446
0
    if (retval == NULL) {
447
0
        return NULL;
448
0
    }
449
450
0
    retval->fmt_name = "xml";
451
0
    retval->request = pcmk__quote_cmdline(argv);
452
453
0
    retval->init = xml_init;
454
0
    retval->free_priv = xml_free_priv;
455
0
    retval->finish = xml_finish;
456
0
    retval->reset = xml_reset;
457
458
0
    retval->register_message = pcmk__register_message;
459
0
    retval->message = pcmk__call_message;
460
461
0
    retval->subprocess_output = xml_subprocess_output;
462
0
    retval->version = xml_version;
463
0
    retval->info = xml_info;
464
0
    retval->transient = xml_info;
465
0
    retval->err = xml_err;
466
0
    retval->output_xml = xml_output_xml;
467
468
0
    retval->begin_list = xml_begin_list;
469
0
    retval->list_item = xml_list_item;
470
0
    retval->increment_list = xml_increment_list;
471
0
    retval->end_list = xml_end_list;
472
473
0
    retval->is_quiet = xml_is_quiet;
474
0
    retval->spacer = xml_spacer;
475
0
    retval->progress = xml_progress;
476
0
    retval->prompt = pcmk__text_prompt;
477
478
0
    return retval;
479
0
}
480
481
xmlNodePtr
482
0
pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) {
483
0
    va_list args;
484
0
    xmlNodePtr node = NULL;
485
486
0
    pcmk__assert(out != NULL);
487
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
488
489
0
    node = pcmk__output_create_xml_node(out, name, NULL);
490
491
0
    va_start(args, name);
492
0
    pcmk__xe_set_propv(node, args);
493
0
    va_end(args);
494
495
0
    pcmk__output_xml_push_parent(out, node);
496
0
    return node;
497
0
}
498
499
void
500
0
pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node) {
501
0
    private_data_t *priv = NULL;
502
0
    xmlNodePtr parent = NULL;
503
504
0
    pcmk__assert((out != NULL) && (out->priv != NULL) && (node != NULL));
505
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
506
507
0
    add_root_node(out);
508
509
0
    priv = out->priv;
510
0
    parent = g_queue_peek_tail(priv->parent_q);
511
512
    // Shouldn't happen unless the caller popped priv->root
513
0
    CRM_CHECK(parent != NULL, return);
514
515
0
    pcmk__xml_copy(parent, node);
516
0
}
517
518
xmlNodePtr
519
0
pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) {
520
0
    xmlNodePtr node = NULL;
521
0
    private_data_t *priv = NULL;
522
0
    va_list args;
523
524
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
525
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
526
527
0
    add_root_node(out);
528
529
0
    priv = out->priv;
530
531
0
    node = pcmk__xe_create(g_queue_peek_tail(priv->parent_q), name);
532
0
    va_start(args, name);
533
0
    pcmk__xe_set_propv(node, args);
534
0
    va_end(args);
535
536
0
    return node;
537
0
}
538
539
xmlNodePtr
540
0
pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
541
0
    xmlNodePtr node = NULL;
542
543
0
    pcmk__assert(out != NULL);
544
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
545
546
0
    node = pcmk__output_create_xml_node(out, name, NULL);
547
0
    pcmk__xe_set_content(node, "%s", content);
548
0
    return node;
549
0
}
550
551
void
552
0
pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) {
553
0
    private_data_t *priv = NULL;
554
555
0
    pcmk__assert((out != NULL) && (out->priv != NULL) && (parent != NULL));
556
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
557
558
0
    add_root_node(out);
559
560
0
    priv = out->priv;
561
562
0
    g_queue_push_tail(priv->parent_q, parent);
563
0
}
564
565
void
566
0
pcmk__output_xml_pop_parent(pcmk__output_t *out) {
567
0
    private_data_t *priv = NULL;
568
569
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
570
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
571
572
0
    add_root_node(out);
573
574
0
    priv = out->priv;
575
576
0
    pcmk__assert(g_queue_get_length(priv->parent_q) > 0);
577
    /* Do not free this result - it's still part of the document */
578
0
    g_queue_pop_tail(priv->parent_q);
579
0
}
580
581
xmlNodePtr
582
0
pcmk__output_xml_peek_parent(pcmk__output_t *out) {
583
0
    private_data_t *priv = NULL;
584
585
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
586
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
587
588
0
    add_root_node(out);
589
590
0
    priv = out->priv;
591
592
    /* If queue is empty NULL will be returned */
593
0
    return g_queue_peek_tail(priv->parent_q);
594
0
}
595
596
bool
597
pcmk__output_get_legacy_xml(pcmk__output_t *out)
598
0
{
599
0
    private_data_t *priv = NULL;
600
601
0
    pcmk__assert(out != NULL);
602
603
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
604
0
        return false;
605
0
    }
606
607
0
    pcmk__assert(out->priv != NULL);
608
609
0
    priv = out->priv;
610
0
    return priv->legacy_xml;
611
0
}
612
613
void
614
pcmk__output_set_legacy_xml(pcmk__output_t *out)
615
0
{
616
0
    private_data_t *priv = NULL;
617
618
0
    pcmk__assert(out != NULL);
619
620
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
621
0
        return;
622
0
    }
623
624
0
    pcmk__assert(out->priv != NULL);
625
626
0
    priv = out->priv;
627
0
    priv->legacy_xml = true;
628
0
}
629
630
void
631
pcmk__output_enable_list_element(pcmk__output_t *out)
632
0
{
633
0
    private_data_t *priv = NULL;
634
635
0
    pcmk__assert(out != NULL);
636
637
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
638
0
        return;
639
0
    }
640
641
0
    pcmk__assert(out->priv != NULL);
642
643
0
    priv = out->priv;
644
    priv->list_element = true;
645
0
}