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