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