pacemaker  2.1.4-dc6eb4362
Scalable High-Availability cluster resource manager
output_xml.c
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2022 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;
81 
82 static void
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
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
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
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
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
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
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 and the "
246  "Pacemaker project contributors",
247  "build", BUILD_VERSION,
248  "features", CRM_FEATURES,
249  NULL);
250 }
251 
252 G_GNUC_PRINTF(2, 3)
253 static void
254 xml_err(pcmk__output_t *out, const char *format, ...) {
255  private_data_t *priv = NULL;
256  int len = 0;
257  char *buf = NULL;
258  va_list ap;
259 
260  CRM_ASSERT(out != NULL && out->priv != NULL);
261  priv = out->priv;
262 
263  va_start(ap, format);
264  len = vasprintf(&buf, format, ap);
265  CRM_ASSERT(len > 0);
266  va_end(ap);
267 
268  priv->errors = g_slist_append(priv->errors, buf);
269 }
270 
271 G_GNUC_PRINTF(2, 3)
272 static int
273 xml_info(pcmk__output_t *out, const char *format, ...) {
274  return pcmk_rc_no_output;
275 }
276 
277 static void
278 xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
279  xmlNodePtr parent = NULL;
280  xmlNodePtr cdata_node = NULL;
281 
282  CRM_ASSERT(out != NULL);
283 
285  cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf));
286  xmlAddChild(parent, cdata_node);
287 }
288 
289 G_GNUC_PRINTF(4, 5)
290 static void
291 xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
292  const char *format, ...) {
293  va_list ap;
294  char *name = NULL;
295  char *buf = NULL;
296  int len;
297 
298  CRM_ASSERT(out != NULL);
299 
300  va_start(ap, format);
301  len = vasprintf(&buf, format, ap);
302  CRM_ASSERT(len >= 0);
303  va_end(ap);
304 
305  if (substitute) {
306  for (subst_t *s = substitutions; s->from != NULL; s++) {
307  if (!strcmp(s->from, buf)) {
308  name = g_strdup(s->to);
309  break;
310  }
311  }
312  }
313 
314  if (name == NULL) {
315  name = g_ascii_strdown(buf, -1);
316  }
317 
318  if (legacy_xml || simple_list) {
320  } else {
321  pcmk__output_xml_create_parent(out, "list",
322  "name", name,
323  NULL);
324  }
325 
326  g_free(name);
327  free(buf);
328 }
329 
330 G_GNUC_PRINTF(3, 4)
331 static void
332 xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
333  xmlNodePtr item_node = NULL;
334  va_list ap;
335  char *buf = NULL;
336  int len;
337 
338  CRM_ASSERT(out != NULL);
339 
340  va_start(ap, format);
341  len = vasprintf(&buf, format, ap);
342  CRM_ASSERT(len >= 0);
343  va_end(ap);
344 
345  item_node = pcmk__output_create_xml_text_node(out, "item", buf);
346 
347  if (name != NULL) {
348  crm_xml_add(item_node, "name", name);
349  }
350 
351  free(buf);
352 }
353 
354 static void
355 xml_increment_list(pcmk__output_t *out) {
356  /* This function intentially left blank */
357 }
358 
359 static void
360 xml_end_list(pcmk__output_t *out) {
361  private_data_t *priv = NULL;
362 
363  CRM_ASSERT(out != NULL && out->priv != NULL);
364  priv = out->priv;
365 
366  if (priv->legacy_xml || simple_list) {
367  g_queue_pop_tail(priv->parent_q);
368  } else {
369  char *buf = NULL;
370  xmlNodePtr node;
371 
372  node = g_queue_pop_tail(priv->parent_q);
373  buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
374  crm_xml_add(node, "count", buf);
375  free(buf);
376  }
377 }
378 
379 static bool
380 xml_is_quiet(pcmk__output_t *out) {
381  return false;
382 }
383 
384 static void
385 xml_spacer(pcmk__output_t *out) {
386  /* This function intentionally left blank */
387 }
388 
389 static void
390 xml_progress(pcmk__output_t *out, bool end) {
391  /* This function intentionally left blank */
392 }
393 
395 pcmk__mk_xml_output(char **argv) {
396  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
397 
398  if (retval == NULL) {
399  return NULL;
400  }
401 
402  retval->fmt_name = "xml";
403  retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
404 
405  retval->init = xml_init;
406  retval->free_priv = xml_free_priv;
407  retval->finish = xml_finish;
408  retval->reset = xml_reset;
409 
411  retval->message = pcmk__call_message;
412 
413  retval->subprocess_output = xml_subprocess_output;
414  retval->version = xml_version;
415  retval->info = xml_info;
416  retval->err = xml_err;
417  retval->output_xml = xml_output_xml;
418 
419  retval->begin_list = xml_begin_list;
420  retval->list_item = xml_list_item;
421  retval->increment_list = xml_increment_list;
422  retval->end_list = xml_end_list;
423 
424  retval->is_quiet = xml_is_quiet;
425  retval->spacer = xml_spacer;
426  retval->progress = xml_progress;
427  retval->prompt = pcmk__text_prompt;
428 
429  return retval;
430 }
431 
432 xmlNodePtr
434  va_list args;
435  xmlNodePtr node = NULL;
436 
437  CRM_ASSERT(out != NULL);
438 
439  node = pcmk__output_create_xml_node(out, name, NULL);
440 
441  va_start(args, name);
442  pcmk__xe_set_propv(node, args);
443  va_end(args);
444 
445  pcmk__output_xml_push_parent(out, node);
446  return node;
447 }
448 
449 void
451  private_data_t *priv = NULL;
452 
453  CRM_ASSERT(out != NULL && out->priv != NULL);
454  CRM_ASSERT(node != NULL);
455 
456  if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
457  return;
458  }
459 
460  priv = out->priv;
461 
462  xmlAddChild(g_queue_peek_tail(priv->parent_q), node);
463 }
464 
465 xmlNodePtr
467  xmlNodePtr node = NULL;
468  private_data_t *priv = NULL;
469  va_list args;
470 
471  CRM_ASSERT(out != NULL && out->priv != NULL);
472  CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL));
473 
474  priv = out->priv;
475 
476  node = create_xml_node(g_queue_peek_tail(priv->parent_q), name);
477  va_start(args, name);
478  pcmk__xe_set_propv(node, args);
479  va_end(args);
480 
481  return node;
482 }
483 
484 xmlNodePtr
485 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
486  xmlNodePtr node = NULL;
487 
488  CRM_ASSERT(out != NULL);
489 
490  node = pcmk__output_create_xml_node(out, name, NULL);
491  xmlNodeSetContent(node, (pcmkXmlStr) content);
492  return node;
493 }
494 
495 void
497  private_data_t *priv = NULL;
498 
499  CRM_ASSERT(out != NULL && out->priv != NULL);
500  CRM_ASSERT(parent != NULL);
501 
502  if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
503  return;
504  }
505 
506  priv = out->priv;
507 
508  g_queue_push_tail(priv->parent_q, parent);
509 }
510 
511 void
513  private_data_t *priv = NULL;
514 
515  CRM_ASSERT(out != NULL && out->priv != NULL);
516 
517  if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) {
518  return;
519  }
520 
521  priv = out->priv;
522 
523  CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
524  g_queue_pop_tail(priv->parent_q);
525 }
526 
527 xmlNodePtr
529  private_data_t *priv = NULL;
530 
531  CRM_ASSERT(out != NULL && out->priv != NULL);
532  CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL));
533 
534  priv = out->priv;
535 
536  /* If queue is empty NULL will be returned */
537  return g_queue_peek_tail(priv->parent_q);
538 }
void(* end_list)(pcmk__output_t *out)
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name,...)
Definition: output_xml.c:466
char data[0]
Definition: cpg.c:55
void void void void void pcmk__text_prompt(const char *prompt, bool echo, char **dest)
Definition: output_text.c:390
const char * name
Definition: cib.c:24
int(* message)(pcmk__output_t *out, const char *message_id,...)
struct subst_s subst_t
const char * fmt_name
The name of this output formatter.
bool(* is_quiet)(pcmk__output_t *out)
void(* spacer)(pcmk__output_t *out)
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: nvpair.c:323
void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
Definition: output.c:139
xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out)
Definition: output_xml.c:528
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:485
int(*) void(*) void(* output_xml)(pcmk__output_t *out, const char *name, const char *buf)
enum crm_exit_e crm_exit_t
void pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
Definition: xml.c:2956
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition: output_xml.c:512
#define PACEMAKER_VERSION
Definition: config.h:504
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:658
int pcmk__call_message(pcmk__output_t *out, const char *message_id,...)
Definition: output.c:119
void(* prompt)(const char *prompt, bool echo, char **dest)
void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent)
Definition: output_xml.c:496
void * priv
Implementation-specific private data.
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
#define PCMK__API_VERSION
const char * crm_exit_str(crm_exit_t exit_code)
Definition: results.c:506
void(* register_message)(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
xmlNode * pcmk_create_xml_text_node(xmlNode *parent, const char *name, const char *content)
Definition: xml.c:721
#define BUILD_VERSION
Definition: config.h:8
struct private_data_s private_data_t
void(* free_priv)(pcmk__output_t *out)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
bool(* init)(pcmk__output_t *out)
int(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
Wrappers for and extensions to libxml2.
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:696
Success.
Definition: results.h:239
void free_xml(xmlNode *child)
Definition: xml.c:824
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:955
void pcmk__xe_set_props(xmlNodePtr node,...) G_GNUC_NULL_TERMINATED
Definition: xml.c:2974
FILE * dest
Where output should be written.
const xmlChar * pcmkXmlStr
Definition: xml.h:52
#define CRM_ASSERT(expr)
Definition: results.h:42
void(*) void(* list_item)(pcmk__output_t *out, const char *name, const char *format,...) G_GNUC_PRINTF(3
gchar * request
A copy of the request that generated this output.
This structure contains everything that makes up a single output formatter.
struct private_data_s private_data_t
void(* version)(pcmk__output_t *out, bool extended)
void(* begin_list)(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format,...) G_GNUC_PRINTF(4
void(* reset)(pcmk__output_t *out)
void(* progress)(pcmk__output_t *out, bool end)
pcmk__output_t * pcmk__mk_xml_output(char **argv)
Definition: output_xml.c:395
const char * parent
Definition: cib.c:25
#define CRM_FEATURES
Definition: config.h:33
GOptionEntry pcmk__xml_output_entries[]
Definition: output_xml.c:24
void(*) void(*) void(* increment_list)(pcmk__output_t *out)
char * dump_xml_formatted_with_text(xmlNode *msg)
Definition: xml.c:1995
xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name,...)
Definition: output_xml.c:433
void(* subprocess_output)(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr)
void pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node)
Definition: output_xml.c:450