pacemaker  2.0.5-ba59be712
Scalable High-Availability cluster resource manager
output_html.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 #include <ctype.h>
15 #include <libxml/HTMLtree.h>
16 #include <stdarg.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 
20 #include <crm/crm.h>
22 #include <crm/common/xml.h>
23 
24 static const char *stylesheet_default =
25  ".bold { font-weight: bold }\n"
26  ".maint { color: blue }\n"
27  ".offline { color: red }\n"
28  ".online { color: green }\n"
29  ".rsc-failed { color: red }\n"
30  ".rsc-failure-ignored { color: yellow }\n"
31  ".rsc-managed { color: yellow }\n"
32  ".rsc-multiple { color: orange }\n"
33  ".rsc-ok { color: green }\n"
34  ".standby { color: orange }\n"
35  ".warning { color: red, font-weight: bold }";
36 
37 static gboolean cgi_output = FALSE;
38 static char *stylesheet_link = NULL;
39 static char *title = NULL;
40 static GSList *extra_headers = NULL;
41 
42 GOptionEntry pcmk__html_output_entries[] = {
43  { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
44  "Add CGI headers (requires --output-as=html)",
45  NULL },
46 
47  { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
48  "Link to an external stylesheet (requires --output-as=html)",
49  "URI" },
50 
51  { "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
52  "Specify a page title (requires --output-as=html)",
53  "TITLE" },
54 
55  { NULL }
56 };
57 
58 typedef struct private_data_s {
59  xmlNode *root;
60  GQueue *parent_q;
61  GSList *errors;
63 
64 static void
65 html_free_priv(pcmk__output_t *out) {
66  private_data_t *priv = out->priv;
67 
68  if (priv == NULL) {
69  return;
70  }
71 
72  xmlFreeNode(priv->root);
73  g_queue_free(priv->parent_q);
74  g_slist_free(priv->errors);
75  free(priv);
76  out->priv = NULL;
77 }
78 
79 static bool
80 html_init(pcmk__output_t *out) {
81  private_data_t *priv = NULL;
82 
83  /* If html_init was previously called on this output struct, just return. */
84  if (out->priv != NULL) {
85  return true;
86  } else {
87  out->priv = calloc(1, sizeof(private_data_t));
88  if (out->priv == NULL) {
89  return false;
90  }
91 
92  priv = out->priv;
93  }
94 
95  priv->parent_q = g_queue_new();
96 
97  priv->root = create_xml_node(NULL, "html");
98  xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
99 
100  xmlSetProp(priv->root, (pcmkXmlStr) "lang", (pcmkXmlStr) "en");
101  g_queue_push_tail(priv->parent_q, priv->root);
102  priv->errors = NULL;
103 
104  pcmk__output_xml_create_parent(out, "body");
105 
106  return true;
107 }
108 
109 static void
110 add_error_node(gpointer data, gpointer user_data) {
111  char *str = (char *) data;
112  pcmk__output_t *out = (pcmk__output_t *) user_data;
113  out->list_item(out, NULL, "%s", str);
114 }
115 
116 static void
117 finish_reset_common(pcmk__output_t *out, crm_exit_t exit_status, bool print) {
118  private_data_t *priv = out->priv;
119  htmlNodePtr head_node = NULL;
120  htmlNodePtr charset_node = NULL;
121 
122  if (cgi_output && print) {
123  fprintf(out->dest, "Content-Type: text/html\n\n");
124  }
125 
126  /* Add the head node last - it's not needed earlier because it doesn't contain
127  * anything else that the user could add, and we want it done last to pick up
128  * any options that may have been given.
129  */
130  head_node = xmlNewNode(NULL, (pcmkXmlStr) "head");
131 
132  if (title != NULL ) {
133  pcmk_create_xml_text_node(head_node, "title", title);
134  } else if (out->request != NULL) {
135  pcmk_create_xml_text_node(head_node, "title", out->request);
136  }
137 
138  charset_node = create_xml_node(head_node, "meta");
139  xmlSetProp(charset_node, (pcmkXmlStr) "charset", (pcmkXmlStr) "utf-8");
140 
141  /* Add any extra header nodes the caller might have created. */
142  for (int i = 0; i < g_slist_length(extra_headers); i++) {
143  xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
144  }
145 
146  /* Stylesheets are included two different ways. The first is via a built-in
147  * default (see the stylesheet_default const above). The second is via the
148  * html-stylesheet option, and this should obviously be a link to a
149  * stylesheet. The second can override the first. At least one should be
150  * given.
151  */
152  pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
153 
154  if (stylesheet_link != NULL) {
155  htmlNodePtr link_node = create_xml_node(head_node, "link");
156  xmlSetProp(link_node, (pcmkXmlStr) "rel", (pcmkXmlStr) "stylesheet");
157  xmlSetProp(link_node, (pcmkXmlStr) "href", (pcmkXmlStr) stylesheet_link);
158  }
159 
160  xmlAddPrevSibling(priv->root->children, head_node);
161 
162  if (g_slist_length(priv->errors) > 0) {
163  out->begin_list(out, "Errors", NULL, NULL);
164  g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
165  out->end_list(out);
166  }
167 
168  if (print) {
169  htmlDocDump(out->dest, priv->root->doc);
170  }
171 }
172 
173 static void
174 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
175  private_data_t *priv = out->priv;
176 
177  /* If root is NULL, html_init failed and we are being called from pcmk__output_free
178  * in the pcmk__output_new path.
179  */
180  if (priv == NULL || priv->root == NULL) {
181  return;
182  }
183 
184  finish_reset_common(out, exit_status, print);
185 
186  if (copy_dest != NULL) {
187  *copy_dest = copy_xml(priv->root);
188  }
189 
190  g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
191 }
192 
193 static void
194 html_reset(pcmk__output_t *out) {
195  CRM_ASSERT(out != NULL);
196 
197  out->dest = freopen(NULL, "w", out->dest);
198  CRM_ASSERT(out->dest != NULL);
199 
200  if (out->priv != NULL) {
201  finish_reset_common(out, CRM_EX_OK, true);
202  }
203 
204  html_free_priv(out);
205  html_init(out);
206 }
207 
208 static void
209 html_subprocess_output(pcmk__output_t *out, int exit_status,
210  const char *proc_stdout, const char *proc_stderr) {
211  char *rc_buf = NULL;
212  private_data_t *priv = out->priv;
213  CRM_ASSERT(priv != NULL);
214 
215  rc_buf = crm_strdup_printf("Return code: %d", exit_status);
216 
217  pcmk__output_create_xml_text_node(out, "h2", "Command Output");
218  pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
219 
220  if (proc_stdout != NULL) {
221  pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
222  pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
223  }
224  if (proc_stderr != NULL) {
225  pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
226  pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
227  }
228 
229  free(rc_buf);
230 }
231 
232 static void
233 html_version(pcmk__output_t *out, bool extended) {
234  private_data_t *priv = out->priv;
235  CRM_ASSERT(priv != NULL);
236 
237  pcmk__output_create_xml_text_node(out, "h2", "Version Information");
238  pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
239  pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
240  pcmk__output_create_html_node(out, "div", NULL, NULL, "Author: Andrew Beekhof");
241  pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
242  pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
243 }
244 
245 G_GNUC_PRINTF(2, 3)
246 static void
247 html_err(pcmk__output_t *out, const char *format, ...) {
248  private_data_t *priv = out->priv;
249  int len = 0;
250  char *buf = NULL;
251  va_list ap;
252 
253  CRM_ASSERT(priv != NULL);
254  va_start(ap, format);
255  len = vasprintf(&buf, format, ap);
256  CRM_ASSERT(len >= 0);
257  va_end(ap);
258 
259  priv->errors = g_slist_append(priv->errors, buf);
260 }
261 
262 G_GNUC_PRINTF(2, 3)
263 static void
264 html_info(pcmk__output_t *out, const char *format, ...) {
265  /* This function intentially left blank */
266 }
267 
268 static void
269 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
270  htmlNodePtr node = NULL;
271  private_data_t *priv = out->priv;
272 
273  CRM_ASSERT(priv != NULL);
274 
275  node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
276  xmlSetProp(node, (pcmkXmlStr) "lang", (pcmkXmlStr) "xml");
277 }
278 
279 G_GNUC_PRINTF(4, 5)
280 static void
281 html_begin_list(pcmk__output_t *out, const char *singular_noun,
282  const char *plural_noun, const char *format, ...) {
283  int q_len = 0;
284  private_data_t *priv = out->priv;
285  xmlNodePtr node = NULL;
286 
287  CRM_ASSERT(priv != NULL);
288 
289  /* If we are already in a list (the queue depth is always at least
290  * one because of the <html> element), first create a <li> element
291  * to hold the <h2> and the new list.
292  */
293  q_len = g_queue_get_length(priv->parent_q);
294  if (q_len > 2) {
296  }
297 
298  if (format != NULL) {
299  va_list ap;
300  char *buf = NULL;
301  int len;
302 
303  va_start(ap, format);
304  len = vasprintf(&buf, format, ap);
305  va_end(ap);
306  CRM_ASSERT(len >= 0);
307 
308  if (q_len > 2) {
309  pcmk__output_create_xml_text_node(out, "h3", buf);
310  } else {
311  pcmk__output_create_xml_text_node(out, "h2", buf);
312  }
313 
314  free(buf);
315  }
316 
317  node = pcmk__output_xml_create_parent(out, "ul");
318  g_queue_push_tail(priv->parent_q, node);
319 }
320 
321 G_GNUC_PRINTF(3, 4)
322 static void
323 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
324  private_data_t *priv = out->priv;
325  htmlNodePtr item_node = NULL;
326  va_list ap;
327  char *buf = NULL;
328  int len;
329 
330  CRM_ASSERT(priv != NULL);
331 
332  va_start(ap, format);
333  len = vasprintf(&buf, format, ap);
334  CRM_ASSERT(len >= 0);
335  va_end(ap);
336 
337  item_node = pcmk__output_create_xml_text_node(out, "li", buf);
338  free(buf);
339 
340  if (name != NULL) {
341  xmlSetProp(item_node, (pcmkXmlStr) "class", (pcmkXmlStr) name);
342  }
343 }
344 
345 static void
346 html_increment_list(pcmk__output_t *out) {
347  /* This function intentially left blank */
348 }
349 
350 static void
351 html_end_list(pcmk__output_t *out) {
352  private_data_t *priv = out->priv;
353 
354  CRM_ASSERT(priv != NULL);
355 
356  /* Remove the <ul> tag. */
357  g_queue_pop_tail(priv->parent_q);
359 
360  /* Remove the <li> created for nested lists. */
361  if (g_queue_get_length(priv->parent_q) > 2) {
363  }
364 }
365 
366 static bool
367 html_is_quiet(pcmk__output_t *out) {
368  return false;
369 }
370 
372 pcmk__mk_html_output(char **argv) {
373  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
374 
375  if (retval == NULL) {
376  return NULL;
377  }
378 
379  retval->fmt_name = "html";
380  retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
381 
382  retval->init = html_init;
383  retval->free_priv = html_free_priv;
384  retval->finish = html_finish;
385  retval->reset = html_reset;
386 
388  retval->message = pcmk__call_message;
389 
390  retval->subprocess_output = html_subprocess_output;
391  retval->version = html_version;
392  retval->info = html_info;
393  retval->err = html_err;
394  retval->output_xml = html_output_xml;
395 
396  retval->begin_list = html_begin_list;
397  retval->list_item = html_list_item;
398  retval->increment_list = html_increment_list;
399  retval->end_list = html_end_list;
400 
401  retval->is_quiet = html_is_quiet;
402 
403  return retval;
404 }
405 
406 xmlNodePtr
407 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
408  const char *class_name, const char *text) {
409  htmlNodePtr node = pcmk__output_create_xml_text_node(out, element_name, text);
410 
411  if (class_name != NULL) {
412  xmlSetProp(node, (pcmkXmlStr) "class", (pcmkXmlStr) class_name);
413  }
414 
415  if (id != NULL) {
416  xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) id);
417  }
418 
419  return node;
420 }
421 
422 void
423 pcmk__html_add_header(const char *name, ...) {
424  htmlNodePtr header_node;
425  va_list ap;
426 
427  va_start(ap, name);
428 
429  header_node = xmlNewNode(NULL, (pcmkXmlStr) name);
430  while (1) {
431  char *key = va_arg(ap, char *);
432  char *value;
433 
434  if (key == NULL) {
435  break;
436  }
437 
438  value = va_arg(ap, char *);
439  xmlSetProp(header_node, (pcmkXmlStr) key, (pcmkXmlStr) value);
440  }
441 
442  extra_headers = g_slist_append(extra_headers, header_node);
443 
444  va_end(ap);
445 }
void(* end_list)(pcmk__output_t *out)
A dumping ground.
int(* message)(pcmk__output_t *out, const char *message_id,...)
const char * fmt_name
The name of this output formatter.
bool(* is_quiet)(pcmk__output_t *out)
void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
Definition: output.c:139
enum crm_exit_e crm_exit_t
void(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PACEMAKER_VERSION
Definition: config.h:514
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
GOptionEntry pcmk__html_output_entries[]
Definition: output_html.c:42
void * priv
Implementation-specific private data.
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:796
void pcmk__html_add_header(const char *name,...)
Definition: output_html.c:423
Formatted output for pacemaker tools.
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
pcmk__output_t * pcmk__mk_html_output(char **argv)
Definition: output_html.c:372
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)
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition: output_xml.c:458
FILE * dest
Where output should be written.
const xmlChar * pcmkXmlStr
Definition: xml.h:51
void void xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name)
Definition: output_xml.c:415
#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
xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text)
Definition: output_html.c:407
gchar * request
A copy of the request that generated this output.
This structure contains everything that makes up a single output formatter.
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)
char * name
Definition: pcmk_fence.c:31
#define CRM_FEATURES
Definition: config.h:35
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:441
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
void(*) void(*) void(* increment_list)(pcmk__output_t *out)
void(* subprocess_output)(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr)