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