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