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