pacemaker 2.1.5-a3f44794f94
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
19#include <crm/common/xml.h>
20
21static const char *stylesheet_default =
22 ".bold { font-weight: bold }\n"
23
24 ".online { color: green }\n"
25 ".offline { color: red }\n"
26 ".maint { color: blue }\n"
27 ".standby { color: blue }\n"
28 ".health_red { color: red }\n"
29 ".health_yellow { color: GoldenRod }\n"
30
31 ".rsc-failed { color: red }\n"
32 ".rsc-failure-ignored { color: DarkGreen }\n"
33 ".rsc-managed { color: blue }\n"
34 ".rsc-multiple { color: orange }\n"
35 ".rsc-ok { color: green }\n"
36
37 ".warning { color: red; font-weight: bold }";
38
39static gboolean cgi_output = FALSE;
40static char *stylesheet_link = NULL;
41static char *title = NULL;
42static GSList *extra_headers = NULL;
43
44GOptionEntry 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 */
66typedef 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
74static void
75html_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 xmlFreeNode(priv->root);
85 g_queue_free(priv->parent_q);
86 g_slist_free(priv->errors);
87 free(priv);
88 out->priv = NULL;
89}
90
91static bool
92html_init(pcmk__output_t *out) {
93 private_data_t *priv = NULL;
94
95 CRM_ASSERT(out != NULL);
96
97 /* If html_init was previously called on this output struct, just return. */
98 if (out->priv != NULL) {
99 return true;
100 } else {
101 out->priv = calloc(1, sizeof(private_data_t));
102 if (out->priv == NULL) {
103 return false;
104 }
105
106 priv = out->priv;
107 }
108
109 priv->parent_q = g_queue_new();
110
111 priv->root = create_xml_node(NULL, "html");
112 xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
113
114 crm_xml_add(priv->root, "lang", "en");
115 g_queue_push_tail(priv->parent_q, priv->root);
116 priv->errors = NULL;
117
118 pcmk__output_xml_create_parent(out, "body", NULL);
119
120 return true;
121}
122
123static void
124add_error_node(gpointer data, gpointer user_data) {
125 char *str = (char *) data;
126 pcmk__output_t *out = (pcmk__output_t *) user_data;
127 out->list_item(out, NULL, "%s", str);
128}
129
130static void
131html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
132 private_data_t *priv = NULL;
133 htmlNodePtr head_node = NULL;
134 htmlNodePtr charset_node = NULL;
135
136 CRM_ASSERT(out != NULL);
137
138 priv = out->priv;
139
140 /* If root is NULL, html_init failed and we are being called from pcmk__output_free
141 * in the pcmk__output_new path.
142 */
143 if (priv == NULL || priv->root == NULL) {
144 return;
145 }
146
147 if (cgi_output && print) {
148 fprintf(out->dest, "Content-Type: text/html\n\n");
149 }
150
151 /* Add the head node last - it's not needed earlier because it doesn't contain
152 * anything else that the user could add, and we want it done last to pick up
153 * any options that may have been given.
154 */
155 head_node = xmlNewNode(NULL, (pcmkXmlStr) "head");
156
157 if (title != NULL ) {
158 pcmk_create_xml_text_node(head_node, "title", title);
159 } else if (out->request != NULL) {
160 pcmk_create_xml_text_node(head_node, "title", out->request);
161 }
162
163 charset_node = create_xml_node(head_node, "meta");
164 crm_xml_add(charset_node, "charset", "utf-8");
165
166 /* Add any extra header nodes the caller might have created. */
167 for (int i = 0; i < g_slist_length(extra_headers); i++) {
168 xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
169 }
170
171 /* Stylesheets are included two different ways. The first is via a built-in
172 * default (see the stylesheet_default const above). The second is via the
173 * html-stylesheet option, and this should obviously be a link to a
174 * stylesheet. The second can override the first. At least one should be
175 * given.
176 */
177 pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
178
179 if (stylesheet_link != NULL) {
180 htmlNodePtr link_node = create_xml_node(head_node, "link");
181 pcmk__xe_set_props(link_node, "rel", "stylesheet",
182 "href", stylesheet_link,
183 NULL);
184 }
185
186 xmlAddPrevSibling(priv->root->children, head_node);
187
188 if (g_slist_length(priv->errors) > 0) {
189 out->begin_list(out, "Errors", NULL, NULL);
190 g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
191 out->end_list(out);
192 }
193
194 if (print) {
195 htmlDocDump(out->dest, priv->root->doc);
196 }
197
198 if (copy_dest != NULL) {
199 *copy_dest = copy_xml(priv->root);
200 }
201
202 g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
203 extra_headers = NULL;
204}
205
206static void
207html_reset(pcmk__output_t *out) {
208 CRM_ASSERT(out != NULL);
209
210 out->dest = freopen(NULL, "w", out->dest);
211 CRM_ASSERT(out->dest != NULL);
212
213 html_free_priv(out);
214 html_init(out);
215}
216
217static void
218html_subprocess_output(pcmk__output_t *out, int exit_status,
219 const char *proc_stdout, const char *proc_stderr) {
220 char *rc_buf = NULL;
221
222 CRM_ASSERT(out != NULL);
223
224 rc_buf = crm_strdup_printf("Return code: %d", exit_status);
225
226 pcmk__output_create_xml_text_node(out, "h2", "Command Output");
227 pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
228
229 if (proc_stdout != NULL) {
230 pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
231 pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
232 }
233 if (proc_stderr != NULL) {
234 pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
235 pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
236 }
237
238 free(rc_buf);
239}
240
241static void
242html_version(pcmk__output_t *out, bool extended) {
243 CRM_ASSERT(out != NULL);
244
245 pcmk__output_create_xml_text_node(out, "h2", "Version Information");
246 pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
247 pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
248 pcmk__output_create_html_node(out, "div", NULL, NULL,
249 "Author: Andrew Beekhof and "
250 "the Pacemaker project contributors");
251 pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
252 pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
253}
254
255G_GNUC_PRINTF(2, 3)
256static void
257html_err(pcmk__output_t *out, const char *format, ...) {
258 private_data_t *priv = NULL;
259 int len = 0;
260 char *buf = NULL;
261 va_list ap;
262
263 CRM_ASSERT(out != NULL && out->priv != NULL);
264 priv = out->priv;
265
266 va_start(ap, format);
267 len = vasprintf(&buf, format, ap);
268 CRM_ASSERT(len >= 0);
269 va_end(ap);
270
271 priv->errors = g_slist_append(priv->errors, buf);
272}
273
274G_GNUC_PRINTF(2, 3)
275static int
276html_info(pcmk__output_t *out, const char *format, ...) {
277 return pcmk_rc_no_output;
278}
279
280static void
281html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
282 htmlNodePtr node = NULL;
283
284 CRM_ASSERT(out != NULL);
285
286 node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
287 crm_xml_add(node, "lang", "xml");
288}
289
290G_GNUC_PRINTF(4, 5)
291static void
292html_begin_list(pcmk__output_t *out, const char *singular_noun,
293 const char *plural_noun, const char *format, ...) {
294 int q_len = 0;
295 private_data_t *priv = NULL;
296 xmlNodePtr node = NULL;
297
298 CRM_ASSERT(out != NULL && out->priv != NULL);
299 priv = out->priv;
300
301 /* If we are already in a list (the queue depth is always at least
302 * one because of the <html> element), first create a <li> element
303 * to hold the <h2> and the new list.
304 */
305 q_len = g_queue_get_length(priv->parent_q);
306 if (q_len > 2) {
307 pcmk__output_xml_create_parent(out, "li", NULL);
308 }
309
310 if (format != NULL) {
311 va_list ap;
312 char *buf = NULL;
313 int len;
314
315 va_start(ap, format);
316 len = vasprintf(&buf, format, ap);
317 va_end(ap);
318 CRM_ASSERT(len >= 0);
319
320 if (q_len > 2) {
321 pcmk__output_create_xml_text_node(out, "h3", buf);
322 } else {
323 pcmk__output_create_xml_text_node(out, "h2", buf);
324 }
325
326 free(buf);
327 }
328
329 node = pcmk__output_xml_create_parent(out, "ul", NULL);
330 g_queue_push_tail(priv->parent_q, node);
331}
332
333G_GNUC_PRINTF(3, 4)
334static void
335html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
336 htmlNodePtr item_node = NULL;
337 va_list ap;
338 char *buf = NULL;
339 int len;
340
341 CRM_ASSERT(out != NULL);
342
343 va_start(ap, format);
344 len = vasprintf(&buf, format, ap);
345 CRM_ASSERT(len >= 0);
346 va_end(ap);
347
348 item_node = pcmk__output_create_xml_text_node(out, "li", buf);
349 free(buf);
350
351 if (name != NULL) {
352 crm_xml_add(item_node, "class", name);
353 }
354}
355
356static void
357html_increment_list(pcmk__output_t *out) {
358 /* This function intentially left blank */
359}
360
361static void
362html_end_list(pcmk__output_t *out) {
363 private_data_t *priv = NULL;
364
365 CRM_ASSERT(out != NULL && out->priv != NULL);
366 priv = out->priv;
367
368 /* Remove the <ul> tag. */
369 g_queue_pop_tail(priv->parent_q);
371
372 /* Remove the <li> created for nested lists. */
373 if (g_queue_get_length(priv->parent_q) > 2) {
375 }
376}
377
378static bool
379html_is_quiet(pcmk__output_t *out) {
380 return false;
381}
382
383static void
384html_spacer(pcmk__output_t *out) {
385 CRM_ASSERT(out != NULL);
386 pcmk__output_create_xml_node(out, "br", NULL);
387}
388
389static void
390html_progress(pcmk__output_t *out, bool end) {
391 /* This function intentially left blank */
392}
393
396 pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
397
398 if (retval == NULL) {
399 return NULL;
400 }
401
402 retval->fmt_name = "html";
403 retval->request = pcmk__quote_cmdline(argv);
404
405 retval->init = html_init;
406 retval->free_priv = html_free_priv;
407 retval->finish = html_finish;
408 retval->reset = html_reset;
409
411 retval->message = pcmk__call_message;
412
413 retval->subprocess_output = html_subprocess_output;
414 retval->version = html_version;
415 retval->info = html_info;
416 retval->err = html_err;
417 retval->output_xml = html_output_xml;
418
419 retval->begin_list = html_begin_list;
420 retval->list_item = html_list_item;
421 retval->increment_list = html_increment_list;
422 retval->end_list = html_end_list;
423
424 retval->is_quiet = html_is_quiet;
425 retval->spacer = html_spacer;
426 retval->progress = html_progress;
427 retval->prompt = pcmk__text_prompt;
428
429 return retval;
430}
431
432xmlNodePtr
433pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
434 const char *class_name, const char *text) {
435 htmlNodePtr node = NULL;
436
437 CRM_ASSERT(out != NULL);
438 CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
439
440 node = pcmk__output_create_xml_text_node(out, element_name, text);
441
442 if (class_name != NULL) {
443 crm_xml_add(node, "class", class_name);
444 }
445
446 if (id != NULL) {
447 crm_xml_add(node, "id", id);
448 }
449
450 return node;
451}
452
453void
454pcmk__html_add_header(const char *name, ...) {
455 htmlNodePtr header_node;
456 va_list ap;
457
458 va_start(ap, name);
459
460 header_node = xmlNewNode(NULL, (pcmkXmlStr) name);
461 while (1) {
462 char *key = va_arg(ap, char *);
463 char *value;
464
465 if (key == NULL) {
466 break;
467 }
468
469 value = va_arg(ap, char *);
470 crm_xml_add(header_node, key, value);
471 }
472
473 extra_headers = g_slist_append(extra_headers, header_node);
474
475 va_end(ap);
476}
const char * name
Definition: cib.c:24
gchar * pcmk__quote_cmdline(gchar **argv)
Definition: cmdline.c:165
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define PACEMAKER_VERSION
Definition: config.h:502
#define CRM_FEATURES
Definition: config.h:33
#define BUILD_VERSION
Definition: config.h:8
char data[0]
Definition: cpg.c:10
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
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
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:433
pcmk__output_t * pcmk__mk_html_output(char **argv)
Definition: output_html.c:395
GOptionEntry pcmk__html_output_entries[]
Definition: output_html.c:44
void pcmk__html_add_header(const char *name,...)
Definition: output_html.c:454
struct private_data_s private_data_t
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition: output_xml.c:513
xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name,...) G_GNUC_NULL_TERMINATED
Definition: output_xml.c:438
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name,...) G_GNUC_NULL_TERMINATED
Definition: output_xml.c:469
void void void void void pcmk__text_prompt(const char *prompt, bool echo, char **dest)
Definition: output_text.c:394
int pcmk__call_message(pcmk__output_t *out, const char *message_id,...)
Definition: output.c:131
void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
Definition: output.c:153
xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content)
Definition: output_xml.c:488
#define CRM_ASSERT(expr)
Definition: results.h:42
@ pcmk_rc_no_output
Definition: results.h:118
enum crm_exit_e crm_exit_t
@ pcmk__str_none
This structure contains everything that makes up a single output formatter.
void(*) void(*) void(* increment_list)(pcmk__output_t *out)
void(* end_list)(pcmk__output_t *out)
void(* version)(pcmk__output_t *out, bool extended)
int(* message)(pcmk__output_t *out, const char *message_id,...)
bool(* is_quiet)(pcmk__output_t *out)
const char * fmt_name
The name of this output formatter.
FILE * dest
Where output should be written.
int(*) void(*) void(* output_xml)(pcmk__output_t *out, const char *name, const char *buf)
void(* register_message)(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn)
void(*) void(* list_item)(pcmk__output_t *out, const char *name, const char *format,...) G_GNUC_PRINTF(3
int(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
void(* prompt)(const char *prompt, bool echo, char **dest)
void(* subprocess_output)(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr)
void(* begin_list)(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format,...) G_GNUC_PRINTF(4
bool(* init)(pcmk__output_t *out)
void * priv
Implementation-specific private data.
void(* spacer)(pcmk__output_t *out)
void(* progress)(pcmk__output_t *out, bool end)
void(* reset)(pcmk__output_t *out)
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void(* free_priv)(pcmk__output_t *out)
gchar * request
A copy of the request that generated this output.
Wrappers for and extensions to libxml2.
xmlNode * pcmk_create_xml_text_node(xmlNode *parent, const char *name, const char *content)
Definition: xml.c:774
const xmlChar * pcmkXmlStr
Definition: xml.h:50
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:891
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:749
void pcmk__xe_set_props(xmlNodePtr node,...) G_GNUC_NULL_TERMINATED
Definition: xml.c:3103