pacemaker  2.1.2-ada5c3b36
Scalable High-Availability cluster resource manager
output_text.c
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2021 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\n");
112  }
113 }
114 
115 G_GNUC_PRINTF(2, 3)
116 static void
117 text_err(pcmk__output_t *out, const char *format, ...) {
118  va_list ap;
119  int len = 0;
120 
121  CRM_ASSERT(out != NULL);
122 
123  va_start(ap, format);
124 
125  /* Informational output does not get indented, to separate it from other
126  * potentially indented list output.
127  */
128  len = vfprintf(stderr, format, ap);
129  CRM_ASSERT(len >= 0);
130  va_end(ap);
131 
132  /* Add a newline. */
133  fprintf(stderr, "\n");
134 }
135 
136 G_GNUC_PRINTF(2, 3)
137 static int
138 text_info(pcmk__output_t *out, const char *format, ...) {
139  va_list ap;
140  int len = 0;
141 
142  CRM_ASSERT(out != NULL);
143 
144  if (out->is_quiet(out)) {
145  return pcmk_rc_no_output;
146  }
147 
148  va_start(ap, format);
149 
150  /* Informational output does not get indented, to separate it from other
151  * potentially indented list output.
152  */
153  len = vfprintf(out->dest, format, ap);
154  CRM_ASSERT(len >= 0);
155  va_end(ap);
156 
157  /* Add a newline. */
158  fprintf(out->dest, "\n");
159  return pcmk_rc_ok;
160 }
161 
162 static void
163 text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
164  CRM_ASSERT(out != NULL);
165  pcmk__indented_printf(out, "%s", buf);
166 }
167 
168 G_GNUC_PRINTF(4, 5)
169 static void
170 text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
171  const char *format, ...) {
172  private_data_t *priv = NULL;
173  text_list_data_t *new_list = NULL;
174  va_list ap;
175 
176  CRM_ASSERT(out != NULL && out->priv != NULL);
177  priv = out->priv;
178 
179  va_start(ap, format);
180 
181  if (fancy && format) {
182  pcmk__indented_vprintf(out, format, ap);
183  fprintf(out->dest, ":\n");
184  }
185 
186  va_end(ap);
187 
188  new_list = calloc(1, sizeof(text_list_data_t));
189  new_list->len = 0;
190  new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun);
191  new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun);
192 
193  g_queue_push_tail(priv->parent_q, new_list);
194 }
195 
196 G_GNUC_PRINTF(3, 4)
197 static void
198 text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
199  va_list ap;
200 
201  CRM_ASSERT(out != NULL);
202 
203  va_start(ap, format);
204 
205  if (fancy) {
206  if (id != NULL) {
207  /* Not really a good way to do this all in one call, so make it two.
208  * The first handles the indentation and list styling. The second
209  * just prints right after that one.
210  */
211  pcmk__indented_printf(out, "%s: ", id);
212  vfprintf(out->dest, format, ap);
213  } else {
214  pcmk__indented_vprintf(out, format, ap);
215  }
216  } else {
217  pcmk__indented_vprintf(out, format, ap);
218  }
219 
220  fputc('\n', out->dest);
221  fflush(out->dest);
222  va_end(ap);
223 
224  out->increment_list(out);
225 }
226 
227 static void
228 text_increment_list(pcmk__output_t *out) {
229  private_data_t *priv = NULL;
230  gpointer tail;
231 
232  CRM_ASSERT(out != NULL && out->priv != NULL);
233  priv = out->priv;
234 
235  tail = g_queue_peek_tail(priv->parent_q);
236  CRM_ASSERT(tail != NULL);
237  ((text_list_data_t *) tail)->len++;
238 }
239 
240 static void
241 text_end_list(pcmk__output_t *out) {
242  private_data_t *priv = NULL;
243  text_list_data_t *node = NULL;
244 
245  CRM_ASSERT(out != NULL && out->priv != NULL);
246  priv = out->priv;
247 
248  node = g_queue_pop_tail(priv->parent_q);
249 
250  if (node->singular_noun != NULL && node->plural_noun != NULL) {
251  if (node->len == 1) {
252  pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
253  } else {
254  pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
255  }
256  }
257 
258  free(node);
259 }
260 
261 static bool
262 text_is_quiet(pcmk__output_t *out) {
263  CRM_ASSERT(out != NULL);
264  return out->quiet;
265 }
266 
267 static void
268 text_spacer(pcmk__output_t *out) {
269  CRM_ASSERT(out != NULL);
270  fprintf(out->dest, "\n");
271 }
272 
273 static void
274 text_progress(pcmk__output_t *out, bool end) {
275  CRM_ASSERT(out != NULL);
276 
277  if (out->dest == stdout) {
278  fprintf(out->dest, ".");
279 
280  if (end) {
281  fprintf(out->dest, "\n");
282  }
283  }
284 }
285 
287 pcmk__mk_text_output(char **argv) {
288  pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
289 
290  if (retval == NULL) {
291  return NULL;
292  }
293 
294  retval->fmt_name = "text";
295  retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
296 
297  retval->init = text_init;
298  retval->free_priv = text_free_priv;
299  retval->finish = text_finish;
300  retval->reset = text_reset;
301 
303  retval->message = pcmk__call_message;
304 
305  retval->subprocess_output = text_subprocess_output;
306  retval->version = text_version;
307  retval->info = text_info;
308  retval->err = text_err;
309  retval->output_xml = text_output_xml;
310 
311  retval->begin_list = text_begin_list;
312  retval->list_item = text_list_item;
313  retval->increment_list = text_increment_list;
314  retval->end_list = text_end_list;
315 
316  retval->is_quiet = text_is_quiet;
317  retval->spacer = text_spacer;
318  retval->progress = text_progress;
319  retval->prompt = pcmk__text_prompt;
320 
321  return retval;
322 }
323 
324 G_GNUC_PRINTF(2, 0)
325 void
326 pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
327  int len = 0;
328 
329  CRM_ASSERT(out != NULL);
330 
331  len = vfprintf(out->dest, format, args);
332  CRM_ASSERT(len >= 0);
333 }
334 
335 G_GNUC_PRINTF(2, 3)
336 void
337 pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) {
338  va_list ap;
339 
340  CRM_ASSERT(out != NULL);
341 
342  va_start(ap, format);
343  pcmk__formatted_vprintf(out, format, ap);
344  va_end(ap);
345 }
346 
347 G_GNUC_PRINTF(2, 0)
348 void
349 pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
350  CRM_ASSERT(out != NULL);
351 
352  if (!pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
353  return;
354  }
355 
356  if (fancy) {
357  int level = 0;
358  private_data_t *priv = out->priv;
359 
360  CRM_ASSERT(priv != NULL);
361 
362  level = g_queue_get_length(priv->parent_q);
363 
364  for (int i = 0; i < level; i++) {
365  fprintf(out->dest, " ");
366  }
367 
368  if (level > 0) {
369  fprintf(out->dest, "* ");
370  }
371  }
372 
373  pcmk__formatted_vprintf(out, format, args);
374 }
375 
376 G_GNUC_PRINTF(2, 3)
377 void
378 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
379  va_list ap;
380 
381  CRM_ASSERT(out != NULL);
382 
383  va_start(ap, format);
384  pcmk__indented_vprintf(out, format, ap);
385  va_end(ap);
386 }
387 
388 void
389 pcmk__text_prompt(const char *prompt, bool echo, char **dest)
390 {
391  int rc = 0;
392  struct termios settings;
393  tcflag_t orig_c_lflag = 0;
394 
395  CRM_ASSERT(prompt != NULL);
396  CRM_ASSERT(dest != NULL);
397 
398  if (!echo) {
399  rc = tcgetattr(0, &settings);
400  if (rc == 0) {
401  orig_c_lflag = settings.c_lflag;
402  settings.c_lflag &= ~ECHO;
403  rc = tcsetattr(0, TCSANOW, &settings);
404  }
405  }
406 
407  if (rc == 0) {
408  fprintf(stderr, "%s: ", prompt);
409 
410  if (*dest != NULL) {
411  free(*dest);
412  *dest = NULL;
413  }
414 
415 #if SSCANF_HAS_M
416  rc = scanf("%ms", dest);
417 #else
418  *dest = calloc(1, 1024);
419  rc = scanf("%1023s", *dest);
420 #endif
421  fprintf(stderr, "\n");
422  }
423 
424  if (rc < 1) {
425  free(*dest);
426  *dest = NULL;
427  }
428 
429  if (orig_c_lflag != 0) {
430  settings.c_lflag = orig_c_lflag;
431  /* rc = */ tcsetattr(0, TCSANOW, &settings);
432  }
433 }
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:349
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:389
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:484
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:326
void(* prompt)(const char *prompt, bool echo, char **dest)
void * priv
Implementation-specific private data.
int rc
Definition: pcmk_fence.c:35
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(* 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:287
void pcmk__indented_printf(pcmk__output_t *out, const char *format,...)
Definition: output_text.c:378
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)
char * name
Definition: pcmk_fence.c:31
#define CRM_FEATURES
Definition: config.h:35
void pcmk__formatted_printf(pcmk__output_t *out, const char *format,...)
Definition: output_text.c:337
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)