Coverage Report

Created: 2026-10-30 06:56

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
xml_free_priv(pcmk__output_t *out)
131
0
{
132
0
    private_data_t *priv = NULL;
133
134
0
    if (out == NULL || out->priv == NULL) {
135
0
        return;
136
0
    }
137
138
0
    priv = out->priv;
139
140
0
    if (has_root_node(out)) {
141
0
        pcmk__xml_free(priv->root);
142
        /* The elements of parent_q are xmlNodes that are a part of the
143
         * priv->root document, so the above line already frees them.  Don't
144
         * call g_queue_free_full here.
145
         */
146
0
        g_queue_free(priv->parent_q);
147
0
    }
148
149
0
    g_slist_free_full(priv->errors, free);
150
0
    g_clear_pointer(&out->priv, free);
151
0
}
152
153
static bool
154
xml_init(pcmk__output_t *out)
155
0
{
156
0
    private_data_t *priv = NULL;
157
158
0
    pcmk__assert(out != NULL);
159
160
    /* If xml_init was previously called on this output struct, just return. */
161
0
    if (out->priv != NULL) {
162
0
        return true;
163
0
    } else {
164
0
        out->priv = calloc(1, sizeof(private_data_t));
165
0
        if (out->priv == NULL) {
166
0
            return false;
167
0
        }
168
169
0
        priv = out->priv;
170
0
    }
171
172
0
    priv->errors = NULL;
173
174
0
    return true;
175
0
}
176
177
static void
178
add_error_node(void *data, void *user_data)
179
0
{
180
0
    const char *str = (const char *) data;
181
0
    xmlNodePtr node = (xmlNodePtr) user_data;
182
183
0
    node = pcmk__xe_create(node, PCMK_XE_ERROR);
184
0
    pcmk__xe_set_content(node, "%s", str);
185
0
}
186
187
static void
188
xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print,
189
           void **copy_dest)
190
0
{
191
0
    private_data_t *priv = NULL;
192
0
    xmlNodePtr node;
193
194
0
    pcmk__assert(out != NULL);
195
0
    priv = out->priv;
196
197
0
    if (priv == NULL) {
198
0
        return;
199
0
    }
200
201
0
    add_root_node(out);
202
203
0
    if (priv->legacy_xml) {
204
0
        GSList *node = priv->errors;
205
206
0
        if (exit_status != CRM_EX_OK) {
207
0
            fprintf(stderr, "%s\n", crm_exit_str(exit_status));
208
0
        }
209
210
0
        while (node != NULL) {
211
0
            fprintf(stderr, "%s\n", (char *) node->data);
212
0
            node = node->next;
213
0
        }
214
0
    } else {
215
0
        node = pcmk__xe_create(priv->root, PCMK_XE_STATUS);
216
0
        pcmk__xe_set_int(node, PCMK_XA_CODE, exit_status);
217
0
        pcmk__xe_set(node, PCMK_XA_MESSAGE, crm_exit_str(exit_status));
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, (void *) errors_node);
222
0
        }
223
0
    }
224
225
0
    if (print) {
226
0
        pcmk__xml2fd(fileno(out->dest), priv->root);
227
0
    }
228
229
0
    if (copy_dest != NULL) {
230
0
        *copy_dest = pcmk__xml_copy(NULL, priv->root);
231
0
    }
232
0
}
233
234
static void
235
xml_reset(pcmk__output_t *out)
236
0
{
237
0
    pcmk__assert(out != NULL);
238
239
0
    out->dest = freopen(NULL, "w", out->dest);
240
0
    pcmk__assert(out->dest != NULL);
241
242
0
    xml_free_priv(out);
243
0
    xml_init(out);
244
0
}
245
246
static void
247
xml_subprocess_output(pcmk__output_t *out, int exit_status,
248
                      const char *proc_stdout, const char *proc_stderr)
249
0
{
250
0
    xmlNodePtr node, child_node;
251
252
0
    pcmk__assert(out != NULL);
253
254
0
    node = pcmk__output_xml_create_parent(out, PCMK_XE_COMMAND);
255
0
    pcmk__xe_set_int(node, PCMK_XA_CODE, exit_status);
256
257
0
    if (proc_stdout != NULL) {
258
0
        child_node = pcmk__xe_create(node, PCMK_XE_OUTPUT);
259
0
        pcmk__xe_set_content(child_node, "%s", proc_stdout);
260
0
        pcmk__xe_set(child_node, PCMK_XA_SOURCE, "stdout");
261
0
    }
262
263
0
    if (proc_stderr != NULL) {
264
0
        child_node = pcmk__xe_create(node, PCMK_XE_OUTPUT);
265
0
        pcmk__xe_set_content(child_node, "%s", proc_stderr);
266
0
        pcmk__xe_set(child_node, PCMK_XA_SOURCE, "stderr");
267
0
    }
268
0
}
269
270
static void
271
xml_version(pcmk__output_t *out)
272
0
{
273
0
    xmlNode *xml = NULL;
274
275
0
    pcmk__assert(out != NULL);
276
277
0
    xml = pcmk__output_create_xml_node(out, PCMK_XE_VERSION);
278
0
    pcmk__xe_set(xml, PCMK_XA_PROGRAM, "Pacemaker");
279
0
    pcmk__xe_set(xml, PCMK_XA_VERSION, PACEMAKER_VERSION);
280
0
    pcmk__xe_set(xml, PCMK_XA_AUTHOR,
281
0
                 "Andrew Beekhof and the Pacemaker project contributors");
282
0
    pcmk__xe_set(xml, PCMK_XA_BUILD, BUILD_VERSION);
283
0
    pcmk__xe_set(xml, PCMK_XA_FEATURES, CRM_FEATURES);
284
0
}
285
286
G_GNUC_PRINTF(2, 3)
287
static void
288
xml_err(pcmk__output_t *out, const char *format, ...)
289
0
{
290
0
    private_data_t *priv = NULL;
291
0
    int len = 0;
292
0
    char *buf = NULL;
293
0
    va_list ap;
294
295
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
296
0
    priv = out->priv;
297
298
0
    add_root_node(out);
299
300
0
    va_start(ap, format);
301
0
    len = vasprintf(&buf, format, ap);
302
0
    pcmk__assert(len > 0);
303
0
    va_end(ap);
304
305
0
    priv->errors = g_slist_append(priv->errors, buf);
306
0
}
307
308
G_GNUC_PRINTF(2, 3)
309
static int
310
xml_info(pcmk__output_t *out, const char *format, ...)
311
0
{
312
0
    return pcmk_rc_no_output;
313
0
}
314
315
static void
316
xml_output_xml(pcmk__output_t *out, const char *name, const char *buf)
317
0
{
318
0
    xmlNodePtr parent = NULL;
319
0
    xmlNodePtr cdata_node = NULL;
320
321
0
    pcmk__assert(out != NULL);
322
323
0
    parent = pcmk__output_create_xml_node(out, name);
324
0
    if (parent == NULL) {
325
0
        return;
326
0
    }
327
0
    cdata_node = xmlNewCDataBlock(parent->doc, (const xmlChar *) buf,
328
0
                                  strlen(buf));
329
0
    xmlAddChild(parent, cdata_node);
330
0
}
331
332
G_GNUC_PRINTF(4, 5)
333
static void
334
xml_begin_list(pcmk__output_t *out, const char *singular_noun,
335
               const char *plural_noun, const char *format, ...)
336
0
{
337
0
    va_list ap;
338
0
    char *name = NULL;
339
0
    char *buf = NULL;
340
0
    int len;
341
0
    private_data_t *priv = NULL;
342
343
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
344
0
    priv = out->priv;
345
346
0
    va_start(ap, format);
347
0
    len = vasprintf(&buf, format, ap);
348
0
    pcmk__assert(len >= 0);
349
0
    va_end(ap);
350
351
0
    for (const subst_t *s = substitutions; s->from != NULL; s++) {
352
0
        if (strcmp(s->from, buf) == 0) {
353
0
            name = g_strdup(s->to);
354
0
            break;
355
0
        }
356
0
    }
357
358
0
    if (name == NULL) {
359
0
        name = g_ascii_strdown(buf, -1);
360
0
    }
361
362
0
    if (priv->list_element) {
363
0
        xmlNode *xml = pcmk__output_xml_create_parent(out, PCMK_XE_LIST);
364
365
0
        pcmk__xe_set(xml, PCMK_XA_NAME, name);
366
367
0
    } else {
368
0
        pcmk__output_xml_create_parent(out, name);
369
0
    }
370
371
0
    g_free(name);
372
0
    free(buf);
373
0
}
374
375
G_GNUC_PRINTF(3, 4)
376
static void
377
xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...)
378
0
{
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
xml_increment_list(pcmk__output_t *out)
402
0
{
403
    /* This function intentially left blank */
404
0
}
405
406
static void
407
xml_end_list(pcmk__output_t *out)
408
0
{
409
0
    private_data_t *priv = NULL;
410
411
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
412
0
    priv = out->priv;
413
414
0
    if (priv->list_element) {
415
0
        char *buf = NULL;
416
0
        xmlNodePtr node;
417
418
        /* Do not free node here - it's still part of the document */
419
0
        node = g_queue_pop_tail(priv->parent_q);
420
0
        buf = pcmk__assert_asprintf("%lu", xmlChildElementCount(node));
421
0
        pcmk__xe_set(node, PCMK_XA_COUNT, buf);
422
0
        free(buf);
423
0
    } else {
424
        /* Do not free this result - it's still part of the document */
425
0
        g_queue_pop_tail(priv->parent_q);
426
0
    }
427
0
}
428
429
static bool
430
xml_is_quiet(pcmk__output_t *out)
431
0
{
432
0
    return false;
433
0
}
434
435
static void
436
xml_spacer(pcmk__output_t *out)
437
0
{
438
    /* This function intentionally left blank */
439
0
}
440
441
static void
442
xml_progress(pcmk__output_t *out, bool end)
443
0
{
444
    /* This function intentionally left blank */
445
0
}
446
447
pcmk__output_t *
448
pcmk__mk_xml_output(char **argv)
449
0
{
450
0
    pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
451
452
0
    if (retval == NULL) {
453
0
        return NULL;
454
0
    }
455
456
0
    retval->fmt_name = "xml";
457
0
    retval->request = pcmk__quote_cmdline(argv);
458
459
0
    retval->init = xml_init;
460
0
    retval->free_priv = xml_free_priv;
461
0
    retval->finish = xml_finish;
462
0
    retval->reset = xml_reset;
463
464
0
    retval->register_message = pcmk__register_message;
465
0
    retval->message = pcmk__call_message;
466
467
0
    retval->subprocess_output = xml_subprocess_output;
468
0
    retval->version = xml_version;
469
0
    retval->info = xml_info;
470
0
    retval->transient = xml_info;
471
0
    retval->err = xml_err;
472
0
    retval->output_xml = xml_output_xml;
473
474
0
    retval->begin_list = xml_begin_list;
475
0
    retval->list_item = xml_list_item;
476
0
    retval->increment_list = xml_increment_list;
477
0
    retval->end_list = xml_end_list;
478
479
0
    retval->is_quiet = xml_is_quiet;
480
0
    retval->spacer = xml_spacer;
481
0
    retval->progress = xml_progress;
482
0
    retval->prompt = pcmk__text_prompt;
483
484
0
    return retval;
485
0
}
486
487
xmlNode *
488
pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name)
489
0
{
490
0
    xmlNodePtr node = NULL;
491
492
0
    pcmk__assert(out != NULL);
493
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
494
495
0
    node = pcmk__output_create_xml_node(out, name);
496
497
0
    pcmk__output_xml_push_parent(out, node);
498
0
    return node;
499
0
}
500
501
void
502
pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node)
503
0
{
504
0
    private_data_t *priv = NULL;
505
0
    xmlNodePtr parent = NULL;
506
507
0
    pcmk__assert((out != NULL) && (out->priv != NULL) && (node != NULL));
508
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
509
510
0
    add_root_node(out);
511
512
0
    priv = out->priv;
513
0
    parent = g_queue_peek_tail(priv->parent_q);
514
515
    // Shouldn't happen unless the caller popped priv->root
516
0
    CRM_CHECK(parent != NULL, return);
517
518
0
    pcmk__xml_copy(parent, node);
519
0
}
520
521
xmlNode *
522
pcmk__output_create_xml_node(pcmk__output_t *out, const char *name)
523
0
{
524
0
    private_data_t *priv = NULL;
525
526
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
527
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
528
529
0
    add_root_node(out);
530
531
0
    priv = out->priv;
532
533
0
    return pcmk__xe_create(g_queue_peek_tail(priv->parent_q), name);
534
0
}
535
536
xmlNodePtr
537
pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name,
538
                                  const char *content)
539
0
{
540
0
    xmlNodePtr node = NULL;
541
542
0
    pcmk__assert(out != NULL);
543
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
544
545
0
    node = pcmk__output_create_xml_node(out, name);
546
0
    pcmk__xe_set_content(node, "%s", content);
547
0
    return node;
548
0
}
549
550
void
551
pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent)
552
0
{
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
pcmk__output_xml_pop_parent(pcmk__output_t *out)
567
0
{
568
0
    private_data_t *priv = NULL;
569
570
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
571
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
572
573
0
    add_root_node(out);
574
575
0
    priv = out->priv;
576
577
0
    pcmk__assert(g_queue_get_length(priv->parent_q) > 0);
578
    /* Do not free this result - it's still part of the document */
579
0
    g_queue_pop_tail(priv->parent_q);
580
0
}
581
582
xmlNodePtr
583
pcmk__output_xml_peek_parent(pcmk__output_t *out)
584
0
{
585
0
    private_data_t *priv = NULL;
586
587
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
588
0
    CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
589
590
0
    add_root_node(out);
591
592
0
    priv = out->priv;
593
594
    /* If queue is empty NULL will be returned */
595
0
    return g_queue_peek_tail(priv->parent_q);
596
0
}
597
598
bool
599
pcmk__output_get_legacy_xml(pcmk__output_t *out)
600
0
{
601
0
    private_data_t *priv = NULL;
602
603
0
    pcmk__assert(out != NULL);
604
605
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
606
0
        return false;
607
0
    }
608
609
0
    pcmk__assert(out->priv != NULL);
610
611
0
    priv = out->priv;
612
0
    return priv->legacy_xml;
613
0
}
614
615
void
616
pcmk__output_set_legacy_xml(pcmk__output_t *out)
617
0
{
618
0
    private_data_t *priv = NULL;
619
620
0
    pcmk__assert(out != NULL);
621
622
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
623
0
        return;
624
0
    }
625
626
0
    pcmk__assert(out->priv != NULL);
627
628
0
    priv = out->priv;
629
0
    priv->legacy_xml = true;
630
0
}
631
632
void
633
pcmk__output_enable_list_element(pcmk__output_t *out)
634
0
{
635
0
    private_data_t *priv = NULL;
636
637
0
    pcmk__assert(out != NULL);
638
639
0
    if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) {
640
0
        return;
641
0
    }
642
643
0
    pcmk__assert(out->priv != NULL);
644
645
0
    priv = out->priv;
646
    priv->list_element = true;
647
0
}