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