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