pacemaker  2.1.2-ada5c3b36
Scalable High-Availability cluster resource manager
output_xml.c
Go to the documentation of this file.
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;
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",
246  "build", BUILD_VERSION,
247  "features", CRM_FEATURES,
248  NULL);
249 }
250 
251 G_GNUC_PRINTF(2, 3)
252 static void
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
272 xml_info(pcmk__output_t *out, const char *format, ...) {
273  return pcmk_rc_no_output;
274 }
275 
276 static void
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
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) {
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
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
354 xml_increment_list(pcmk__output_t *out) {
355  /* This function intentially left blank */
356 }
357 
358 static void
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
379 xml_is_quiet(pcmk__output_t *out) {
380  return false;
381 }
382 
383 static void
384 xml_spacer(pcmk__output_t *out) {
385  /* This function intentionally left blank */
386 }
387 
388 static void
389 xml_progress(pcmk__output_t *out, bool end) {
390  /* This function intentionally left blank */
391 }
392 
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 
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
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
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
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
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
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
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
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 }
void(* end_list)(pcmk__output_t *out)
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name,...)
Definition: output_xml.c:465
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:389
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:527
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:484
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:2951
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:511
#define PACEMAKER_VERSION
Definition: config.h:484
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:495
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:540
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:2969
FILE * dest
Where output should be written.
const xmlChar * pcmkXmlStr
Definition: xml.h:51
#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:394
char * name
Definition: pcmk_fence.c:31
#define CRM_FEATURES
Definition: config.h:35
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:1990
xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name,...)
Definition: output_xml.c:432
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:449