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