Line data Source code
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 : #include <crm/common/cmdline_internal.h>
12 :
13 : #include <stdarg.h>
14 : #include <stdlib.h>
15 : #include <glib.h>
16 : #include <termios.h>
17 :
18 : #include "crmcommon_private.h"
19 :
20 : // @COMPAT Drop at 3.0.0
21 : static gboolean fancy = FALSE;
22 :
23 : // @COMPAT Drop at 3.0.0
24 : GOptionEntry pcmk__text_output_entries[] = {
25 : { "text-fancy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &fancy,
26 : "Use more highly formatted output (requires --output-as=text)",
27 : NULL },
28 :
29 : { NULL }
30 : };
31 :
32 : typedef struct text_list_data_s {
33 : unsigned int len;
34 : char *singular_noun;
35 : char *plural_noun;
36 : } text_list_data_t;
37 :
38 : typedef struct private_data_s {
39 : GQueue *parent_q;
40 : bool fancy;
41 : } private_data_t;
42 :
43 : static void
44 0 : free_list_data(gpointer data) {
45 0 : text_list_data_t *list_data = data;
46 :
47 0 : free(list_data->singular_noun);
48 0 : free(list_data->plural_noun);
49 0 : }
50 :
51 : static void
52 0 : text_free_priv(pcmk__output_t *out) {
53 0 : private_data_t *priv = NULL;
54 :
55 0 : if (out == NULL || out->priv == NULL) {
56 0 : return;
57 : }
58 :
59 0 : priv = out->priv;
60 :
61 0 : g_queue_free_full(priv->parent_q, free_list_data);
62 0 : free(priv);
63 0 : out->priv = NULL;
64 : }
65 :
66 : static bool
67 0 : text_init(pcmk__output_t *out) {
68 0 : private_data_t *priv = NULL;
69 :
70 0 : CRM_ASSERT(out != NULL);
71 :
72 : /* If text_init was previously called on this output struct, just return. */
73 0 : if (out->priv != NULL) {
74 0 : return true;
75 : }
76 :
77 0 : out->priv = calloc(1, sizeof(private_data_t));
78 0 : if (out->priv == NULL) {
79 0 : return false;
80 : }
81 :
82 0 : priv = out->priv;
83 0 : priv->parent_q = g_queue_new();
84 0 : return true;
85 : }
86 :
87 : static void
88 0 : text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
89 0 : CRM_ASSERT(out != NULL && out->dest != NULL);
90 0 : fflush(out->dest);
91 0 : }
92 :
93 : static void
94 0 : text_reset(pcmk__output_t *out) {
95 0 : private_data_t *priv = NULL;
96 0 : bool old_fancy = false;
97 :
98 0 : CRM_ASSERT(out != NULL);
99 :
100 0 : if (out->dest != stdout) {
101 0 : out->dest = freopen(NULL, "w", out->dest);
102 : }
103 :
104 0 : CRM_ASSERT(out->dest != NULL);
105 :
106 : // Save priv->fancy before free/init sequence overwrites it
107 0 : priv = out->priv;
108 0 : old_fancy = priv->fancy;
109 :
110 0 : text_free_priv(out);
111 0 : text_init(out);
112 :
113 0 : priv = out->priv;
114 0 : priv->fancy = old_fancy;
115 0 : }
116 :
117 : static void
118 0 : text_subprocess_output(pcmk__output_t *out, int exit_status,
119 : const char *proc_stdout, const char *proc_stderr) {
120 0 : CRM_ASSERT(out != NULL);
121 :
122 0 : if (proc_stdout != NULL) {
123 0 : fprintf(out->dest, "%s\n", proc_stdout);
124 : }
125 :
126 0 : if (proc_stderr != NULL) {
127 0 : fprintf(out->dest, "%s\n", proc_stderr);
128 : }
129 0 : }
130 :
131 : static void
132 0 : text_version(pcmk__output_t *out, bool extended) {
133 0 : CRM_ASSERT(out != NULL && out->dest != NULL);
134 :
135 0 : if (extended) {
136 0 : fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
137 : } else {
138 0 : fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION);
139 0 : fprintf(out->dest, "Written by Andrew Beekhof and "
140 : "the Pacemaker project contributors\n");
141 : }
142 0 : }
143 :
144 : G_GNUC_PRINTF(2, 3)
145 : static void
146 0 : text_err(pcmk__output_t *out, const char *format, ...) {
147 : va_list ap;
148 0 : int len = 0;
149 :
150 0 : CRM_ASSERT(out != NULL);
151 :
152 0 : va_start(ap, format);
153 :
154 : /* Informational output does not get indented, to separate it from other
155 : * potentially indented list output.
156 : */
157 0 : len = vfprintf(stderr, format, ap);
158 0 : CRM_ASSERT(len >= 0);
159 0 : va_end(ap);
160 :
161 : /* Add a newline. */
162 0 : fprintf(stderr, "\n");
163 0 : }
164 :
165 : G_GNUC_PRINTF(2, 3)
166 : static int
167 0 : text_info(pcmk__output_t *out, const char *format, ...) {
168 : va_list ap;
169 0 : int len = 0;
170 :
171 0 : CRM_ASSERT(out != NULL);
172 :
173 0 : if (out->is_quiet(out)) {
174 0 : return pcmk_rc_no_output;
175 : }
176 :
177 0 : va_start(ap, format);
178 :
179 : /* Informational output does not get indented, to separate it from other
180 : * potentially indented list output.
181 : */
182 0 : len = vfprintf(out->dest, format, ap);
183 0 : CRM_ASSERT(len >= 0);
184 0 : va_end(ap);
185 :
186 : /* Add a newline. */
187 0 : fprintf(out->dest, "\n");
188 0 : return pcmk_rc_ok;
189 : }
190 :
191 : G_GNUC_PRINTF(2, 3)
192 : static int
193 0 : text_transient(pcmk__output_t *out, const char *format, ...)
194 : {
195 0 : return pcmk_rc_no_output;
196 : }
197 :
198 : static void
199 0 : text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
200 0 : CRM_ASSERT(out != NULL);
201 0 : pcmk__indented_printf(out, "%s", buf);
202 0 : }
203 :
204 : G_GNUC_PRINTF(4, 5)
205 : static void
206 0 : text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
207 : const char *format, ...) {
208 0 : private_data_t *priv = NULL;
209 0 : text_list_data_t *new_list = NULL;
210 : va_list ap;
211 :
212 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
213 0 : priv = out->priv;
214 :
215 0 : va_start(ap, format);
216 :
217 0 : if ((fancy || priv->fancy) && (format != NULL)) {
218 0 : pcmk__indented_vprintf(out, format, ap);
219 0 : fprintf(out->dest, ":\n");
220 : }
221 :
222 0 : va_end(ap);
223 :
224 0 : new_list = pcmk__assert_alloc(1, sizeof(text_list_data_t));
225 0 : new_list->len = 0;
226 0 : new_list->singular_noun = pcmk__str_copy(singular_noun);
227 0 : new_list->plural_noun = pcmk__str_copy(plural_noun);
228 :
229 0 : g_queue_push_tail(priv->parent_q, new_list);
230 0 : }
231 :
232 : G_GNUC_PRINTF(3, 4)
233 : static void
234 0 : text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
235 0 : private_data_t *priv = NULL;
236 : va_list ap;
237 :
238 0 : CRM_ASSERT(out != NULL);
239 :
240 0 : priv = out->priv;
241 0 : va_start(ap, format);
242 :
243 0 : if (fancy || priv->fancy) {
244 0 : if (id != NULL) {
245 : /* Not really a good way to do this all in one call, so make it two.
246 : * The first handles the indentation and list styling. The second
247 : * just prints right after that one.
248 : */
249 0 : pcmk__indented_printf(out, "%s: ", id);
250 0 : vfprintf(out->dest, format, ap);
251 : } else {
252 0 : pcmk__indented_vprintf(out, format, ap);
253 : }
254 : } else {
255 0 : pcmk__indented_vprintf(out, format, ap);
256 : }
257 :
258 0 : fputc('\n', out->dest);
259 0 : fflush(out->dest);
260 0 : va_end(ap);
261 :
262 0 : out->increment_list(out);
263 0 : }
264 :
265 : static void
266 0 : text_increment_list(pcmk__output_t *out) {
267 0 : private_data_t *priv = NULL;
268 : gpointer tail;
269 :
270 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
271 0 : priv = out->priv;
272 :
273 0 : tail = g_queue_peek_tail(priv->parent_q);
274 0 : CRM_ASSERT(tail != NULL);
275 0 : ((text_list_data_t *) tail)->len++;
276 0 : }
277 :
278 : static void
279 0 : text_end_list(pcmk__output_t *out) {
280 0 : private_data_t *priv = NULL;
281 0 : text_list_data_t *node = NULL;
282 :
283 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
284 0 : priv = out->priv;
285 :
286 0 : node = g_queue_pop_tail(priv->parent_q);
287 :
288 0 : if (node->singular_noun != NULL && node->plural_noun != NULL) {
289 0 : if (node->len == 1) {
290 0 : pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
291 : } else {
292 0 : pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
293 : }
294 : }
295 :
296 0 : free_list_data(node);
297 0 : }
298 :
299 : static bool
300 0 : text_is_quiet(pcmk__output_t *out) {
301 0 : CRM_ASSERT(out != NULL);
302 0 : return out->quiet;
303 : }
304 :
305 : static void
306 0 : text_spacer(pcmk__output_t *out) {
307 0 : CRM_ASSERT(out != NULL);
308 0 : fprintf(out->dest, "\n");
309 0 : }
310 :
311 : static void
312 0 : text_progress(pcmk__output_t *out, bool end) {
313 0 : CRM_ASSERT(out != NULL);
314 :
315 0 : if (out->dest == stdout) {
316 0 : fprintf(out->dest, ".");
317 :
318 0 : if (end) {
319 0 : fprintf(out->dest, "\n");
320 : }
321 : }
322 0 : }
323 :
324 : pcmk__output_t *
325 0 : pcmk__mk_text_output(char **argv) {
326 0 : pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
327 :
328 0 : if (retval == NULL) {
329 0 : return NULL;
330 : }
331 :
332 0 : retval->fmt_name = "text";
333 0 : retval->request = pcmk__quote_cmdline(argv);
334 :
335 0 : retval->init = text_init;
336 0 : retval->free_priv = text_free_priv;
337 0 : retval->finish = text_finish;
338 0 : retval->reset = text_reset;
339 :
340 0 : retval->register_message = pcmk__register_message;
341 0 : retval->message = pcmk__call_message;
342 :
343 0 : retval->subprocess_output = text_subprocess_output;
344 0 : retval->version = text_version;
345 0 : retval->info = text_info;
346 0 : retval->transient = text_transient;
347 0 : retval->err = text_err;
348 0 : retval->output_xml = text_output_xml;
349 :
350 0 : retval->begin_list = text_begin_list;
351 0 : retval->list_item = text_list_item;
352 0 : retval->increment_list = text_increment_list;
353 0 : retval->end_list = text_end_list;
354 :
355 0 : retval->is_quiet = text_is_quiet;
356 0 : retval->spacer = text_spacer;
357 0 : retval->progress = text_progress;
358 0 : retval->prompt = pcmk__text_prompt;
359 :
360 0 : return retval;
361 : }
362 :
363 : /*!
364 : * \internal
365 : * \brief Check whether fancy output is enabled for a text output object
366 : *
367 : * This returns \c false if the output object is not of text format.
368 : *
369 : * \param[in] out Output object
370 : *
371 : * \return \c true if \p out has fancy output enabled, or \c false otherwise
372 : */
373 : bool
374 0 : pcmk__output_text_get_fancy(pcmk__output_t *out)
375 : {
376 0 : CRM_ASSERT(out != NULL);
377 :
378 0 : if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
379 0 : private_data_t *priv = out->priv;
380 :
381 0 : CRM_ASSERT(priv != NULL);
382 0 : return priv->fancy;
383 : }
384 0 : return false;
385 : }
386 :
387 : /*!
388 : * \internal
389 : * \brief Enable or disable fancy output for a text output object
390 : *
391 : * This does nothing if the output object is not of text format.
392 : *
393 : * \param[in,out] out Output object
394 : * \param[in] enabled Whether fancy output should be enabled for \p out
395 : */
396 : void
397 0 : pcmk__output_text_set_fancy(pcmk__output_t *out, bool enabled)
398 : {
399 0 : CRM_ASSERT(out != NULL);
400 :
401 0 : if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
402 0 : private_data_t *priv = out->priv;
403 :
404 0 : CRM_ASSERT(priv != NULL);
405 0 : priv->fancy = enabled;
406 : }
407 0 : }
408 :
409 : G_GNUC_PRINTF(2, 0)
410 : void
411 0 : pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
412 0 : int len = 0;
413 :
414 0 : CRM_ASSERT(out != NULL);
415 0 : CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
416 :
417 0 : len = vfprintf(out->dest, format, args);
418 0 : CRM_ASSERT(len >= 0);
419 : }
420 :
421 : G_GNUC_PRINTF(2, 3)
422 : void
423 0 : pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) {
424 : va_list ap;
425 :
426 0 : CRM_ASSERT(out != NULL);
427 :
428 0 : va_start(ap, format);
429 0 : pcmk__formatted_vprintf(out, format, ap);
430 0 : va_end(ap);
431 0 : }
432 :
433 : G_GNUC_PRINTF(2, 0)
434 : void
435 0 : pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
436 0 : private_data_t *priv = NULL;
437 :
438 0 : CRM_ASSERT(out != NULL);
439 0 : CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
440 :
441 0 : priv = out->priv;
442 :
443 0 : if (fancy || priv->fancy) {
444 0 : int level = 0;
445 0 : private_data_t *priv = out->priv;
446 :
447 0 : CRM_ASSERT(priv != NULL);
448 :
449 0 : level = g_queue_get_length(priv->parent_q);
450 :
451 0 : for (int i = 0; i < level; i++) {
452 0 : fprintf(out->dest, " ");
453 : }
454 :
455 0 : if (level > 0) {
456 0 : fprintf(out->dest, "* ");
457 : }
458 : }
459 :
460 0 : pcmk__formatted_vprintf(out, format, args);
461 : }
462 :
463 : G_GNUC_PRINTF(2, 3)
464 : void
465 0 : pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
466 : va_list ap;
467 :
468 0 : CRM_ASSERT(out != NULL);
469 :
470 0 : va_start(ap, format);
471 0 : pcmk__indented_vprintf(out, format, ap);
472 0 : va_end(ap);
473 0 : }
474 :
475 : void
476 0 : pcmk__text_prompt(const char *prompt, bool echo, char **dest)
477 : {
478 0 : int rc = 0;
479 : struct termios settings;
480 0 : tcflag_t orig_c_lflag = 0;
481 :
482 0 : CRM_ASSERT(prompt != NULL);
483 0 : CRM_ASSERT(dest != NULL);
484 :
485 0 : if (!echo) {
486 0 : rc = tcgetattr(0, &settings);
487 0 : if (rc == 0) {
488 0 : orig_c_lflag = settings.c_lflag;
489 0 : settings.c_lflag &= ~ECHO;
490 0 : rc = tcsetattr(0, TCSANOW, &settings);
491 : }
492 : }
493 :
494 0 : if (rc == 0) {
495 0 : fprintf(stderr, "%s: ", prompt);
496 :
497 0 : if (*dest != NULL) {
498 0 : free(*dest);
499 0 : *dest = NULL;
500 : }
501 :
502 : #if HAVE_SSCANF_M
503 0 : rc = scanf("%ms", dest);
504 : #else
505 : *dest = pcmk__assert_alloc(1, 1024);
506 : rc = scanf("%1023s", *dest);
507 : #endif
508 0 : fprintf(stderr, "\n");
509 : }
510 :
511 0 : if (rc < 1) {
512 0 : free(*dest);
513 0 : *dest = NULL;
514 : }
515 :
516 0 : if (orig_c_lflag != 0) {
517 0 : settings.c_lflag = orig_c_lflag;
518 0 : /* rc = */ tcsetattr(0, TCSANOW, &settings);
519 : }
520 0 : }
|