1 /*
2  * Copyright 2019-2021 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 <glib.h>
17 
18 #include <crm/common/xml.h>
19 
20 static gboolean legacy_xml = FALSE;
21 static gboolean simple_list = FALSE;
22 static gboolean substitute = FALSE;
23 
24 GOptionEntry pcmk__xml_output_entries[] = {
25     { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml,
26       NULL,
27       NULL },
28     { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list,
29       NULL,
30       NULL },
31     { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute,
32       NULL,
33       NULL },
34 
35     { NULL }
36 };
37 
38 typedef struct subst_s {
39     const char *from;
40     const char *to;
41 } subst_t;
42 
43 static subst_t substitutions[] = {
44     { "Active Resources",                               "resources" },
45     { "Allocation Scores",                              "allocations" },
46     { "Allocation Scores and Utilization Information",  "allocations_utilizations" },
47     { "Cluster Summary",                                "summary" },
48     { "Current cluster status",                         "cluster_status" },
49     { "Executing Cluster Transition",                   "transition" },
50     { "Failed Resource Actions",                        "failures" },
51     { "Fencing History",                                "fence_history" },
52     { "Full List of Resources",                         "resources" },
53     { "Inactive Resources",                             "resources" },
54     { "Migration Summary",                              "node_history" },
55     { "Negative Location Constraints",                  "bans" },
56     { "Node Attributes",                                "node_attributes" },
57     { "Operations",                                     "node_history" },
58     { "Resource Config",                                "resource_config" },
59     { "Resource Operations",                            "operations" },
60     { "Revised Cluster Status",                         "revised_cluster_status" },
61     { "Transition Summary",                             "actions" },
62     { "Utilization Information",                        "utilizations" },
63 
64     { NULL, NULL }
65 };
66 
67 /* The first several elements of this struct must be the same as the first
68  * several elements of private_data_s in lib/common/output_html.c.  That
69  * struct gets passed to a bunch of the pcmk__output_xml_* functions which
70  * assume an XML private_data_s.  Keeping them laid out the same means this
71  * still works.
72  */
73 typedef struct private_data_s {
74     /* Begin members that must match the HTML version */
75     xmlNode *root;
76     GQueue *parent_q;
77     GSList *errors;
78     /* End members that must match the HTML version */
79     bool legacy_xml;
80 } private_data_t;
81 
82 static void
xml_free_priv(pcmk__output_t * out)83 xml_free_priv(pcmk__output_t *out) {
84     private_data_t *priv = out->priv;
85 
86     if (priv == NULL) {
87         return;
88     }
89 
90     free_xml(priv->root);
91     g_queue_free(priv->parent_q);
92     g_slist_free(priv->errors);
93     free(priv);
94     out->priv = NULL;
95 }
96 
97 static bool
xml_init(pcmk__output_t * out)98 xml_init(pcmk__output_t *out) {
99     private_data_t *priv = NULL;
100 
101     /* If xml_init was previously called on this output struct, just return. */
102     if (out->priv != NULL) {
103         return true;
104     } else {
105         out->priv = calloc(1, sizeof(private_data_t));
106         if (out->priv == NULL) {
107             return false;
108         }
109 
110         priv = out->priv;
111     }
112 
113     if (legacy_xml) {
114         priv->root = create_xml_node(NULL, "crm_mon");
115         crm_xml_add(priv->root, "version", PACEMAKER_VERSION);
116     } else {
117         priv->root = create_xml_node(NULL, "pacemaker-result");
118         crm_xml_add(priv->root, "api-version", PCMK__API_VERSION);
119 
120         if (out->request != NULL) {
121             crm_xml_add(priv->root, "request", out->request);
122         }
123     }
124 
125     priv->parent_q = g_queue_new();
126     priv->errors = NULL;
127     g_queue_push_tail(priv->parent_q, priv->root);
128 
129     /* Copy this from the file-level variable.  This means that it is only settable
130      * as a command line option, and that pcmk__output_new must be called after all
131      * command line processing is completed.
132      */
133     priv->legacy_xml = legacy_xml;
134 
135     return true;
136 }
137 
138 static void
add_error_node(gpointer data,gpointer user_data)139 add_error_node(gpointer data, gpointer user_data) {
140     char *str = (char *) data;
141     xmlNodePtr node = (xmlNodePtr) user_data;
142     pcmk_create_xml_text_node(node, "error", str);
143 }
144 
145 static void
xml_finish(pcmk__output_t * out,crm_exit_t exit_status,bool print,void ** copy_dest)146 xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
147     private_data_t *priv = NULL;
148     xmlNodePtr node;
149 
150     CRM_ASSERT(out != NULL);
151     priv = out->priv;
152 
153     /* If root is NULL, xml_init failed and we are being called from pcmk__output_free
154      * in the pcmk__output_new path.
155      */
156     if (priv == NULL || priv->root == NULL) {
157         return;
158     }
159 
160     if (legacy_xml) {
161         GSList *node = priv->errors;
162 
163         if (exit_status != CRM_EX_OK) {
164             fprintf(stderr, "%s\n", crm_exit_str(exit_status));
165         }
166 
167         while (node != NULL) {
168             fprintf(stderr, "%s\n", (char *) node->data);
169             node = node->next;
170         }
171     } else {
172         char *rc_as_str = pcmk__itoa(exit_status);
173 
174         node = create_xml_node(priv->root, "status");
175         pcmk__xe_set_props(node, "code", rc_as_str,
176                            "message", crm_exit_str(exit_status),
177                            NULL);
178 
179         if (g_slist_length(priv->errors) > 0) {
180             xmlNodePtr errors_node = create_xml_node(node, "errors");
181             g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
182         }
183 
184         free(rc_as_str);
185     }
186 
187     if (print) {
188         char *buf = dump_xml_formatted_with_text(priv->root);
189         fprintf(out->dest, "%s", buf);
190         fflush(out->dest);
191         free(buf);
192     }
193 
194     if (copy_dest != NULL) {
195         *copy_dest = copy_xml(priv->root);
196     }
197 }
198 
199 static void
xml_reset(pcmk__output_t * out)200 xml_reset(pcmk__output_t *out) {
201     CRM_ASSERT(out != NULL);
202 
203     out->dest = freopen(NULL, "w", out->dest);
204     CRM_ASSERT(out->dest != NULL);
205 
206     xml_free_priv(out);
207     xml_init(out);
208 }
209 
210 static void
xml_subprocess_output(pcmk__output_t * out,int exit_status,const char * proc_stdout,const char * proc_stderr)211 xml_subprocess_output(pcmk__output_t *out, int exit_status,
212                       const char *proc_stdout, const char *proc_stderr) {
213     xmlNodePtr node, child_node;
214     char *rc_as_str = NULL;
215 
216     CRM_ASSERT(out != NULL);
217 
218     rc_as_str = pcmk__itoa(exit_status);
219 
220     node = pcmk__output_xml_create_parent(out, "command",
221                                           "code", rc_as_str,
222                                           NULL);
223 
224     if (proc_stdout != NULL) {
225         child_node = pcmk_create_xml_text_node(node, "output", proc_stdout);
226         crm_xml_add(child_node, "source", "stdout");
227     }
228 
229     if (proc_stderr != NULL) {
230         child_node = pcmk_create_xml_text_node(node, "output", proc_stderr);
231         crm_xml_add(child_node, "source", "stderr");
232     }
233 
234     pcmk__output_xml_add_node(out, node);
235     free(rc_as_str);
236 }
237 
238 static void
xml_version(pcmk__output_t * out,bool extended)239 xml_version(pcmk__output_t *out, bool extended) {
240     CRM_ASSERT(out != NULL);
241 
242     pcmk__output_create_xml_node(out, "version",
243                                  "program", "Pacemaker",
244                                  "version", PACEMAKER_VERSION,
245                                  "author", "Andrew Beekhof",
246                                  "build", BUILD_VERSION,
247                                  "features", CRM_FEATURES,
248                                  NULL);
249 }
250 
251 G_GNUC_PRINTF(2, 3)
252 static void
xml_err(pcmk__output_t * out,const char * format,...)253 xml_err(pcmk__output_t *out, const char *format, ...) {
254     private_data_t *priv = NULL;
255     int len = 0;
256     char *buf = NULL;
257     va_list ap;
258 
259     CRM_ASSERT(out != NULL && out->priv != NULL);
260     priv = out->priv;
261 
262     va_start(ap, format);
263     len = vasprintf(&buf, format, ap);
264     CRM_ASSERT(len > 0);
265     va_end(ap);
266 
267     priv->errors = g_slist_append(priv->errors, buf);
268 }
269 
270 G_GNUC_PRINTF(2, 3)
271 static int
xml_info(pcmk__output_t * out,const char * format,...)272 xml_info(pcmk__output_t *out, const char *format, ...) {
273     return pcmk_rc_no_output;
274 }
275 
276 static void
xml_output_xml(pcmk__output_t * out,const char * name,const char * buf)277 xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
278     xmlNodePtr parent = NULL;
279     xmlNodePtr cdata_node = NULL;
280 
281     CRM_ASSERT(out != NULL);
282 
283     parent = pcmk__output_create_xml_node(out, name, NULL);
284     cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf));
285     xmlAddChild(parent, cdata_node);
286 }
287 
288 G_GNUC_PRINTF(4, 5)
289 static void
xml_begin_list(pcmk__output_t * out,const char * singular_noun,const char * plural_noun,const char * format,...)290 xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
291                const char *format, ...) {
292     va_list ap;
293     char *name = NULL;
294     char *buf = NULL;
295     int len;
296 
297     CRM_ASSERT(out != NULL);
298 
299     va_start(ap, format);
300     len = vasprintf(&buf, format, ap);
301     CRM_ASSERT(len >= 0);
302     va_end(ap);
303 
304     if (substitute) {
305         for (subst_t *s = substitutions; s->from != NULL; s++) {
306             if (!strcmp(s->from, buf)) {
307                 name = g_strdup(s->to);
308                 break;
309             }
310         }
311     }
312 
313     if (name == NULL) {
314         name = g_ascii_strdown(buf, -1);
315     }
316 
317     if (legacy_xml || simple_list) {
318         pcmk__output_xml_create_parent(out, name, NULL);
319     } else {
320         pcmk__output_xml_create_parent(out, "list",
321                                        "name", name,
322                                        NULL);
323     }
324 
325     g_free(name);
326     free(buf);
327 }
328 
329 G_GNUC_PRINTF(3, 4)
330 static void
xml_list_item(pcmk__output_t * out,const char * name,const char * format,...)331 xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
332     xmlNodePtr item_node = NULL;
333     va_list ap;
334     char *buf = NULL;
335     int len;
336 
337     CRM_ASSERT(out != NULL);
338 
339     va_start(ap, format);
340     len = vasprintf(&buf, format, ap);
341     CRM_ASSERT(len >= 0);
342     va_end(ap);
343 
344     item_node = pcmk__output_create_xml_text_node(out, "item", buf);
345 
346     if (name != NULL) {
347         crm_xml_add(item_node, "name", name);
348     }
349 
350     free(buf);
351 }
352 
353 static void
xml_increment_list(pcmk__output_t * out)354 xml_increment_list(pcmk__output_t *out) {
355     /* This function intentially left blank */
356 }
357 
358 static void
xml_end_list(pcmk__output_t * out)359 xml_end_list(pcmk__output_t *out) {
360     private_data_t *priv = NULL;
361 
362     CRM_ASSERT(out != NULL && out->priv != NULL);
363     priv = out->priv;
364 
365     if (priv->legacy_xml || simple_list) {
366         g_queue_pop_tail(priv->parent_q);
367     } else {
368         char *buf = NULL;
369         xmlNodePtr node;
370 
371         node = g_queue_pop_tail(priv->parent_q);
372         buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
373         crm_xml_add(node, "count", buf);
374         free(buf);
375     }
376 }
377 
378 static bool
xml_is_quiet(pcmk__output_t * out)379 xml_is_quiet(pcmk__output_t *out) {
380     return false;
381 }
382 
383 static void
xml_spacer(pcmk__output_t * out)384 xml_spacer(pcmk__output_t *out) {
385     /* This function intentionally left blank */
386 }
387 
388 static void
xml_progress(pcmk__output_t * out,bool end)389 xml_progress(pcmk__output_t *out, bool end) {
390     /* This function intentionally left blank */
391 }
392 
393 pcmk__output_t *
pcmk__mk_xml_output(char ** argv)394 pcmk__mk_xml_output(char **argv) {
395     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
396 
397     if (retval == NULL) {
398         return NULL;
399     }
400 
401     retval->fmt_name = "xml";
402     retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
403 
404     retval->init = xml_init;
405     retval->free_priv = xml_free_priv;
406     retval->finish = xml_finish;
407     retval->reset = xml_reset;
408 
409     retval->register_message = pcmk__register_message;
410     retval->message = pcmk__call_message;
411 
412     retval->subprocess_output = xml_subprocess_output;
413     retval->version = xml_version;
414     retval->info = xml_info;
415     retval->err = xml_err;
416     retval->output_xml = xml_output_xml;
417 
418     retval->begin_list = xml_begin_list;
419     retval->list_item = xml_list_item;
420     retval->increment_list = xml_increment_list;
421     retval->end_list = xml_end_list;
422 
423     retval->is_quiet = xml_is_quiet;
424     retval->spacer = xml_spacer;
425     retval->progress = xml_progress;
426     retval->prompt = pcmk__text_prompt;
427 
428     return retval;
429 }
430 
431 xmlNodePtr
pcmk__output_xml_create_parent(pcmk__output_t * out,const char * name,...)432 pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) {
433     va_list args;
434     xmlNodePtr node = NULL;
435 
436     CRM_ASSERT(out != NULL);
437 
438     node = pcmk__output_create_xml_node(out, name, NULL);
439 
440     va_start(args, name);
441     pcmk__xe_set_propv(node, args);
442     va_end(args);
443 
444     pcmk__output_xml_push_parent(out, node);
445     return node;
446 }
447 
448 void
pcmk__output_xml_add_node(pcmk__output_t * out,xmlNodePtr node)449 pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node) {
450     private_data_t *priv = NULL;
451 
452     CRM_ASSERT(out != NULL && out->priv != NULL);
453     CRM_ASSERT(node != NULL);
454 
455     if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
456         return;
457     }
458 
459     priv = out->priv;
460 
461     xmlAddChild(g_queue_peek_tail(priv->parent_q), node);
462 }
463 
464 xmlNodePtr
pcmk__output_create_xml_node(pcmk__output_t * out,const char * name,...)465 pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) {
466     xmlNodePtr node = NULL;
467     private_data_t *priv = NULL;
468     va_list args;
469 
470     CRM_ASSERT(out != NULL && out->priv != NULL);
471     CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL));
472 
473     priv = out->priv;
474 
475     node = create_xml_node(g_queue_peek_tail(priv->parent_q), name);
476     va_start(args, name);
477     pcmk__xe_set_propv(node, args);
478     va_end(args);
479 
480     return node;
481 }
482 
483 xmlNodePtr
pcmk__output_create_xml_text_node(pcmk__output_t * out,const char * name,const char * content)484 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
485     xmlNodePtr node = NULL;
486 
487     CRM_ASSERT(out != NULL);
488 
489     node = pcmk__output_create_xml_node(out, name, NULL);
490     xmlNodeSetContent(node, (pcmkXmlStr) content);
491     return node;
492 }
493 
494 void
pcmk__output_xml_push_parent(pcmk__output_t * out,xmlNodePtr parent)495 pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) {
496     private_data_t *priv = NULL;
497 
498     CRM_ASSERT(out != NULL && out->priv != NULL);
499     CRM_ASSERT(parent != NULL);
500 
501     if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
502         return;
503     }
504 
505     priv = out->priv;
506 
507     g_queue_push_tail(priv->parent_q, parent);
508 }
509 
510 void
pcmk__output_xml_pop_parent(pcmk__output_t * out)511 pcmk__output_xml_pop_parent(pcmk__output_t *out) {
512     private_data_t *priv = NULL;
513 
514     CRM_ASSERT(out != NULL && out->priv != NULL);
515 
516     if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
517         return;
518     }
519 
520     priv = out->priv;
521 
522     CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
523     g_queue_pop_tail(priv->parent_q);
524 }
525 
526 xmlNodePtr
pcmk__output_xml_peek_parent(pcmk__output_t * out)527 pcmk__output_xml_peek_parent(pcmk__output_t *out) {
528     private_data_t *priv = NULL;
529 
530     CRM_ASSERT(out != NULL && out->priv != NULL);
531     CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL));
532 
533     priv = out->priv;
534 
535     /* If queue is empty NULL will be returned */
536     return g_queue_peek_tail(priv->parent_q);
537 }
538