pacemaker  2.0.5-ba59be712
Scalable High-Availability cluster resource manager
output_xml.c
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2020 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 #ifndef _GNU_SOURCE
11 # define _GNU_SOURCE
12 #endif
13 
14 #ifndef PCMK__CONFIG_H
15 # define PCMK__CONFIG_H
16 # include <config.h>
17 #endif
18 
19 #include <ctype.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <glib.h>
24 
25 #include <crm/crm.h>
26 #include <crm/common/xml.h>
28 
29 static gboolean legacy_xml = FALSE;
30 static gboolean simple_list = FALSE;
31 static gboolean substitute = FALSE;
32 
33 GOptionEntry pcmk__xml_output_entries[] = {
34  { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml,
35  NULL,
36  NULL },
37  { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list,
38  NULL,
39  NULL },
40  { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute,
41  NULL,
42  NULL },
43 
44  { NULL }
45 };
46 
47 typedef struct subst_s {
48  const char *from;
49  const char *to;
50 } subst_t;
51 
52 static subst_t substitutions[] = {
53  { "Attributes", "attributes" },
54  { "Active Resources", "resources" },
55  { "Full List of Resources", "resources" },
56  { "Inactive Resources", "resources" },
57  { "Cluster Summary", "summary" },
58  { "Failed Resource Actions", "failures" },
59  { "Fencing History", "fence_history" },
60  { "Migration Summary", "node_history" },
61  { "Operations", "node_history" },
62  { "Negative Location Constraints", "bans" },
63  { "Node Attributes", "node_attributes" },
64  { "Resources", "resources" },
65  { "Tickets", "tickets" },
66 
67  { NULL, NULL }
68 };
69 
70 typedef struct private_data_s {
71  xmlNode *root;
72  GQueue *parent_q;
73  GSList *errors;
74  bool legacy_xml;
76 
77 static void
78 xml_free_priv(pcmk__output_t *out) {
79  private_data_t *priv = out->priv;
80 
81  if (priv == NULL) {
82  return;
83  }
84 
85  xmlFreeNode(priv->root);
86  g_queue_free(priv->parent_q);
87  g_slist_free(priv->errors);
88  free(priv);
89  out->priv = NULL;
90 }
91 
92 static bool
93 xml_init(pcmk__output_t *out) {
94  private_data_t *priv = NULL;
95 
96  /* If xml_init was previously called on this output struct, just return. */
97  if (out->priv != NULL) {
98  return true;
99  } else {
100  out->priv = calloc(1, sizeof(private_data_t));
101  if (out->priv == NULL) {
102  return false;
103  }
104 
105  priv = out->priv;
106  }
107 
108  if (legacy_xml) {
109  priv->root = create_xml_node(NULL, "crm_mon");
110  xmlSetProp(priv->root, (pcmkXmlStr) "version", (pcmkXmlStr) VERSION);
111  } else {
112  priv->root = create_xml_node(NULL, "pacemaker-result");
113  xmlSetProp(priv->root, (pcmkXmlStr) "api-version", (pcmkXmlStr) PCMK__API_VERSION);
114 
115  if (out->request != NULL) {
116  xmlSetProp(priv->root, (pcmkXmlStr) "request", (pcmkXmlStr) out->request);
117  }
118  }
119 
120  priv->parent_q = g_queue_new();
121  priv->errors = NULL;
122  g_queue_push_tail(priv->parent_q, priv->root);
123 
124  /* Copy this from the file-level variable. This means that it is only settable
125  * as a command line option, and that pcmk__output_new must be called after all
126  * command line processing is completed.
127  */
128  priv->legacy_xml = legacy_xml;
129 
130  return true;
131 }
132 
133 static void
134 add_error_node(gpointer data, gpointer user_data) {
135  char *str = (char *) data;
136  xmlNodePtr node = (xmlNodePtr) user_data;
137  pcmk_create_xml_text_node(node, "error", str);
138 }
139 
140 static void
141 finish_reset_common(pcmk__output_t *out, crm_exit_t exit_status, bool print) {
142  xmlNodePtr node;
143  private_data_t *priv = out->priv;
144 
145  if (legacy_xml) {
146  GSList *node = priv->errors;
147 
148  if (exit_status != CRM_EX_OK) {
149  fprintf(stderr, "%s\n", crm_exit_str(exit_status));
150  }
151 
152  while (node != NULL) {
153  fprintf(stderr, "%s\n", (char *) node->data);
154  node = node->next;
155  }
156  } else {
157  char *rc_as_str = crm_itoa(exit_status);
158 
159  node = create_xml_node(priv->root, "status");
160  xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
161  xmlSetProp(node, (pcmkXmlStr) "message", (pcmkXmlStr) crm_exit_str(exit_status));
162 
163  if (g_slist_length(priv->errors) > 0) {
164  xmlNodePtr errors_node = create_xml_node(node, "errors");
165  g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
166  }
167 
168  free(rc_as_str);
169  }
170 
171  if (print) {
172  char *buf = dump_xml_formatted_with_text(priv->root);
173  fprintf(out->dest, "%s", buf);
174  free(buf);
175  }
176 }
177 
178 static void
179 xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
180  private_data_t *priv = out->priv;
181 
182  /* If root is NULL, xml_init failed and we are being called from pcmk__output_free
183  * in the pcmk__output_new path.
184  */
185  if (priv == NULL || priv->root == NULL) {
186  return;
187  }
188 
189  finish_reset_common(out, exit_status, print);
190 
191  if (copy_dest != NULL) {
192  *copy_dest = copy_xml(priv->root);
193  }
194 }
195 
196 static void
197 xml_reset(pcmk__output_t *out) {
198  CRM_ASSERT(out != NULL);
199 
200  out->dest = freopen(NULL, "w", out->dest);
201  CRM_ASSERT(out->dest != NULL);
202 
203  if (out->priv != NULL) {
204  finish_reset_common(out, CRM_EX_OK, true);
205  }
206 
207  xml_free_priv(out);
208  xml_init(out);
209 }
210 
211 static void
212 xml_subprocess_output(pcmk__output_t *out, int exit_status,
213  const char *proc_stdout, const char *proc_stderr) {
214  xmlNodePtr node, child_node;
215  char *rc_as_str = NULL;
216 
217  rc_as_str = crm_itoa(exit_status);
218 
219  node = pcmk__output_xml_create_parent(out, "command");
220  xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
221 
222  if (proc_stdout != NULL) {
223  child_node = pcmk_create_xml_text_node(node, "output", proc_stdout);
224  xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stdout");
225  }
226 
227  if (proc_stderr != NULL) {
228  child_node = pcmk_create_xml_text_node(node, "output", proc_stderr);
229  xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stderr");
230  }
231 
232  pcmk__output_xml_add_node(out, node);
233  free(rc_as_str);
234 }
235 
236 static void
237 xml_version(pcmk__output_t *out, bool extended) {
238  xmlNodePtr node;
239  private_data_t *priv = out->priv;
240  CRM_ASSERT(priv != NULL);
241 
242  node = pcmk__output_create_xml_node(out, "version");
243  xmlSetProp(node, (pcmkXmlStr) "program", (pcmkXmlStr) "Pacemaker");
244  xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) PACEMAKER_VERSION);
245  xmlSetProp(node, (pcmkXmlStr) "author", (pcmkXmlStr) "Andrew Beekhof");
246  xmlSetProp(node, (pcmkXmlStr) "build", (pcmkXmlStr) BUILD_VERSION);
247  xmlSetProp(node, (pcmkXmlStr) "features", (pcmkXmlStr) CRM_FEATURES);
248 }
249 
250 G_GNUC_PRINTF(2, 3)
251 static void
252 xml_err(pcmk__output_t *out, const char *format, ...) {
253  private_data_t *priv = out->priv;
254  int len = 0;
255  char *buf = NULL;
256  va_list ap;
257 
258  CRM_ASSERT(priv != NULL);
259  va_start(ap, format);
260  len = vasprintf(&buf, format, ap);
261  CRM_ASSERT(len > 0);
262  va_end(ap);
263 
264  priv->errors = g_slist_append(priv->errors, buf);
265 }
266 
267 G_GNUC_PRINTF(2, 3)
268 static void
269 xml_info(pcmk__output_t *out, const char *format, ...) {
270  /* This function intentially left blank */
271 }
272 
273 static void
274 xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
275  xmlNodePtr parent = NULL;
276  xmlNodePtr cdata_node = NULL;
277  private_data_t *priv = out->priv;
278 
279  CRM_ASSERT(priv != NULL);
280 
281  parent = pcmk__output_create_xml_node(out, name);
282  cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf));
283  xmlAddChild(parent, cdata_node);
284 }
285 
286 G_GNUC_PRINTF(4, 5)
287 static void
288 xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
289  const char *format, ...) {
290  va_list ap;
291  const char *name = NULL;
292  char *buf = NULL;
293  int len;
294 
295  va_start(ap, format);
296  len = vasprintf(&buf, format, ap);
297  CRM_ASSERT(len >= 0);
298  va_end(ap);
299 
300  if (substitute) {
301  for (subst_t *s = substitutions; s->from != NULL; s++) {
302  if (!strcmp(s->from, buf)) {
303  name = s->to;
304  break;
305  }
306  }
307  }
308 
309  if (name == NULL) {
310  name = buf;
311  }
312 
313  if (legacy_xml || simple_list) {
315  } else {
316  xmlNodePtr list_node = NULL;
317 
318  list_node = pcmk__output_xml_create_parent(out, "list");
319  xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
320  }
321 
322  free(buf);
323 }
324 
325 G_GNUC_PRINTF(3, 4)
326 static void
327 xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
328  private_data_t *priv = out->priv;
329  xmlNodePtr item_node = NULL;
330  va_list ap;
331  char *buf = NULL;
332  int len;
333 
334  CRM_ASSERT(priv != NULL);
335 
336  va_start(ap, format);
337  len = vasprintf(&buf, format, ap);
338  CRM_ASSERT(len >= 0);
339  va_end(ap);
340 
341  item_node = pcmk__output_create_xml_text_node(out, "item", buf);
342 
343  if (name != NULL) {
344  xmlSetProp(item_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
345  }
346 
347  free(buf);
348 }
349 
350 static void
351 xml_increment_list(pcmk__output_t *out) {
352  /* This function intentially left blank */
353 }
354 
355 static void
356 xml_end_list(pcmk__output_t *out) {
357  private_data_t *priv = out->priv;
358 
359  CRM_ASSERT(priv != NULL);
360 
361  if (priv->legacy_xml || simple_list) {
362  g_queue_pop_tail(priv->parent_q);
363  } else {
364  char *buf = NULL;
365  xmlNodePtr node;
366 
367  node = g_queue_pop_tail(priv->parent_q);
368  buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
369  xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf);
370  free(buf);
371  }
372 }
373 
374 static bool
375 xml_is_quiet(pcmk__output_t *out) {
376  return false;
377 }
378 
380 pcmk__mk_xml_output(char **argv) {
381  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
382 
383  if (retval == NULL) {
384  return NULL;
385  }
386 
387  retval->fmt_name = "xml";
388  retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
389 
390  retval->init = xml_init;
391  retval->free_priv = xml_free_priv;
392  retval->finish = xml_finish;
393  retval->reset = xml_reset;
394 
396  retval->message = pcmk__call_message;
397 
398  retval->subprocess_output = xml_subprocess_output;
399  retval->version = xml_version;
400  retval->info = xml_info;
401  retval->err = xml_err;
402  retval->output_xml = xml_output_xml;
403 
404  retval->begin_list = xml_begin_list;
405  retval->list_item = xml_list_item;
406  retval->increment_list = xml_increment_list;
407  retval->end_list = xml_end_list;
408 
409  retval->is_quiet = xml_is_quiet;
410 
411  return retval;
412 }
413 
414 xmlNodePtr
416  xmlNodePtr node = pcmk__output_create_xml_node(out, name);
417  pcmk__output_xml_push_parent(out, node);
418  return node;
419 }
420 
421 void
423  private_data_t *priv = out->priv;
424 
425  CRM_ASSERT(priv != NULL);
426  CRM_ASSERT(node != NULL);
427 
428  xmlAddChild(g_queue_peek_tail(priv->parent_q), node);
429 }
430 
431 xmlNodePtr
433  private_data_t *priv = out->priv;
434 
435  CRM_ASSERT(priv != NULL);
436 
437  return create_xml_node(g_queue_peek_tail(priv->parent_q), name);
438 }
439 
440 xmlNodePtr
441 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
442  xmlNodePtr node = pcmk__output_create_xml_node(out, name);
443  xmlNodeSetContent(node, (pcmkXmlStr) content);
444  return node;
445 }
446 
447 void
449  private_data_t *priv = out->priv;
450 
451  CRM_ASSERT(priv != NULL);
452  CRM_ASSERT(parent != NULL);
453 
454  g_queue_push_tail(priv->parent_q, parent);
455 }
456 
457 void
459  private_data_t *priv = out->priv;
460 
461  CRM_ASSERT(priv != NULL);
462  CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
463 
464  g_queue_pop_tail(priv->parent_q);
465 }
466 
467 xmlNodePtr
469  private_data_t *priv = out->priv;
470 
471  CRM_ASSERT(priv != NULL);
472 
473  /* If queue is empty NULL will be returned */
474  return g_queue_peek_tail(priv->parent_q);
475 }
void(* end_list)(pcmk__output_t *out)
A dumping ground.
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)
#define VERSION
Definition: config.h:621
xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name)
Definition: output_xml.c:415
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:468
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:441
enum crm_exit_e crm_exit_t
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition: output_xml.c:458
void(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PACEMAKER_VERSION
Definition: config.h:514
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:625
int pcmk__call_message(pcmk__output_t *out, const char *message_id,...)
Definition: output.c:121
void(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent)
Definition: output_xml.c:448
void * priv
Implementation-specific private data.
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:796
#define PCMK__API_VERSION
Formatted output for pacemaker tools.
const char * crm_exit_str(crm_exit_t exit_code)
Definition: results.c:526
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:688
#define BUILD_VERSION
Definition: config.h:8
struct private_data_s private_data_t
void(* free_priv)(pcmk__output_t *out)
bool(* init)(pcmk__output_t *out)
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:663
void(*) void(*) void(* output_xml)(pcmk__output_t *out, const char *name, const char *buf)
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
char data[0]
Definition: internal.h:90
void(* reset)(pcmk__output_t *out)
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name)
Definition: output_xml.c:432
pcmk__output_t * pcmk__mk_xml_output(char **argv)
Definition: output_xml.c:380
char * name
Definition: pcmk_fence.c:31
#define CRM_FEATURES
Definition: config.h:35
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
GOptionEntry pcmk__xml_output_entries[]
Definition: output_xml.c:33
void(*) void(*) void(* increment_list)(pcmk__output_t *out)
char * dump_xml_formatted_with_text(xmlNode *msg)
Definition: xml.c:1977
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:422