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