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