pacemaker  2.1.8-3980678f03
Scalable High-Availability cluster resource manager
output_html.c
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2024 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 
19 #include <crm/common/xml.h>
20 
21 static const char *stylesheet_default =
22  "." PCMK__VALUE_BOLD " { font-weight: bold }\n"
23 
24  "." PCMK_VALUE_ONLINE " { color: green }\n"
25  "." PCMK_VALUE_OFFLINE " { color: red }\n"
26  "." PCMK__VALUE_MAINT " { color: blue }\n"
27  "." PCMK_VALUE_STANDBY " { color: blue }\n"
28  "." PCMK__VALUE_HEALTH_RED " { color: red }\n"
29  "." PCMK__VALUE_HEALTH_YELLOW " { color: GoldenRod }\n"
30 
31  "." PCMK__VALUE_RSC_FAILED " { color: red }\n"
32  "." PCMK__VALUE_RSC_FAILURE_IGNORED " { color: DarkGreen }\n"
33  "." PCMK__VALUE_RSC_MANAGED " { color: blue }\n"
34  "." PCMK__VALUE_RSC_MULTIPLE " { color: orange }\n"
35  "." PCMK__VALUE_RSC_OK " { color: green }\n"
36 
37  "." PCMK__VALUE_WARNING " { color: red; font-weight: bold }";
38 
39 static gboolean cgi_output = FALSE;
40 static char *stylesheet_link = NULL;
41 static char *title = NULL;
42 static GSList *extra_headers = NULL;
43 
44 GOptionEntry pcmk__html_output_entries[] = {
45  { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
46  "Add CGI headers (requires --output-as=html)",
47  NULL },
48 
49  { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
50  "Link to an external stylesheet (requires --output-as=html)",
51  "URI" },
52 
53  { "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
54  "Specify a page title (requires --output-as=html)",
55  "TITLE" },
56 
57  { NULL }
58 };
59 
60 /* The first several elements of this struct must be the same as the first
61  * several elements of private_data_s in lib/common/output_xml.c. This
62  * struct gets passed to a bunch of the pcmk__output_xml_* functions which
63  * assume an XML private_data_s. Keeping them laid out the same means this
64  * still works.
65  */
66 typedef struct private_data_s {
67  /* Begin members that must match the XML version */
68  xmlNode *root;
69  GQueue *parent_q;
70  GSList *errors;
71  /* End members that must match the XML version */
73 
74 static void
75 html_free_priv(pcmk__output_t *out) {
76  private_data_t *priv = NULL;
77 
78  if (out == NULL || out->priv == NULL) {
79  return;
80  }
81 
82  priv = out->priv;
83 
84  free_xml(priv->root);
85  /* The elements of parent_q are xmlNodes that are a part of the
86  * priv->root document, so the above line already frees them. Don't
87  * call g_queue_free_full here.
88  */
89  g_queue_free(priv->parent_q);
90  g_slist_free_full(priv->errors, free);
91  free(priv);
92  out->priv = NULL;
93 }
94 
95 static bool
96 html_init(pcmk__output_t *out) {
97  private_data_t *priv = NULL;
98 
99  CRM_ASSERT(out != NULL);
100 
101  /* If html_init was previously called on this output struct, just return. */
102  if (out->priv != NULL) {
103  return true;
104  } else {
105  out->priv = calloc(1, sizeof(private_data_t));
106  if (out->priv == NULL) {
107  return false;
108  }
109 
110  priv = out->priv;
111  }
112 
113  priv->parent_q = g_queue_new();
114 
115  priv->root = pcmk__xe_create(NULL, "html");
116  xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
117 
119  g_queue_push_tail(priv->parent_q, priv->root);
120  priv->errors = NULL;
121 
122  pcmk__output_xml_create_parent(out, "body", NULL);
123 
124  return true;
125 }
126 
127 static void
128 add_error_node(gpointer data, gpointer user_data) {
129  char *str = (char *) data;
130  pcmk__output_t *out = (pcmk__output_t *) user_data;
131  out->list_item(out, NULL, "%s", str);
132 }
133 
134 static void
135 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
136  private_data_t *priv = NULL;
137  htmlNodePtr head_node = NULL;
138  htmlNodePtr charset_node = NULL;
139  xmlNode *child_node = NULL;
140 
141  CRM_ASSERT(out != NULL);
142 
143  priv = out->priv;
144 
145  /* If root is NULL, html_init failed and we are being called from pcmk__output_free
146  * in the pcmk__output_new path.
147  */
148  if (priv == NULL || priv->root == NULL) {
149  return;
150  }
151 
152  if (cgi_output && print) {
153  fprintf(out->dest, "Content-Type: text/html\n\n");
154  }
155 
156  /* Add the head node last - it's not needed earlier because it doesn't contain
157  * anything else that the user could add, and we want it done last to pick up
158  * any options that may have been given.
159  */
160  head_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL);
161 
162  if (title != NULL ) {
163  child_node = pcmk__xe_create(head_node, "title");
164  pcmk__xe_set_content(child_node, "%s", title);
165  } else if (out->request != NULL) {
166  child_node = pcmk__xe_create(head_node, "title");
167  pcmk__xe_set_content(child_node, "%s", out->request);
168  }
169 
170  charset_node = pcmk__xe_create(head_node, PCMK__XE_META);
171  crm_xml_add(charset_node, "charset", "utf-8");
172 
173  /* Add any extra header nodes the caller might have created. */
174  for (int i = 0; i < g_slist_length(extra_headers); i++) {
175  xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
176  }
177 
178  /* Stylesheets are included two different ways. The first is via a built-in
179  * default (see the stylesheet_default const above). The second is via the
180  * html-stylesheet option, and this should obviously be a link to a
181  * stylesheet. The second can override the first. At least one should be
182  * given.
183  */
184  child_node = pcmk__xe_create(head_node, "style");
185  pcmk__xe_set_content(child_node, "%s", stylesheet_default);
186 
187  if (stylesheet_link != NULL) {
188  htmlNodePtr link_node = pcmk__xe_create(head_node, "link");
189  pcmk__xe_set_props(link_node, "rel", "stylesheet",
190  "href", stylesheet_link,
191  NULL);
192  }
193 
194  xmlAddPrevSibling(priv->root->children, head_node);
195 
196  if (g_slist_length(priv->errors) > 0) {
197  out->begin_list(out, "Errors", NULL, NULL);
198  g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
199  out->end_list(out);
200  }
201 
202  if (print) {
203  htmlDocDump(out->dest, priv->root->doc);
204  }
205 
206  if (copy_dest != NULL) {
207  *copy_dest = pcmk__xml_copy(NULL, priv->root);
208  }
209 
210  g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
211  extra_headers = NULL;
212 }
213 
214 static void
215 html_reset(pcmk__output_t *out) {
216  CRM_ASSERT(out != NULL);
217 
218  out->dest = freopen(NULL, "w", out->dest);
219  CRM_ASSERT(out->dest != NULL);
220 
221  html_free_priv(out);
222  html_init(out);
223 }
224 
225 static void
226 html_subprocess_output(pcmk__output_t *out, int exit_status,
227  const char *proc_stdout, const char *proc_stderr) {
228  char *rc_buf = NULL;
229 
230  CRM_ASSERT(out != NULL);
231 
232  rc_buf = crm_strdup_printf("Return code: %d", exit_status);
233 
234  pcmk__output_create_xml_text_node(out, "h2", "Command Output");
235  pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, rc_buf);
236 
237  if (proc_stdout != NULL) {
238  pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stdout");
240  PCMK__VALUE_OUTPUT, proc_stdout);
241  }
242  if (proc_stderr != NULL) {
243  pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stderr");
245  PCMK__VALUE_OUTPUT, proc_stderr);
246  }
247 
248  free(rc_buf);
249 }
250 
251 static void
252 html_version(pcmk__output_t *out, bool extended) {
253  CRM_ASSERT(out != NULL);
254 
255  pcmk__output_create_xml_text_node(out, "h2", "Version Information");
257  "Program: Pacemaker");
259  "Version: " PACEMAKER_VERSION);
261  "Author: Andrew Beekhof and "
262  "the Pacemaker project contributors");
264  "Build: " BUILD_VERSION);
266  "Features: " CRM_FEATURES);
267 }
268 
269 G_GNUC_PRINTF(2, 3)
270 static void
271 html_err(pcmk__output_t *out, const char *format, ...) {
272  private_data_t *priv = NULL;
273  int len = 0;
274  char *buf = NULL;
275  va_list ap;
276 
277  CRM_ASSERT(out != NULL && out->priv != NULL);
278  priv = out->priv;
279 
280  va_start(ap, format);
281  len = vasprintf(&buf, format, ap);
282  CRM_ASSERT(len >= 0);
283  va_end(ap);
284 
285  priv->errors = g_slist_append(priv->errors, buf);
286 }
287 
288 G_GNUC_PRINTF(2, 3)
289 static int
290 html_info(pcmk__output_t *out, const char *format, ...) {
291  return pcmk_rc_no_output;
292 }
293 
294 static void
295 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
296  htmlNodePtr node = NULL;
297 
298  CRM_ASSERT(out != NULL);
299 
300  node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
301  crm_xml_add(node, PCMK_XA_LANG, "xml");
302 }
303 
304 G_GNUC_PRINTF(4, 5)
305 static void
306 html_begin_list(pcmk__output_t *out, const char *singular_noun,
307  const char *plural_noun, const char *format, ...) {
308  int q_len = 0;
309  private_data_t *priv = NULL;
310  xmlNodePtr node = NULL;
311 
312  CRM_ASSERT(out != NULL && out->priv != NULL);
313  priv = out->priv;
314 
315  /* If we are already in a list (the queue depth is always at least
316  * one because of the <html> element), first create a <li> element
317  * to hold the <h2> and the new list.
318  */
319  q_len = g_queue_get_length(priv->parent_q);
320  if (q_len > 2) {
321  pcmk__output_xml_create_parent(out, "li", NULL);
322  }
323 
324  if (format != NULL) {
325  va_list ap;
326  char *buf = NULL;
327  int len;
328 
329  va_start(ap, format);
330  len = vasprintf(&buf, format, ap);
331  va_end(ap);
332  CRM_ASSERT(len >= 0);
333 
334  if (q_len > 2) {
335  pcmk__output_create_xml_text_node(out, "h3", buf);
336  } else {
337  pcmk__output_create_xml_text_node(out, "h2", buf);
338  }
339 
340  free(buf);
341  }
342 
343  node = pcmk__output_xml_create_parent(out, "ul", NULL);
344  g_queue_push_tail(priv->parent_q, node);
345 }
346 
347 G_GNUC_PRINTF(3, 4)
348 static void
349 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
350  htmlNodePtr item_node = NULL;
351  va_list ap;
352  char *buf = NULL;
353  int len;
354 
355  CRM_ASSERT(out != NULL);
356 
357  va_start(ap, format);
358  len = vasprintf(&buf, format, ap);
359  CRM_ASSERT(len >= 0);
360  va_end(ap);
361 
362  item_node = pcmk__output_create_xml_text_node(out, "li", buf);
363  free(buf);
364 
365  if (name != NULL) {
366  crm_xml_add(item_node, PCMK_XA_CLASS, name);
367  }
368 }
369 
370 static void
371 html_increment_list(pcmk__output_t *out) {
372  /* This function intentially left blank */
373 }
374 
375 static void
376 html_end_list(pcmk__output_t *out) {
377  private_data_t *priv = NULL;
378 
379  CRM_ASSERT(out != NULL && out->priv != NULL);
380  priv = out->priv;
381 
382  /* Remove the <ul> tag, but do not free this result - it's still
383  * part of the document.
384  */
385  g_queue_pop_tail(priv->parent_q);
387 
388  /* Remove the <li> created for nested lists. */
389  if (g_queue_get_length(priv->parent_q) > 2) {
391  }
392 }
393 
394 static bool
395 html_is_quiet(pcmk__output_t *out) {
396  return false;
397 }
398 
399 static void
400 html_spacer(pcmk__output_t *out) {
401  CRM_ASSERT(out != NULL);
402  pcmk__output_create_xml_node(out, "br", NULL);
403 }
404 
405 static void
406 html_progress(pcmk__output_t *out, bool end) {
407  /* This function intentially left blank */
408 }
409 
411 pcmk__mk_html_output(char **argv) {
412  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
413 
414  if (retval == NULL) {
415  return NULL;
416  }
417 
418  retval->fmt_name = "html";
419  retval->request = pcmk__quote_cmdline(argv);
420 
421  retval->init = html_init;
422  retval->free_priv = html_free_priv;
423  retval->finish = html_finish;
424  retval->reset = html_reset;
425 
427  retval->message = pcmk__call_message;
428 
429  retval->subprocess_output = html_subprocess_output;
430  retval->version = html_version;
431  retval->info = html_info;
432  retval->transient = html_info;
433  retval->err = html_err;
434  retval->output_xml = html_output_xml;
435 
436  retval->begin_list = html_begin_list;
437  retval->list_item = html_list_item;
438  retval->increment_list = html_increment_list;
439  retval->end_list = html_end_list;
440 
441  retval->is_quiet = html_is_quiet;
442  retval->spacer = html_spacer;
443  retval->progress = html_progress;
444  retval->prompt = pcmk__text_prompt;
445 
446  return retval;
447 }
448 
449 xmlNodePtr
450 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
451  const char *class_name, const char *text) {
452  htmlNodePtr node = NULL;
453 
454  CRM_ASSERT(out != NULL);
455  CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
456 
457  node = pcmk__output_create_xml_text_node(out, element_name, text);
458 
459  if (class_name != NULL) {
460  crm_xml_add(node, PCMK_XA_CLASS, class_name);
461  }
462 
463  if (id != NULL) {
464  crm_xml_add(node, PCMK_XA_ID, id);
465  }
466 
467  return node;
468 }
469 
483 xmlNode *
484 pcmk__html_create(xmlNode *parent, const char *name, const char *id,
485  const char *class)
486 {
487  xmlNode *node = pcmk__xe_create(parent, name);
488 
489  pcmk__xe_set_props(node,
490  PCMK_XA_CLASS, class,
491  PCMK_XA_ID, id,
492  NULL);
493  return node;
494 }
495 
496 void
497 pcmk__html_add_header(const char *name, ...) {
498  htmlNodePtr header_node;
499  va_list ap;
500 
501  va_start(ap, name);
502 
503  header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL);
504  while (1) {
505  char *key = va_arg(ap, char *);
506  char *value;
507 
508  if (key == NULL) {
509  break;
510  }
511 
512  value = va_arg(ap, char *);
513  crm_xml_add(header_node, key, value);
514  }
515 
516  extra_headers = g_slist_append(extra_headers, header_node);
517 
518  va_end(ap);
519 }
void(* end_list)(pcmk__output_t *out)
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
#define PCMK__VALUE_RSC_OK
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:883
char data[0]
Definition: cpg.c:58
xmlNode * pcmk__html_create(xmlNode *parent, const char *name, const char *id, const char *class)
Definition: output_html.c:484
void void void void void pcmk__text_prompt(const char *prompt, bool echo, char **dest)
Definition: output_text.c:476
const char * name
Definition: cib.c:26
int(* message)(pcmk__output_t *out, const char *message_id,...)
#define PCMK__VALUE_RSC_MULTIPLE
const char * fmt_name
The name of this output formatter.
#define PCMK_VALUE_STANDBY
Definition: options.h:205
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:478
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:301
void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
Definition: output.c:196
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:517
#define PCMK__XE_META
int pcmk__call_message(pcmk__output_t *out, const char *message_id,...)
Definition: output.c:174
void pcmk__xe_set_content(xmlNode *node, const char *format,...) G_GNUC_PRINTF(2
void(* prompt)(const char *prompt, bool echo, char **dest)
GOptionEntry pcmk__html_output_entries[]
Definition: output_html.c:44
#define PCMK__VALUE_RSC_FAILURE_IGNORED
void * priv
Implementation-specific private data.
void pcmk__html_add_header(const char *name,...)
Definition: output_html.c:497
void(* register_message)(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
#define PCMK__VALUE_EN
#define BUILD_VERSION
Definition: config.h:8
#define PCMK__VALUE_WARNING
pcmk__output_t * pcmk__mk_html_output(char **argv)
Definition: output_html.c:411
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(*) int(*) void(*) void(* output_xml)(pcmk__output_t *out, const char *name, const char *buf)
int(*) int(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PCMK__VALUE_RSC_FAILED
#define PCMK__VALUE_OUTPUT
Wrappers for and extensions to libxml2.
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
#define PCMK__VALUE_BOLD
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name,...) G_GNUC_NULL_TERMINATED
Definition: output_xml.c:516
#define PCMK_XA_ID
Definition: xml_names.h:296
int(*) int(* transient)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PCMK__XE_DIV
void free_xml(xmlNode *child)
Definition: xml.c:867
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition: output_xml.c:564
void pcmk__xe_set_props(xmlNodePtr node,...) G_GNUC_NULL_TERMINATED
Definition: xml.c:2279
FILE * dest
Where output should be written.
const xmlChar * pcmkXmlStr
Definition: xml.h:41
#define PCMK__VALUE_HEALTH_RED
#define PCMK_VALUE_ONLINE
Definition: options.h:184
#define PCMK_XA_CLASS
Definition: xml_names.h:241
#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:450
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
#define PCMK__VALUE_HEALTH_YELLOW
#define PCMK__VALUE_RSC_MANAGED
#define PCMK__VALUE_MAINT
void(* reset)(pcmk__output_t *out)
void(* progress)(pcmk__output_t *out, bool end)
#define PCMK_XA_LANG
Definition: xml_names.h:308
const char * parent
Definition: cib.c:27
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml.c:720
#define CRM_FEATURES
Definition: config.h:33
gchar * pcmk__quote_cmdline(gchar **argv)
Definition: cmdline.c:163
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:537
#define PCMK_VALUE_OFFLINE
Definition: options.h:183
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)