pacemaker  2.1.3-ea053b43a
Scalable High-Availability cluster resource manager
output_text.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 <stdarg.h>
13 #include <stdlib.h>
14 #include <glib.h>
15 #include <termios.h>
16 
17 static gboolean fancy = FALSE;
18 
19 GOptionEntry pcmk__text_output_entries[] = {
20  { "text-fancy", 0, 0, G_OPTION_ARG_NONE, &fancy,
21  "Use more highly formatted output (requires --output-as=text)",
22  NULL },
23 
24  { NULL }
25 };
26 
27 typedef struct text_list_data_s {
28  unsigned int len;
29  char *singular_noun;
30  char *plural_noun;
32 
33 typedef struct private_data_s {
34  GQueue *parent_q;
36 
37 static void
38 text_free_priv(pcmk__output_t *out) {
39  private_data_t *priv = out->priv;
40 
41  if (priv == NULL) {
42  return;
43  }
44 
45  g_queue_free(priv->parent_q);
46  free(priv);
47  out->priv = NULL;
48 }
49 
50 static bool
51 text_init(pcmk__output_t *out) {
52  private_data_t *priv = NULL;
53 
54  /* If text_init was previously called on this output struct, just return. */
55  if (out->priv != NULL) {
56  return true;
57  } else {
58  out->priv = calloc(1, sizeof(private_data_t));
59  if (out->priv == NULL) {
60  return false;
61  }
62 
63  priv = out->priv;
64  }
65 
66  priv->parent_q = g_queue_new();
67  return true;
68 }
69 
70 static void
71 text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
72  fflush(out->dest);
73 }
74 
75 static void
76 text_reset(pcmk__output_t *out) {
77  CRM_ASSERT(out != NULL);
78 
79  if (out->dest != stdout) {
80  out->dest = freopen(NULL, "w", out->dest);
81  }
82 
83  CRM_ASSERT(out->dest != NULL);
84 
85  text_free_priv(out);
86  text_init(out);
87 }
88 
89 static void
90 text_subprocess_output(pcmk__output_t *out, int exit_status,
91  const char *proc_stdout, const char *proc_stderr) {
92  CRM_ASSERT(out != NULL);
93 
94  if (proc_stdout != NULL) {
95  fprintf(out->dest, "%s\n", proc_stdout);
96  }
97 
98  if (proc_stderr != NULL) {
99  fprintf(out->dest, "%s\n", proc_stderr);
100  }
101 }
102 
103 static void
104 text_version(pcmk__output_t *out, bool extended) {
105  CRM_ASSERT(out != NULL);
106 
107  if (extended) {
108  fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
109  } else {
110  fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION);
111  fprintf(out->dest, "Written by Andrew Beekhof and "
112  "the Pacemaker project contributors\n");
113  }
114 }
115 
116 G_GNUC_PRINTF(2, 3)
117 static void
118 text_err(pcmk__output_t *out, const char *format, ...) {
119  va_list ap;
120  int len = 0;
121 
122  CRM_ASSERT(out != NULL);
123 
124  va_start(ap, format);
125 
126  /* Informational output does not get indented, to separate it from other
127  * potentially indented list output.
128  */
129  len = vfprintf(stderr, format, ap);
130  CRM_ASSERT(len >= 0);
131  va_end(ap);
132 
133  /* Add a newline. */
134  fprintf(stderr, "\n");
135 }
136 
137 G_GNUC_PRINTF(2, 3)
138 static int
139 text_info(pcmk__output_t *out, const char *format, ...) {
140  va_list ap;
141  int len = 0;
142 
143  CRM_ASSERT(out != NULL);
144 
145  if (out->is_quiet(out)) {
146  return pcmk_rc_no_output;
147  }
148 
149  va_start(ap, format);
150 
151  /* Informational output does not get indented, to separate it from other
152  * potentially indented list output.
153  */
154  len = vfprintf(out->dest, format, ap);
155  CRM_ASSERT(len >= 0);
156  va_end(ap);
157 
158  /* Add a newline. */
159  fprintf(out->dest, "\n");
160  return pcmk_rc_ok;
161 }
162 
163 static void
164 text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
165  CRM_ASSERT(out != NULL);
166  pcmk__indented_printf(out, "%s", buf);
167 }
168 
169 G_GNUC_PRINTF(4, 5)
170 static void
171 text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
172  const char *format, ...) {
173  private_data_t *priv = NULL;
174  text_list_data_t *new_list = NULL;
175  va_list ap;
176 
177  CRM_ASSERT(out != NULL && out->priv != NULL);
178  priv = out->priv;
179 
180  va_start(ap, format);
181 
182  if (fancy && format) {
183  pcmk__indented_vprintf(out, format, ap);
184  fprintf(out->dest, ":\n");
185  }
186 
187  va_end(ap);
188 
189  new_list = calloc(1, sizeof(text_list_data_t));
190  new_list->len = 0;
191  pcmk__str_update(&new_list->singular_noun, singular_noun);
192  pcmk__str_update(&new_list->plural_noun, plural_noun);
193 
194  g_queue_push_tail(priv->parent_q, new_list);
195 }
196 
197 G_GNUC_PRINTF(3, 4)
198 static void
199 text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
200  va_list ap;
201 
202  CRM_ASSERT(out != NULL);
203 
204  va_start(ap, format);
205 
206  if (fancy) {
207  if (id != NULL) {
208  /* Not really a good way to do this all in one call, so make it two.
209  * The first handles the indentation and list styling. The second
210  * just prints right after that one.
211  */
212  pcmk__indented_printf(out, "%s: ", id);
213  vfprintf(out->dest, format, ap);
214  } else {
215  pcmk__indented_vprintf(out, format, ap);
216  }
217  } else {
218  pcmk__indented_vprintf(out, format, ap);
219  }
220 
221  fputc('\n', out->dest);
222  fflush(out->dest);
223  va_end(ap);
224 
225  out->increment_list(out);
226 }
227 
228 static void
229 text_increment_list(pcmk__output_t *out) {
230  private_data_t *priv = NULL;
231  gpointer tail;
232 
233  CRM_ASSERT(out != NULL && out->priv != NULL);
234  priv = out->priv;
235 
236  tail = g_queue_peek_tail(priv->parent_q);
237  CRM_ASSERT(tail != NULL);
238  ((text_list_data_t *) tail)->len++;
239 }
240 
241 static void
242 text_end_list(pcmk__output_t *out) {
243  private_data_t *priv = NULL;
244  text_list_data_t *node = NULL;
245 
246  CRM_ASSERT(out != NULL && out->priv != NULL);
247  priv = out->priv;
248 
249  node = g_queue_pop_tail(priv->parent_q);
250 
251  if (node->singular_noun != NULL && node->plural_noun != NULL) {
252  if (node->len == 1) {
253  pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
254  } else {
255  pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
256  }
257  }
258 
259  free(node);
260 }
261 
262 static bool
263 text_is_quiet(pcmk__output_t *out) {
264  CRM_ASSERT(out != NULL);
265  return out->quiet;
266 }
267 
268 static void
269 text_spacer(pcmk__output_t *out) {
270  CRM_ASSERT(out != NULL);
271  fprintf(out->dest, "\n");
272 }
273 
274 static void
275 text_progress(pcmk__output_t *out, bool end) {
276  CRM_ASSERT(out != NULL);
277 
278  if (out->dest == stdout) {
279  fprintf(out->dest, ".");
280 
281  if (end) {
282  fprintf(out->dest, "\n");
283  }
284  }
285 }
286 
288 pcmk__mk_text_output(char **argv) {
289  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
290 
291  if (retval == NULL) {
292  return NULL;
293  }
294 
295  retval->fmt_name = "text";
296  retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
297 
298  retval->init = text_init;
299  retval->free_priv = text_free_priv;
300  retval->finish = text_finish;
301  retval->reset = text_reset;
302 
304  retval->message = pcmk__call_message;
305 
306  retval->subprocess_output = text_subprocess_output;
307  retval->version = text_version;
308  retval->info = text_info;
309  retval->err = text_err;
310  retval->output_xml = text_output_xml;
311 
312  retval->begin_list = text_begin_list;
313  retval->list_item = text_list_item;
314  retval->increment_list = text_increment_list;
315  retval->end_list = text_end_list;
316 
317  retval->is_quiet = text_is_quiet;
318  retval->spacer = text_spacer;
319  retval->progress = text_progress;
320  retval->prompt = pcmk__text_prompt;
321 
322  return retval;
323 }
324 
325 G_GNUC_PRINTF(2, 0)
326 void
327 pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
328  int len = 0;
329 
330  CRM_ASSERT(out != NULL);
331 
332  len = vfprintf(out->dest, format, args);
333  CRM_ASSERT(len >= 0);
334 }
335 
336 G_GNUC_PRINTF(2, 3)
337 void
338 pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) {
339  va_list ap;
340 
341  CRM_ASSERT(out != NULL);
342 
343  va_start(ap, format);
344  pcmk__formatted_vprintf(out, format, ap);
345  va_end(ap);
346 }
347 
348 G_GNUC_PRINTF(2, 0)
349 void
350 pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
351  CRM_ASSERT(out != NULL);
352 
353  if (!pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
354  return;
355  }
356 
357  if (fancy) {
358  int level = 0;
359  private_data_t *priv = out->priv;
360 
361  CRM_ASSERT(priv != NULL);
362 
363  level = g_queue_get_length(priv->parent_q);
364 
365  for (int i = 0; i < level; i++) {
366  fprintf(out->dest, " ");
367  }
368 
369  if (level > 0) {
370  fprintf(out->dest, "* ");
371  }
372  }
373 
374  pcmk__formatted_vprintf(out, format, args);
375 }
376 
377 G_GNUC_PRINTF(2, 3)
378 void
379 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
380  va_list ap;
381 
382  CRM_ASSERT(out != NULL);
383 
384  va_start(ap, format);
385  pcmk__indented_vprintf(out, format, ap);
386  va_end(ap);
387 }
388 
389 void
390 pcmk__text_prompt(const char *prompt, bool echo, char **dest)
391 {
392  int rc = 0;
393  struct termios settings;
394  tcflag_t orig_c_lflag = 0;
395 
396  CRM_ASSERT(prompt != NULL);
397  CRM_ASSERT(dest != NULL);
398 
399  if (!echo) {
400  rc = tcgetattr(0, &settings);
401  if (rc == 0) {
402  orig_c_lflag = settings.c_lflag;
403  settings.c_lflag &= ~ECHO;
404  rc = tcsetattr(0, TCSANOW, &settings);
405  }
406  }
407 
408  if (rc == 0) {
409  fprintf(stderr, "%s: ", prompt);
410 
411  if (*dest != NULL) {
412  free(*dest);
413  *dest = NULL;
414  }
415 
416 #if SSCANF_HAS_M
417  rc = scanf("%ms", dest);
418 #else
419  *dest = calloc(1, 1024);
420  rc = scanf("%1023s", *dest);
421 #endif
422  fprintf(stderr, "\n");
423  }
424 
425  if (rc < 1) {
426  free(*dest);
427  *dest = NULL;
428  }
429 
430  if (orig_c_lflag != 0) {
431  settings.c_lflag = orig_c_lflag;
432  /* rc = */ tcsetattr(0, TCSANOW, &settings);
433  }
434 }
void(* end_list)(pcmk__output_t *out)
void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args)
Definition: output_text.c:350
const char * name
Definition: cib.c:24
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:390
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:139
int(*) void(*) void(* output_xml)(pcmk__output_t *out, const char *name, const char *buf)
enum crm_exit_e crm_exit_t
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PACEMAKER_VERSION
Definition: config.h:504
int pcmk__call_message(pcmk__output_t *out, const char *message_id,...)
Definition: output.c:119
void pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args)
Definition: output_text.c:327
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(*) void(* err)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__str_update(char **str, const char *value)
Definition: strings.c:1188
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
pcmk__output_t * pcmk__mk_text_output(char **argv)
Definition: output_text.c:288
void pcmk__indented_printf(pcmk__output_t *out, const char *format,...)
Definition: output_text.c:379
struct private_data_s private_data_t
FILE * dest
Where output should be written.
#define CRM_ASSERT(expr)
Definition: results.h:42
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:19
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
void pcmk__formatted_printf(pcmk__output_t *out, const char *format,...)
Definition: output_text.c:338
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)