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